@spfn/auth 0.2.0-beta.85 → 0.2.0-beta.86
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/README.md +60 -0
- package/dist/{authenticate-ngWdlWWL.d.ts → authenticate-DTA7W4v2.d.ts} +4 -4
- package/dist/client-proof.d.ts +8 -2
- package/dist/client-proof.js +53 -14
- package/dist/client-proof.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/server.d.ts +70 -70
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -580,6 +580,66 @@ HTTP status).
|
|
|
580
580
|
- Dev/test scope: key provisioning is injection at construction; no persistence. A production
|
|
581
581
|
key/issuance story is a separate work item.
|
|
582
582
|
|
|
583
|
+
### Usage — dev surface (mobile integration target)
|
|
584
|
+
|
|
585
|
+
The fastest path: run the packaged dev handler, which already serves the three contract
|
|
586
|
+
operations and `/control`. `examples/04-mobile-contract-dev` is exactly this, runnable.
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
import { serve } from '@hono/node-server';
|
|
590
|
+
import { createClientProofDevHandler } from '@spfn/auth/client-proof';
|
|
591
|
+
|
|
592
|
+
const handler = createClientProofDevHandler({
|
|
593
|
+
keys: { 'key-dev-0001': process.env.SPFN_CLIENT_PROOF_KEY! }, // keyId → HMAC key
|
|
594
|
+
sessionTtlMillis: 600_000,
|
|
595
|
+
});
|
|
596
|
+
serve({ fetch: handler.fetch, port: 8791, hostname: '127.0.0.1' });
|
|
597
|
+
// handler.controlToken — pass to the test harness for /control routes
|
|
598
|
+
// handler.state — revokeKey() / expireSessions() / stats() from code
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Usage — mounting on your own Hono/SPFN server
|
|
602
|
+
|
|
603
|
+
Protect `requiresSession` operations with the guard, and assemble the handshake route from
|
|
604
|
+
the exported primitives (`admitClientProofRequest` + `state.openSession`):
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import { Hono } from 'hono';
|
|
608
|
+
import {
|
|
609
|
+
ClientProofState, createClientProofGuard, admitClientProofRequest,
|
|
610
|
+
decodeHandshakeRequest, encodeHandshakeResponse, encodeCanonicalJson,
|
|
611
|
+
ClientProofRefusal, newHexId,
|
|
612
|
+
} from '@spfn/auth/client-proof';
|
|
613
|
+
|
|
614
|
+
const state = new ClientProofState({ keys: { 'key-dev-0001': process.env.SPFN_CLIENT_PROOF_KEY! } });
|
|
615
|
+
const app = new Hono();
|
|
616
|
+
|
|
617
|
+
app.post('/v1/auth/client-proof/handshake', async (c) =>
|
|
618
|
+
{
|
|
619
|
+
const body = new Uint8Array(await c.req.arrayBuffer());
|
|
620
|
+
const admission = admitClientProofRequest({
|
|
621
|
+
state, headers: c.req.raw.headers, method: 'POST',
|
|
622
|
+
path: '/v1/auth/client-proof/handshake', requiresSession: false, body,
|
|
623
|
+
});
|
|
624
|
+
if (!admission.admitted)
|
|
625
|
+
{
|
|
626
|
+
return c.newResponse(admission.refusal.envelopeBytes(newHexId()).slice().buffer,
|
|
627
|
+
admission.refusal.httpStatus as 401, { 'content-type': 'application/json' });
|
|
628
|
+
}
|
|
629
|
+
const request = decodeHandshakeRequest(admission.value);
|
|
630
|
+
const opened = state.openSession(request.clientId, request.keyId);
|
|
631
|
+
return c.newResponse(
|
|
632
|
+
encodeCanonicalJson(encodeHandshakeResponse(opened.sessionId, BigInt(opened.expiresAtMillis))).slice().buffer,
|
|
633
|
+
200, { 'content-type': 'application/json' });
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// Any route behind the guard sees clientType='mobile' and c.get('clientProof')
|
|
637
|
+
app.post('/v1/echo', createClientProofGuard(state), (c) => { /* handler */ });
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Responses and errors MUST be canonical bytes with the contract envelope — build them with
|
|
641
|
+
`encodeCanonicalJson`/`ClientProofRefusal`, never `c.json()` (key order and int64 differ).
|
|
642
|
+
|
|
583
643
|
## Account Deletion & Recovery
|
|
584
644
|
|
|
585
645
|
Grace-period deletion with in-window recovery, an admin/GDPR-response entry point for immediate
|
|
@@ -732,7 +732,7 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
732
732
|
id: number;
|
|
733
733
|
name: string;
|
|
734
734
|
displayName: string;
|
|
735
|
-
category: "
|
|
735
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
|
|
736
736
|
}[];
|
|
737
737
|
userId: number;
|
|
738
738
|
publicId: string;
|
|
@@ -1029,8 +1029,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
1029
1029
|
}, {}, {
|
|
1030
1030
|
roles: {
|
|
1031
1031
|
description: string | null;
|
|
1032
|
-
name: string;
|
|
1033
1032
|
id: number;
|
|
1033
|
+
name: string;
|
|
1034
1034
|
displayName: string;
|
|
1035
1035
|
isBuiltin: boolean;
|
|
1036
1036
|
isSystem: boolean;
|
|
@@ -1051,8 +1051,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
1051
1051
|
}, {}, {
|
|
1052
1052
|
role: {
|
|
1053
1053
|
description: string | null;
|
|
1054
|
-
name: string;
|
|
1055
1054
|
id: number;
|
|
1055
|
+
name: string;
|
|
1056
1056
|
displayName: string;
|
|
1057
1057
|
isBuiltin: boolean;
|
|
1058
1058
|
isSystem: boolean;
|
|
@@ -1075,8 +1075,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
1075
1075
|
}, {}, {
|
|
1076
1076
|
role: {
|
|
1077
1077
|
description: string | null;
|
|
1078
|
-
name: string;
|
|
1079
1078
|
id: number;
|
|
1079
|
+
name: string;
|
|
1080
1080
|
displayName: string;
|
|
1081
1081
|
isBuiltin: boolean;
|
|
1082
1082
|
isSystem: boolean;
|
package/dist/client-proof.d.ts
CHANGED
|
@@ -301,8 +301,10 @@ declare function admitClientProofRequest(args: {
|
|
|
301
301
|
* canonical values. Strict on purpose: a missing required field, a wrong type
|
|
302
302
|
* or an unknown field is "not the request type this operation declares".
|
|
303
303
|
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
304
|
+
* This module is the source of truth for `operations`. The exported contract
|
|
305
|
+
* bundle (`contracts/mobile/spfn-mobile-contract.v1.json`) is generated from it
|
|
306
|
+
* by `contract-bundle.ts`; spfn-mobile consumes that export rather than the
|
|
307
|
+
* other way round.
|
|
306
308
|
*
|
|
307
309
|
* @module server/client-proof/contract-types
|
|
308
310
|
*/
|
|
@@ -311,7 +313,11 @@ interface ContractOperation {
|
|
|
311
313
|
id: 'auth.clientProof.handshake' | 'echo.send' | 'items.list';
|
|
312
314
|
method: 'POST';
|
|
313
315
|
path: string;
|
|
316
|
+
authProfile: 'clientProofV1';
|
|
314
317
|
requiresSession: boolean;
|
|
318
|
+
requestType: string;
|
|
319
|
+
responseType: string;
|
|
320
|
+
summary: string;
|
|
315
321
|
}
|
|
316
322
|
declare const CONTRACT_OPERATIONS: readonly ContractOperation[];
|
|
317
323
|
/** The body is canonical JSON but not the declared request type. */
|
package/dist/client-proof.js
CHANGED
|
@@ -349,6 +349,17 @@ import { createHash, createHmac, timingSafeEqual } from "crypto";
|
|
|
349
349
|
var CLIENT_PROOF_PROFILE = "clientProofV1";
|
|
350
350
|
var ABSENT_BODY_SHA256 = "0".repeat(64);
|
|
351
351
|
var DEFAULT_REPLAY_WINDOW_MILLIS = 3e5;
|
|
352
|
+
var PROOF_INPUT_FIELDS = [
|
|
353
|
+
"profile",
|
|
354
|
+
"method",
|
|
355
|
+
"path",
|
|
356
|
+
"clientId",
|
|
357
|
+
"keyId",
|
|
358
|
+
"nonce",
|
|
359
|
+
"issuedAtMillis",
|
|
360
|
+
"bodySha256"
|
|
361
|
+
];
|
|
362
|
+
var PROOF_INPUT_SEPARATOR = "\n";
|
|
352
363
|
var ProofInputError = class extends Error {
|
|
353
364
|
constructor() {
|
|
354
365
|
super("proof input field contains a C0 control character");
|
|
@@ -356,16 +367,17 @@ var ProofInputError = class extends Error {
|
|
|
356
367
|
}
|
|
357
368
|
};
|
|
358
369
|
function canonicalProofInput(input) {
|
|
359
|
-
const
|
|
360
|
-
CLIENT_PROOF_PROFILE,
|
|
361
|
-
input.method,
|
|
362
|
-
input.path,
|
|
363
|
-
input.clientId,
|
|
364
|
-
input.keyId,
|
|
365
|
-
input.nonce,
|
|
366
|
-
input.issuedAtMillis.toString(),
|
|
367
|
-
input.bodySha256
|
|
368
|
-
|
|
370
|
+
const values = {
|
|
371
|
+
profile: CLIENT_PROOF_PROFILE,
|
|
372
|
+
method: input.method,
|
|
373
|
+
path: input.path,
|
|
374
|
+
clientId: input.clientId,
|
|
375
|
+
keyId: input.keyId,
|
|
376
|
+
nonce: input.nonce,
|
|
377
|
+
issuedAtMillis: input.issuedAtMillis.toString(),
|
|
378
|
+
bodySha256: input.bodySha256
|
|
379
|
+
};
|
|
380
|
+
const fields = PROOF_INPUT_FIELDS.map((name) => values[name]);
|
|
369
381
|
for (const field of fields) {
|
|
370
382
|
for (const ch of field) {
|
|
371
383
|
if (ch.codePointAt(0) < 32) {
|
|
@@ -373,7 +385,7 @@ function canonicalProofInput(input) {
|
|
|
373
385
|
}
|
|
374
386
|
}
|
|
375
387
|
}
|
|
376
|
-
return fields.join(
|
|
388
|
+
return fields.join(PROOF_INPUT_SEPARATOR);
|
|
377
389
|
}
|
|
378
390
|
function computeClientProof(input, key) {
|
|
379
391
|
return createHmac("sha256", key).update(canonicalProofInput(input), "utf8").digest("hex");
|
|
@@ -783,9 +795,36 @@ function isRequestContentType(value) {
|
|
|
783
795
|
|
|
784
796
|
// src/server/client-proof/contract-types.ts
|
|
785
797
|
var CONTRACT_OPERATIONS = [
|
|
786
|
-
{
|
|
787
|
-
|
|
788
|
-
|
|
798
|
+
{
|
|
799
|
+
id: "auth.clientProof.handshake",
|
|
800
|
+
method: "POST",
|
|
801
|
+
path: "/v1/auth/client-proof/handshake",
|
|
802
|
+
authProfile: "clientProofV1",
|
|
803
|
+
requiresSession: false,
|
|
804
|
+
requestType: "HandshakeRequest",
|
|
805
|
+
responseType: "HandshakeResponse",
|
|
806
|
+
summary: "Presents a client proof and opens a session."
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
id: "echo.send",
|
|
810
|
+
method: "POST",
|
|
811
|
+
path: "/v1/echo",
|
|
812
|
+
authProfile: "clientProofV1",
|
|
813
|
+
requiresSession: true,
|
|
814
|
+
requestType: "EchoRequest",
|
|
815
|
+
responseType: "EchoResponse",
|
|
816
|
+
summary: "Authenticated round trip used as the smallest real vertical slice."
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
id: "items.list",
|
|
820
|
+
method: "POST",
|
|
821
|
+
path: "/v1/items/list",
|
|
822
|
+
authProfile: "clientProofV1",
|
|
823
|
+
requiresSession: true,
|
|
824
|
+
requestType: "ListItemsRequest",
|
|
825
|
+
responseType: "ListItemsResponse",
|
|
826
|
+
summary: "Authenticated paged read covering optional fields and arrays."
|
|
827
|
+
}
|
|
789
828
|
];
|
|
790
829
|
var ContractTypeError = class extends Error {
|
|
791
830
|
constructor() {
|
package/dist/client-proof.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/client-proof/canonical-json.ts","../src/server/client-proof/proof.ts","../src/server/client-proof/refusal.ts","../src/server/client-proof/state.ts","../src/server/client-proof/admission.ts","../src/server/client-proof/contract-types.ts","../src/server/client-proof/dev-control.ts","../src/server/client-proof/dev-handler.ts","../src/server/client-proof/guard.ts"],"sourcesContent":["/**\n * SPFN-CANON-JSON-1 — the canonical JSON form the mobile contract pins.\n *\n * The rules (Contracts/spfn-mobile-contract.v1.json `canonicalJson`):\n * - object keys sorted ascending by UTF-8 byte sequence\n * - no insignificant whitespace\n * - numbers are signed 64-bit integers only\n * - string escapes: `\"` and `\\` escaped; C0 controls use \\b \\f \\n \\r \\t where\n * defined and lowercase \\u00XX otherwise; every other scalar is emitted\n * literally as UTF-8\n * - absent optional fields are omitted, never null\n *\n * JSON.parse cannot implement this: it loses int64 precision, accepts duplicate\n * keys and (in V8) raw control characters, so both directions are hand-rolled.\n * A proof binds the received bytes — parse-then-re-encode equality is what makes\n * canonicity a rule a client can actually break.\n *\n * @module server/client-proof/canonical-json\n */\n\nexport type CanonicalObject = Map<string, CanonicalValue>;\n\nexport type CanonicalValue = null | boolean | bigint | string | CanonicalValue[] | CanonicalObject;\n\n/**\n * Parse failures carry the code the mobile conformance fixtures name\n * (Contracts/fixtures/canonical/rejects.json), so the fixtures can assert on it.\n */\nexport type CanonicalJsonErrorCode =\n | 'DUPLICATE_KEY'\n | 'NON_INTEGER_NUMBER'\n | 'TRAILING_CONTENT'\n | 'UNEXPECTED_END'\n | 'INVALID_TOKEN'\n | 'INVALID_ESCAPE'\n | 'INTEGER_OUT_OF_RANGE'\n | 'INVALID_UTF8';\n\nexport class CanonicalJsonError extends Error\n{\n constructor(readonly code: CanonicalJsonErrorCode)\n {\n super(`canonical JSON: ${code}`);\n this.name = 'CanonicalJsonError';\n }\n}\n\nconst INT64_MIN = -(2n ** 63n);\nconst INT64_MAX = 2n ** 63n - 1n;\n\n// ============================================================================\n// Parsing\n// ============================================================================\n\n/**\n * Parse bytes as SPFN-CANON-JSON-1.\n *\n * Arbitrary whitespace and key order are accepted here — parsing alone proves\n * nothing about canonicity. Callers that must enforce it re-encode the result\n * and compare bytes (see `isCanonicalBytes`).\n */\nexport function parseCanonicalJson(bytes: Uint8Array): CanonicalValue\n{\n let text: string;\n try\n {\n text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n }\n catch\n {\n throw new CanonicalJsonError('INVALID_UTF8');\n }\n\n const parser = new Parser(text);\n const value = parser.parseValue();\n parser.skipWhitespace();\n if (!parser.atEnd())\n {\n throw new CanonicalJsonError('TRAILING_CONTENT');\n }\n\n return value;\n}\n\n/** True when `bytes` are exactly the canonical encoding of the value they parse to. */\nexport function isCanonicalBytes(bytes: Uint8Array, value: CanonicalValue): boolean\n{\n const encoded = encodeCanonicalJson(value);\n if (encoded.length !== bytes.length)\n {\n return false;\n }\n for (let i = 0; i < encoded.length; i++)\n {\n if (encoded[i] !== bytes[i])\n {\n return false;\n }\n }\n\n return true;\n}\n\nclass Parser\n{\n private pos = 0;\n\n constructor(private readonly text: string) \n {}\n\n atEnd(): boolean\n {\n return this.pos >= this.text.length;\n }\n\n skipWhitespace(): void\n {\n while (!this.atEnd())\n {\n const c = this.text[this.pos];\n if (c === ' ' || c === '\\t' || c === '\\n' || c === '\\r')\n {\n this.pos++;\n continue;\n }\n break;\n }\n }\n\n parseValue(): CanonicalValue\n {\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n if (c === '{')\n {\n return this.parseObject();\n }\n if (c === '[')\n {\n return this.parseArray();\n }\n if (c === '\"')\n {\n return this.parseString();\n }\n if (c === '-' || (c >= '0' && c <= '9'))\n {\n return this.parseNumber();\n }\n if (this.text.startsWith('null', this.pos))\n {\n this.pos += 4;\n\n return null;\n }\n if (this.text.startsWith('true', this.pos))\n {\n this.pos += 4;\n\n return true;\n }\n if (this.text.startsWith('false', this.pos))\n {\n this.pos += 5;\n\n return false;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n\n private parseObject(): CanonicalObject\n {\n this.pos++; // '{'\n const members: CanonicalObject = new Map();\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] === '}')\n {\n this.pos++;\n\n return members;\n }\n for (;;)\n {\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] !== '\"')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n const key = this.parseString();\n if (members.has(key))\n {\n throw new CanonicalJsonError('DUPLICATE_KEY');\n }\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] !== ':')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n this.pos++;\n members.set(key, this.parseValue());\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const next = this.text[this.pos];\n if (next === ',')\n {\n this.pos++;\n continue;\n }\n if (next === '}')\n {\n this.pos++;\n\n return members;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n }\n\n private parseArray(): CanonicalValue[]\n {\n this.pos++; // '['\n const items: CanonicalValue[] = [];\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] === ']')\n {\n this.pos++;\n\n return items;\n }\n for (;;)\n {\n items.push(this.parseValue());\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const next = this.text[this.pos];\n if (next === ',')\n {\n this.pos++;\n continue;\n }\n if (next === ']')\n {\n this.pos++;\n\n return items;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n }\n\n private parseString(): string\n {\n this.pos++; // '\"'\n let out = '';\n for (;;)\n {\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n const code = this.text.charCodeAt(this.pos);\n if (c === '\"')\n {\n this.pos++;\n\n return out;\n }\n if (c === '\\\\')\n {\n out += this.parseEscape();\n continue;\n }\n if (code < 0x20)\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n out += c;\n this.pos++;\n }\n }\n\n private parseEscape(): string\n {\n this.pos++; // '\\'\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n this.pos++;\n switch (c)\n {\n case '\"': return '\"';\n case '\\\\': return '\\\\';\n case '/': return '/';\n case 'b': return '\\b';\n case 'f': return '\\f';\n case 'n': return '\\n';\n case 'r': return '\\r';\n case 't': return '\\t';\n case 'u': return this.parseUnicodeEscape();\n default: throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n }\n\n private parseUnicodeEscape(): string\n {\n const high = this.readHex4();\n if (high >= 0xdc00 && high <= 0xdfff)\n {\n // A low surrogate with no preceding high surrogate.\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n if (high < 0xd800 || high > 0xdbff)\n {\n return String.fromCharCode(high);\n }\n // A high surrogate must be completed by an escaped low surrogate.\n if (this.text[this.pos] !== '\\\\' || this.text[this.pos + 1] !== 'u')\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n this.pos += 2;\n const low = this.readHex4();\n if (low < 0xdc00 || low > 0xdfff)\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n\n return String.fromCharCode(high, low);\n }\n\n private readHex4(): number\n {\n if (this.pos + 4 > this.text.length)\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const hex = this.text.slice(this.pos, this.pos + 4);\n if (!/^[0-9a-fA-F]{4}$/.test(hex))\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n this.pos += 4;\n\n return parseInt(hex, 16);\n }\n\n private parseNumber(): bigint\n {\n const start = this.pos;\n if (this.text[this.pos] === '-')\n {\n this.pos++;\n }\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const first = this.text[this.pos];\n if (first < '0' || first > '9')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n if (first === '0')\n {\n this.pos++;\n }\n else\n {\n while (!this.atEnd() && this.text[this.pos] >= '0' && this.text[this.pos] <= '9')\n {\n this.pos++;\n }\n }\n if (!this.atEnd())\n {\n const next = this.text[this.pos];\n if (next >= '0' && next <= '9')\n {\n // A leading zero followed by more digits.\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n if (next === '.' || next === 'e' || next === 'E')\n {\n throw new CanonicalJsonError('NON_INTEGER_NUMBER');\n }\n }\n const value = BigInt(this.text.slice(start, this.pos));\n if (value < INT64_MIN || value > INT64_MAX)\n {\n throw new CanonicalJsonError('INTEGER_OUT_OF_RANGE');\n }\n\n return value;\n }\n}\n\n// ============================================================================\n// Encoding\n// ============================================================================\n\n/** Encode a value as SPFN-CANON-JSON-1 bytes. */\nexport function encodeCanonicalJson(value: CanonicalValue): Uint8Array\n{\n return new TextEncoder().encode(encodeToString(value));\n}\n\nfunction encodeToString(value: CanonicalValue): string\n{\n if (value === null)\n {\n return 'null';\n }\n if (typeof value === 'boolean')\n {\n return value ? 'true' : 'false';\n }\n if (typeof value === 'bigint')\n {\n return value.toString();\n }\n if (typeof value === 'string')\n {\n return encodeString(value);\n }\n if (Array.isArray(value))\n {\n return `[${value.map(encodeToString).join(',')}]`;\n }\n const keys = [...value.keys()].sort(compareByCodePoints);\n const members = keys.map((key) => `${encodeString(key)}:${encodeToString(value.get(key)!)}`);\n\n return `{${members.join(',')}}`;\n}\n\n/**\n * UTF-8 byte order equals code point order, so keys are compared by code\n * points rather than UTF-16 code units (which would misorder U+E000..U+FFFF\n * against supplementary-plane characters).\n */\nfunction compareByCodePoints(a: string, b: string): number\n{\n let i = 0;\n let j = 0;\n while (i < a.length && j < b.length)\n {\n const ca = a.codePointAt(i)!;\n const cb = b.codePointAt(j)!;\n if (ca !== cb)\n {\n return ca - cb;\n }\n i += ca > 0xffff ? 2 : 1;\n j += cb > 0xffff ? 2 : 1;\n }\n\n return (a.length - i) - (b.length - j);\n}\n\nfunction encodeString(value: string): string\n{\n let out = '\"';\n for (const ch of value)\n {\n const code = ch.codePointAt(0)!;\n if (ch === '\"')\n {\n out += '\\\\\"';\n }\n else if (ch === '\\\\')\n {\n out += '\\\\\\\\';\n }\n else if (code === 0x08)\n {\n out += '\\\\b';\n }\n else if (code === 0x0c)\n {\n out += '\\\\f';\n }\n else if (code === 0x0a)\n {\n out += '\\\\n';\n }\n else if (code === 0x0d)\n {\n out += '\\\\r';\n }\n else if (code === 0x09)\n {\n out += '\\\\t';\n }\n else if (code < 0x20)\n {\n out += `\\\\u00${code.toString(16).padStart(2, '0')}`;\n }\n else\n {\n out += ch;\n }\n }\n\n return out + '\"';\n}\n","/**\n * SPFN-PROOF-INPUT-1 — proof-input assembly and verification for clientProofV1.\n *\n * The proof input is 8 fields joined by `\\n` in fixed order: profile, method,\n * path, clientId, keyId, nonce, issuedAtMillis, bodySha256. Any C0 control\n * character in any field is a hard refusal (the separator would otherwise be\n * ambiguous), never something to escape. The MAC is HMAC-SHA-256 over the\n * canonical input's UTF-8 bytes, encoded base16-lower.\n *\n * @module server/client-proof/proof\n */\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto';\n\n/** The only auth profile this module implements. */\nexport const CLIENT_PROOF_PROFILE = 'clientProofV1';\n\n/** `bodySha256` when an operation carries no body: 64 zero characters. */\nexport const ABSENT_BODY_SHA256 = '0'.repeat(64);\n\n/** The contract's `clientProofV1.replayWindowMillis`. */\nexport const DEFAULT_REPLAY_WINDOW_MILLIS = 300_000;\n\nexport interface ClientProofInput\n{\n method: string;\n path: string;\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n bodySha256: string;\n}\n\n/** A C0 control character appeared in a proof field. */\nexport class ProofInputError extends Error\n{\n constructor()\n {\n super('proof input field contains a C0 control character');\n this.name = 'ProofInputError';\n }\n}\n\n/**\n * The canonical proof-input string the MAC is taken over.\n *\n * @throws ProofInputError when any field contains a C0 control character.\n */\nexport function canonicalProofInput(input: ClientProofInput): string\n{\n const fields = [\n CLIENT_PROOF_PROFILE,\n input.method,\n input.path,\n input.clientId,\n input.keyId,\n input.nonce,\n input.issuedAtMillis.toString(),\n input.bodySha256,\n ];\n for (const field of fields)\n {\n for (const ch of field)\n {\n if (ch.codePointAt(0)! < 0x20)\n {\n throw new ProofInputError();\n }\n }\n }\n\n return fields.join('\\n');\n}\n\n/** The base16-lower HMAC-SHA-256 proof for `input` under `key`. */\nexport function computeClientProof(input: ClientProofInput, key: Uint8Array): string\n{\n return createHmac('sha256', key).update(canonicalProofInput(input), 'utf8').digest('hex');\n}\n\n/** Lowercase base16 SHA-256 of `bytes`. */\nexport function sha256Hex(bytes: Uint8Array): string\n{\n return createHash('sha256').update(bytes).digest('hex');\n}\n\n/**\n * Constant-time comparison of two proof strings.\n *\n * Length is checked first (its leak reveals nothing — the expected length is\n * public), then the bytes are compared with `timingSafeEqual`.\n */\nexport function constantTimeEqualsProof(expected: string, presented: string): boolean\n{\n const a = Buffer.from(expected, 'utf8');\n const b = Buffer.from(presented, 'utf8');\n if (a.length !== b.length)\n {\n return false;\n }\n\n return timingSafeEqual(a, b);\n}\n","/**\n * Every way a clientProofV1 server refuses a request.\n *\n * The contract declares six error codes and forbids inventing a seventh, so\n * every refusal here is one of the six. Two rules decide which code a refusal\n * gets (mirroring the spfn-mobile reference server, the executable spec):\n *\n * 1. A refusal a new session could clear is an auth-family code (401). The SDK\n * re-handshakes exactly once on those.\n * 2. Everything else — the request is not the shape the contract describes —\n * is CONTRACT_UNSUPPORTED: the two ends do not agree on what the contract\n * is. PROOF_INVALID would provoke a pointless re-handshake and\n * PROFILE_REJECTED names one specific thing (a profile outside the\n * allowlist), used for exactly and only that.\n *\n * Every message is a fixed string: a message assembled from the request would\n * put a nonce, session id or body fragment into an error the client may log.\n *\n * @module server/client-proof/refusal\n */\nimport { randomBytes } from 'node:crypto';\n\nimport { encodeCanonicalJson, type CanonicalObject, type CanonicalValue } from './canonical-json';\n\n/** The six wire codes. The SDKs classify by code, never HTTP status. */\nexport type ClientProofErrorCode =\n | 'PROOF_INVALID'\n | 'PROOF_REPLAYED'\n | 'PROOF_EXPIRED'\n | 'SESSION_REVOKED'\n | 'PROFILE_REJECTED'\n | 'CONTRACT_UNSUPPORTED';\n\nconst HTTP_STATUS: Record<ClientProofErrorCode, number> = {\n PROOF_INVALID: 401,\n PROOF_REPLAYED: 401,\n PROOF_EXPIRED: 401,\n SESSION_REVOKED: 401,\n PROFILE_REJECTED: 400,\n CONTRACT_UNSUPPORTED: 409,\n};\n\n/** 128 random bits as lowercase base16 — request ids and control tokens. */\nexport function newHexId(): string\n{\n return randomBytes(16).toString('hex');\n}\n\nexport class ClientProofRefusal\n{\n constructor(\n readonly code: ClientProofErrorCode,\n readonly message: string,\n ) \n {}\n\n get httpStatus(): number\n {\n return HTTP_STATUS[this.code];\n }\n\n /** The canonical bytes of `{\"error\":{\"code\":…,\"message\":…,\"requestId\":…}}`. */\n envelopeBytes(requestId: string): Uint8Array\n {\n const error: CanonicalObject = new Map<string, CanonicalValue>([\n ['code', this.code],\n ['message', this.message],\n ['requestId', requestId],\n ]);\n\n return encodeCanonicalJson(new Map<string, CanonicalValue>([['error', error]]));\n }\n\n /** Nothing request-derived reaches a log through this. */\n toString(): string\n {\n return `ClientProofRefusal(${this.code})`;\n }\n\n // ---- shape: what arrived is not the contract (rule 2) -------------------\n\n static unroutable(): ClientProofRefusal\n {\n return contractViolation('no operation in this contract answers that method and path');\n }\n\n static malformedHeaders(): ClientProofRefusal\n {\n return contractViolation('the request does not carry the contract header fields exactly once each');\n }\n\n static missingContentType(): ClientProofRefusal\n {\n return contractViolation('a request that carries a body must declare the contract content type');\n }\n\n static bodyTooLarge(): ClientProofRefusal\n {\n return contractViolation('the request body exceeds the size this server accepts');\n }\n\n /**\n * The body parsed but its bytes are not the canonical form of what it\n * parsed to. Not PROOF_INVALID even though it is discovered next to the\n * proof: the proof over these bytes verifies perfectly well, and an\n * auth-family answer would tell the client to re-handshake and send the\n * same non-canonical bytes again.\n */\n static bodyNotCanonical(): ClientProofRefusal\n {\n return contractViolation('the request body is not the canonical JSON form of the value it encodes');\n }\n\n static bodyNotTheDeclaredType(): ClientProofRefusal\n {\n return contractViolation('the request body is not the request type this operation declares');\n }\n\n static sessionHeaderMisplaced(): ClientProofRefusal\n {\n return contractViolation('the session header is present exactly on the operations that require one');\n }\n\n static unprocessable(): ClientProofRefusal\n {\n return contractViolation('the request could not be processed');\n }\n\n // ---- the profile allowlist ----------------------------------------------\n\n static profileRejected(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROFILE_REJECTED', \"the named auth profile is not on this contract's allowlist\");\n }\n\n // ---- auth: a new session might clear it (rule 1) -------------------------\n\n static sessionRevoked(): ClientProofRefusal\n {\n return new ClientProofRefusal('SESSION_REVOKED', 'the key or session was revoked');\n }\n\n static proofExpired(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_EXPIRED', 'issuedAtMillis falls outside the replay window');\n }\n\n static proofReplayed(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_REPLAYED', 'the nonce was already used inside the replay window');\n }\n\n static proofInvalid(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_INVALID', 'the client proof did not verify');\n }\n}\n\nfunction contractViolation(message: string): ClientProofRefusal\n{\n return new ClientProofRefusal('CONTRACT_UNSUPPORTED', message);\n}\n","/**\n * Everything a clientProofV1 server remembers between requests: issued\n * sessions, the replay ledger, revoked keys and the key directory.\n *\n * The admission order is the contract's, not this file's invention\n * (`clientProofV1.revocationRule` + the replay fixtures):\n *\n * 1. revoked keyId / invalid session → SESSION_REVOKED — before proof\n * verification, so revocation stays distinguishable from a bad proof;\n * 2. issuedAtMillis outside the replay window (0 <= age <= window) → PROOF_EXPIRED;\n * 3. a repeated (clientId, nonce) pair inside the window → PROOF_REPLAYED;\n * 4. only then HMAC verification → PROOF_INVALID on mismatch.\n *\n * A nonce is recorded as spent only on admission: a request refused for any\n * earlier reason has not spent anything, so a client that fixes the reason and\n * retries with the same nonce is not punished twice for one mistake. This is\n * why core's `NonceStore.checkAndSet` (which records on check) is not reused\n * here — its semantics would spend a nonce on a refused request.\n *\n * `admit` is synchronous, so on Node's single thread the whole sequence is\n * atomic: two requests presenting the same nonce cannot interleave inside it.\n *\n * @module server/client-proof/state\n */\nimport {\n computeClientProof,\n constantTimeEqualsProof,\n DEFAULT_REPLAY_WINDOW_MILLIS,\n type ClientProofInput,\n} from './proof';\nimport { ClientProofRefusal, newHexId } from './refusal';\n\n/** Millisecond clock. Injectable so expiry paths are testable without waiting. */\nexport interface ClientProofClock\n{\n nowMillis(): number;\n}\n\nexport function systemClock(): ClientProofClock\n{\n return { nowMillis: () => Date.now() };\n}\n\n/** A clock a test (or the dev control surface) can move forward. */\nexport class TestClock implements ClientProofClock\n{\n constructor(private millis: number) \n {}\n\n nowMillis(): number\n {\n return this.millis;\n }\n\n advance(byMillis: number): void\n {\n this.millis += byMillis;\n }\n}\n\n/** What `stats()` reports. Counters only; nothing a request carried. */\nexport interface ClientProofStats\n{\n requestCount: number;\n handshakeCount: number;\n echoCount: number;\n itemsListCount: number;\n refusalCount: number;\n liveSessionCount: number;\n spentNonceCount: number;\n}\n\ninterface ClientProofSession\n{\n clientId: string;\n keyId: string;\n expiresAtMillis: number;\n}\n\ninterface PathHold\n{\n millis: number;\n remaining: number;\n}\n\nexport interface ClientProofStateOptions\n{\n /**\n * keyId → HMAC key. A string is taken as UTF-8 bytes. Dev provisioning is\n * injection at construction; any issuance flow works as long as\n * clientId/keyId/key triples exist on both ends.\n */\n keys: Record<string, string | Uint8Array>;\n\n clock?: ClientProofClock;\n\n /** @default 600000 */\n sessionTtlMillis?: number;\n\n /** The contract's replay window. @default 300000 */\n replayWindowMillis?: number;\n}\n\nexport const DEFAULT_SESSION_TTL_MILLIS = 600_000;\n\n/** The ledger key: joined with a C0 control, which no proof field may contain. */\nfunction replayKeyOf(clientId: string, nonce: string): string\n{\n return `${clientId}\u001f${nonce}`;\n}\n\nexport class ClientProofState\n{\n readonly replayWindowMillis: number;\n\n private readonly clock: ClientProofClock;\n private readonly keys = new Map<string, Uint8Array>();\n private readonly sessions = new Map<string, ClientProofSession>();\n\n /** replayKeyOf(...) → the issuedAtMillis it was spent at. */\n private readonly spentNonces = new Map<string, number>();\n\n private readonly revokedKeyIds = new Set<string>();\n private readonly holds = new Map<string, PathHold>();\n\n private readonly initialSessionTtlMillis: number;\n private sessionTtlMillis: number;\n\n private requestCount = 0;\n private handshakeCount = 0;\n private echoCount = 0;\n private itemsListCount = 0;\n private refusalCount = 0;\n\n constructor(options: ClientProofStateOptions)\n {\n this.clock = options.clock ?? systemClock();\n this.initialSessionTtlMillis = options.sessionTtlMillis ?? DEFAULT_SESSION_TTL_MILLIS;\n this.sessionTtlMillis = this.initialSessionTtlMillis;\n this.replayWindowMillis = options.replayWindowMillis ?? DEFAULT_REPLAY_WINDOW_MILLIS;\n for (const [keyId, key] of Object.entries(options.keys))\n {\n this.keys.set(keyId, typeof key === 'string' ? new TextEncoder().encode(key) : key);\n }\n }\n\n // ---- admission ---------------------------------------------------------\n\n /**\n * Runs the contract's checks in the contract's order and returns the\n * refusal, or null when the request is admitted (spending its nonce).\n */\n admit(args: {\n clientId: string;\n keyId: string;\n presentedSessionId: string | null;\n requiresSession: boolean;\n proofInput: ClientProofInput;\n presentedProof: string;\n }): ClientProofRefusal | null\n {\n const now = this.clock.nowMillis();\n this.prune(now);\n\n // 1. Revocation, before anything the proof could explain. A revoked key\n // and a dropped session are the same answer on purpose: both are\n // cleared by opening a new session.\n if (this.revokedKeyIds.has(args.keyId))\n {\n return ClientProofRefusal.sessionRevoked();\n }\n if (args.requiresSession)\n {\n const session = args.presentedSessionId === null ? undefined : this.sessions.get(args.presentedSessionId);\n if (session === undefined || session.expiresAtMillis <= now\n || session.keyId !== args.keyId || session.clientId !== args.clientId)\n {\n return ClientProofRefusal.sessionRevoked();\n }\n }\n\n // 2. The replay window, judged against this server's clock.\n const age = now - Number(args.proofInput.issuedAtMillis);\n if (age < 0 || age > this.replayWindowMillis)\n {\n return ClientProofRefusal.proofExpired();\n }\n\n // 3. One acceptance per (clientId, nonce) inside that window.\n const replayKey = replayKeyOf(args.clientId, args.proofInput.nonce);\n if (this.spentNonces.has(replayKey))\n {\n return ClientProofRefusal.proofReplayed();\n }\n\n // 4. The proof itself, last, so the three answers above stay\n // distinguishable. An unrecognised keyId lands here rather than in\n // step 1: it was never issued, so it was never revoked, and there is\n // nothing for a new session to fix.\n const key = this.keys.get(args.keyId);\n if (key === undefined)\n {\n return ClientProofRefusal.proofInvalid();\n }\n if (!constantTimeEqualsProof(computeClientProof(args.proofInput, key), args.presentedProof))\n {\n return ClientProofRefusal.proofInvalid();\n }\n\n this.spentNonces.set(replayKey, Number(args.proofInput.issuedAtMillis));\n\n return null;\n }\n\n // ---- sessions ----------------------------------------------------------\n\n /** Opens a session and returns its id and the expiry the server advertises. */\n openSession(clientId: string, keyId: string): { sessionId: string; expiresAtMillis: number }\n {\n const now = this.clock.nowMillis();\n this.prune(now);\n const sessionId = newHexId();\n const expiresAtMillis = now + this.sessionTtlMillis;\n this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });\n\n return { sessionId, expiresAtMillis };\n }\n\n /** Test hook: installs a session with a chosen id (wire-fixture replays). */\n seedSession(sessionId: string, clientId: string, keyId: string, expiresAtMillis: number): void\n {\n this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });\n }\n\n /** Drops every session, as a restart would. Advertised expiries stay told. */\n expireSessions(): void\n {\n this.sessions.clear();\n }\n\n /** Revokes a key and drops the sessions it opened. */\n revokeKey(keyId: string): void\n {\n this.revokedKeyIds.add(keyId);\n for (const [sessionId, session] of this.sessions)\n {\n if (session.keyId === keyId)\n {\n this.sessions.delete(sessionId);\n }\n }\n }\n\n setSessionTtlMillis(millis: number): void\n {\n this.sessionTtlMillis = millis;\n }\n\n /** Returns the state to how it started, counters included. */\n reset(): void\n {\n this.sessions.clear();\n this.spentNonces.clear();\n this.revokedKeyIds.clear();\n this.holds.clear();\n this.sessionTtlMillis = this.initialSessionTtlMillis;\n this.requestCount = 0;\n this.handshakeCount = 0;\n this.echoCount = 0;\n this.itemsListCount = 0;\n this.refusalCount = 0;\n }\n\n // ---- delays (dev/test only) --------------------------------------------\n\n /** Makes the next `count` requests to `path` wait `millis` before processing. */\n holdPath(path: string, millis: number, count: number): void\n {\n this.holds.set(path, { millis, remaining: count });\n }\n\n /** Consumes one configured delay for `path`; returns how long to wait, or 0. */\n takeHoldMillis(path: string): number\n {\n const hold = this.holds.get(path);\n if (hold === undefined)\n {\n return 0;\n }\n hold.remaining -= 1;\n if (hold.remaining <= 0)\n {\n this.holds.delete(path);\n }\n\n return hold.millis;\n }\n\n // ---- counters ----------------------------------------------------------\n\n recordRequest(): void\n {\n this.requestCount += 1;\n }\n\n recordOperation(operationId: string): void\n {\n if (operationId === 'auth.clientProof.handshake')\n {\n this.handshakeCount += 1;\n }\n else if (operationId === 'echo.send')\n {\n this.echoCount += 1;\n }\n else if (operationId === 'items.list')\n {\n this.itemsListCount += 1;\n }\n }\n\n recordRefusal(): void\n {\n this.refusalCount += 1;\n }\n\n stats(): ClientProofStats\n {\n this.prune(this.clock.nowMillis());\n\n return {\n requestCount: this.requestCount,\n handshakeCount: this.handshakeCount,\n echoCount: this.echoCount,\n itemsListCount: this.itemsListCount,\n refusalCount: this.refusalCount,\n liveSessionCount: this.sessions.size,\n spentNonceCount: this.spentNonces.size,\n };\n }\n\n nowMillis(): number\n {\n return this.clock.nowMillis();\n }\n\n /** The clock, exposed for the dev control surface's advance-clock route. */\n get clockRef(): ClientProofClock\n {\n return this.clock;\n }\n\n // ---- housekeeping ------------------------------------------------------\n\n /**\n * Drops what can no longer affect an answer. The nonce predicate is the\n * exact negation of the window check in `admit`: an entry is dropped only\n * once a proof carrying that issuedAtMillis would be refused as expired\n * anyway. Dropping one moment earlier would let a nonce inside the window\n * be spent twice.\n */\n private prune(nowMillis: number): void\n {\n for (const [sessionId, session] of this.sessions)\n {\n if (session.expiresAtMillis <= nowMillis)\n {\n this.sessions.delete(sessionId);\n }\n }\n for (const [key, issuedAtMillis] of this.spentNonces)\n {\n if (nowMillis - issuedAtMillis > this.replayWindowMillis)\n {\n this.spentNonces.delete(key);\n }\n }\n }\n}\n","/**\n * The checks between a clientProofV1 request arriving and being applied.\n *\n * Shape first, then the profile allowlist, then the proof. That order is\n * forced: none of the proof checks can run until the fields they read are\n * known to be present and the body is known to be the bytes the digest is\n * supposed to cover. The order *inside* the proof checks is the contract's and\n * lives in `ClientProofState.admit`.\n *\n * @module server/client-proof/admission\n */\nimport { isCanonicalBytes, parseCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { CLIENT_PROOF_PROFILE, sha256Hex, type ClientProofInput } from './proof';\nimport { ClientProofRefusal } from './refusal';\nimport type { ClientProofState } from './state';\n\n/** D23 wire-header names, ratified as proposed by the mobile dev bundle. */\nexport const CLIENT_PROOF_HEADERS = {\n profile: 'x-spfn-auth-profile',\n clientId: 'x-spfn-client-id',\n keyId: 'x-spfn-key-id',\n nonce: 'x-spfn-nonce',\n issuedAtMillis: 'x-spfn-issued-at',\n proof: 'x-spfn-proof',\n session: 'x-spfn-session',\n} as const;\n\nexport const CLIENT_PROOF_CONTENT_TYPE = 'application/json';\n\nconst INT64_MIN = -(2n ** 63n);\nconst INT64_MAX = 2n ** 63n - 1n;\n\n/** The contract header fields one request presented. */\nexport interface ClientProofCredentials\n{\n profile: string;\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n proof: string;\n sessionId: string | null;\n}\n\nexport type Admission =\n | { admitted: false; refusal: ClientProofRefusal }\n | { admitted: true; value: CanonicalValue; credentials: ClientProofCredentials };\n\n/**\n * Runs every check for one operation over already-read body bytes.\n *\n * `path` must be the operation's contract path (what the client signed), not a\n * proxied or rewritten one.\n */\nexport function admitClientProofRequest(args: {\n state: ClientProofState;\n headers: Headers;\n method: string;\n path: string;\n requiresSession: boolean;\n body: Uint8Array;\n}): Admission\n{\n const credentials = readCredentials(args.headers);\n if (credentials === null)\n {\n return refused(ClientProofRefusal.malformedHeaders());\n }\n if (credentials.profile !== CLIENT_PROOF_PROFILE)\n {\n return refused(ClientProofRefusal.profileRejected());\n }\n if (!isRequestContentType(args.headers.get('content-type')))\n {\n return refused(ClientProofRefusal.missingContentType());\n }\n if (args.requiresSession !== (credentials.sessionId !== null))\n {\n return refused(ClientProofRefusal.sessionHeaderMisplaced());\n }\n\n let value: CanonicalValue;\n try\n {\n value = parseCanonicalJson(args.body);\n }\n catch\n {\n return refused(ClientProofRefusal.bodyNotCanonical());\n }\n // The proof binds the received bytes; accepting a re-serialization would\n // let two implementations disagree about what was signed.\n if (!isCanonicalBytes(args.body, value))\n {\n return refused(ClientProofRefusal.bodyNotCanonical());\n }\n\n const proofInput: ClientProofInput = {\n method: args.method,\n path: args.path,\n clientId: credentials.clientId,\n keyId: credentials.keyId,\n nonce: credentials.nonce,\n issuedAtMillis: credentials.issuedAtMillis,\n bodySha256: sha256Hex(args.body),\n };\n\n let refusal: ClientProofRefusal | null;\n try\n {\n refusal = args.state.admit({\n clientId: credentials.clientId,\n keyId: credentials.keyId,\n presentedSessionId: credentials.sessionId,\n requiresSession: args.requiresSession,\n proofInput,\n presentedProof: credentials.proof,\n });\n }\n catch\n {\n // A C0 control character in a header field makes the proof input\n // unassemblable — the request is not the shape the contract describes.\n return refused(ClientProofRefusal.unprocessable());\n }\n if (refusal !== null)\n {\n return refused(refusal);\n }\n\n return { admitted: true, value, credentials };\n}\n\nfunction refused(refusal: ClientProofRefusal): Admission\n{\n return { admitted: false, refusal };\n}\n\n/**\n * The contract header fields, or null when any is absent or malformed.\n *\n * Fetch `Headers` folds a repeated field into one comma-joined value, so\n * \"sent more than once\" is not directly observable here; a folded value fails\n * either the issuedAt grammar or proof verification instead.\n */\nfunction readCredentials(headers: Headers): ClientProofCredentials | null\n{\n const profile = headers.get(CLIENT_PROOF_HEADERS.profile);\n const clientId = headers.get(CLIENT_PROOF_HEADERS.clientId);\n const keyId = headers.get(CLIENT_PROOF_HEADERS.keyId);\n const nonce = headers.get(CLIENT_PROOF_HEADERS.nonce);\n const issuedAtRaw = headers.get(CLIENT_PROOF_HEADERS.issuedAtMillis);\n const proof = headers.get(CLIENT_PROOF_HEADERS.proof);\n if (profile === null || clientId === null || keyId === null\n || nonce === null || issuedAtRaw === null || proof === null)\n {\n return null;\n }\n const issuedAtMillis = parseInt64(issuedAtRaw);\n if (issuedAtMillis === null)\n {\n return null;\n }\n\n return {\n profile,\n clientId,\n keyId,\n nonce,\n issuedAtMillis,\n proof,\n sessionId: headers.get(CLIENT_PROOF_HEADERS.session),\n };\n}\n\nfunction parseInt64(raw: string): bigint | null\n{\n if (!/^[+-]?\\d{1,19}$/.test(raw))\n {\n return null;\n }\n const value = BigInt(raw);\n if (value < INT64_MIN || value > INT64_MAX)\n {\n return null;\n }\n\n return value;\n}\n\nfunction isRequestContentType(value: string | null): boolean\n{\n if (value === null)\n {\n return false;\n }\n\n return value.split(';')[0].trim().toLowerCase() === CLIENT_PROOF_CONTENT_TYPE;\n}\n","/**\n * The mobile dev-contract types and operations, decoded from / encoded to\n * canonical values. Strict on purpose: a missing required field, a wrong type\n * or an unknown field is \"not the request type this operation declares\".\n *\n * Source of truth: spfn-mobile Contracts/spfn-mobile-contract.v1.json\n * (dev bundle sha256 07fd8268…a433e45) — `types` and `operations`.\n *\n * @module server/client-proof/contract-types\n */\nimport type { CanonicalObject, CanonicalValue } from './canonical-json';\n\nexport interface ContractOperation\n{\n id: 'auth.clientProof.handshake' | 'echo.send' | 'items.list';\n method: 'POST';\n path: string;\n requiresSession: boolean;\n}\n\nexport const CONTRACT_OPERATIONS: readonly ContractOperation[] = [\n { id: 'auth.clientProof.handshake', method: 'POST', path: '/v1/auth/client-proof/handshake', requiresSession: false },\n { id: 'echo.send', method: 'POST', path: '/v1/echo', requiresSession: true },\n { id: 'items.list', method: 'POST', path: '/v1/items/list', requiresSession: true },\n];\n\n/** The body is canonical JSON but not the declared request type. */\nexport class ContractTypeError extends Error\n{\n constructor()\n {\n super('not the declared contract type');\n this.name = 'ContractTypeError';\n }\n}\n\nexport interface HandshakeRequest\n{\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n}\n\nexport interface EchoRequest\n{\n message: string;\n sequence: bigint;\n}\n\nexport interface ListItemsRequest\n{\n limit: bigint;\n cursor?: string;\n}\n\nexport interface ContractItem\n{\n id: string;\n name: string;\n updatedAtMillis: bigint;\n}\n\n// ============================================================================\n// Decoding\n// ============================================================================\n\nexport function decodeHandshakeRequest(value: CanonicalValue): HandshakeRequest\n{\n const members = objectWithKeys(value, ['clientId', 'keyId', 'nonce', 'issuedAtMillis'], []);\n\n return {\n clientId: text(members.get('clientId')),\n keyId: text(members.get('keyId')),\n nonce: text(members.get('nonce')),\n issuedAtMillis: integer(members.get('issuedAtMillis')),\n };\n}\n\nexport function decodeEchoRequest(value: CanonicalValue): EchoRequest\n{\n const members = objectWithKeys(value, ['message', 'sequence'], []);\n\n return {\n message: text(members.get('message')),\n sequence: integer(members.get('sequence')),\n };\n}\n\nexport function decodeListItemsRequest(value: CanonicalValue): ListItemsRequest\n{\n const members = objectWithKeys(value, ['limit'], ['cursor']);\n const request: ListItemsRequest = { limit: integer(members.get('limit')) };\n if (members.has('cursor'))\n {\n request.cursor = text(members.get('cursor'));\n }\n\n return request;\n}\n\nfunction objectWithKeys(\n value: CanonicalValue,\n required: string[],\n optional: string[],\n): CanonicalObject\n{\n if (!(value instanceof Map))\n {\n throw new ContractTypeError();\n }\n for (const key of required)\n {\n if (!value.has(key))\n {\n throw new ContractTypeError();\n }\n }\n for (const key of value.keys())\n {\n if (!required.includes(key) && !optional.includes(key))\n {\n throw new ContractTypeError();\n }\n }\n\n return value;\n}\n\nfunction text(value: CanonicalValue | undefined): string\n{\n if (typeof value !== 'string')\n {\n throw new ContractTypeError();\n }\n\n return value;\n}\n\nfunction integer(value: CanonicalValue | undefined): bigint\n{\n if (typeof value !== 'bigint')\n {\n throw new ContractTypeError();\n }\n\n return value;\n}\n\n// ============================================================================\n// Encoding\n// ============================================================================\n\nexport function encodeHandshakeResponse(sessionId: string, expiresAtMillis: bigint): CanonicalValue\n{\n return new Map<string, CanonicalValue>([\n ['sessionId', sessionId],\n ['expiresAtMillis', expiresAtMillis],\n ]);\n}\n\nexport function encodeEchoResponse(message: string, sequence: bigint, serverTimeMillis: bigint): CanonicalValue\n{\n return new Map<string, CanonicalValue>([\n ['message', message],\n ['sequence', sequence],\n ['serverTimeMillis', serverTimeMillis],\n ]);\n}\n\nexport function encodeListItemsResponse(items: ContractItem[], nextCursor: string | null): CanonicalValue\n{\n const encodedItems: CanonicalValue = items.map((item) => new Map<string, CanonicalValue>([\n ['id', item.id],\n ['name', item.name],\n ['updatedAtMillis', item.updatedAtMillis],\n ]));\n const members = new Map<string, CanonicalValue>([['items', encodedItems]]);\n if (nextCursor !== null)\n {\n members.set('nextCursor', nextCursor);\n }\n\n return members;\n}\n","/**\n * The dev server's test hooks, mirroring the spfn-mobile reference server's\n * `/control` surface route for route so the mobile integration suites can\n * drive either server with only a URL change.\n *\n * `/control` is NOT part of the contract: nothing under it appears in the\n * bundle, no SDK knows it exists, and its answers are plain objects rather\n * than contract envelopes. Every route except the readiness probe requires\n * the per-launch token; the token is never logged.\n *\n * @module server/client-proof/dev-control\n */\nimport { encodeCanonicalJson, parseCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { ClientProofState, TestClock } from './state';\n\nexport const CONTROL_PREFIX = '/control/';\n\nexport const CONTROL_TOKEN_HEADER = 'x-spfn-reference-control';\n\nconst HTTP_OK = 200;\nconst HTTP_BAD_REQUEST = 400;\nconst HTTP_FORBIDDEN = 403;\nconst HTTP_NOT_FOUND = 404;\nconst HTTP_CONFLICT = 409;\n\nconst MAX_CONTROL_BODY_BYTES = 4096;\n\nexport async function handleControlRequest(\n state: ClientProofState,\n controlToken: string,\n path: string,\n request: Request,\n): Promise<Response>\n{\n if (path === '/control/health')\n {\n return answer(HTTP_OK, new Map<string, CanonicalValue>([['status', 'ok']]));\n }\n if (request.headers.get(CONTROL_TOKEN_HEADER) !== controlToken)\n {\n return answer(HTTP_FORBIDDEN, failure('control token'));\n }\n\n const raw = new Uint8Array(await request.arrayBuffer());\n const body = raw.length > MAX_CONTROL_BODY_BYTES ? raw.slice(0, MAX_CONTROL_BODY_BYTES) : raw;\n\n switch (path)\n {\n case '/control/stats':\n return stats(state);\n case '/control/reset':\n state.reset();\n\n return ok();\n case '/control/expire-sessions':\n state.expireSessions();\n\n return ok();\n case '/control/revoke-key':\n return revokeKey(state, body);\n case '/control/session-ttl':\n return sessionTtl(state, body);\n case '/control/hold':\n return hold(state, body);\n case '/control/advance-clock':\n return advanceClock(state, body);\n default:\n return answer(HTTP_NOT_FOUND, failure('unknown control route'));\n }\n}\n\n// ---- routes ----------------------------------------------------------------\n\nfunction stats(state: ClientProofState): Response\n{\n const counters = state.stats();\n\n return answer(HTTP_OK, withOk(new Map<string, CanonicalValue>([\n ['echoCount', BigInt(counters.echoCount)],\n ['handshakeCount', BigInt(counters.handshakeCount)],\n ['itemsListCount', BigInt(counters.itemsListCount)],\n ['liveSessionCount', BigInt(counters.liveSessionCount)],\n ['refusalCount', BigInt(counters.refusalCount)],\n ['requestCount', BigInt(counters.requestCount)],\n ['spentNonceCount', BigInt(counters.spentNonceCount)],\n ])));\n}\n\nfunction revokeKey(state: ClientProofState, body: Uint8Array): Response\n{\n const keyId = stringField(body, 'keyId');\n if (keyId === null)\n {\n return badRequest('keyId');\n }\n state.revokeKey(keyId);\n\n return ok();\n}\n\nfunction sessionTtl(state: ClientProofState, body: Uint8Array): Response\n{\n const ttlMillis = integerField(body, 'ttlMillis');\n if (ttlMillis === null)\n {\n return badRequest('ttlMillis');\n }\n state.setSessionTtlMillis(Number(ttlMillis));\n\n return ok();\n}\n\nfunction hold(state: ClientProofState, body: Uint8Array): Response\n{\n const path = stringField(body, 'path');\n const millis = integerField(body, 'millis');\n const count = integerField(body, 'count');\n if (path === null)\n {\n return badRequest('path');\n }\n if (millis === null)\n {\n return badRequest('millis');\n }\n if (count === null)\n {\n return badRequest('count');\n }\n state.holdPath(path, Number(millis), Number(count));\n\n return ok();\n}\n\n/**\n * Moves a test clock forward. Refused when the server runs on the wall clock,\n * because silently doing nothing is how a test passes for the wrong reason.\n */\nfunction advanceClock(state: ClientProofState, body: Uint8Array): Response\n{\n const clock = state.clockRef;\n if (!(clock instanceof TestClock))\n {\n return answer(HTTP_CONFLICT, failure('server is running on the system clock'));\n }\n const millis = integerField(body, 'millis');\n if (millis === null)\n {\n return badRequest('millis');\n }\n clock.advance(Number(millis));\n\n return ok();\n}\n\n// ---- plumbing --------------------------------------------------------------\n\nfunction members(body: Uint8Array): Map<string, CanonicalValue> | null\n{\n if (body.length === 0)\n {\n return new Map();\n }\n let parsed: CanonicalValue;\n try\n {\n parsed = parseCanonicalJson(body);\n }\n catch\n {\n return null;\n }\n\n return parsed instanceof Map ? parsed : null;\n}\n\nfunction stringField(body: Uint8Array, field: string): string | null\n{\n const value = members(body)?.get(field);\n\n return typeof value === 'string' ? value : null;\n}\n\nfunction integerField(body: Uint8Array, field: string): bigint | null\n{\n const value = members(body)?.get(field);\n\n return typeof value === 'bigint' ? value : null;\n}\n\nfunction badRequest(field: string): Response\n{\n return answer(HTTP_BAD_REQUEST, failure(`missing or malformed field: ${field}`));\n}\n\nfunction ok(): Response\n{\n return answer(HTTP_OK, withOk(new Map()));\n}\n\nfunction failure(reason: string): Map<string, CanonicalValue>\n{\n return new Map<string, CanonicalValue>([['ok', false], ['reason', reason]]);\n}\n\nfunction withOk(extra: Map<string, CanonicalValue>): Map<string, CanonicalValue>\n{\n extra.set('ok', true);\n\n return extra;\n}\n\nfunction answer(status: number, value: Map<string, CanonicalValue>): Response\n{\n const bytes = encodeCanonicalJson(value);\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\n return new Response(buffer, { status, headers: { 'content-type': 'application/json' } });\n}\n","/**\n * The mobile-contract dev surface: a fetch-style handler exposing the three\n * dev operations (handshake / echo.send / items.list) plus the `/control`\n * test hooks the spfn-mobile integration suites drive.\n *\n * Framework-free on purpose — `fetch(request) => Response` plugs into\n * `@hono/node-server`'s `serve({ fetch })` or any Web-standard runtime, and\n * the contract needs byte-exact control over bodies and envelopes that a\n * validating router would take away.\n *\n * This is a dev/test surface, not a production deployment target: keys are\n * injected at construction, state is in-memory, and `/control` mutates it.\n *\n * @module server/client-proof/dev-handler\n */\nimport { encodeCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { admitClientProofRequest, type Admission } from './admission';\nimport {\n CONTRACT_OPERATIONS,\n ContractTypeError,\n decodeEchoRequest,\n decodeHandshakeRequest,\n decodeListItemsRequest,\n encodeEchoResponse,\n encodeHandshakeResponse,\n encodeListItemsResponse,\n type ContractItem,\n type ContractOperation,\n type ListItemsRequest,\n} from './contract-types';\nimport { ClientProofRefusal, newHexId } from './refusal';\nimport { ClientProofState, type ClientProofStateOptions, TestClock } from './state';\nimport { handleControlRequest, CONTROL_PREFIX } from './dev-control';\n\n/** Far above any contract request and far below anything worth buffering. */\nconst MAX_BODY_BYTES = 1 << 20;\n\nconst HTTP_OK = 200;\n\n/**\n * The items `items.list` pages through — fixed and small on purpose, matching\n * the spfn-mobile reference catalogue byte for byte so an integration test can\n * assert exact values against either server.\n */\nexport const DEV_CATALOGUE: readonly ContractItem[] = [\n { id: 'item-0001', name: 'alpha', updatedAtMillis: 1_750_000_000_001n },\n { id: 'item-0002', name: 'bravo', updatedAtMillis: 1_750_000_000_002n },\n { id: 'item-0003', name: 'charlie', updatedAtMillis: 1_750_000_000_003n },\n { id: 'item-0004', name: 'delta', updatedAtMillis: 1_750_000_000_004n },\n { id: 'item-0005', name: 'echo', updatedAtMillis: 1_750_000_000_005n },\n];\n\n/** The largest `items.list` page this server will answer with. */\nexport const DEV_MAX_LIMIT = 100n;\n\nexport interface ClientProofDevHandlerOptions extends ClientProofStateOptions\n{\n /**\n * Token the `/control` routes require (header `x-spfn-reference-control`).\n * Generated per construction when omitted; never logged.\n */\n controlToken?: string;\n\n /** Disables the `/control` surface entirely. @default true */\n enableControl?: boolean;\n\n /** One line per request: method, path, status. Nothing a request carried. */\n log?: (line: string) => void;\n}\n\nexport interface ClientProofDevHandler\n{\n fetch(request: Request): Promise<Response>;\n state: ClientProofState;\n controlToken: string;\n}\n\nexport function createClientProofDevHandler(options: ClientProofDevHandlerOptions): ClientProofDevHandler\n{\n const state = new ClientProofState(options);\n const controlToken = options.controlToken ?? newHexId();\n const enableControl = options.enableControl ?? true;\n const log = options.log ?? (() => undefined);\n\n async function dispatch(request: Request): Promise<Response>\n {\n state.recordRequest();\n const url = new URL(request.url);\n\n if (enableControl && url.pathname.startsWith(CONTROL_PREFIX))\n {\n return handleControlRequest(state, controlToken, url.pathname, request);\n }\n\n // A query string is refused by omission: no contract path carries one,\n // and a proof is taken over the path alone.\n const operation = url.search === ''\n ? CONTRACT_OPERATIONS.find((op) => op.path === url.pathname && op.method === request.method)\n : undefined;\n if (operation === undefined)\n {\n return refuse(ClientProofRefusal.unroutable());\n }\n\n const body = await readBodyCapped(request);\n if (body === null)\n {\n return refuse(ClientProofRefusal.bodyTooLarge());\n }\n\n // Before verification, so a request a test is holding open has not\n // spent its nonce by the time the client gives up waiting for it.\n await waitOutHold(url.pathname);\n\n const admission = admitClientProofRequest({\n state,\n headers: request.headers,\n method: operation.method,\n path: operation.path,\n requiresSession: operation.requiresSession,\n body,\n });\n if (!admission.admitted)\n {\n return refuse(admission.refusal);\n }\n\n return apply(operation, admission);\n }\n\n function apply(operation: ContractOperation, admission: Extract<Admission, { admitted: true }>): Response\n {\n let value: CanonicalValue;\n try\n {\n if (operation.id === 'auth.clientProof.handshake')\n {\n const request = decodeHandshakeRequest(admission.value);\n // The proof already binds the header identity to the key that\n // signed it, so a body naming a different client is a request\n // whose two halves disagree about who sent it.\n if (request.clientId !== admission.credentials.clientId\n || request.keyId !== admission.credentials.keyId)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n const opened = state.openSession(request.clientId, request.keyId);\n value = encodeHandshakeResponse(opened.sessionId, BigInt(opened.expiresAtMillis));\n }\n else if (operation.id === 'echo.send')\n {\n const request = decodeEchoRequest(admission.value);\n value = encodeEchoResponse(request.message, request.sequence, BigInt(state.nowMillis()));\n }\n else\n {\n const listed = listItems(decodeListItemsRequest(admission.value));\n if (listed === null)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n value = listed;\n }\n }\n catch (error)\n {\n if (error instanceof ContractTypeError)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n\n return refuse(ClientProofRefusal.unprocessable());\n }\n\n state.recordOperation(operation.id);\n\n return contractResponse(HTTP_OK, encodeCanonicalJson(value));\n }\n\n function refuse(refusal: ClientProofRefusal): Response\n {\n state.recordRefusal();\n\n return contractResponse(refusal.httpStatus, refusal.envelopeBytes(newHexId()));\n }\n\n async function waitOutHold(path: string): Promise<void>\n {\n const millis = state.takeHoldMillis(path);\n if (millis > 0)\n {\n await new Promise((resolve) => setTimeout(resolve, millis));\n }\n }\n\n return {\n state,\n controlToken,\n fetch: async (request: Request): Promise<Response> =>\n {\n try\n {\n const response = await dispatch(request);\n log(`${request.method} ${new URL(request.url).pathname} -> ${response.status}`);\n\n return response;\n }\n catch\n {\n // A contract answer rather than a stack trace: an exception\n // message can quote the request that produced it.\n return refuse(ClientProofRefusal.unprocessable());\n }\n },\n };\n}\n\n/**\n * One page of the catalogue, or null when the request is not one this\n * contract describes. An unknown cursor and a limit outside 1…MAX are refused\n * rather than clamped — a server that quietly repaired a request would hide\n * the client bug that produced it.\n */\nfunction listItems(request: ListItemsRequest): CanonicalValue | null\n{\n if (request.limit < 1n || request.limit > DEV_MAX_LIMIT)\n {\n return null;\n }\n let start = 0;\n if (request.cursor !== undefined)\n {\n const index = DEV_CATALOGUE.findIndex((item) => item.id === request.cursor);\n if (index < 0)\n {\n return null;\n }\n start = index + 1;\n }\n const end = Math.min(DEV_CATALOGUE.length, start + Number(request.limit));\n const page = DEV_CATALOGUE.slice(start, end);\n // Present only when a further page exists, so \"nextCursor is absent\" is a\n // fact about the data rather than a value the client has to interpret.\n const nextCursor = end < DEV_CATALOGUE.length && page.length > 0 ? page[page.length - 1].id : null;\n\n return encodeListItemsResponse([...page], nextCursor);\n}\n\nfunction contractResponse(status: number, body: Uint8Array): Response\n{\n return new Response(toArrayBuffer(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n}\n\n/** The body, or null when it is larger than this server will read. */\nasync function readBodyCapped(request: Request): Promise<Uint8Array | null>\n{\n if (request.body === null)\n {\n return new Uint8Array(0);\n }\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;)\n {\n const { done, value } = await reader.read();\n if (done)\n {\n break;\n }\n total += value.length;\n if (total > MAX_BODY_BYTES)\n {\n await reader.cancel();\n\n return null;\n }\n chunks.push(value);\n }\n const body = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks)\n {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n\n return body;\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer\n{\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nexport { TestClock };\n","/**\n * Hono middleware adapter for clientProofV1 — the `requiresSession` guard for\n * SPFN servers that mount contract operations as ordinary routes.\n *\n * Runs the full admission sequence over the raw request bytes and, on\n * acceptance, tags the request `clientType: 'mobile'` (the attestation slot\n * PROXY-BACKEND-AUTH-SPEC reserved) and exposes the parsed canonical body and\n * credentials under the `clientProof` context key.\n *\n * hono is imported as types only — the middleware itself is a plain async\n * function, so this module adds no runtime dependency.\n *\n * @module server/client-proof/guard\n */\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport { admitClientProofRequest, type ClientProofCredentials } from './admission';\nimport type { CanonicalValue } from './canonical-json';\nimport { newHexId } from './refusal';\nimport type { ClientProofState } from './state';\n\n/** What the guard leaves in the context for the route handler. */\nexport interface ClientProofContext\n{\n credentials: ClientProofCredentials;\n\n /** The request body as a canonical value (already byte-verified). */\n value: CanonicalValue;\n}\n\nexport interface ClientProofGuardOptions\n{\n /**\n * The contract path the client signed, when it differs from the mounted\n * path (e.g. behind a stripped ingress prefix). Defaults to the request\n * path.\n */\n contractPath?: string;\n}\n\n/**\n * A guard for operations with `requiresSession: true`.\n *\n * Refusals are answered with the contract envelope and never reach the route.\n */\nexport function createClientProofGuard(\n state: ClientProofState,\n options: ClientProofGuardOptions = {},\n): MiddlewareHandler\n{\n return async (c: Context, next: Next) =>\n {\n const body = new Uint8Array(await c.req.arrayBuffer());\n const admission = admitClientProofRequest({\n state,\n headers: c.req.raw.headers,\n method: c.req.method,\n path: options.contractPath ?? c.req.path,\n requiresSession: true,\n body,\n });\n if (!admission.admitted)\n {\n state.recordRefusal();\n const bytes = admission.refusal.envelopeBytes(newHexId());\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\n return c.newResponse(buffer, admission.refusal.httpStatus as 401, {\n 'content-type': 'application/json',\n });\n }\n c.set('clientType', 'mobile');\n c.set('clientProof', {\n credentials: admission.credentials,\n value: admission.value,\n } satisfies ClientProofContext);\n await next();\n\n return undefined;\n };\n}\n"],"mappings":"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","names":["text","members","hold","INT64_MIN","INT64_MAX","members","HTTP_OK"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/client-proof/canonical-json.ts","../src/server/client-proof/proof.ts","../src/server/client-proof/refusal.ts","../src/server/client-proof/state.ts","../src/server/client-proof/admission.ts","../src/server/client-proof/contract-types.ts","../src/server/client-proof/dev-control.ts","../src/server/client-proof/dev-handler.ts","../src/server/client-proof/guard.ts"],"sourcesContent":["/**\n * SPFN-CANON-JSON-1 — the canonical JSON form the mobile contract pins.\n *\n * The rules (Contracts/spfn-mobile-contract.v1.json `canonicalJson`):\n * - object keys sorted ascending by UTF-8 byte sequence\n * - no insignificant whitespace\n * - numbers are signed 64-bit integers only\n * - string escapes: `\"` and `\\` escaped; C0 controls use \\b \\f \\n \\r \\t where\n * defined and lowercase \\u00XX otherwise; every other scalar is emitted\n * literally as UTF-8\n * - absent optional fields are omitted, never null\n *\n * JSON.parse cannot implement this: it loses int64 precision, accepts duplicate\n * keys and (in V8) raw control characters, so both directions are hand-rolled.\n * A proof binds the received bytes — parse-then-re-encode equality is what makes\n * canonicity a rule a client can actually break.\n *\n * @module server/client-proof/canonical-json\n */\n\nexport type CanonicalObject = Map<string, CanonicalValue>;\n\nexport type CanonicalValue = null | boolean | bigint | string | CanonicalValue[] | CanonicalObject;\n\n/**\n * Parse failures carry the code the mobile conformance fixtures name\n * (Contracts/fixtures/canonical/rejects.json), so the fixtures can assert on it.\n */\nexport type CanonicalJsonErrorCode =\n | 'DUPLICATE_KEY'\n | 'NON_INTEGER_NUMBER'\n | 'TRAILING_CONTENT'\n | 'UNEXPECTED_END'\n | 'INVALID_TOKEN'\n | 'INVALID_ESCAPE'\n | 'INTEGER_OUT_OF_RANGE'\n | 'INVALID_UTF8';\n\nexport class CanonicalJsonError extends Error\n{\n constructor(readonly code: CanonicalJsonErrorCode)\n {\n super(`canonical JSON: ${code}`);\n this.name = 'CanonicalJsonError';\n }\n}\n\nconst INT64_MIN = -(2n ** 63n);\nconst INT64_MAX = 2n ** 63n - 1n;\n\n// ============================================================================\n// Parsing\n// ============================================================================\n\n/**\n * Parse bytes as SPFN-CANON-JSON-1.\n *\n * Arbitrary whitespace and key order are accepted here — parsing alone proves\n * nothing about canonicity. Callers that must enforce it re-encode the result\n * and compare bytes (see `isCanonicalBytes`).\n */\nexport function parseCanonicalJson(bytes: Uint8Array): CanonicalValue\n{\n let text: string;\n try\n {\n text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n }\n catch\n {\n throw new CanonicalJsonError('INVALID_UTF8');\n }\n\n const parser = new Parser(text);\n const value = parser.parseValue();\n parser.skipWhitespace();\n if (!parser.atEnd())\n {\n throw new CanonicalJsonError('TRAILING_CONTENT');\n }\n\n return value;\n}\n\n/** True when `bytes` are exactly the canonical encoding of the value they parse to. */\nexport function isCanonicalBytes(bytes: Uint8Array, value: CanonicalValue): boolean\n{\n const encoded = encodeCanonicalJson(value);\n if (encoded.length !== bytes.length)\n {\n return false;\n }\n for (let i = 0; i < encoded.length; i++)\n {\n if (encoded[i] !== bytes[i])\n {\n return false;\n }\n }\n\n return true;\n}\n\nclass Parser\n{\n private pos = 0;\n\n constructor(private readonly text: string) \n {}\n\n atEnd(): boolean\n {\n return this.pos >= this.text.length;\n }\n\n skipWhitespace(): void\n {\n while (!this.atEnd())\n {\n const c = this.text[this.pos];\n if (c === ' ' || c === '\\t' || c === '\\n' || c === '\\r')\n {\n this.pos++;\n continue;\n }\n break;\n }\n }\n\n parseValue(): CanonicalValue\n {\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n if (c === '{')\n {\n return this.parseObject();\n }\n if (c === '[')\n {\n return this.parseArray();\n }\n if (c === '\"')\n {\n return this.parseString();\n }\n if (c === '-' || (c >= '0' && c <= '9'))\n {\n return this.parseNumber();\n }\n if (this.text.startsWith('null', this.pos))\n {\n this.pos += 4;\n\n return null;\n }\n if (this.text.startsWith('true', this.pos))\n {\n this.pos += 4;\n\n return true;\n }\n if (this.text.startsWith('false', this.pos))\n {\n this.pos += 5;\n\n return false;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n\n private parseObject(): CanonicalObject\n {\n this.pos++; // '{'\n const members: CanonicalObject = new Map();\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] === '}')\n {\n this.pos++;\n\n return members;\n }\n for (;;)\n {\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] !== '\"')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n const key = this.parseString();\n if (members.has(key))\n {\n throw new CanonicalJsonError('DUPLICATE_KEY');\n }\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] !== ':')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n this.pos++;\n members.set(key, this.parseValue());\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const next = this.text[this.pos];\n if (next === ',')\n {\n this.pos++;\n continue;\n }\n if (next === '}')\n {\n this.pos++;\n\n return members;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n }\n\n private parseArray(): CanonicalValue[]\n {\n this.pos++; // '['\n const items: CanonicalValue[] = [];\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] === ']')\n {\n this.pos++;\n\n return items;\n }\n for (;;)\n {\n items.push(this.parseValue());\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const next = this.text[this.pos];\n if (next === ',')\n {\n this.pos++;\n continue;\n }\n if (next === ']')\n {\n this.pos++;\n\n return items;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n }\n\n private parseString(): string\n {\n this.pos++; // '\"'\n let out = '';\n for (;;)\n {\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n const code = this.text.charCodeAt(this.pos);\n if (c === '\"')\n {\n this.pos++;\n\n return out;\n }\n if (c === '\\\\')\n {\n out += this.parseEscape();\n continue;\n }\n if (code < 0x20)\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n out += c;\n this.pos++;\n }\n }\n\n private parseEscape(): string\n {\n this.pos++; // '\\'\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n this.pos++;\n switch (c)\n {\n case '\"': return '\"';\n case '\\\\': return '\\\\';\n case '/': return '/';\n case 'b': return '\\b';\n case 'f': return '\\f';\n case 'n': return '\\n';\n case 'r': return '\\r';\n case 't': return '\\t';\n case 'u': return this.parseUnicodeEscape();\n default: throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n }\n\n private parseUnicodeEscape(): string\n {\n const high = this.readHex4();\n if (high >= 0xdc00 && high <= 0xdfff)\n {\n // A low surrogate with no preceding high surrogate.\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n if (high < 0xd800 || high > 0xdbff)\n {\n return String.fromCharCode(high);\n }\n // A high surrogate must be completed by an escaped low surrogate.\n if (this.text[this.pos] !== '\\\\' || this.text[this.pos + 1] !== 'u')\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n this.pos += 2;\n const low = this.readHex4();\n if (low < 0xdc00 || low > 0xdfff)\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n\n return String.fromCharCode(high, low);\n }\n\n private readHex4(): number\n {\n if (this.pos + 4 > this.text.length)\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const hex = this.text.slice(this.pos, this.pos + 4);\n if (!/^[0-9a-fA-F]{4}$/.test(hex))\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n this.pos += 4;\n\n return parseInt(hex, 16);\n }\n\n private parseNumber(): bigint\n {\n const start = this.pos;\n if (this.text[this.pos] === '-')\n {\n this.pos++;\n }\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const first = this.text[this.pos];\n if (first < '0' || first > '9')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n if (first === '0')\n {\n this.pos++;\n }\n else\n {\n while (!this.atEnd() && this.text[this.pos] >= '0' && this.text[this.pos] <= '9')\n {\n this.pos++;\n }\n }\n if (!this.atEnd())\n {\n const next = this.text[this.pos];\n if (next >= '0' && next <= '9')\n {\n // A leading zero followed by more digits.\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n if (next === '.' || next === 'e' || next === 'E')\n {\n throw new CanonicalJsonError('NON_INTEGER_NUMBER');\n }\n }\n const value = BigInt(this.text.slice(start, this.pos));\n if (value < INT64_MIN || value > INT64_MAX)\n {\n throw new CanonicalJsonError('INTEGER_OUT_OF_RANGE');\n }\n\n return value;\n }\n}\n\n// ============================================================================\n// Encoding\n// ============================================================================\n\n/** Encode a value as SPFN-CANON-JSON-1 bytes. */\nexport function encodeCanonicalJson(value: CanonicalValue): Uint8Array\n{\n return new TextEncoder().encode(encodeToString(value));\n}\n\nfunction encodeToString(value: CanonicalValue): string\n{\n if (value === null)\n {\n return 'null';\n }\n if (typeof value === 'boolean')\n {\n return value ? 'true' : 'false';\n }\n if (typeof value === 'bigint')\n {\n return value.toString();\n }\n if (typeof value === 'string')\n {\n return encodeString(value);\n }\n if (Array.isArray(value))\n {\n return `[${value.map(encodeToString).join(',')}]`;\n }\n const keys = [...value.keys()].sort(compareByCodePoints);\n const members = keys.map((key) => `${encodeString(key)}:${encodeToString(value.get(key)!)}`);\n\n return `{${members.join(',')}}`;\n}\n\n/**\n * UTF-8 byte order equals code point order, so keys are compared by code\n * points rather than UTF-16 code units (which would misorder U+E000..U+FFFF\n * against supplementary-plane characters).\n */\nfunction compareByCodePoints(a: string, b: string): number\n{\n let i = 0;\n let j = 0;\n while (i < a.length && j < b.length)\n {\n const ca = a.codePointAt(i)!;\n const cb = b.codePointAt(j)!;\n if (ca !== cb)\n {\n return ca - cb;\n }\n i += ca > 0xffff ? 2 : 1;\n j += cb > 0xffff ? 2 : 1;\n }\n\n return (a.length - i) - (b.length - j);\n}\n\nfunction encodeString(value: string): string\n{\n let out = '\"';\n for (const ch of value)\n {\n const code = ch.codePointAt(0)!;\n if (ch === '\"')\n {\n out += '\\\\\"';\n }\n else if (ch === '\\\\')\n {\n out += '\\\\\\\\';\n }\n else if (code === 0x08)\n {\n out += '\\\\b';\n }\n else if (code === 0x0c)\n {\n out += '\\\\f';\n }\n else if (code === 0x0a)\n {\n out += '\\\\n';\n }\n else if (code === 0x0d)\n {\n out += '\\\\r';\n }\n else if (code === 0x09)\n {\n out += '\\\\t';\n }\n else if (code < 0x20)\n {\n out += `\\\\u00${code.toString(16).padStart(2, '0')}`;\n }\n else\n {\n out += ch;\n }\n }\n\n return out + '\"';\n}\n","/**\n * SPFN-PROOF-INPUT-1 — proof-input assembly and verification for clientProofV1.\n *\n * The proof input is 8 fields joined by `\\n` in fixed order: profile, method,\n * path, clientId, keyId, nonce, issuedAtMillis, bodySha256. Any C0 control\n * character in any field is a hard refusal (the separator would otherwise be\n * ambiguous), never something to escape. The MAC is HMAC-SHA-256 over the\n * canonical input's UTF-8 bytes, encoded base16-lower.\n *\n * @module server/client-proof/proof\n */\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto';\n\n/** The only auth profile this module implements. */\nexport const CLIENT_PROOF_PROFILE = 'clientProofV1';\n\n/** `bodySha256` when an operation carries no body: 64 zero characters. */\nexport const ABSENT_BODY_SHA256 = '0'.repeat(64);\n\n/** The contract's `clientProofV1.replayWindowMillis`. */\nexport const DEFAULT_REPLAY_WINDOW_MILLIS = 300_000;\n\n/** The eight proof-input fields, in the order the MAC is taken over. */\nexport const PROOF_INPUT_FIELDS = [\n 'profile',\n 'method',\n 'path',\n 'clientId',\n 'keyId',\n 'nonce',\n 'issuedAtMillis',\n 'bodySha256',\n] as const;\n\n/** What joins the proof-input fields. */\nexport const PROOF_INPUT_SEPARATOR = '\\n';\n\ntype ProofInputField = (typeof PROOF_INPUT_FIELDS)[number];\n\nexport interface ClientProofInput\n{\n method: string;\n path: string;\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n bodySha256: string;\n}\n\n/** A C0 control character appeared in a proof field. */\nexport class ProofInputError extends Error\n{\n constructor()\n {\n super('proof input field contains a C0 control character');\n this.name = 'ProofInputError';\n }\n}\n\n/**\n * The canonical proof-input string the MAC is taken over.\n *\n * @throws ProofInputError when any field contains a C0 control character.\n */\nexport function canonicalProofInput(input: ClientProofInput): string\n{\n const values: Record<ProofInputField, string> = {\n profile: CLIENT_PROOF_PROFILE,\n method: input.method,\n path: input.path,\n clientId: input.clientId,\n keyId: input.keyId,\n nonce: input.nonce,\n issuedAtMillis: input.issuedAtMillis.toString(),\n bodySha256: input.bodySha256,\n };\n const fields = PROOF_INPUT_FIELDS.map((name) => values[name]);\n for (const field of fields)\n {\n for (const ch of field)\n {\n if (ch.codePointAt(0)! < 0x20)\n {\n throw new ProofInputError();\n }\n }\n }\n\n return fields.join(PROOF_INPUT_SEPARATOR);\n}\n\n/** The base16-lower HMAC-SHA-256 proof for `input` under `key`. */\nexport function computeClientProof(input: ClientProofInput, key: Uint8Array): string\n{\n return createHmac('sha256', key).update(canonicalProofInput(input), 'utf8').digest('hex');\n}\n\n/** Lowercase base16 SHA-256 of `bytes`. */\nexport function sha256Hex(bytes: Uint8Array): string\n{\n return createHash('sha256').update(bytes).digest('hex');\n}\n\n/**\n * Constant-time comparison of two proof strings.\n *\n * Length is checked first (its leak reveals nothing — the expected length is\n * public), then the bytes are compared with `timingSafeEqual`.\n */\nexport function constantTimeEqualsProof(expected: string, presented: string): boolean\n{\n const a = Buffer.from(expected, 'utf8');\n const b = Buffer.from(presented, 'utf8');\n if (a.length !== b.length)\n {\n return false;\n }\n\n return timingSafeEqual(a, b);\n}\n","/**\n * Every way a clientProofV1 server refuses a request.\n *\n * The contract declares six error codes and forbids inventing a seventh, so\n * every refusal here is one of the six. Two rules decide which code a refusal\n * gets (mirroring the spfn-mobile reference server, the executable spec):\n *\n * 1. A refusal a new session could clear is an auth-family code (401). The SDK\n * re-handshakes exactly once on those.\n * 2. Everything else — the request is not the shape the contract describes —\n * is CONTRACT_UNSUPPORTED: the two ends do not agree on what the contract\n * is. PROOF_INVALID would provoke a pointless re-handshake and\n * PROFILE_REJECTED names one specific thing (a profile outside the\n * allowlist), used for exactly and only that.\n *\n * Every message is a fixed string: a message assembled from the request would\n * put a nonce, session id or body fragment into an error the client may log.\n *\n * @module server/client-proof/refusal\n */\nimport { randomBytes } from 'node:crypto';\n\nimport { encodeCanonicalJson, type CanonicalObject, type CanonicalValue } from './canonical-json';\n\n/** The six wire codes. The SDKs classify by code, never HTTP status. */\nexport type ClientProofErrorCode =\n | 'PROOF_INVALID'\n | 'PROOF_REPLAYED'\n | 'PROOF_EXPIRED'\n | 'SESSION_REVOKED'\n | 'PROFILE_REJECTED'\n | 'CONTRACT_UNSUPPORTED';\n\n/** The declaration order of the six codes — the contract export emits this order. */\nexport const CLIENT_PROOF_ERROR_CODES: readonly ClientProofErrorCode[] = [\n 'PROOF_INVALID',\n 'PROOF_REPLAYED',\n 'PROOF_EXPIRED',\n 'SESSION_REVOKED',\n 'PROFILE_REJECTED',\n 'CONTRACT_UNSUPPORTED',\n];\n\n/** The status each code answers with. The contract export reads this. */\nexport const HTTP_STATUS: Record<ClientProofErrorCode, number> = {\n PROOF_INVALID: 401,\n PROOF_REPLAYED: 401,\n PROOF_EXPIRED: 401,\n SESSION_REVOKED: 401,\n PROFILE_REJECTED: 400,\n CONTRACT_UNSUPPORTED: 409,\n};\n\n/** 128 random bits as lowercase base16 — request ids and control tokens. */\nexport function newHexId(): string\n{\n return randomBytes(16).toString('hex');\n}\n\nexport class ClientProofRefusal\n{\n constructor(\n readonly code: ClientProofErrorCode,\n readonly message: string,\n ) \n {}\n\n get httpStatus(): number\n {\n return HTTP_STATUS[this.code];\n }\n\n /** The canonical bytes of `{\"error\":{\"code\":…,\"message\":…,\"requestId\":…}}`. */\n envelopeBytes(requestId: string): Uint8Array\n {\n const error: CanonicalObject = new Map<string, CanonicalValue>([\n ['code', this.code],\n ['message', this.message],\n ['requestId', requestId],\n ]);\n\n return encodeCanonicalJson(new Map<string, CanonicalValue>([['error', error]]));\n }\n\n /** Nothing request-derived reaches a log through this. */\n toString(): string\n {\n return `ClientProofRefusal(${this.code})`;\n }\n\n // ---- shape: what arrived is not the contract (rule 2) -------------------\n\n static unroutable(): ClientProofRefusal\n {\n return contractViolation('no operation in this contract answers that method and path');\n }\n\n static malformedHeaders(): ClientProofRefusal\n {\n return contractViolation('the request does not carry the contract header fields exactly once each');\n }\n\n static missingContentType(): ClientProofRefusal\n {\n return contractViolation('a request that carries a body must declare the contract content type');\n }\n\n static bodyTooLarge(): ClientProofRefusal\n {\n return contractViolation('the request body exceeds the size this server accepts');\n }\n\n /**\n * The body parsed but its bytes are not the canonical form of what it\n * parsed to. Not PROOF_INVALID even though it is discovered next to the\n * proof: the proof over these bytes verifies perfectly well, and an\n * auth-family answer would tell the client to re-handshake and send the\n * same non-canonical bytes again.\n */\n static bodyNotCanonical(): ClientProofRefusal\n {\n return contractViolation('the request body is not the canonical JSON form of the value it encodes');\n }\n\n static bodyNotTheDeclaredType(): ClientProofRefusal\n {\n return contractViolation('the request body is not the request type this operation declares');\n }\n\n static sessionHeaderMisplaced(): ClientProofRefusal\n {\n return contractViolation('the session header is present exactly on the operations that require one');\n }\n\n static unprocessable(): ClientProofRefusal\n {\n return contractViolation('the request could not be processed');\n }\n\n // ---- the profile allowlist ----------------------------------------------\n\n static profileRejected(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROFILE_REJECTED', \"the named auth profile is not on this contract's allowlist\");\n }\n\n // ---- auth: a new session might clear it (rule 1) -------------------------\n\n static sessionRevoked(): ClientProofRefusal\n {\n return new ClientProofRefusal('SESSION_REVOKED', 'the key or session was revoked');\n }\n\n static proofExpired(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_EXPIRED', 'issuedAtMillis falls outside the replay window');\n }\n\n static proofReplayed(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_REPLAYED', 'the nonce was already used inside the replay window');\n }\n\n static proofInvalid(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_INVALID', 'the client proof did not verify');\n }\n}\n\nfunction contractViolation(message: string): ClientProofRefusal\n{\n return new ClientProofRefusal('CONTRACT_UNSUPPORTED', message);\n}\n","/**\n * Everything a clientProofV1 server remembers between requests: issued\n * sessions, the replay ledger, revoked keys and the key directory.\n *\n * The admission order is the contract's, not this file's invention\n * (`clientProofV1.revocationRule` + the replay fixtures):\n *\n * 1. revoked keyId / invalid session → SESSION_REVOKED — before proof\n * verification, so revocation stays distinguishable from a bad proof;\n * 2. issuedAtMillis outside the replay window (0 <= age <= window) → PROOF_EXPIRED;\n * 3. a repeated (clientId, nonce) pair inside the window → PROOF_REPLAYED;\n * 4. only then HMAC verification → PROOF_INVALID on mismatch.\n *\n * A nonce is recorded as spent only on admission: a request refused for any\n * earlier reason has not spent anything, so a client that fixes the reason and\n * retries with the same nonce is not punished twice for one mistake. This is\n * why core's `NonceStore.checkAndSet` (which records on check) is not reused\n * here — its semantics would spend a nonce on a refused request.\n *\n * `admit` is synchronous, so on Node's single thread the whole sequence is\n * atomic: two requests presenting the same nonce cannot interleave inside it.\n *\n * @module server/client-proof/state\n */\nimport {\n computeClientProof,\n constantTimeEqualsProof,\n DEFAULT_REPLAY_WINDOW_MILLIS,\n type ClientProofInput,\n} from './proof';\nimport { ClientProofRefusal, newHexId } from './refusal';\n\n/** Millisecond clock. Injectable so expiry paths are testable without waiting. */\nexport interface ClientProofClock\n{\n nowMillis(): number;\n}\n\nexport function systemClock(): ClientProofClock\n{\n return { nowMillis: () => Date.now() };\n}\n\n/** A clock a test (or the dev control surface) can move forward. */\nexport class TestClock implements ClientProofClock\n{\n constructor(private millis: number) \n {}\n\n nowMillis(): number\n {\n return this.millis;\n }\n\n advance(byMillis: number): void\n {\n this.millis += byMillis;\n }\n}\n\n/** What `stats()` reports. Counters only; nothing a request carried. */\nexport interface ClientProofStats\n{\n requestCount: number;\n handshakeCount: number;\n echoCount: number;\n itemsListCount: number;\n refusalCount: number;\n liveSessionCount: number;\n spentNonceCount: number;\n}\n\ninterface ClientProofSession\n{\n clientId: string;\n keyId: string;\n expiresAtMillis: number;\n}\n\ninterface PathHold\n{\n millis: number;\n remaining: number;\n}\n\nexport interface ClientProofStateOptions\n{\n /**\n * keyId → HMAC key. A string is taken as UTF-8 bytes. Dev provisioning is\n * injection at construction; any issuance flow works as long as\n * clientId/keyId/key triples exist on both ends.\n */\n keys: Record<string, string | Uint8Array>;\n\n clock?: ClientProofClock;\n\n /** @default 600000 */\n sessionTtlMillis?: number;\n\n /** The contract's replay window. @default 300000 */\n replayWindowMillis?: number;\n}\n\nexport const DEFAULT_SESSION_TTL_MILLIS = 600_000;\n\n/** The ledger key: joined with a C0 control, which no proof field may contain. */\nfunction replayKeyOf(clientId: string, nonce: string): string\n{\n return `${clientId}\u001f${nonce}`;\n}\n\nexport class ClientProofState\n{\n readonly replayWindowMillis: number;\n\n private readonly clock: ClientProofClock;\n private readonly keys = new Map<string, Uint8Array>();\n private readonly sessions = new Map<string, ClientProofSession>();\n\n /** replayKeyOf(...) → the issuedAtMillis it was spent at. */\n private readonly spentNonces = new Map<string, number>();\n\n private readonly revokedKeyIds = new Set<string>();\n private readonly holds = new Map<string, PathHold>();\n\n private readonly initialSessionTtlMillis: number;\n private sessionTtlMillis: number;\n\n private requestCount = 0;\n private handshakeCount = 0;\n private echoCount = 0;\n private itemsListCount = 0;\n private refusalCount = 0;\n\n constructor(options: ClientProofStateOptions)\n {\n this.clock = options.clock ?? systemClock();\n this.initialSessionTtlMillis = options.sessionTtlMillis ?? DEFAULT_SESSION_TTL_MILLIS;\n this.sessionTtlMillis = this.initialSessionTtlMillis;\n this.replayWindowMillis = options.replayWindowMillis ?? DEFAULT_REPLAY_WINDOW_MILLIS;\n for (const [keyId, key] of Object.entries(options.keys))\n {\n this.keys.set(keyId, typeof key === 'string' ? new TextEncoder().encode(key) : key);\n }\n }\n\n // ---- admission ---------------------------------------------------------\n\n /**\n * Runs the contract's checks in the contract's order and returns the\n * refusal, or null when the request is admitted (spending its nonce).\n */\n admit(args: {\n clientId: string;\n keyId: string;\n presentedSessionId: string | null;\n requiresSession: boolean;\n proofInput: ClientProofInput;\n presentedProof: string;\n }): ClientProofRefusal | null\n {\n const now = this.clock.nowMillis();\n this.prune(now);\n\n // 1. Revocation, before anything the proof could explain. A revoked key\n // and a dropped session are the same answer on purpose: both are\n // cleared by opening a new session.\n if (this.revokedKeyIds.has(args.keyId))\n {\n return ClientProofRefusal.sessionRevoked();\n }\n if (args.requiresSession)\n {\n const session = args.presentedSessionId === null ? undefined : this.sessions.get(args.presentedSessionId);\n if (session === undefined || session.expiresAtMillis <= now\n || session.keyId !== args.keyId || session.clientId !== args.clientId)\n {\n return ClientProofRefusal.sessionRevoked();\n }\n }\n\n // 2. The replay window, judged against this server's clock.\n const age = now - Number(args.proofInput.issuedAtMillis);\n if (age < 0 || age > this.replayWindowMillis)\n {\n return ClientProofRefusal.proofExpired();\n }\n\n // 3. One acceptance per (clientId, nonce) inside that window.\n const replayKey = replayKeyOf(args.clientId, args.proofInput.nonce);\n if (this.spentNonces.has(replayKey))\n {\n return ClientProofRefusal.proofReplayed();\n }\n\n // 4. The proof itself, last, so the three answers above stay\n // distinguishable. An unrecognised keyId lands here rather than in\n // step 1: it was never issued, so it was never revoked, and there is\n // nothing for a new session to fix.\n const key = this.keys.get(args.keyId);\n if (key === undefined)\n {\n return ClientProofRefusal.proofInvalid();\n }\n if (!constantTimeEqualsProof(computeClientProof(args.proofInput, key), args.presentedProof))\n {\n return ClientProofRefusal.proofInvalid();\n }\n\n this.spentNonces.set(replayKey, Number(args.proofInput.issuedAtMillis));\n\n return null;\n }\n\n // ---- sessions ----------------------------------------------------------\n\n /** Opens a session and returns its id and the expiry the server advertises. */\n openSession(clientId: string, keyId: string): { sessionId: string; expiresAtMillis: number }\n {\n const now = this.clock.nowMillis();\n this.prune(now);\n const sessionId = newHexId();\n const expiresAtMillis = now + this.sessionTtlMillis;\n this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });\n\n return { sessionId, expiresAtMillis };\n }\n\n /** Test hook: installs a session with a chosen id (wire-fixture replays). */\n seedSession(sessionId: string, clientId: string, keyId: string, expiresAtMillis: number): void\n {\n this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });\n }\n\n /** Drops every session, as a restart would. Advertised expiries stay told. */\n expireSessions(): void\n {\n this.sessions.clear();\n }\n\n /** Revokes a key and drops the sessions it opened. */\n revokeKey(keyId: string): void\n {\n this.revokedKeyIds.add(keyId);\n for (const [sessionId, session] of this.sessions)\n {\n if (session.keyId === keyId)\n {\n this.sessions.delete(sessionId);\n }\n }\n }\n\n setSessionTtlMillis(millis: number): void\n {\n this.sessionTtlMillis = millis;\n }\n\n /** Returns the state to how it started, counters included. */\n reset(): void\n {\n this.sessions.clear();\n this.spentNonces.clear();\n this.revokedKeyIds.clear();\n this.holds.clear();\n this.sessionTtlMillis = this.initialSessionTtlMillis;\n this.requestCount = 0;\n this.handshakeCount = 0;\n this.echoCount = 0;\n this.itemsListCount = 0;\n this.refusalCount = 0;\n }\n\n // ---- delays (dev/test only) --------------------------------------------\n\n /** Makes the next `count` requests to `path` wait `millis` before processing. */\n holdPath(path: string, millis: number, count: number): void\n {\n this.holds.set(path, { millis, remaining: count });\n }\n\n /** Consumes one configured delay for `path`; returns how long to wait, or 0. */\n takeHoldMillis(path: string): number\n {\n const hold = this.holds.get(path);\n if (hold === undefined)\n {\n return 0;\n }\n hold.remaining -= 1;\n if (hold.remaining <= 0)\n {\n this.holds.delete(path);\n }\n\n return hold.millis;\n }\n\n // ---- counters ----------------------------------------------------------\n\n recordRequest(): void\n {\n this.requestCount += 1;\n }\n\n recordOperation(operationId: string): void\n {\n if (operationId === 'auth.clientProof.handshake')\n {\n this.handshakeCount += 1;\n }\n else if (operationId === 'echo.send')\n {\n this.echoCount += 1;\n }\n else if (operationId === 'items.list')\n {\n this.itemsListCount += 1;\n }\n }\n\n recordRefusal(): void\n {\n this.refusalCount += 1;\n }\n\n stats(): ClientProofStats\n {\n this.prune(this.clock.nowMillis());\n\n return {\n requestCount: this.requestCount,\n handshakeCount: this.handshakeCount,\n echoCount: this.echoCount,\n itemsListCount: this.itemsListCount,\n refusalCount: this.refusalCount,\n liveSessionCount: this.sessions.size,\n spentNonceCount: this.spentNonces.size,\n };\n }\n\n nowMillis(): number\n {\n return this.clock.nowMillis();\n }\n\n /** The clock, exposed for the dev control surface's advance-clock route. */\n get clockRef(): ClientProofClock\n {\n return this.clock;\n }\n\n // ---- housekeeping ------------------------------------------------------\n\n /**\n * Drops what can no longer affect an answer. The nonce predicate is the\n * exact negation of the window check in `admit`: an entry is dropped only\n * once a proof carrying that issuedAtMillis would be refused as expired\n * anyway. Dropping one moment earlier would let a nonce inside the window\n * be spent twice.\n */\n private prune(nowMillis: number): void\n {\n for (const [sessionId, session] of this.sessions)\n {\n if (session.expiresAtMillis <= nowMillis)\n {\n this.sessions.delete(sessionId);\n }\n }\n for (const [key, issuedAtMillis] of this.spentNonces)\n {\n if (nowMillis - issuedAtMillis > this.replayWindowMillis)\n {\n this.spentNonces.delete(key);\n }\n }\n }\n}\n","/**\n * The checks between a clientProofV1 request arriving and being applied.\n *\n * Shape first, then the profile allowlist, then the proof. That order is\n * forced: none of the proof checks can run until the fields they read are\n * known to be present and the body is known to be the bytes the digest is\n * supposed to cover. The order *inside* the proof checks is the contract's and\n * lives in `ClientProofState.admit`.\n *\n * @module server/client-proof/admission\n */\nimport { isCanonicalBytes, parseCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { CLIENT_PROOF_PROFILE, sha256Hex, type ClientProofInput } from './proof';\nimport { ClientProofRefusal } from './refusal';\nimport type { ClientProofState } from './state';\n\n/** D23 wire-header names, ratified as proposed by the mobile dev bundle. */\nexport const CLIENT_PROOF_HEADERS = {\n profile: 'x-spfn-auth-profile',\n clientId: 'x-spfn-client-id',\n keyId: 'x-spfn-key-id',\n nonce: 'x-spfn-nonce',\n issuedAtMillis: 'x-spfn-issued-at',\n proof: 'x-spfn-proof',\n session: 'x-spfn-session',\n} as const;\n\nexport const CLIENT_PROOF_CONTENT_TYPE = 'application/json';\n\nconst INT64_MIN = -(2n ** 63n);\nconst INT64_MAX = 2n ** 63n - 1n;\n\n/** The contract header fields one request presented. */\nexport interface ClientProofCredentials\n{\n profile: string;\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n proof: string;\n sessionId: string | null;\n}\n\nexport type Admission =\n | { admitted: false; refusal: ClientProofRefusal }\n | { admitted: true; value: CanonicalValue; credentials: ClientProofCredentials };\n\n/**\n * Runs every check for one operation over already-read body bytes.\n *\n * `path` must be the operation's contract path (what the client signed), not a\n * proxied or rewritten one.\n */\nexport function admitClientProofRequest(args: {\n state: ClientProofState;\n headers: Headers;\n method: string;\n path: string;\n requiresSession: boolean;\n body: Uint8Array;\n}): Admission\n{\n const credentials = readCredentials(args.headers);\n if (credentials === null)\n {\n return refused(ClientProofRefusal.malformedHeaders());\n }\n if (credentials.profile !== CLIENT_PROOF_PROFILE)\n {\n return refused(ClientProofRefusal.profileRejected());\n }\n if (!isRequestContentType(args.headers.get('content-type')))\n {\n return refused(ClientProofRefusal.missingContentType());\n }\n if (args.requiresSession !== (credentials.sessionId !== null))\n {\n return refused(ClientProofRefusal.sessionHeaderMisplaced());\n }\n\n let value: CanonicalValue;\n try\n {\n value = parseCanonicalJson(args.body);\n }\n catch\n {\n return refused(ClientProofRefusal.bodyNotCanonical());\n }\n // The proof binds the received bytes; accepting a re-serialization would\n // let two implementations disagree about what was signed.\n if (!isCanonicalBytes(args.body, value))\n {\n return refused(ClientProofRefusal.bodyNotCanonical());\n }\n\n const proofInput: ClientProofInput = {\n method: args.method,\n path: args.path,\n clientId: credentials.clientId,\n keyId: credentials.keyId,\n nonce: credentials.nonce,\n issuedAtMillis: credentials.issuedAtMillis,\n bodySha256: sha256Hex(args.body),\n };\n\n let refusal: ClientProofRefusal | null;\n try\n {\n refusal = args.state.admit({\n clientId: credentials.clientId,\n keyId: credentials.keyId,\n presentedSessionId: credentials.sessionId,\n requiresSession: args.requiresSession,\n proofInput,\n presentedProof: credentials.proof,\n });\n }\n catch\n {\n // A C0 control character in a header field makes the proof input\n // unassemblable — the request is not the shape the contract describes.\n return refused(ClientProofRefusal.unprocessable());\n }\n if (refusal !== null)\n {\n return refused(refusal);\n }\n\n return { admitted: true, value, credentials };\n}\n\nfunction refused(refusal: ClientProofRefusal): Admission\n{\n return { admitted: false, refusal };\n}\n\n/**\n * The contract header fields, or null when any is absent or malformed.\n *\n * Fetch `Headers` folds a repeated field into one comma-joined value, so\n * \"sent more than once\" is not directly observable here; a folded value fails\n * either the issuedAt grammar or proof verification instead.\n */\nfunction readCredentials(headers: Headers): ClientProofCredentials | null\n{\n const profile = headers.get(CLIENT_PROOF_HEADERS.profile);\n const clientId = headers.get(CLIENT_PROOF_HEADERS.clientId);\n const keyId = headers.get(CLIENT_PROOF_HEADERS.keyId);\n const nonce = headers.get(CLIENT_PROOF_HEADERS.nonce);\n const issuedAtRaw = headers.get(CLIENT_PROOF_HEADERS.issuedAtMillis);\n const proof = headers.get(CLIENT_PROOF_HEADERS.proof);\n if (profile === null || clientId === null || keyId === null\n || nonce === null || issuedAtRaw === null || proof === null)\n {\n return null;\n }\n const issuedAtMillis = parseInt64(issuedAtRaw);\n if (issuedAtMillis === null)\n {\n return null;\n }\n\n return {\n profile,\n clientId,\n keyId,\n nonce,\n issuedAtMillis,\n proof,\n sessionId: headers.get(CLIENT_PROOF_HEADERS.session),\n };\n}\n\nfunction parseInt64(raw: string): bigint | null\n{\n if (!/^[+-]?\\d{1,19}$/.test(raw))\n {\n return null;\n }\n const value = BigInt(raw);\n if (value < INT64_MIN || value > INT64_MAX)\n {\n return null;\n }\n\n return value;\n}\n\nfunction isRequestContentType(value: string | null): boolean\n{\n if (value === null)\n {\n return false;\n }\n\n return value.split(';')[0].trim().toLowerCase() === CLIENT_PROOF_CONTENT_TYPE;\n}\n","/**\n * The mobile dev-contract types and operations, decoded from / encoded to\n * canonical values. Strict on purpose: a missing required field, a wrong type\n * or an unknown field is \"not the request type this operation declares\".\n *\n * This module is the source of truth for `operations`. The exported contract\n * bundle (`contracts/mobile/spfn-mobile-contract.v1.json`) is generated from it\n * by `contract-bundle.ts`; spfn-mobile consumes that export rather than the\n * other way round.\n *\n * @module server/client-proof/contract-types\n */\nimport type { CanonicalObject, CanonicalValue } from './canonical-json';\n\nexport interface ContractOperation\n{\n id: 'auth.clientProof.handshake' | 'echo.send' | 'items.list';\n method: 'POST';\n path: string;\n authProfile: 'clientProofV1';\n requiresSession: boolean;\n requestType: string;\n responseType: string;\n summary: string;\n}\n\nexport const CONTRACT_OPERATIONS: readonly ContractOperation[] = [\n {\n id: 'auth.clientProof.handshake',\n method: 'POST',\n path: '/v1/auth/client-proof/handshake',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'HandshakeRequest',\n responseType: 'HandshakeResponse',\n summary: 'Presents a client proof and opens a session.',\n },\n {\n id: 'echo.send',\n method: 'POST',\n path: '/v1/echo',\n authProfile: 'clientProofV1',\n requiresSession: true,\n requestType: 'EchoRequest',\n responseType: 'EchoResponse',\n summary: 'Authenticated round trip used as the smallest real vertical slice.',\n },\n {\n id: 'items.list',\n method: 'POST',\n path: '/v1/items/list',\n authProfile: 'clientProofV1',\n requiresSession: true,\n requestType: 'ListItemsRequest',\n responseType: 'ListItemsResponse',\n summary: 'Authenticated paged read covering optional fields and arrays.',\n },\n];\n\n/** The body is canonical JSON but not the declared request type. */\nexport class ContractTypeError extends Error\n{\n constructor()\n {\n super('not the declared contract type');\n this.name = 'ContractTypeError';\n }\n}\n\nexport interface HandshakeRequest\n{\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n}\n\nexport interface EchoRequest\n{\n message: string;\n sequence: bigint;\n}\n\nexport interface ListItemsRequest\n{\n limit: bigint;\n cursor?: string;\n}\n\nexport interface ContractItem\n{\n id: string;\n name: string;\n updatedAtMillis: bigint;\n}\n\n// ============================================================================\n// Decoding\n// ============================================================================\n\nexport function decodeHandshakeRequest(value: CanonicalValue): HandshakeRequest\n{\n const members = objectWithKeys(value, ['clientId', 'keyId', 'nonce', 'issuedAtMillis'], []);\n\n return {\n clientId: text(members.get('clientId')),\n keyId: text(members.get('keyId')),\n nonce: text(members.get('nonce')),\n issuedAtMillis: integer(members.get('issuedAtMillis')),\n };\n}\n\nexport function decodeEchoRequest(value: CanonicalValue): EchoRequest\n{\n const members = objectWithKeys(value, ['message', 'sequence'], []);\n\n return {\n message: text(members.get('message')),\n sequence: integer(members.get('sequence')),\n };\n}\n\nexport function decodeListItemsRequest(value: CanonicalValue): ListItemsRequest\n{\n const members = objectWithKeys(value, ['limit'], ['cursor']);\n const request: ListItemsRequest = { limit: integer(members.get('limit')) };\n if (members.has('cursor'))\n {\n request.cursor = text(members.get('cursor'));\n }\n\n return request;\n}\n\nfunction objectWithKeys(\n value: CanonicalValue,\n required: string[],\n optional: string[],\n): CanonicalObject\n{\n if (!(value instanceof Map))\n {\n throw new ContractTypeError();\n }\n for (const key of required)\n {\n if (!value.has(key))\n {\n throw new ContractTypeError();\n }\n }\n for (const key of value.keys())\n {\n if (!required.includes(key) && !optional.includes(key))\n {\n throw new ContractTypeError();\n }\n }\n\n return value;\n}\n\nfunction text(value: CanonicalValue | undefined): string\n{\n if (typeof value !== 'string')\n {\n throw new ContractTypeError();\n }\n\n return value;\n}\n\nfunction integer(value: CanonicalValue | undefined): bigint\n{\n if (typeof value !== 'bigint')\n {\n throw new ContractTypeError();\n }\n\n return value;\n}\n\n// ============================================================================\n// Encoding\n// ============================================================================\n\nexport function encodeHandshakeResponse(sessionId: string, expiresAtMillis: bigint): CanonicalValue\n{\n return new Map<string, CanonicalValue>([\n ['sessionId', sessionId],\n ['expiresAtMillis', expiresAtMillis],\n ]);\n}\n\nexport function encodeEchoResponse(message: string, sequence: bigint, serverTimeMillis: bigint): CanonicalValue\n{\n return new Map<string, CanonicalValue>([\n ['message', message],\n ['sequence', sequence],\n ['serverTimeMillis', serverTimeMillis],\n ]);\n}\n\nexport function encodeListItemsResponse(items: ContractItem[], nextCursor: string | null): CanonicalValue\n{\n const encodedItems: CanonicalValue = items.map((item) => new Map<string, CanonicalValue>([\n ['id', item.id],\n ['name', item.name],\n ['updatedAtMillis', item.updatedAtMillis],\n ]));\n const members = new Map<string, CanonicalValue>([['items', encodedItems]]);\n if (nextCursor !== null)\n {\n members.set('nextCursor', nextCursor);\n }\n\n return members;\n}\n","/**\n * The dev server's test hooks, mirroring the spfn-mobile reference server's\n * `/control` surface route for route so the mobile integration suites can\n * drive either server with only a URL change.\n *\n * `/control` is NOT part of the contract: nothing under it appears in the\n * bundle, no SDK knows it exists, and its answers are plain objects rather\n * than contract envelopes. Every route except the readiness probe requires\n * the per-launch token; the token is never logged.\n *\n * @module server/client-proof/dev-control\n */\nimport { encodeCanonicalJson, parseCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { ClientProofState, TestClock } from './state';\n\nexport const CONTROL_PREFIX = '/control/';\n\nexport const CONTROL_TOKEN_HEADER = 'x-spfn-reference-control';\n\nconst HTTP_OK = 200;\nconst HTTP_BAD_REQUEST = 400;\nconst HTTP_FORBIDDEN = 403;\nconst HTTP_NOT_FOUND = 404;\nconst HTTP_CONFLICT = 409;\n\nconst MAX_CONTROL_BODY_BYTES = 4096;\n\nexport async function handleControlRequest(\n state: ClientProofState,\n controlToken: string,\n path: string,\n request: Request,\n): Promise<Response>\n{\n if (path === '/control/health')\n {\n return answer(HTTP_OK, new Map<string, CanonicalValue>([['status', 'ok']]));\n }\n if (request.headers.get(CONTROL_TOKEN_HEADER) !== controlToken)\n {\n return answer(HTTP_FORBIDDEN, failure('control token'));\n }\n\n const raw = new Uint8Array(await request.arrayBuffer());\n const body = raw.length > MAX_CONTROL_BODY_BYTES ? raw.slice(0, MAX_CONTROL_BODY_BYTES) : raw;\n\n switch (path)\n {\n case '/control/stats':\n return stats(state);\n case '/control/reset':\n state.reset();\n\n return ok();\n case '/control/expire-sessions':\n state.expireSessions();\n\n return ok();\n case '/control/revoke-key':\n return revokeKey(state, body);\n case '/control/session-ttl':\n return sessionTtl(state, body);\n case '/control/hold':\n return hold(state, body);\n case '/control/advance-clock':\n return advanceClock(state, body);\n default:\n return answer(HTTP_NOT_FOUND, failure('unknown control route'));\n }\n}\n\n// ---- routes ----------------------------------------------------------------\n\nfunction stats(state: ClientProofState): Response\n{\n const counters = state.stats();\n\n return answer(HTTP_OK, withOk(new Map<string, CanonicalValue>([\n ['echoCount', BigInt(counters.echoCount)],\n ['handshakeCount', BigInt(counters.handshakeCount)],\n ['itemsListCount', BigInt(counters.itemsListCount)],\n ['liveSessionCount', BigInt(counters.liveSessionCount)],\n ['refusalCount', BigInt(counters.refusalCount)],\n ['requestCount', BigInt(counters.requestCount)],\n ['spentNonceCount', BigInt(counters.spentNonceCount)],\n ])));\n}\n\nfunction revokeKey(state: ClientProofState, body: Uint8Array): Response\n{\n const keyId = stringField(body, 'keyId');\n if (keyId === null)\n {\n return badRequest('keyId');\n }\n state.revokeKey(keyId);\n\n return ok();\n}\n\nfunction sessionTtl(state: ClientProofState, body: Uint8Array): Response\n{\n const ttlMillis = integerField(body, 'ttlMillis');\n if (ttlMillis === null)\n {\n return badRequest('ttlMillis');\n }\n state.setSessionTtlMillis(Number(ttlMillis));\n\n return ok();\n}\n\nfunction hold(state: ClientProofState, body: Uint8Array): Response\n{\n const path = stringField(body, 'path');\n const millis = integerField(body, 'millis');\n const count = integerField(body, 'count');\n if (path === null)\n {\n return badRequest('path');\n }\n if (millis === null)\n {\n return badRequest('millis');\n }\n if (count === null)\n {\n return badRequest('count');\n }\n state.holdPath(path, Number(millis), Number(count));\n\n return ok();\n}\n\n/**\n * Moves a test clock forward. Refused when the server runs on the wall clock,\n * because silently doing nothing is how a test passes for the wrong reason.\n */\nfunction advanceClock(state: ClientProofState, body: Uint8Array): Response\n{\n const clock = state.clockRef;\n if (!(clock instanceof TestClock))\n {\n return answer(HTTP_CONFLICT, failure('server is running on the system clock'));\n }\n const millis = integerField(body, 'millis');\n if (millis === null)\n {\n return badRequest('millis');\n }\n clock.advance(Number(millis));\n\n return ok();\n}\n\n// ---- plumbing --------------------------------------------------------------\n\nfunction members(body: Uint8Array): Map<string, CanonicalValue> | null\n{\n if (body.length === 0)\n {\n return new Map();\n }\n let parsed: CanonicalValue;\n try\n {\n parsed = parseCanonicalJson(body);\n }\n catch\n {\n return null;\n }\n\n return parsed instanceof Map ? parsed : null;\n}\n\nfunction stringField(body: Uint8Array, field: string): string | null\n{\n const value = members(body)?.get(field);\n\n return typeof value === 'string' ? value : null;\n}\n\nfunction integerField(body: Uint8Array, field: string): bigint | null\n{\n const value = members(body)?.get(field);\n\n return typeof value === 'bigint' ? value : null;\n}\n\nfunction badRequest(field: string): Response\n{\n return answer(HTTP_BAD_REQUEST, failure(`missing or malformed field: ${field}`));\n}\n\nfunction ok(): Response\n{\n return answer(HTTP_OK, withOk(new Map()));\n}\n\nfunction failure(reason: string): Map<string, CanonicalValue>\n{\n return new Map<string, CanonicalValue>([['ok', false], ['reason', reason]]);\n}\n\nfunction withOk(extra: Map<string, CanonicalValue>): Map<string, CanonicalValue>\n{\n extra.set('ok', true);\n\n return extra;\n}\n\nfunction answer(status: number, value: Map<string, CanonicalValue>): Response\n{\n const bytes = encodeCanonicalJson(value);\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\n return new Response(buffer, { status, headers: { 'content-type': 'application/json' } });\n}\n","/**\n * The mobile-contract dev surface: a fetch-style handler exposing the three\n * dev operations (handshake / echo.send / items.list) plus the `/control`\n * test hooks the spfn-mobile integration suites drive.\n *\n * Framework-free on purpose — `fetch(request) => Response` plugs into\n * `@hono/node-server`'s `serve({ fetch })` or any Web-standard runtime, and\n * the contract needs byte-exact control over bodies and envelopes that a\n * validating router would take away.\n *\n * This is a dev/test surface, not a production deployment target: keys are\n * injected at construction, state is in-memory, and `/control` mutates it.\n *\n * @module server/client-proof/dev-handler\n */\nimport { encodeCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { admitClientProofRequest, type Admission } from './admission';\nimport {\n CONTRACT_OPERATIONS,\n ContractTypeError,\n decodeEchoRequest,\n decodeHandshakeRequest,\n decodeListItemsRequest,\n encodeEchoResponse,\n encodeHandshakeResponse,\n encodeListItemsResponse,\n type ContractItem,\n type ContractOperation,\n type ListItemsRequest,\n} from './contract-types';\nimport { ClientProofRefusal, newHexId } from './refusal';\nimport { ClientProofState, type ClientProofStateOptions, TestClock } from './state';\nimport { handleControlRequest, CONTROL_PREFIX } from './dev-control';\n\n/** Far above any contract request and far below anything worth buffering. */\nconst MAX_BODY_BYTES = 1 << 20;\n\nconst HTTP_OK = 200;\n\n/**\n * The items `items.list` pages through — fixed and small on purpose, matching\n * the spfn-mobile reference catalogue byte for byte so an integration test can\n * assert exact values against either server.\n */\nexport const DEV_CATALOGUE: readonly ContractItem[] = [\n { id: 'item-0001', name: 'alpha', updatedAtMillis: 1_750_000_000_001n },\n { id: 'item-0002', name: 'bravo', updatedAtMillis: 1_750_000_000_002n },\n { id: 'item-0003', name: 'charlie', updatedAtMillis: 1_750_000_000_003n },\n { id: 'item-0004', name: 'delta', updatedAtMillis: 1_750_000_000_004n },\n { id: 'item-0005', name: 'echo', updatedAtMillis: 1_750_000_000_005n },\n];\n\n/** The largest `items.list` page this server will answer with. */\nexport const DEV_MAX_LIMIT = 100n;\n\nexport interface ClientProofDevHandlerOptions extends ClientProofStateOptions\n{\n /**\n * Token the `/control` routes require (header `x-spfn-reference-control`).\n * Generated per construction when omitted; never logged.\n */\n controlToken?: string;\n\n /** Disables the `/control` surface entirely. @default true */\n enableControl?: boolean;\n\n /** One line per request: method, path, status. Nothing a request carried. */\n log?: (line: string) => void;\n}\n\nexport interface ClientProofDevHandler\n{\n fetch(request: Request): Promise<Response>;\n state: ClientProofState;\n controlToken: string;\n}\n\nexport function createClientProofDevHandler(options: ClientProofDevHandlerOptions): ClientProofDevHandler\n{\n const state = new ClientProofState(options);\n const controlToken = options.controlToken ?? newHexId();\n const enableControl = options.enableControl ?? true;\n const log = options.log ?? (() => undefined);\n\n async function dispatch(request: Request): Promise<Response>\n {\n state.recordRequest();\n const url = new URL(request.url);\n\n if (enableControl && url.pathname.startsWith(CONTROL_PREFIX))\n {\n return handleControlRequest(state, controlToken, url.pathname, request);\n }\n\n // A query string is refused by omission: no contract path carries one,\n // and a proof is taken over the path alone.\n const operation = url.search === ''\n ? CONTRACT_OPERATIONS.find((op) => op.path === url.pathname && op.method === request.method)\n : undefined;\n if (operation === undefined)\n {\n return refuse(ClientProofRefusal.unroutable());\n }\n\n const body = await readBodyCapped(request);\n if (body === null)\n {\n return refuse(ClientProofRefusal.bodyTooLarge());\n }\n\n // Before verification, so a request a test is holding open has not\n // spent its nonce by the time the client gives up waiting for it.\n await waitOutHold(url.pathname);\n\n const admission = admitClientProofRequest({\n state,\n headers: request.headers,\n method: operation.method,\n path: operation.path,\n requiresSession: operation.requiresSession,\n body,\n });\n if (!admission.admitted)\n {\n return refuse(admission.refusal);\n }\n\n return apply(operation, admission);\n }\n\n function apply(operation: ContractOperation, admission: Extract<Admission, { admitted: true }>): Response\n {\n let value: CanonicalValue;\n try\n {\n if (operation.id === 'auth.clientProof.handshake')\n {\n const request = decodeHandshakeRequest(admission.value);\n // The proof already binds the header identity to the key that\n // signed it, so a body naming a different client is a request\n // whose two halves disagree about who sent it.\n if (request.clientId !== admission.credentials.clientId\n || request.keyId !== admission.credentials.keyId)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n const opened = state.openSession(request.clientId, request.keyId);\n value = encodeHandshakeResponse(opened.sessionId, BigInt(opened.expiresAtMillis));\n }\n else if (operation.id === 'echo.send')\n {\n const request = decodeEchoRequest(admission.value);\n value = encodeEchoResponse(request.message, request.sequence, BigInt(state.nowMillis()));\n }\n else\n {\n const listed = listItems(decodeListItemsRequest(admission.value));\n if (listed === null)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n value = listed;\n }\n }\n catch (error)\n {\n if (error instanceof ContractTypeError)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n\n return refuse(ClientProofRefusal.unprocessable());\n }\n\n state.recordOperation(operation.id);\n\n return contractResponse(HTTP_OK, encodeCanonicalJson(value));\n }\n\n function refuse(refusal: ClientProofRefusal): Response\n {\n state.recordRefusal();\n\n return contractResponse(refusal.httpStatus, refusal.envelopeBytes(newHexId()));\n }\n\n async function waitOutHold(path: string): Promise<void>\n {\n const millis = state.takeHoldMillis(path);\n if (millis > 0)\n {\n await new Promise((resolve) => setTimeout(resolve, millis));\n }\n }\n\n return {\n state,\n controlToken,\n fetch: async (request: Request): Promise<Response> =>\n {\n try\n {\n const response = await dispatch(request);\n log(`${request.method} ${new URL(request.url).pathname} -> ${response.status}`);\n\n return response;\n }\n catch\n {\n // A contract answer rather than a stack trace: an exception\n // message can quote the request that produced it.\n return refuse(ClientProofRefusal.unprocessable());\n }\n },\n };\n}\n\n/**\n * One page of the catalogue, or null when the request is not one this\n * contract describes. An unknown cursor and a limit outside 1…MAX are refused\n * rather than clamped — a server that quietly repaired a request would hide\n * the client bug that produced it.\n */\nfunction listItems(request: ListItemsRequest): CanonicalValue | null\n{\n if (request.limit < 1n || request.limit > DEV_MAX_LIMIT)\n {\n return null;\n }\n let start = 0;\n if (request.cursor !== undefined)\n {\n const index = DEV_CATALOGUE.findIndex((item) => item.id === request.cursor);\n if (index < 0)\n {\n return null;\n }\n start = index + 1;\n }\n const end = Math.min(DEV_CATALOGUE.length, start + Number(request.limit));\n const page = DEV_CATALOGUE.slice(start, end);\n // Present only when a further page exists, so \"nextCursor is absent\" is a\n // fact about the data rather than a value the client has to interpret.\n const nextCursor = end < DEV_CATALOGUE.length && page.length > 0 ? page[page.length - 1].id : null;\n\n return encodeListItemsResponse([...page], nextCursor);\n}\n\nfunction contractResponse(status: number, body: Uint8Array): Response\n{\n return new Response(toArrayBuffer(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n}\n\n/** The body, or null when it is larger than this server will read. */\nasync function readBodyCapped(request: Request): Promise<Uint8Array | null>\n{\n if (request.body === null)\n {\n return new Uint8Array(0);\n }\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;)\n {\n const { done, value } = await reader.read();\n if (done)\n {\n break;\n }\n total += value.length;\n if (total > MAX_BODY_BYTES)\n {\n await reader.cancel();\n\n return null;\n }\n chunks.push(value);\n }\n const body = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks)\n {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n\n return body;\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer\n{\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nexport { TestClock };\n","/**\n * Hono middleware adapter for clientProofV1 — the `requiresSession` guard for\n * SPFN servers that mount contract operations as ordinary routes.\n *\n * Runs the full admission sequence over the raw request bytes and, on\n * acceptance, tags the request `clientType: 'mobile'` (the attestation slot\n * PROXY-BACKEND-AUTH-SPEC reserved) and exposes the parsed canonical body and\n * credentials under the `clientProof` context key.\n *\n * hono is imported as types only — the middleware itself is a plain async\n * function, so this module adds no runtime dependency.\n *\n * @module server/client-proof/guard\n */\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport { admitClientProofRequest, type ClientProofCredentials } from './admission';\nimport type { CanonicalValue } from './canonical-json';\nimport { newHexId } from './refusal';\nimport type { ClientProofState } from './state';\n\n/** What the guard leaves in the context for the route handler. */\nexport interface ClientProofContext\n{\n credentials: ClientProofCredentials;\n\n /** The request body as a canonical value (already byte-verified). */\n value: CanonicalValue;\n}\n\nexport interface ClientProofGuardOptions\n{\n /**\n * The contract path the client signed, when it differs from the mounted\n * path (e.g. behind a stripped ingress prefix). Defaults to the request\n * path.\n */\n contractPath?: string;\n}\n\n/**\n * A guard for operations with `requiresSession: true`.\n *\n * Refusals are answered with the contract envelope and never reach the route.\n */\nexport function createClientProofGuard(\n state: ClientProofState,\n options: ClientProofGuardOptions = {},\n): MiddlewareHandler\n{\n return async (c: Context, next: Next) =>\n {\n const body = new Uint8Array(await c.req.arrayBuffer());\n const admission = admitClientProofRequest({\n state,\n headers: c.req.raw.headers,\n method: c.req.method,\n path: options.contractPath ?? c.req.path,\n requiresSession: true,\n body,\n });\n if (!admission.admitted)\n {\n state.recordRefusal();\n const bytes = admission.refusal.envelopeBytes(newHexId());\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\n return c.newResponse(buffer, admission.refusal.httpStatus as 401, {\n 'content-type': 'application/json',\n });\n }\n c.set('clientType', 'mobile');\n c.set('clientProof', {\n credentials: admission.credentials,\n value: admission.value,\n } satisfies ClientProofContext);\n await next();\n\n return undefined;\n };\n}\n"],"mappings":"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","names":["text","members","hold","INT64_MIN","INT64_MAX","members","HTTP_OK"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _spfn_core_nextjs from '@spfn/core/nextjs';
|
|
2
|
-
import { R as RoleConfig, P as PermissionConfig, S as SendVerificationCodeResult, a as RegisterResult, L as LoginResult, b as RotateKeyResult, I as IssueOneTimeTokenResult, O as OAuthStartResult, c as OAuthNativeResult, U as UserProfile, d as ProfileInfo, m as mainAuthRouter } from './authenticate-
|
|
3
|
-
export { j as AuthInitOptions, A as AuthSession, h as PERMISSION_CATEGORIES, i as PermissionCategory, g as VERIFICATION_PURPOSES, f as VERIFICATION_TARGET_TYPES, e as VerificationPurpose, V as VerificationTargetType } from './authenticate-
|
|
2
|
+
import { R as RoleConfig, P as PermissionConfig, S as SendVerificationCodeResult, a as RegisterResult, L as LoginResult, b as RotateKeyResult, I as IssueOneTimeTokenResult, O as OAuthStartResult, c as OAuthNativeResult, U as UserProfile, d as ProfileInfo, m as mainAuthRouter } from './authenticate-DTA7W4v2.js';
|
|
3
|
+
export { j as AuthInitOptions, A as AuthSession, h as PERMISSION_CATEGORIES, i as PermissionCategory, g as VERIFICATION_PURPOSES, f as VERIFICATION_TARGET_TYPES, e as VerificationPurpose, V as VerificationTargetType } from './authenticate-DTA7W4v2.js';
|
|
4
4
|
import * as _spfn_core_route from '@spfn/core/route';
|
|
5
5
|
import { HttpMethod } from '@spfn/core/route';
|
|
6
6
|
export { b as ACCOUNT_DELETION_REQUESTED_BY, A as ACCOUNT_DELETION_REQUEST_STATUSES, f as AccountDeletionRequestStatus, g as AccountDeletionRequestedBy, I as INVITATION_STATUSES, c as InvitationStatus, a as KEY_ALGORITHM, K as KeyAlgorithmType, P as PURGE_STRATEGIES, h as PurgeStrategy, S as SOCIAL_PROVIDERS, e as SocialProvider, U as USER_STATUSES, d as UserStatus } from './types-1BMx0OX1.js';
|
|
@@ -163,7 +163,7 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
|
163
163
|
id: number;
|
|
164
164
|
name: string;
|
|
165
165
|
displayName: string;
|
|
166
|
-
category: "
|
|
166
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
|
|
167
167
|
}[];
|
|
168
168
|
userId: number;
|
|
169
169
|
publicId: string;
|
|
@@ -460,8 +460,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
|
460
460
|
}, {}, {
|
|
461
461
|
roles: {
|
|
462
462
|
description: string | null;
|
|
463
|
-
name: string;
|
|
464
463
|
id: number;
|
|
464
|
+
name: string;
|
|
465
465
|
displayName: string;
|
|
466
466
|
isBuiltin: boolean;
|
|
467
467
|
isSystem: boolean;
|
|
@@ -482,8 +482,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
|
482
482
|
}, {}, {
|
|
483
483
|
role: {
|
|
484
484
|
description: string | null;
|
|
485
|
-
name: string;
|
|
486
485
|
id: number;
|
|
486
|
+
name: string;
|
|
487
487
|
displayName: string;
|
|
488
488
|
isBuiltin: boolean;
|
|
489
489
|
isSystem: boolean;
|
|
@@ -506,8 +506,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
|
506
506
|
}, {}, {
|
|
507
507
|
role: {
|
|
508
508
|
description: string | null;
|
|
509
|
-
name: string;
|
|
510
509
|
id: number;
|
|
510
|
+
name: string;
|
|
511
511
|
displayName: string;
|
|
512
512
|
isBuiltin: boolean;
|
|
513
513
|
isSystem: boolean;
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { j as AuthInitOptions, k as OAuthProvider, e as VerificationPurpose, i as PermissionCategory, l as AuthContext } from './authenticate-
|
|
2
|
-
export { C as ChangePasswordParams, a5 as EmailSchema, I as IssueOneTimeTokenResult, s as LoginParams, L as LoginResult, t as LogoutParams, ac as NativeVerifyOptions, aa as NormalizedIdentity, _ as OAuthCallbackParams, $ as OAuthCallbackResult, ad as OAuthCodeExchangeOptions, a2 as OAuthNativeParams, c as OAuthNativeResult, Z as OAuthStartParams, O as OAuthStartResult, ab as OAuthTokens, a7 as PasswordSchema, a6 as PhoneSchema, q as RegisterParams, E as RegisterPublicKeyParams, a as RegisterResult, G as RevokeKeyParams, F as RotateKeyParams, b as RotateKeyResult, w as SendVerificationCodeParams, S as SendVerificationCodeResult, a8 as TargetTypeSchema, af as UnlinkNotification, ag as UnlinkNotifyRejection, ae as UnlinkNotifyRequest, a0 as UnlinkNotifyResult, g as VERIFICATION_PURPOSES, f as VERIFICATION_TARGET_TYPES, a9 as VerificationPurposeSchema, V as VerificationTargetType, x as VerifyCodeParams, y as VerifyCodeResult, m as authRouter, a3 as authenticate, N as buildOAuthErrorUrl, p as changePasswordService, W as getEnabledOAuthProviders, X as getGoogleAccessToken, ai as getOAuthProvider, aj as getRegisteredProviders, Q as isOAuthProviderEnabled, H as issueOneTimeTokenService, n as loginService, o as logoutService, M as oauthCallbackService, a1 as oauthNativeService, K as oauthStartService, Y as oauthUnlinkNotifyService, a4 as optionalAuth, ah as registerOAuthProvider, z as registerPublicKeyService, r as registerService, T as requireEnabledProvider, D as revokeKeyService, B as rotateKeyService, u as sendVerificationCodeService, v as verifyCodeService, J as verifyOneTimeTokenService } from './authenticate-
|
|
1
|
+
import { j as AuthInitOptions, k as OAuthProvider, e as VerificationPurpose, i as PermissionCategory, l as AuthContext } from './authenticate-DTA7W4v2.js';
|
|
2
|
+
export { C as ChangePasswordParams, a5 as EmailSchema, I as IssueOneTimeTokenResult, s as LoginParams, L as LoginResult, t as LogoutParams, ac as NativeVerifyOptions, aa as NormalizedIdentity, _ as OAuthCallbackParams, $ as OAuthCallbackResult, ad as OAuthCodeExchangeOptions, a2 as OAuthNativeParams, c as OAuthNativeResult, Z as OAuthStartParams, O as OAuthStartResult, ab as OAuthTokens, a7 as PasswordSchema, a6 as PhoneSchema, q as RegisterParams, E as RegisterPublicKeyParams, a as RegisterResult, G as RevokeKeyParams, F as RotateKeyParams, b as RotateKeyResult, w as SendVerificationCodeParams, S as SendVerificationCodeResult, a8 as TargetTypeSchema, af as UnlinkNotification, ag as UnlinkNotifyRejection, ae as UnlinkNotifyRequest, a0 as UnlinkNotifyResult, g as VERIFICATION_PURPOSES, f as VERIFICATION_TARGET_TYPES, a9 as VerificationPurposeSchema, V as VerificationTargetType, x as VerifyCodeParams, y as VerifyCodeResult, m as authRouter, a3 as authenticate, N as buildOAuthErrorUrl, p as changePasswordService, W as getEnabledOAuthProviders, X as getGoogleAccessToken, ai as getOAuthProvider, aj as getRegisteredProviders, Q as isOAuthProviderEnabled, H as issueOneTimeTokenService, n as loginService, o as logoutService, M as oauthCallbackService, a1 as oauthNativeService, K as oauthStartService, Y as oauthUnlinkNotifyService, a4 as optionalAuth, ah as registerOAuthProvider, z as registerPublicKeyService, r as registerService, T as requireEnabledProvider, D as revokeKeyService, B as rotateKeyService, u as sendVerificationCodeService, v as verifyCodeService, J as verifyOneTimeTokenService } from './authenticate-DTA7W4v2.js';
|
|
3
3
|
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
4
4
|
import { K as KeyAlgorithmType, c as InvitationStatus, e as SocialProvider, g as AccountDeletionRequestedBy, h as PurgeStrategy } from './types-1BMx0OX1.js';
|
|
5
5
|
export { b as ACCOUNT_DELETION_REQUESTED_BY, A as ACCOUNT_DELETION_REQUEST_STATUSES, f as AccountDeletionRequestStatus, I as INVITATION_STATUSES, a as KEY_ALGORITHM, P as PURGE_STRATEGIES, S as SOCIAL_PROVIDERS, U as USER_STATUSES, d as UserStatus } from './types-1BMx0OX1.js';
|
|
@@ -1317,7 +1317,7 @@ declare function getAuthSessionService(userId: string | number | bigint): Promis
|
|
|
1317
1317
|
id: number;
|
|
1318
1318
|
name: string;
|
|
1319
1319
|
displayName: string;
|
|
1320
|
-
category: "
|
|
1320
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
|
|
1321
1321
|
}[];
|
|
1322
1322
|
userId: number;
|
|
1323
1323
|
publicId: string;
|
|
@@ -1763,7 +1763,7 @@ declare const accountDeletionRequests: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
1763
1763
|
name: string;
|
|
1764
1764
|
tableName: "account_deletion_requests";
|
|
1765
1765
|
dataType: "string enum";
|
|
1766
|
-
data: "
|
|
1766
|
+
data: "self" | "admin";
|
|
1767
1767
|
driverParam: string;
|
|
1768
1768
|
notNull: true;
|
|
1769
1769
|
hasDefault: true;
|
|
@@ -2877,7 +2877,7 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
2877
2877
|
name: string;
|
|
2878
2878
|
tableName: "permissions";
|
|
2879
2879
|
dataType: "string enum";
|
|
2880
|
-
data: "
|
|
2880
|
+
data: "custom" | "user" | "auth" | "rbac" | "system";
|
|
2881
2881
|
driverParam: string;
|
|
2882
2882
|
notNull: false;
|
|
2883
2883
|
hasDefault: false;
|
|
@@ -3466,15 +3466,15 @@ declare class UsersRepository extends BaseRepository {
|
|
|
3466
3466
|
create(data: NewUser): Promise<{
|
|
3467
3467
|
email: string | null;
|
|
3468
3468
|
phone: string | null;
|
|
3469
|
+
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
3470
|
+
username: string | null;
|
|
3469
3471
|
id: number;
|
|
3472
|
+
createdAt: Date;
|
|
3473
|
+
updatedAt: Date;
|
|
3470
3474
|
publicId: string;
|
|
3471
|
-
username: string | null;
|
|
3472
3475
|
passwordHash: string | null;
|
|
3473
3476
|
passwordChangeRequired: boolean;
|
|
3474
3477
|
roleId: number;
|
|
3475
|
-
createdAt: Date;
|
|
3476
|
-
updatedAt: Date;
|
|
3477
|
-
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
3478
3478
|
emailVerifiedAt: Date | null;
|
|
3479
3479
|
phoneVerifiedAt: Date | null;
|
|
3480
3480
|
lastLoginAt: Date | null;
|
|
@@ -3581,15 +3581,15 @@ declare class UsersRepository extends BaseRepository {
|
|
|
3581
3581
|
deleteById(id: number): Promise<{
|
|
3582
3582
|
email: string | null;
|
|
3583
3583
|
phone: string | null;
|
|
3584
|
+
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
3585
|
+
username: string | null;
|
|
3584
3586
|
id: number;
|
|
3587
|
+
createdAt: Date;
|
|
3588
|
+
updatedAt: Date;
|
|
3585
3589
|
publicId: string;
|
|
3586
|
-
username: string | null;
|
|
3587
3590
|
passwordHash: string | null;
|
|
3588
3591
|
passwordChangeRequired: boolean;
|
|
3589
3592
|
roleId: number;
|
|
3590
|
-
createdAt: Date;
|
|
3591
|
-
updatedAt: Date;
|
|
3592
|
-
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
3593
3593
|
emailVerifiedAt: Date | null;
|
|
3594
3594
|
phoneVerifiedAt: Date | null;
|
|
3595
3595
|
lastLoginAt: Date | null;
|
|
@@ -3614,7 +3614,7 @@ declare class UsersRepository extends BaseRepository {
|
|
|
3614
3614
|
id: number;
|
|
3615
3615
|
name: string;
|
|
3616
3616
|
displayName: string;
|
|
3617
|
-
category: "
|
|
3617
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
|
|
3618
3618
|
}[];
|
|
3619
3619
|
}>;
|
|
3620
3620
|
/**
|
|
@@ -3729,16 +3729,16 @@ declare class KeysRepository extends BaseRepository {
|
|
|
3729
3729
|
* Write primary 사용
|
|
3730
3730
|
*/
|
|
3731
3731
|
create(data: NewUserPublicKey): Promise<{
|
|
3732
|
-
publicKey: string;
|
|
3733
|
-
keyId: string;
|
|
3734
|
-
fingerprint: string;
|
|
3735
|
-
algorithm: "ES256" | "RS256";
|
|
3736
3732
|
userId: number;
|
|
3733
|
+
keyId: string;
|
|
3737
3734
|
id: number;
|
|
3738
3735
|
isActive: boolean;
|
|
3739
3736
|
createdAt: Date;
|
|
3740
|
-
|
|
3737
|
+
publicKey: string;
|
|
3738
|
+
algorithm: "ES256" | "RS256";
|
|
3739
|
+
fingerprint: string;
|
|
3741
3740
|
lastUsedAt: Date | null;
|
|
3741
|
+
expiresAt: Date | null;
|
|
3742
3742
|
revokedAt: Date | null;
|
|
3743
3743
|
revokedReason: string | null;
|
|
3744
3744
|
}>;
|
|
@@ -3786,16 +3786,16 @@ declare class KeysRepository extends BaseRepository {
|
|
|
3786
3786
|
* Write primary 사용
|
|
3787
3787
|
*/
|
|
3788
3788
|
deleteByKeyIdAndUserId(keyId: string, userId: number): Promise<{
|
|
3789
|
-
publicKey: string;
|
|
3790
|
-
keyId: string;
|
|
3791
|
-
fingerprint: string;
|
|
3792
|
-
algorithm: "ES256" | "RS256";
|
|
3793
3789
|
userId: number;
|
|
3790
|
+
keyId: string;
|
|
3794
3791
|
id: number;
|
|
3795
3792
|
isActive: boolean;
|
|
3796
3793
|
createdAt: Date;
|
|
3797
|
-
|
|
3794
|
+
publicKey: string;
|
|
3795
|
+
algorithm: "ES256" | "RS256";
|
|
3796
|
+
fingerprint: string;
|
|
3798
3797
|
lastUsedAt: Date | null;
|
|
3798
|
+
expiresAt: Date | null;
|
|
3799
3799
|
revokedAt: Date | null;
|
|
3800
3800
|
revokedReason: string | null;
|
|
3801
3801
|
}>;
|
|
@@ -3910,14 +3910,14 @@ declare class VerificationCodesRepository extends BaseRepository {
|
|
|
3910
3910
|
* Write primary 사용
|
|
3911
3911
|
*/
|
|
3912
3912
|
create(data: NewVerificationCode): Promise<{
|
|
3913
|
-
target: string;
|
|
3914
|
-
targetType: "email" | "phone";
|
|
3915
|
-
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
3916
|
-
code: string;
|
|
3917
3913
|
id: number;
|
|
3918
3914
|
createdAt: Date;
|
|
3919
3915
|
updatedAt: Date;
|
|
3920
3916
|
expiresAt: Date;
|
|
3917
|
+
target: string;
|
|
3918
|
+
targetType: "email" | "phone";
|
|
3919
|
+
code: string;
|
|
3920
|
+
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
3921
3921
|
usedAt: Date | null;
|
|
3922
3922
|
attempts: number;
|
|
3923
3923
|
}>;
|
|
@@ -4050,8 +4050,8 @@ declare class RolesRepository extends BaseRepository {
|
|
|
4050
4050
|
*/
|
|
4051
4051
|
create(data: NewRoleEntity): Promise<{
|
|
4052
4052
|
description: string | null;
|
|
4053
|
-
name: string;
|
|
4054
4053
|
id: number;
|
|
4054
|
+
name: string;
|
|
4055
4055
|
displayName: string;
|
|
4056
4056
|
isBuiltin: boolean;
|
|
4057
4057
|
isSystem: boolean;
|
|
@@ -4080,8 +4080,8 @@ declare class RolesRepository extends BaseRepository {
|
|
|
4080
4080
|
*/
|
|
4081
4081
|
deleteById(id: number): Promise<{
|
|
4082
4082
|
description: string | null;
|
|
4083
|
-
name: string;
|
|
4084
4083
|
id: number;
|
|
4084
|
+
name: string;
|
|
4085
4085
|
displayName: string;
|
|
4086
4086
|
isBuiltin: boolean;
|
|
4087
4087
|
isSystem: boolean;
|
|
@@ -4114,7 +4114,7 @@ declare class PermissionsRepository extends BaseRepository {
|
|
|
4114
4114
|
name: string;
|
|
4115
4115
|
displayName: string;
|
|
4116
4116
|
description: string | null;
|
|
4117
|
-
category: "
|
|
4117
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | null;
|
|
4118
4118
|
isBuiltin: boolean;
|
|
4119
4119
|
isSystem: boolean;
|
|
4120
4120
|
isActive: boolean;
|
|
@@ -4130,7 +4130,7 @@ declare class PermissionsRepository extends BaseRepository {
|
|
|
4130
4130
|
name: string;
|
|
4131
4131
|
displayName: string;
|
|
4132
4132
|
description: string | null;
|
|
4133
|
-
category: "
|
|
4133
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | null;
|
|
4134
4134
|
isBuiltin: boolean;
|
|
4135
4135
|
isSystem: boolean;
|
|
4136
4136
|
isActive: boolean;
|
|
@@ -4170,7 +4170,7 @@ declare class PermissionsRepository extends BaseRepository {
|
|
|
4170
4170
|
name: string;
|
|
4171
4171
|
displayName: string;
|
|
4172
4172
|
description: string | null;
|
|
4173
|
-
category: "
|
|
4173
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | null;
|
|
4174
4174
|
isBuiltin: boolean;
|
|
4175
4175
|
isSystem: boolean;
|
|
4176
4176
|
isActive: boolean;
|
|
@@ -4181,16 +4181,16 @@ declare class PermissionsRepository extends BaseRepository {
|
|
|
4181
4181
|
*/
|
|
4182
4182
|
deleteById(id: number): Promise<{
|
|
4183
4183
|
description: string | null;
|
|
4184
|
-
metadata: Record<string, any> | null;
|
|
4185
|
-
name: string;
|
|
4186
4184
|
id: number;
|
|
4185
|
+
name: string;
|
|
4187
4186
|
displayName: string;
|
|
4188
4187
|
isBuiltin: boolean;
|
|
4189
4188
|
isSystem: boolean;
|
|
4190
4189
|
isActive: boolean;
|
|
4191
4190
|
createdAt: Date;
|
|
4192
4191
|
updatedAt: Date;
|
|
4193
|
-
|
|
4192
|
+
metadata: Record<string, any> | null;
|
|
4193
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | null;
|
|
4194
4194
|
}>;
|
|
4195
4195
|
}
|
|
4196
4196
|
declare const permissionsRepository: PermissionsRepository;
|
|
@@ -4235,9 +4235,9 @@ declare class RolePermissionsRepository extends BaseRepository {
|
|
|
4235
4235
|
*/
|
|
4236
4236
|
createMany(data: NewRolePermission[]): Promise<{
|
|
4237
4237
|
id: number;
|
|
4238
|
-
roleId: number;
|
|
4239
4238
|
createdAt: Date;
|
|
4240
4239
|
updatedAt: Date;
|
|
4240
|
+
roleId: number;
|
|
4241
4241
|
permissionId: number;
|
|
4242
4242
|
}[]>;
|
|
4243
4243
|
/**
|
|
@@ -4253,9 +4253,9 @@ declare class RolePermissionsRepository extends BaseRepository {
|
|
|
4253
4253
|
*/
|
|
4254
4254
|
setPermissionsForRole(roleId: number, permissionIds: number[]): Promise<{
|
|
4255
4255
|
id: number;
|
|
4256
|
-
roleId: number;
|
|
4257
4256
|
createdAt: Date;
|
|
4258
4257
|
updatedAt: Date;
|
|
4258
|
+
roleId: number;
|
|
4259
4259
|
permissionId: number;
|
|
4260
4260
|
}[]>;
|
|
4261
4261
|
}
|
|
@@ -4320,9 +4320,9 @@ declare class UserPermissionsRepository extends BaseRepository {
|
|
|
4320
4320
|
id: number;
|
|
4321
4321
|
createdAt: Date;
|
|
4322
4322
|
updatedAt: Date;
|
|
4323
|
-
permissionId: number;
|
|
4324
|
-
reason: string | null;
|
|
4325
4323
|
expiresAt: Date | null;
|
|
4324
|
+
reason: string | null;
|
|
4325
|
+
permissionId: number;
|
|
4326
4326
|
granted: boolean;
|
|
4327
4327
|
}>;
|
|
4328
4328
|
/**
|
|
@@ -4346,9 +4346,9 @@ declare class UserPermissionsRepository extends BaseRepository {
|
|
|
4346
4346
|
id: number;
|
|
4347
4347
|
createdAt: Date;
|
|
4348
4348
|
updatedAt: Date;
|
|
4349
|
-
permissionId: number;
|
|
4350
|
-
reason: string | null;
|
|
4351
4349
|
expiresAt: Date | null;
|
|
4350
|
+
reason: string | null;
|
|
4351
|
+
permissionId: number;
|
|
4352
4352
|
granted: boolean;
|
|
4353
4353
|
}>;
|
|
4354
4354
|
/**
|
|
@@ -4427,7 +4427,6 @@ declare class UserProfilesRepository extends BaseRepository {
|
|
|
4427
4427
|
* 프로필 생성
|
|
4428
4428
|
*/
|
|
4429
4429
|
create(data: NewUserProfile): Promise<{
|
|
4430
|
-
metadata: Record<string, any> | null;
|
|
4431
4430
|
userId: number;
|
|
4432
4431
|
id: number;
|
|
4433
4432
|
displayName: string | null;
|
|
@@ -4445,6 +4444,7 @@ declare class UserProfilesRepository extends BaseRepository {
|
|
|
4445
4444
|
location: string | null;
|
|
4446
4445
|
company: string | null;
|
|
4447
4446
|
jobTitle: string | null;
|
|
4447
|
+
metadata: Record<string, any> | null;
|
|
4448
4448
|
}>;
|
|
4449
4449
|
/**
|
|
4450
4450
|
* 프로필 업데이트 (by ID)
|
|
@@ -4496,7 +4496,6 @@ declare class UserProfilesRepository extends BaseRepository {
|
|
|
4496
4496
|
* 프로필 삭제 (by ID)
|
|
4497
4497
|
*/
|
|
4498
4498
|
deleteById(id: number): Promise<{
|
|
4499
|
-
metadata: Record<string, any> | null;
|
|
4500
4499
|
userId: number;
|
|
4501
4500
|
id: number;
|
|
4502
4501
|
displayName: string | null;
|
|
@@ -4514,12 +4513,12 @@ declare class UserProfilesRepository extends BaseRepository {
|
|
|
4514
4513
|
location: string | null;
|
|
4515
4514
|
company: string | null;
|
|
4516
4515
|
jobTitle: string | null;
|
|
4516
|
+
metadata: Record<string, any> | null;
|
|
4517
4517
|
}>;
|
|
4518
4518
|
/**
|
|
4519
4519
|
* 프로필 삭제 (by User ID)
|
|
4520
4520
|
*/
|
|
4521
4521
|
deleteByUserId(userId: number): Promise<{
|
|
4522
|
-
metadata: Record<string, any> | null;
|
|
4523
4522
|
userId: number;
|
|
4524
4523
|
id: number;
|
|
4525
4524
|
displayName: string | null;
|
|
@@ -4537,6 +4536,7 @@ declare class UserProfilesRepository extends BaseRepository {
|
|
|
4537
4536
|
location: string | null;
|
|
4538
4537
|
company: string | null;
|
|
4539
4538
|
jobTitle: string | null;
|
|
4539
|
+
metadata: Record<string, any> | null;
|
|
4540
4540
|
}>;
|
|
4541
4541
|
/**
|
|
4542
4542
|
* 프로필 Upsert (by User ID)
|
|
@@ -4545,7 +4545,6 @@ declare class UserProfilesRepository extends BaseRepository {
|
|
|
4545
4545
|
* 새로 생성 시 displayName은 필수 (없으면 'User'로 설정)
|
|
4546
4546
|
*/
|
|
4547
4547
|
upsertByUserId(userId: number, data: Partial<Omit<NewUserProfile, 'userId'>>): Promise<{
|
|
4548
|
-
metadata: Record<string, any> | null;
|
|
4549
4548
|
userId: number;
|
|
4550
4549
|
id: number;
|
|
4551
4550
|
displayName: string | null;
|
|
@@ -4563,6 +4562,7 @@ declare class UserProfilesRepository extends BaseRepository {
|
|
|
4563
4562
|
location: string | null;
|
|
4564
4563
|
company: string | null;
|
|
4565
4564
|
jobTitle: string | null;
|
|
4565
|
+
metadata: Record<string, any> | null;
|
|
4566
4566
|
}>;
|
|
4567
4567
|
/**
|
|
4568
4568
|
* User ID로 프로필 데이터 조회 (formatted)
|
|
@@ -4690,15 +4690,15 @@ declare class InvitationsRepository extends BaseRepository {
|
|
|
4690
4690
|
*/
|
|
4691
4691
|
create(data: NewInvitation): Promise<{
|
|
4692
4692
|
email: string;
|
|
4693
|
-
|
|
4693
|
+
status: "pending" | "accepted" | "expired" | "cancelled";
|
|
4694
4694
|
id: number;
|
|
4695
|
-
roleId: number;
|
|
4696
4695
|
createdAt: Date;
|
|
4697
4696
|
updatedAt: Date;
|
|
4698
|
-
|
|
4697
|
+
roleId: number;
|
|
4698
|
+
metadata: Record<string, any> | null;
|
|
4699
|
+
expiresAt: Date;
|
|
4699
4700
|
token: string;
|
|
4700
4701
|
invitedBy: number;
|
|
4701
|
-
expiresAt: Date;
|
|
4702
4702
|
acceptedAt: Date | null;
|
|
4703
4703
|
cancelledAt: Date | null;
|
|
4704
4704
|
}>;
|
|
@@ -4724,15 +4724,15 @@ declare class InvitationsRepository extends BaseRepository {
|
|
|
4724
4724
|
*/
|
|
4725
4725
|
deleteById(id: number): Promise<{
|
|
4726
4726
|
email: string;
|
|
4727
|
-
|
|
4727
|
+
status: "pending" | "accepted" | "expired" | "cancelled";
|
|
4728
4728
|
id: number;
|
|
4729
|
-
roleId: number;
|
|
4730
4729
|
createdAt: Date;
|
|
4731
4730
|
updatedAt: Date;
|
|
4732
|
-
|
|
4731
|
+
roleId: number;
|
|
4732
|
+
metadata: Record<string, any> | null;
|
|
4733
|
+
expiresAt: Date;
|
|
4733
4734
|
token: string;
|
|
4734
4735
|
invitedBy: number;
|
|
4735
|
-
expiresAt: Date;
|
|
4736
4736
|
acceptedAt: Date | null;
|
|
4737
4737
|
cancelledAt: Date | null;
|
|
4738
4738
|
}>;
|
|
@@ -5048,7 +5048,7 @@ declare class AccountDeletionRequestsRepository extends BaseRepository {
|
|
|
5048
5048
|
requestedAt: Date;
|
|
5049
5049
|
purgeScheduledAt: Date;
|
|
5050
5050
|
status: "pending" | "cancelled" | "completed";
|
|
5051
|
-
requestedBy: "
|
|
5051
|
+
requestedBy: "self" | "admin";
|
|
5052
5052
|
reason: string | null;
|
|
5053
5053
|
cancelledAt: Date | null;
|
|
5054
5054
|
completedAt: Date | null;
|
|
@@ -5067,7 +5067,7 @@ declare class AccountDeletionRequestsRepository extends BaseRepository {
|
|
|
5067
5067
|
requestedAt: Date;
|
|
5068
5068
|
purgeScheduledAt: Date;
|
|
5069
5069
|
status: "pending" | "cancelled" | "completed";
|
|
5070
|
-
requestedBy: "
|
|
5070
|
+
requestedBy: "self" | "admin";
|
|
5071
5071
|
reason: string | null;
|
|
5072
5072
|
cancelledAt: Date | null;
|
|
5073
5073
|
completedAt: Date | null;
|
|
@@ -5089,7 +5089,7 @@ declare class AccountDeletionRequestsRepository extends BaseRepository {
|
|
|
5089
5089
|
requestedAt: Date;
|
|
5090
5090
|
purgeScheduledAt: Date;
|
|
5091
5091
|
status: "pending" | "cancelled" | "completed";
|
|
5092
|
-
requestedBy: "
|
|
5092
|
+
requestedBy: "self" | "admin";
|
|
5093
5093
|
reason: string | null;
|
|
5094
5094
|
cancelledAt: Date | null;
|
|
5095
5095
|
completedAt: Date | null;
|
|
@@ -5108,7 +5108,7 @@ declare class AccountDeletionRequestsRepository extends BaseRepository {
|
|
|
5108
5108
|
requestedAt: Date;
|
|
5109
5109
|
purgeScheduledAt: Date;
|
|
5110
5110
|
status: "pending" | "cancelled" | "completed";
|
|
5111
|
-
requestedBy: "
|
|
5111
|
+
requestedBy: "self" | "admin";
|
|
5112
5112
|
reason: string | null;
|
|
5113
5113
|
cancelledAt: Date | null;
|
|
5114
5114
|
completedAt: Date | null;
|
|
@@ -5120,16 +5120,16 @@ declare class AccountDeletionRequestsRepository extends BaseRepository {
|
|
|
5120
5120
|
*/
|
|
5121
5121
|
create(data: NewAccountDeletionRequest): Promise<{
|
|
5122
5122
|
userId: number | null;
|
|
5123
|
+
status: "pending" | "cancelled" | "completed";
|
|
5124
|
+
purgeScheduledAt: Date;
|
|
5123
5125
|
id: number;
|
|
5124
5126
|
createdAt: Date;
|
|
5125
5127
|
updatedAt: Date;
|
|
5126
|
-
status: "pending" | "cancelled" | "completed";
|
|
5127
|
-
reason: string | null;
|
|
5128
|
-
purgeScheduledAt: Date;
|
|
5129
5128
|
cancelledAt: Date | null;
|
|
5130
5129
|
userPublicId: string;
|
|
5131
5130
|
requestedAt: Date;
|
|
5132
|
-
requestedBy: "
|
|
5131
|
+
requestedBy: "self" | "admin";
|
|
5132
|
+
reason: string | null;
|
|
5133
5133
|
completedAt: Date | null;
|
|
5134
5134
|
purgeStrategy: "anonymize" | "hard-delete" | null;
|
|
5135
5135
|
}>;
|
|
@@ -5150,7 +5150,7 @@ declare class AccountDeletionRequestsRepository extends BaseRepository {
|
|
|
5150
5150
|
requestedAt: Date;
|
|
5151
5151
|
purgeScheduledAt: Date;
|
|
5152
5152
|
status: "pending" | "cancelled" | "completed";
|
|
5153
|
-
requestedBy: "
|
|
5153
|
+
requestedBy: "self" | "admin";
|
|
5154
5154
|
reason: string | null;
|
|
5155
5155
|
cancelledAt: Date | null;
|
|
5156
5156
|
completedAt: Date | null;
|
|
@@ -5175,7 +5175,7 @@ declare class AccountDeletionRequestsRepository extends BaseRepository {
|
|
|
5175
5175
|
requestedAt: Date;
|
|
5176
5176
|
purgeScheduledAt: Date;
|
|
5177
5177
|
status: "pending" | "cancelled" | "completed";
|
|
5178
|
-
requestedBy: "
|
|
5178
|
+
requestedBy: "self" | "admin";
|
|
5179
5179
|
reason: string | null;
|
|
5180
5180
|
cancelledAt: Date | null;
|
|
5181
5181
|
completedAt: Date | null;
|
|
@@ -5627,15 +5627,15 @@ declare function getUser(c: Context | {
|
|
|
5627
5627
|
}): {
|
|
5628
5628
|
email: string | null;
|
|
5629
5629
|
phone: string | null;
|
|
5630
|
+
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
5631
|
+
username: string | null;
|
|
5630
5632
|
id: number;
|
|
5633
|
+
createdAt: Date;
|
|
5634
|
+
updatedAt: Date;
|
|
5631
5635
|
publicId: string;
|
|
5632
|
-
username: string | null;
|
|
5633
5636
|
passwordHash: string | null;
|
|
5634
5637
|
passwordChangeRequired: boolean;
|
|
5635
5638
|
roleId: number;
|
|
5636
|
-
createdAt: Date;
|
|
5637
|
-
updatedAt: Date;
|
|
5638
|
-
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
5639
5639
|
emailVerifiedAt: Date | null;
|
|
5640
5640
|
phoneVerifiedAt: Date | null;
|
|
5641
5641
|
lastLoginAt: Date | null;
|
|
@@ -6363,9 +6363,9 @@ declare const invitationCreatedEvent: _spfn_core_event.EventDef<{
|
|
|
6363
6363
|
} | undefined;
|
|
6364
6364
|
email: string;
|
|
6365
6365
|
roleId: number;
|
|
6366
|
+
expiresAt: string;
|
|
6366
6367
|
token: string;
|
|
6367
6368
|
invitedBy: string;
|
|
6368
|
-
expiresAt: string;
|
|
6369
6369
|
invitationId: string;
|
|
6370
6370
|
isResend: boolean;
|
|
6371
6371
|
}>;
|
|
@@ -6409,7 +6409,7 @@ declare const authDeletionRequestedEvent: _spfn_core_event.EventDef<{
|
|
|
6409
6409
|
userId: string;
|
|
6410
6410
|
purgeScheduledAt: string;
|
|
6411
6411
|
userPublicId: string;
|
|
6412
|
-
requestedBy: "
|
|
6412
|
+
requestedBy: "self" | "admin";
|
|
6413
6413
|
}>;
|
|
6414
6414
|
/**
|
|
6415
6415
|
* auth.deletion.cancelled - 계정 탈퇴 복구 이벤트
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfn/auth",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.86",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Authentication, authorization, and RBAC module for SPFN",
|
|
6
6
|
"author": "Ray Im <rayim@fxy.global>",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"clean": "rm -rf dist",
|
|
68
68
|
"db:generate": "drizzle-kit generate",
|
|
69
69
|
"codegen": "spfn codegen run",
|
|
70
|
+
"export:mobile-contract": "tsx scripts/export-mobile-contract.ts",
|
|
70
71
|
"test": "vitest run",
|
|
71
72
|
"test:unit": "vitest run src/__tests__/unit/",
|
|
72
73
|
"test:integration": "vitest run src/__tests__/integration/",
|