mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 06:31:27 -07:00
add a few CLI commands
This commit is contained in:
@@ -9,16 +9,17 @@ import {
|
||||
getAccountByUsername,
|
||||
getAccountsByPlatformId,
|
||||
getPasswordHash,
|
||||
hashPassword,
|
||||
RoomInstanceType,
|
||||
setLastLoginTime,
|
||||
setLoginContext,
|
||||
setPasswordHash,
|
||||
setPresence,
|
||||
verifyPassword,
|
||||
} from '@repo/domain'
|
||||
import { intVar, logger, withNotFound, withOnError } from '@repo/hono-helpers'
|
||||
import { generateToken, TOKEN_TTL_SECONDS, validateAndGetAccountId } from '@repo/jwt'
|
||||
|
||||
import { hashPassword, verifyPassword } from './password'
|
||||
import { consumeRefreshToken, issueRefreshToken } from './refresh-db'
|
||||
import { verifySteamTicket } from './steam-ticket'
|
||||
|
||||
@@ -497,11 +498,14 @@ const app = new Hono<App>()
|
||||
return c.json({ success: true })
|
||||
})
|
||||
|
||||
// Developer role lookup. No developer role granted by default.
|
||||
.get('/role/developer/:id', (c) => {
|
||||
// Developer role lookup. The role is off by default and only an operator grants
|
||||
// it (via `runx admin grant-developer`, which sets the account's isDeveloper flag).
|
||||
.get('/role/developer/:id', async (c) => {
|
||||
const { id } = c.req.param()
|
||||
logger.info('developer role lookup', { id })
|
||||
return c.json({ success: false })
|
||||
const accountId = Number.parseInt(id, 10)
|
||||
const account = Number.isNaN(accountId) ? null : await getAccount(c.env.DB, accountId)
|
||||
return c.json({ success: account?.isDeveloper === true })
|
||||
})
|
||||
|
||||
export default app
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Password hashing for /connect/token credential login and
|
||||
* /account/me/changepassword. PBKDF2-SHA256 with a random per-password salt,
|
||||
* stored as `salt:hash` (both base64). The raw password is never persisted.
|
||||
*/
|
||||
const ITERATIONS = 100_000
|
||||
|
||||
const b64 = (bytes: Uint8Array): string => btoa(String.fromCharCode(...bytes))
|
||||
const fromB64 = (s: string): Uint8Array => Uint8Array.from(atob(s), (ch) => ch.charCodeAt(0))
|
||||
|
||||
async function deriveBits(password: string, salt: Uint8Array): Promise<Uint8Array> {
|
||||
const keyMaterial = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
new TextEncoder().encode(password),
|
||||
'PBKDF2',
|
||||
false,
|
||||
['deriveBits']
|
||||
)
|
||||
const bits = await crypto.subtle.deriveBits(
|
||||
{ name: 'PBKDF2', salt, iterations: ITERATIONS, hash: 'SHA-256' },
|
||||
keyMaterial,
|
||||
256
|
||||
)
|
||||
return new Uint8Array(bits)
|
||||
}
|
||||
|
||||
/** Hash a password into a `salt:hash` string (both base64). */
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const salt = crypto.getRandomValues(new Uint8Array(16))
|
||||
return `${b64(salt)}:${b64(await deriveBits(password, salt))}`
|
||||
}
|
||||
|
||||
/** Verify a password against a stored `salt:hash`. */
|
||||
export async function verifyPassword(password: string, stored: string): Promise<boolean> {
|
||||
const [saltB64, hashB64] = stored.split(':')
|
||||
if (!saltB64 || !hashB64) return false
|
||||
const actual = b64(await deriveBits(password, fromB64(saltB64)))
|
||||
return actual === hashB64
|
||||
}
|
||||
@@ -4,10 +4,9 @@ import { beforeAll, describe, expect, test } from 'vitest'
|
||||
|
||||
import '../../auth.app'
|
||||
|
||||
import { getAccountsByDeviceId, PRESENCE_SCHEMA_DDL, SCHEMA_DDL } from '@repo/domain'
|
||||
import { getAccountsByDeviceId, hashPassword, PRESENCE_SCHEMA_DDL, SCHEMA_DDL } from '@repo/domain'
|
||||
|
||||
import { isLinkedToPlatformIdentity } from '../../auth.app'
|
||||
import { hashPassword } from '../../password'
|
||||
import { REFRESH_SCHEMA_DDL } from '../../refresh-db'
|
||||
|
||||
import type { Env } from '../../context'
|
||||
@@ -575,6 +574,15 @@ describe('auth worker routes', () => {
|
||||
expect(await res.json()).toEqual({ success: false })
|
||||
})
|
||||
|
||||
test('GET /role/developer/:id grants developer when the account is flagged', async () => {
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(JSON.stringify({ accountId: 4242, username: 'DevPlayer', isDeveloper: true }))
|
||||
.run()
|
||||
const res = await exports.default.fetch(`${ORIGIN}/role/developer/4242`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ success: true })
|
||||
})
|
||||
|
||||
test('unknown path returns 404', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/nope`)
|
||||
expect(res.status).toBe(404)
|
||||
|
||||
Reference in New Issue
Block a user