mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 06:31:27 -07:00
move to JWT_SECRET
This commit is contained in:
+13
-2
@@ -13,10 +13,21 @@ KV/D1/DO bindings yet.
|
||||
| POST | `/connect/token` | OAuth token endpoint, issues a JWT |
|
||||
| GET | `/role/developer/:id` | Developer role lookup (TODO) |
|
||||
|
||||
## Signing key
|
||||
|
||||
Tokens are signed HS256 with the `JWT_SECRET` binding (see `src/jwt.ts`). It's a
|
||||
Cloudflare secret in deployed environments and read from `.dev.vars` locally
|
||||
(gitignored) — never committed. `"keep_vars": true` in `wrangler.jsonc` keeps
|
||||
deploys from clearing it.
|
||||
|
||||
Set the deployed secret once (persists across deploys):
|
||||
|
||||
```sh
|
||||
bunx wrangler secret put JWT_SECRET
|
||||
```
|
||||
|
||||
## Notes / TODO
|
||||
|
||||
- `JWT` is signed with a placeholder dev secret in `src/jwt.ts`. Move to a secret
|
||||
binding before real use.
|
||||
- `/eac/challenge` content is inlined in `src/auth.app.ts` (Workers have no
|
||||
filesystem) — replace `EAC_CHALLENGE` with the real challenge text.
|
||||
- `/cachedlogin/...` and the `RoomInstance` cleanup in `/connect/token` need a DB
|
||||
|
||||
@@ -97,7 +97,7 @@ async function placeNewPlayerInOrientation(env: App['Bindings'], accountId: numb
|
||||
async function authedId(c: Context<App>): Promise<number | null> {
|
||||
const authHeader = c.req.header('Authorization') ?? ''
|
||||
if (!authHeader.toLowerCase().startsWith('bearer ')) return null
|
||||
const sub = await validateAndGetAccountId(authHeader.slice('Bearer '.length))
|
||||
const sub = await validateAndGetAccountId(authHeader.slice('Bearer '.length), c.env.JWT_SECRET)
|
||||
const id = sub ? Number.parseInt(sub, 10) : Number.NaN
|
||||
return Number.isNaN(id) ? null : id
|
||||
}
|
||||
@@ -153,7 +153,7 @@ const app = new Hono<App>()
|
||||
const account = await createAccount(c.env.DB, { platforms: platformInt || 0 })
|
||||
accountId = String(account.accountId)
|
||||
// Place the new player in Orientation (they don't matchmake into it).
|
||||
await placeNewPlayerInOrientation(c.env, account.accountId)
|
||||
//await placeNewPlayerInOrientation(c.env, account.accountId)
|
||||
} else {
|
||||
const posted = typeof body.account_id === 'string' ? body.account_id.trim() : ''
|
||||
if (!/^\d+$/.test(posted)) {
|
||||
@@ -165,10 +165,7 @@ const app = new Hono<App>()
|
||||
accountId = posted
|
||||
}
|
||||
|
||||
const accessToken = await generateToken(accountId, platformId, platform)
|
||||
|
||||
// TODO: also create the player's dorm on create_account, and remove any
|
||||
// RoomInstance owned by accountId on login.
|
||||
const accessToken = await generateToken(accountId, platformId, platform, c.env.JWT_SECRET)
|
||||
|
||||
return c.json({
|
||||
access_token: accessToken,
|
||||
|
||||
@@ -9,6 +9,11 @@ export type Env = SharedHonoEnv & {
|
||||
// the new player's presence is seeded to the Orientation room so the match
|
||||
// heartbeat keeps them there instead of bouncing them to the dorm.
|
||||
RECFLARE_MATCH_PRESENCE: KVNamespace
|
||||
// HS256 signing key for issued access tokens. Set as a Cloudflare secret
|
||||
// (`wrangler secret put JWT_SECRET`) in deployed environments and via `.dev.vars`
|
||||
// locally — never committed. `keep_vars` in wrangler.jsonc stops deploys from
|
||||
// clearing it.
|
||||
JWT_SECRET: string
|
||||
}
|
||||
|
||||
/** Variables can be extended */
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/**
|
||||
* Minimal HS256 JWT generation.
|
||||
*
|
||||
* No real signing-key binding yet — uses a placeholder dev secret. Swap this for
|
||||
* a secret binding (e.g. `c.env.JWT_SECRET`) before this is used for anything real.
|
||||
* The signing key is supplied by the caller from the `JWT_SECRET` binding
|
||||
* (a Cloudflare secret in deployed envs, `.dev.vars` locally) — see context.ts.
|
||||
*/
|
||||
const DEV_SECRET = 'dev-insecure-signing-key-change-me'
|
||||
|
||||
/** Token lifetime in seconds (mirrored in the `expires_in` response field). */
|
||||
export const TOKEN_TTL_SECONDS = 3600
|
||||
@@ -32,7 +31,7 @@ function base64urlToBytes(input: string): Uint8Array {
|
||||
*/
|
||||
export async function validateAndGetAccountId(
|
||||
token: string,
|
||||
secret: string = DEV_SECRET
|
||||
secret: string
|
||||
): Promise<string | null> {
|
||||
const parts = token.split('.')
|
||||
if (parts.length !== 3) return null
|
||||
@@ -88,7 +87,7 @@ export async function generateToken(
|
||||
accountId: string,
|
||||
platformId: string,
|
||||
platform: string,
|
||||
secret: string = DEV_SECRET
|
||||
secret: string
|
||||
): Promise<string> {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const header = { alg: 'HS256', typ: 'JWT' }
|
||||
|
||||
@@ -8,6 +8,9 @@ export default defineConfig({
|
||||
miniflare: {
|
||||
bindings: {
|
||||
ENVIRONMENT: 'VITEST',
|
||||
// `.dev.vars` is gitignored, so provide a deterministic signing key
|
||||
// for tests (and CI, which has no `.dev.vars`).
|
||||
JWT_SECRET: 'test-signing-key',
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
"id": "local"
|
||||
}
|
||||
],
|
||||
// Preserve environment variables and secrets already set in Cloudflare (e.g.
|
||||
// JWT_SECRET, managed via `wrangler secret put`) instead of clearing them on
|
||||
// deploy — keeps the signing key out of source.
|
||||
"keep_vars": true,
|
||||
"logpush": false,
|
||||
"upload_source_maps": true,
|
||||
"observability": {
|
||||
|
||||
Reference in New Issue
Block a user