scorezilla 0.3.0-next.1 → 0.3.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +77 -7
- package/CHANGELOG.md +58 -0
- package/README.md +77 -0
- package/RECIPES.md +186 -0
- package/dist/{errors-B7hyC-C5.d.cts → errors-CtXMAHtJ.d.cts} +1 -1
- package/dist/{errors-B7hyC-C5.d.ts → errors-CtXMAHtJ.d.ts} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/server.cjs +215 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +249 -3
- package/dist/server.d.ts +249 -3
- package/dist/server.js +210 -2
- package/dist/server.js.map +1 -1
- package/package.json +11 -1
package/dist/server.d.cts
CHANGED
|
@@ -1,5 +1,135 @@
|
|
|
1
|
-
import { S as SecretKeyConfig, A as ApiSuccess, a as SubmitScoreResponse, L as LeaderboardResponse, P as PlayerRankResponse, W as WindowAroundResponse } from './errors-
|
|
2
|
-
export { R as RankedEntry, b as ScorezillaError, c as ScorezillaErrorCode } from './errors-
|
|
1
|
+
import { F as FetchImpl, S as SecretKeyConfig, A as ApiSuccess, a as SubmitScoreResponse, L as LeaderboardResponse, P as PlayerRankResponse, W as WindowAroundResponse } from './errors-CtXMAHtJ.cjs';
|
|
2
|
+
export { R as RankedEntry, b as ScorezillaError, c as ScorezillaErrorCode } from './errors-CtXMAHtJ.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Built-in request verifiers for {@link createScoreSubmitHandler} (#211).
|
|
6
|
+
*
|
|
7
|
+
* These turn the common "verify a JWT, derive the player id from a claim"
|
|
8
|
+
* shape into a one-liner. They produce a function with the same signature as
|
|
9
|
+
* the handler's `verify` callback, so they drop straight in:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createScoreSubmitHandler, verifySupabaseJwt } from 'scorezilla/server';
|
|
13
|
+
*
|
|
14
|
+
* createScoreSubmitHandler({
|
|
15
|
+
* secretKey, boardId,
|
|
16
|
+
* verify: verifySupabaseJwt({ supabaseUrl: process.env.SUPABASE_URL! }),
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* **`jose` is an optional peer dependency.** Only these verifiers use it, and
|
|
21
|
+
* only via a lazy `import('jose')` — so consumers who use the public-key
|
|
22
|
+
* client, the factory with their own `verify`, or a provider backend SDK never
|
|
23
|
+
* install or load it. If you call a built-in verifier without `jose` present,
|
|
24
|
+
* it throws a clear "install jose" error.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Same shape as the handler's `verify` callback. */
|
|
28
|
+
type RequestVerifier = (req: Request) => Promise<VerifiedIdentity | null>;
|
|
29
|
+
/** Options for the generic JWKS verifier {@link verifyJwt}. */
|
|
30
|
+
interface VerifyJwtOptions {
|
|
31
|
+
/** JWKS endpoint, e.g. `https://issuer/.well-known/jwks.json`. */
|
|
32
|
+
readonly jwksUrl: string | URL;
|
|
33
|
+
/** Expected `iss` claim — strongly recommended. */
|
|
34
|
+
readonly issuer?: string | string[];
|
|
35
|
+
/** Expected `aud` claim — strongly recommended. */
|
|
36
|
+
readonly audience?: string | string[];
|
|
37
|
+
/** Which claim becomes the `playerId`. Default `'sub'`. */
|
|
38
|
+
readonly claim?: string;
|
|
39
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
40
|
+
readonly fetch?: typeof fetch;
|
|
41
|
+
}
|
|
42
|
+
/** Options for the Supabase preset {@link verifySupabaseJwt}. */
|
|
43
|
+
interface VerifySupabaseJwtOptions {
|
|
44
|
+
/** Your Supabase project URL, e.g. `https://abcd.supabase.co`. */
|
|
45
|
+
readonly supabaseUrl: string;
|
|
46
|
+
/** Which claim becomes the `playerId`. Default `'sub'`. */
|
|
47
|
+
readonly claim?: string;
|
|
48
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
49
|
+
readonly fetch?: typeof fetch;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generic JWKS-backed JWT verifier. Verifies a `Bearer` token against the
|
|
53
|
+
* given JWKS and returns `{ playerId }` from the configured claim (default
|
|
54
|
+
* `sub`), or `null` if there's no token or verification fails.
|
|
55
|
+
*
|
|
56
|
+
* Covers the modern provider majority (Supabase, Clerk, Auth0, Firebase,
|
|
57
|
+
* WorkOS, …) — they differ only by `jwksUrl` / `issuer` / `audience`.
|
|
58
|
+
*
|
|
59
|
+
* @since 0.3.0
|
|
60
|
+
*/
|
|
61
|
+
declare function verifyJwt(options: VerifyJwtOptions): RequestVerifier;
|
|
62
|
+
/**
|
|
63
|
+
* Supabase preset over {@link verifyJwt}. Verifies a Supabase user JWT via the
|
|
64
|
+
* project's JWKS and derives the `playerId` from `sub`.
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* verify: verifySupabaseJwt({ supabaseUrl: process.env.SUPABASE_URL! })
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @since 0.3.0
|
|
71
|
+
*/
|
|
72
|
+
declare function verifySupabaseJwt(options: VerifySupabaseJwtOptions): RequestVerifier;
|
|
73
|
+
/** Options for the Clerk preset {@link verifyClerkJwt}. */
|
|
74
|
+
interface VerifyClerkJwtOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Your Clerk instance issuer (the token's `iss`), e.g.
|
|
77
|
+
* `https://clerk.your-app.com` or `https://<slug>.clerk.accounts.dev`. The
|
|
78
|
+
* JWKS URL is derived from it.
|
|
79
|
+
*/
|
|
80
|
+
readonly issuer: string;
|
|
81
|
+
/**
|
|
82
|
+
* Expected `aud`. Clerk session tokens have **no** `aud` by default, so leave
|
|
83
|
+
* this unset unless you added one via a custom JWT template.
|
|
84
|
+
*/
|
|
85
|
+
readonly audience?: string | string[];
|
|
86
|
+
/** Which claim becomes the `playerId`. Default `'sub'` (the Clerk user id). */
|
|
87
|
+
readonly claim?: string;
|
|
88
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
89
|
+
readonly fetch?: typeof fetch;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Clerk preset over {@link verifyJwt}. Verifies a Clerk session JWT against the
|
|
93
|
+
* instance JWKS and derives the `playerId` from `sub` (the Clerk user id).
|
|
94
|
+
*
|
|
95
|
+
* @since 0.3.0
|
|
96
|
+
*/
|
|
97
|
+
declare function verifyClerkJwt(options: VerifyClerkJwtOptions): RequestVerifier;
|
|
98
|
+
/** Options for the Auth0 preset {@link verifyAuth0Jwt}. */
|
|
99
|
+
interface VerifyAuth0JwtOptions {
|
|
100
|
+
/** Your Auth0 domain, e.g. `your-tenant.us.auth0.com` (scheme optional). */
|
|
101
|
+
readonly domain: string;
|
|
102
|
+
/** Your API identifier — the access token's `aud`. */
|
|
103
|
+
readonly audience: string | string[];
|
|
104
|
+
/** Which claim becomes the `playerId`. Default `'sub'`. */
|
|
105
|
+
readonly claim?: string;
|
|
106
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
107
|
+
readonly fetch?: typeof fetch;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Auth0 preset over {@link verifyJwt}. Note Auth0's issuer carries a trailing
|
|
111
|
+
* slash (`https://<domain>/`) — the preset adds it for you.
|
|
112
|
+
*
|
|
113
|
+
* @since 0.3.0
|
|
114
|
+
*/
|
|
115
|
+
declare function verifyAuth0Jwt(options: VerifyAuth0JwtOptions): RequestVerifier;
|
|
116
|
+
/** Options for the Firebase preset {@link verifyFirebaseIdToken}. */
|
|
117
|
+
interface VerifyFirebaseIdTokenOptions {
|
|
118
|
+
/** Your Firebase project id — both the token's `aud` and the issuer suffix. */
|
|
119
|
+
readonly projectId: string;
|
|
120
|
+
/** Which claim becomes the `playerId`. Default `'sub'` (the Firebase uid). */
|
|
121
|
+
readonly claim?: string;
|
|
122
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
123
|
+
readonly fetch?: typeof fetch;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Firebase Authentication preset over {@link verifyJwt}. Verifies a Firebase ID
|
|
127
|
+
* token (`iss = https://securetoken.google.com/<projectId>`, `aud = projectId`)
|
|
128
|
+
* and derives the `playerId` from `sub` (the Firebase uid).
|
|
129
|
+
*
|
|
130
|
+
* @since 0.3.0
|
|
131
|
+
*/
|
|
132
|
+
declare function verifyFirebaseIdToken(options: VerifyFirebaseIdTokenOptions): RequestVerifier;
|
|
3
133
|
|
|
4
134
|
/**
|
|
5
135
|
* `scorezilla/server` — HMAC-signed adapter for game backends (#17, v0.2.0).
|
|
@@ -144,5 +274,121 @@ declare class Scorezilla {
|
|
|
144
274
|
/** Fetch the slice of entries surrounding a player. */
|
|
145
275
|
getWindowAround(input: GetWindowAroundInput): Promise<ApiSuccess<WindowAroundResponse>>;
|
|
146
276
|
}
|
|
277
|
+
/** A value or a promise of it. */
|
|
278
|
+
type Awaitable<T> = T | Promise<T>;
|
|
279
|
+
/**
|
|
280
|
+
* The trusted identity produced by your `verify` callback. `playerId` is what
|
|
281
|
+
* gets submitted — derived from the *verified* request, never the request body.
|
|
282
|
+
*/
|
|
283
|
+
interface VerifiedIdentity {
|
|
284
|
+
/** Stable, trusted per-player id (e.g. your auth user id). Avoid PII. */
|
|
285
|
+
readonly playerId: string;
|
|
286
|
+
/** Optional trusted metadata merged into the submission (wins over the body). */
|
|
287
|
+
readonly metadata?: Record<string, unknown> | undefined;
|
|
288
|
+
}
|
|
289
|
+
/** The score payload extracted from the request body. */
|
|
290
|
+
interface ParsedScoreSubmission {
|
|
291
|
+
readonly score: number;
|
|
292
|
+
readonly metadata?: Record<string, unknown> | undefined;
|
|
293
|
+
}
|
|
294
|
+
/** Decision returned by an optional pre-verify rate-limit gate. */
|
|
295
|
+
interface RateLimitDecision {
|
|
296
|
+
readonly ok: boolean;
|
|
297
|
+
readonly retryAfterSeconds?: number | undefined;
|
|
298
|
+
}
|
|
299
|
+
/** CORS config for the handler. Omit entirely for same-origin endpoints. */
|
|
300
|
+
interface ScoreSubmitCorsOptions {
|
|
301
|
+
/**
|
|
302
|
+
* Allowed origin(s): an exact string, an array of strings, a predicate, or
|
|
303
|
+
* `true` to reflect any `Origin`. `false` disables reflection.
|
|
304
|
+
*/
|
|
305
|
+
readonly origin: string | readonly string[] | boolean | ((origin: string | null) => boolean);
|
|
306
|
+
/** Methods for the preflight. Default `['POST', 'OPTIONS']`. */
|
|
307
|
+
readonly methods?: readonly string[];
|
|
308
|
+
/** Request headers for the preflight. Default `['content-type', 'authorization']`. */
|
|
309
|
+
readonly headers?: readonly string[];
|
|
310
|
+
/** `Access-Control-Max-Age` seconds. Default `600`. */
|
|
311
|
+
readonly maxAgeSeconds?: number;
|
|
312
|
+
}
|
|
313
|
+
/** Configuration for {@link createScoreSubmitHandler}. */
|
|
314
|
+
interface CreateScoreSubmitHandlerConfig {
|
|
315
|
+
/** Your `sk_live_*` secret. Server-only — never ship to a browser. */
|
|
316
|
+
readonly secretKey: string;
|
|
317
|
+
/** The board UUID this handler submits to. */
|
|
318
|
+
readonly boardId: string;
|
|
319
|
+
/**
|
|
320
|
+
* Authenticate the request and return the **trusted** `playerId` (and any
|
|
321
|
+
* trusted metadata). Return `null` to reject (→ 401); throwing also rejects.
|
|
322
|
+
* Read identity from headers/cookies — the request **body** is reserved for
|
|
323
|
+
* the score payload (see `parseSubmission`). This callback is the universal
|
|
324
|
+
* seam: wire any auth (provider SDK, JWT verify, DB session lookup) here.
|
|
325
|
+
*/
|
|
326
|
+
readonly verify: (req: Request) => Awaitable<VerifiedIdentity | null>;
|
|
327
|
+
/**
|
|
328
|
+
* Extract the score (+ optional client metadata) from the request. Defaults
|
|
329
|
+
* to JSON `{ score: number, metadata?: object }`. Return `null` (or throw)
|
|
330
|
+
* to reject as a bad request (→ 400). Note: a `playerId` in the body is
|
|
331
|
+
* always ignored — identity comes only from `verify`.
|
|
332
|
+
*/
|
|
333
|
+
readonly parseSubmission?: (req: Request) => Awaitable<ParsedScoreSubmission | null>;
|
|
334
|
+
/**
|
|
335
|
+
* Optional gate that runs **before** `verify` (cheap defense — e.g. a per-IP
|
|
336
|
+
* rate limit, so unauthenticated spam can't drive auth/crypto work). Return
|
|
337
|
+
* `{ ok: false }` to reject with 429. Per-user limits belong inside `verify`.
|
|
338
|
+
*/
|
|
339
|
+
readonly rateLimit?: (req: Request) => Awaitable<RateLimitDecision>;
|
|
340
|
+
/** Optional CORS handling (OPTIONS preflight + reflected `Access-Control-Allow-Origin`). */
|
|
341
|
+
readonly cors?: ScoreSubmitCorsOptions;
|
|
342
|
+
/** Override the API base URL (self-host / testing). */
|
|
343
|
+
readonly baseUrl?: string;
|
|
344
|
+
/** Inject a `fetch` (testing / custom transport). */
|
|
345
|
+
readonly fetch?: FetchImpl;
|
|
346
|
+
/** Max transport retries (default SDK policy). Set `0` to disable. */
|
|
347
|
+
readonly maxRetries?: number;
|
|
348
|
+
/** Per-attempt timeout in ms. */
|
|
349
|
+
readonly timeoutMs?: number;
|
|
350
|
+
}
|
|
351
|
+
/** The web-standard request handler returned by {@link createScoreSubmitHandler}. */
|
|
352
|
+
type ScoreSubmitHandler = (req: Request) => Promise<Response>;
|
|
353
|
+
/**
|
|
354
|
+
* Build a turnkey, framework-agnostic secure score-submit endpoint.
|
|
355
|
+
*
|
|
356
|
+
* Returns a standard `(Request) => Promise<Response>` handler — drop it into a
|
|
357
|
+
* Cloudflare Worker, a Next.js route handler, Hono, Deno, Bun, or any runtime
|
|
358
|
+
* that speaks web `Request`/`Response`. You supply only what's app-specific:
|
|
359
|
+
* the `secretKey`, the `boardId`, and a `verify` callback that proves identity
|
|
360
|
+
* and returns the trusted `playerId`. The handler owns parsing/validation, the
|
|
361
|
+
* "playerId comes from `verify`, never the body" guarantee, signing via the
|
|
362
|
+
* HMAC server client, and error → HTTP mapping.
|
|
363
|
+
*
|
|
364
|
+
* **Construction patterns.** In Node/Next, secrets are in `process.env`, so
|
|
365
|
+
* build the handler once at module scope. In Cloudflare Workers, secrets live
|
|
366
|
+
* on the per-request `env` binding, so build it inside `fetch` (closing over
|
|
367
|
+
* `env`) — the construction is cheap (no I/O).
|
|
368
|
+
*
|
|
369
|
+
* @example Cloudflare Worker
|
|
370
|
+
* ```ts
|
|
371
|
+
* import { createScoreSubmitHandler } from 'scorezilla/server';
|
|
372
|
+
*
|
|
373
|
+
* export default {
|
|
374
|
+
* fetch(req, env) {
|
|
375
|
+
* const handler = createScoreSubmitHandler({
|
|
376
|
+
* secretKey: env.SCOREZILLA_SECRET_KEY,
|
|
377
|
+
* boardId: env.SCOREZILLA_BOARD_ID,
|
|
378
|
+
* verify: async (r) => {
|
|
379
|
+
* const user = await verifyMyAuth(r, env); // your auth: SDK / JWT / DB
|
|
380
|
+
* return user ? { playerId: user.id } : null;
|
|
381
|
+
* },
|
|
382
|
+
* cors: { origin: 'https://mygame.example' },
|
|
383
|
+
* });
|
|
384
|
+
* return handler(req);
|
|
385
|
+
* },
|
|
386
|
+
* };
|
|
387
|
+
* ```
|
|
388
|
+
*
|
|
389
|
+
* @since 0.3.0
|
|
390
|
+
* @stability stable
|
|
391
|
+
*/
|
|
392
|
+
declare function createScoreSubmitHandler(config: CreateScoreSubmitHandlerConfig): ScoreSubmitHandler;
|
|
147
393
|
|
|
148
|
-
export { ApiSuccess, type CancellableInput, type GetLeaderboardInput, type GetPlayerRankInput, type GetWindowAroundInput, LeaderboardResponse, PlayerRankResponse, Scorezilla, type SubmitScoreInput, SubmitScoreResponse, WindowAroundResponse };
|
|
394
|
+
export { ApiSuccess, type CancellableInput, type CreateScoreSubmitHandlerConfig, type GetLeaderboardInput, type GetPlayerRankInput, type GetWindowAroundInput, LeaderboardResponse, type ParsedScoreSubmission, PlayerRankResponse, type RateLimitDecision, type RequestVerifier, type ScoreSubmitCorsOptions, type ScoreSubmitHandler, Scorezilla, type SubmitScoreInput, SubmitScoreResponse, type VerifiedIdentity, type VerifyAuth0JwtOptions, type VerifyClerkJwtOptions, type VerifyFirebaseIdTokenOptions, type VerifyJwtOptions, type VerifySupabaseJwtOptions, WindowAroundResponse, createScoreSubmitHandler, verifyAuth0Jwt, verifyClerkJwt, verifyFirebaseIdToken, verifyJwt, verifySupabaseJwt };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,135 @@
|
|
|
1
|
-
import { S as SecretKeyConfig, A as ApiSuccess, a as SubmitScoreResponse, L as LeaderboardResponse, P as PlayerRankResponse, W as WindowAroundResponse } from './errors-
|
|
2
|
-
export { R as RankedEntry, b as ScorezillaError, c as ScorezillaErrorCode } from './errors-
|
|
1
|
+
import { F as FetchImpl, S as SecretKeyConfig, A as ApiSuccess, a as SubmitScoreResponse, L as LeaderboardResponse, P as PlayerRankResponse, W as WindowAroundResponse } from './errors-CtXMAHtJ.js';
|
|
2
|
+
export { R as RankedEntry, b as ScorezillaError, c as ScorezillaErrorCode } from './errors-CtXMAHtJ.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Built-in request verifiers for {@link createScoreSubmitHandler} (#211).
|
|
6
|
+
*
|
|
7
|
+
* These turn the common "verify a JWT, derive the player id from a claim"
|
|
8
|
+
* shape into a one-liner. They produce a function with the same signature as
|
|
9
|
+
* the handler's `verify` callback, so they drop straight in:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createScoreSubmitHandler, verifySupabaseJwt } from 'scorezilla/server';
|
|
13
|
+
*
|
|
14
|
+
* createScoreSubmitHandler({
|
|
15
|
+
* secretKey, boardId,
|
|
16
|
+
* verify: verifySupabaseJwt({ supabaseUrl: process.env.SUPABASE_URL! }),
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* **`jose` is an optional peer dependency.** Only these verifiers use it, and
|
|
21
|
+
* only via a lazy `import('jose')` — so consumers who use the public-key
|
|
22
|
+
* client, the factory with their own `verify`, or a provider backend SDK never
|
|
23
|
+
* install or load it. If you call a built-in verifier without `jose` present,
|
|
24
|
+
* it throws a clear "install jose" error.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Same shape as the handler's `verify` callback. */
|
|
28
|
+
type RequestVerifier = (req: Request) => Promise<VerifiedIdentity | null>;
|
|
29
|
+
/** Options for the generic JWKS verifier {@link verifyJwt}. */
|
|
30
|
+
interface VerifyJwtOptions {
|
|
31
|
+
/** JWKS endpoint, e.g. `https://issuer/.well-known/jwks.json`. */
|
|
32
|
+
readonly jwksUrl: string | URL;
|
|
33
|
+
/** Expected `iss` claim — strongly recommended. */
|
|
34
|
+
readonly issuer?: string | string[];
|
|
35
|
+
/** Expected `aud` claim — strongly recommended. */
|
|
36
|
+
readonly audience?: string | string[];
|
|
37
|
+
/** Which claim becomes the `playerId`. Default `'sub'`. */
|
|
38
|
+
readonly claim?: string;
|
|
39
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
40
|
+
readonly fetch?: typeof fetch;
|
|
41
|
+
}
|
|
42
|
+
/** Options for the Supabase preset {@link verifySupabaseJwt}. */
|
|
43
|
+
interface VerifySupabaseJwtOptions {
|
|
44
|
+
/** Your Supabase project URL, e.g. `https://abcd.supabase.co`. */
|
|
45
|
+
readonly supabaseUrl: string;
|
|
46
|
+
/** Which claim becomes the `playerId`. Default `'sub'`. */
|
|
47
|
+
readonly claim?: string;
|
|
48
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
49
|
+
readonly fetch?: typeof fetch;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generic JWKS-backed JWT verifier. Verifies a `Bearer` token against the
|
|
53
|
+
* given JWKS and returns `{ playerId }` from the configured claim (default
|
|
54
|
+
* `sub`), or `null` if there's no token or verification fails.
|
|
55
|
+
*
|
|
56
|
+
* Covers the modern provider majority (Supabase, Clerk, Auth0, Firebase,
|
|
57
|
+
* WorkOS, …) — they differ only by `jwksUrl` / `issuer` / `audience`.
|
|
58
|
+
*
|
|
59
|
+
* @since 0.3.0
|
|
60
|
+
*/
|
|
61
|
+
declare function verifyJwt(options: VerifyJwtOptions): RequestVerifier;
|
|
62
|
+
/**
|
|
63
|
+
* Supabase preset over {@link verifyJwt}. Verifies a Supabase user JWT via the
|
|
64
|
+
* project's JWKS and derives the `playerId` from `sub`.
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* verify: verifySupabaseJwt({ supabaseUrl: process.env.SUPABASE_URL! })
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @since 0.3.0
|
|
71
|
+
*/
|
|
72
|
+
declare function verifySupabaseJwt(options: VerifySupabaseJwtOptions): RequestVerifier;
|
|
73
|
+
/** Options for the Clerk preset {@link verifyClerkJwt}. */
|
|
74
|
+
interface VerifyClerkJwtOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Your Clerk instance issuer (the token's `iss`), e.g.
|
|
77
|
+
* `https://clerk.your-app.com` or `https://<slug>.clerk.accounts.dev`. The
|
|
78
|
+
* JWKS URL is derived from it.
|
|
79
|
+
*/
|
|
80
|
+
readonly issuer: string;
|
|
81
|
+
/**
|
|
82
|
+
* Expected `aud`. Clerk session tokens have **no** `aud` by default, so leave
|
|
83
|
+
* this unset unless you added one via a custom JWT template.
|
|
84
|
+
*/
|
|
85
|
+
readonly audience?: string | string[];
|
|
86
|
+
/** Which claim becomes the `playerId`. Default `'sub'` (the Clerk user id). */
|
|
87
|
+
readonly claim?: string;
|
|
88
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
89
|
+
readonly fetch?: typeof fetch;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Clerk preset over {@link verifyJwt}. Verifies a Clerk session JWT against the
|
|
93
|
+
* instance JWKS and derives the `playerId` from `sub` (the Clerk user id).
|
|
94
|
+
*
|
|
95
|
+
* @since 0.3.0
|
|
96
|
+
*/
|
|
97
|
+
declare function verifyClerkJwt(options: VerifyClerkJwtOptions): RequestVerifier;
|
|
98
|
+
/** Options for the Auth0 preset {@link verifyAuth0Jwt}. */
|
|
99
|
+
interface VerifyAuth0JwtOptions {
|
|
100
|
+
/** Your Auth0 domain, e.g. `your-tenant.us.auth0.com` (scheme optional). */
|
|
101
|
+
readonly domain: string;
|
|
102
|
+
/** Your API identifier — the access token's `aud`. */
|
|
103
|
+
readonly audience: string | string[];
|
|
104
|
+
/** Which claim becomes the `playerId`. Default `'sub'`. */
|
|
105
|
+
readonly claim?: string;
|
|
106
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
107
|
+
readonly fetch?: typeof fetch;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Auth0 preset over {@link verifyJwt}. Note Auth0's issuer carries a trailing
|
|
111
|
+
* slash (`https://<domain>/`) — the preset adds it for you.
|
|
112
|
+
*
|
|
113
|
+
* @since 0.3.0
|
|
114
|
+
*/
|
|
115
|
+
declare function verifyAuth0Jwt(options: VerifyAuth0JwtOptions): RequestVerifier;
|
|
116
|
+
/** Options for the Firebase preset {@link verifyFirebaseIdToken}. */
|
|
117
|
+
interface VerifyFirebaseIdTokenOptions {
|
|
118
|
+
/** Your Firebase project id — both the token's `aud` and the issuer suffix. */
|
|
119
|
+
readonly projectId: string;
|
|
120
|
+
/** Which claim becomes the `playerId`. Default `'sub'` (the Firebase uid). */
|
|
121
|
+
readonly claim?: string;
|
|
122
|
+
/** Custom `fetch` for retrieving the JWKS (proxy / self-host / testing). */
|
|
123
|
+
readonly fetch?: typeof fetch;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Firebase Authentication preset over {@link verifyJwt}. Verifies a Firebase ID
|
|
127
|
+
* token (`iss = https://securetoken.google.com/<projectId>`, `aud = projectId`)
|
|
128
|
+
* and derives the `playerId` from `sub` (the Firebase uid).
|
|
129
|
+
*
|
|
130
|
+
* @since 0.3.0
|
|
131
|
+
*/
|
|
132
|
+
declare function verifyFirebaseIdToken(options: VerifyFirebaseIdTokenOptions): RequestVerifier;
|
|
3
133
|
|
|
4
134
|
/**
|
|
5
135
|
* `scorezilla/server` — HMAC-signed adapter for game backends (#17, v0.2.0).
|
|
@@ -144,5 +274,121 @@ declare class Scorezilla {
|
|
|
144
274
|
/** Fetch the slice of entries surrounding a player. */
|
|
145
275
|
getWindowAround(input: GetWindowAroundInput): Promise<ApiSuccess<WindowAroundResponse>>;
|
|
146
276
|
}
|
|
277
|
+
/** A value or a promise of it. */
|
|
278
|
+
type Awaitable<T> = T | Promise<T>;
|
|
279
|
+
/**
|
|
280
|
+
* The trusted identity produced by your `verify` callback. `playerId` is what
|
|
281
|
+
* gets submitted — derived from the *verified* request, never the request body.
|
|
282
|
+
*/
|
|
283
|
+
interface VerifiedIdentity {
|
|
284
|
+
/** Stable, trusted per-player id (e.g. your auth user id). Avoid PII. */
|
|
285
|
+
readonly playerId: string;
|
|
286
|
+
/** Optional trusted metadata merged into the submission (wins over the body). */
|
|
287
|
+
readonly metadata?: Record<string, unknown> | undefined;
|
|
288
|
+
}
|
|
289
|
+
/** The score payload extracted from the request body. */
|
|
290
|
+
interface ParsedScoreSubmission {
|
|
291
|
+
readonly score: number;
|
|
292
|
+
readonly metadata?: Record<string, unknown> | undefined;
|
|
293
|
+
}
|
|
294
|
+
/** Decision returned by an optional pre-verify rate-limit gate. */
|
|
295
|
+
interface RateLimitDecision {
|
|
296
|
+
readonly ok: boolean;
|
|
297
|
+
readonly retryAfterSeconds?: number | undefined;
|
|
298
|
+
}
|
|
299
|
+
/** CORS config for the handler. Omit entirely for same-origin endpoints. */
|
|
300
|
+
interface ScoreSubmitCorsOptions {
|
|
301
|
+
/**
|
|
302
|
+
* Allowed origin(s): an exact string, an array of strings, a predicate, or
|
|
303
|
+
* `true` to reflect any `Origin`. `false` disables reflection.
|
|
304
|
+
*/
|
|
305
|
+
readonly origin: string | readonly string[] | boolean | ((origin: string | null) => boolean);
|
|
306
|
+
/** Methods for the preflight. Default `['POST', 'OPTIONS']`. */
|
|
307
|
+
readonly methods?: readonly string[];
|
|
308
|
+
/** Request headers for the preflight. Default `['content-type', 'authorization']`. */
|
|
309
|
+
readonly headers?: readonly string[];
|
|
310
|
+
/** `Access-Control-Max-Age` seconds. Default `600`. */
|
|
311
|
+
readonly maxAgeSeconds?: number;
|
|
312
|
+
}
|
|
313
|
+
/** Configuration for {@link createScoreSubmitHandler}. */
|
|
314
|
+
interface CreateScoreSubmitHandlerConfig {
|
|
315
|
+
/** Your `sk_live_*` secret. Server-only — never ship to a browser. */
|
|
316
|
+
readonly secretKey: string;
|
|
317
|
+
/** The board UUID this handler submits to. */
|
|
318
|
+
readonly boardId: string;
|
|
319
|
+
/**
|
|
320
|
+
* Authenticate the request and return the **trusted** `playerId` (and any
|
|
321
|
+
* trusted metadata). Return `null` to reject (→ 401); throwing also rejects.
|
|
322
|
+
* Read identity from headers/cookies — the request **body** is reserved for
|
|
323
|
+
* the score payload (see `parseSubmission`). This callback is the universal
|
|
324
|
+
* seam: wire any auth (provider SDK, JWT verify, DB session lookup) here.
|
|
325
|
+
*/
|
|
326
|
+
readonly verify: (req: Request) => Awaitable<VerifiedIdentity | null>;
|
|
327
|
+
/**
|
|
328
|
+
* Extract the score (+ optional client metadata) from the request. Defaults
|
|
329
|
+
* to JSON `{ score: number, metadata?: object }`. Return `null` (or throw)
|
|
330
|
+
* to reject as a bad request (→ 400). Note: a `playerId` in the body is
|
|
331
|
+
* always ignored — identity comes only from `verify`.
|
|
332
|
+
*/
|
|
333
|
+
readonly parseSubmission?: (req: Request) => Awaitable<ParsedScoreSubmission | null>;
|
|
334
|
+
/**
|
|
335
|
+
* Optional gate that runs **before** `verify` (cheap defense — e.g. a per-IP
|
|
336
|
+
* rate limit, so unauthenticated spam can't drive auth/crypto work). Return
|
|
337
|
+
* `{ ok: false }` to reject with 429. Per-user limits belong inside `verify`.
|
|
338
|
+
*/
|
|
339
|
+
readonly rateLimit?: (req: Request) => Awaitable<RateLimitDecision>;
|
|
340
|
+
/** Optional CORS handling (OPTIONS preflight + reflected `Access-Control-Allow-Origin`). */
|
|
341
|
+
readonly cors?: ScoreSubmitCorsOptions;
|
|
342
|
+
/** Override the API base URL (self-host / testing). */
|
|
343
|
+
readonly baseUrl?: string;
|
|
344
|
+
/** Inject a `fetch` (testing / custom transport). */
|
|
345
|
+
readonly fetch?: FetchImpl;
|
|
346
|
+
/** Max transport retries (default SDK policy). Set `0` to disable. */
|
|
347
|
+
readonly maxRetries?: number;
|
|
348
|
+
/** Per-attempt timeout in ms. */
|
|
349
|
+
readonly timeoutMs?: number;
|
|
350
|
+
}
|
|
351
|
+
/** The web-standard request handler returned by {@link createScoreSubmitHandler}. */
|
|
352
|
+
type ScoreSubmitHandler = (req: Request) => Promise<Response>;
|
|
353
|
+
/**
|
|
354
|
+
* Build a turnkey, framework-agnostic secure score-submit endpoint.
|
|
355
|
+
*
|
|
356
|
+
* Returns a standard `(Request) => Promise<Response>` handler — drop it into a
|
|
357
|
+
* Cloudflare Worker, a Next.js route handler, Hono, Deno, Bun, or any runtime
|
|
358
|
+
* that speaks web `Request`/`Response`. You supply only what's app-specific:
|
|
359
|
+
* the `secretKey`, the `boardId`, and a `verify` callback that proves identity
|
|
360
|
+
* and returns the trusted `playerId`. The handler owns parsing/validation, the
|
|
361
|
+
* "playerId comes from `verify`, never the body" guarantee, signing via the
|
|
362
|
+
* HMAC server client, and error → HTTP mapping.
|
|
363
|
+
*
|
|
364
|
+
* **Construction patterns.** In Node/Next, secrets are in `process.env`, so
|
|
365
|
+
* build the handler once at module scope. In Cloudflare Workers, secrets live
|
|
366
|
+
* on the per-request `env` binding, so build it inside `fetch` (closing over
|
|
367
|
+
* `env`) — the construction is cheap (no I/O).
|
|
368
|
+
*
|
|
369
|
+
* @example Cloudflare Worker
|
|
370
|
+
* ```ts
|
|
371
|
+
* import { createScoreSubmitHandler } from 'scorezilla/server';
|
|
372
|
+
*
|
|
373
|
+
* export default {
|
|
374
|
+
* fetch(req, env) {
|
|
375
|
+
* const handler = createScoreSubmitHandler({
|
|
376
|
+
* secretKey: env.SCOREZILLA_SECRET_KEY,
|
|
377
|
+
* boardId: env.SCOREZILLA_BOARD_ID,
|
|
378
|
+
* verify: async (r) => {
|
|
379
|
+
* const user = await verifyMyAuth(r, env); // your auth: SDK / JWT / DB
|
|
380
|
+
* return user ? { playerId: user.id } : null;
|
|
381
|
+
* },
|
|
382
|
+
* cors: { origin: 'https://mygame.example' },
|
|
383
|
+
* });
|
|
384
|
+
* return handler(req);
|
|
385
|
+
* },
|
|
386
|
+
* };
|
|
387
|
+
* ```
|
|
388
|
+
*
|
|
389
|
+
* @since 0.3.0
|
|
390
|
+
* @stability stable
|
|
391
|
+
*/
|
|
392
|
+
declare function createScoreSubmitHandler(config: CreateScoreSubmitHandlerConfig): ScoreSubmitHandler;
|
|
147
393
|
|
|
148
|
-
export { ApiSuccess, type CancellableInput, type GetLeaderboardInput, type GetPlayerRankInput, type GetWindowAroundInput, LeaderboardResponse, PlayerRankResponse, Scorezilla, type SubmitScoreInput, SubmitScoreResponse, WindowAroundResponse };
|
|
394
|
+
export { ApiSuccess, type CancellableInput, type CreateScoreSubmitHandlerConfig, type GetLeaderboardInput, type GetPlayerRankInput, type GetWindowAroundInput, LeaderboardResponse, type ParsedScoreSubmission, PlayerRankResponse, type RateLimitDecision, type RequestVerifier, type ScoreSubmitCorsOptions, type ScoreSubmitHandler, Scorezilla, type SubmitScoreInput, SubmitScoreResponse, type VerifiedIdentity, type VerifyAuth0JwtOptions, type VerifyClerkJwtOptions, type VerifyFirebaseIdTokenOptions, type VerifyJwtOptions, type VerifySupabaseJwtOptions, WindowAroundResponse, createScoreSubmitHandler, verifyAuth0Jwt, verifyClerkJwt, verifyFirebaseIdToken, verifyJwt, verifySupabaseJwt };
|