@oaath/server 0.1.0 → 0.3.0

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.
Files changed (50) hide show
  1. package/README.md +239 -5
  2. package/dist/apns.d.ts +2 -2
  3. package/dist/apns.js +2 -2
  4. package/dist/{challenge-AGTmUfj2.js → challenge-DmpGSogj.js} +2 -2
  5. package/dist/{challenge-AGTmUfj2.js.map → challenge-DmpGSogj.js.map} +1 -1
  6. package/dist/inbox-B80EUVht.js +249 -0
  7. package/dist/inbox-B80EUVht.js.map +1 -0
  8. package/dist/index.d.ts +9 -255
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +631 -48
  11. package/dist/index.js.map +1 -1
  12. package/dist/interface-DHpwP0wr.d.ts +245 -0
  13. package/dist/interface-DHpwP0wr.d.ts.map +1 -0
  14. package/dist/kernel.d.ts +42 -0
  15. package/dist/kernel.d.ts.map +1 -0
  16. package/dist/kernel.js +80 -0
  17. package/dist/kernel.js.map +1 -0
  18. package/dist/kms-BWkywdJM.d.ts +18 -0
  19. package/dist/kms-BWkywdJM.d.ts.map +1 -0
  20. package/dist/native.d.ts +3 -30
  21. package/dist/native.js +115 -2
  22. package/dist/native.js.map +1 -0
  23. package/dist/postgres.d.ts +38 -3
  24. package/dist/postgres.d.ts.map +1 -1
  25. package/dist/postgres.js +301 -14
  26. package/dist/postgres.js.map +1 -1
  27. package/dist/projection-B7IB9irs.d.ts +228 -0
  28. package/dist/projection-B7IB9irs.d.ts.map +1 -0
  29. package/dist/{records-Bgr-9PUh.js → records-BQNDxbPg.js} +32 -8
  30. package/dist/records-BQNDxbPg.js.map +1 -0
  31. package/dist/records-BUvqmp0D.js +70 -0
  32. package/dist/records-BUvqmp0D.js.map +1 -0
  33. package/dist/service-Cf4f-9KP.js +706 -0
  34. package/dist/service-Cf4f-9KP.js.map +1 -0
  35. package/dist/service-J51bOVe6.d.ts +483 -0
  36. package/dist/service-J51bOVe6.d.ts.map +1 -0
  37. package/package.json +39 -8
  38. package/LICENSE +0 -201
  39. package/dist/authentication-HUYWk5Y2.d.ts +0 -46
  40. package/dist/authentication-HUYWk5Y2.d.ts.map +0 -1
  41. package/dist/decision-dEe9n108.d.ts +0 -53
  42. package/dist/decision-dEe9n108.d.ts.map +0 -1
  43. package/dist/interface-WwvwWs8e.d.ts +0 -114
  44. package/dist/interface-WwvwWs8e.d.ts.map +0 -1
  45. package/dist/native.d.ts.map +0 -1
  46. package/dist/projection-dGIkE0rI.js +0 -516
  47. package/dist/projection-dGIkE0rI.js.map +0 -1
  48. package/dist/projection-g6k_CCUK.d.ts +0 -136
  49. package/dist/projection-g6k_CCUK.d.ts.map +0 -1
  50. package/dist/records-Bgr-9PUh.js.map +0 -1
@@ -1,516 +0,0 @@
1
- import { b as relayFailure, h as OaathRelayError, n as OAATH_AUTHORIZATION_DECISION_RECORD_VERSION, o as RELAY_LIMITS, r as OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION, s as boundedText } from "./records-Bgr-9PUh.js";
2
- import { a as relayNow, n as randomIdentifier, r as sha256Base64Url, t as isCodeChallengeS256 } from "./challenge-AGTmUfj2.js";
3
- import { hashOwnerSigningRequest, parseKernelV4ReplayableInstallOwnerSigningRequest, parseOwnerSigningArtifact, parseOwnerSigningRequest, parsePermissionRequest, serializeOwnerSigningArtifact } from "@oaath/protocol";
4
- import { p256 } from "@noble/curves/nist.js";
5
- import { hexToBytes } from "@noble/hashes/utils.js";
6
- //#region src/store/interface.ts
7
- /**
8
- * Runs one transition inside one transaction. The body commits, or the
9
- * transaction is rolled back and the canonical failure is preserved.
10
- */
11
- async function withRelayTransaction(store, run) {
12
- const transaction = await store.begin();
13
- let result;
14
- try {
15
- result = await run(transaction);
16
- } catch (error) {
17
- await transaction.rollback();
18
- throw error;
19
- }
20
- await transaction.commit();
21
- return result;
22
- }
23
- //#endregion
24
- //#region src/artifact/encrypt.ts
25
- /**
26
- * KMS sealing boundary. Plaintext never reaches the store.
27
- *
28
- * @author taek <leekt216@gmail.com>
29
- */
30
- const KMS_UNAVAILABLE = "relay_kms_unavailable";
31
- /**
32
- * Encrypts an approval artifact and returns the opaque reference to store.
33
- * A port that echoes plaintext is rejected: the store must never hold it.
34
- */
35
- async function sealArtifact(kms, plaintext) {
36
- let sealed;
37
- try {
38
- sealed = await kms.encrypt(plaintext);
39
- } catch {
40
- return relayFailure(KMS_UNAVAILABLE, "KMS encrypt failed");
41
- }
42
- const ciphertextRef = boundedText(sealed, RELAY_LIMITS.ciphertextRef, "KMS ciphertext reference", KMS_UNAVAILABLE);
43
- if (ciphertextRef.includes(plaintext)) return relayFailure(KMS_UNAVAILABLE, "KMS reference contains plaintext");
44
- return ciphertextRef;
45
- }
46
- /** Opens a reference this KMS previously sealed. Called only after the claim commits. */
47
- async function openArtifact(kms, ciphertextRef) {
48
- let opened;
49
- try {
50
- opened = await kms.decrypt(ciphertextRef);
51
- } catch {
52
- return relayFailure(KMS_UNAVAILABLE, "KMS decrypt failed");
53
- }
54
- return boundedText(opened, RELAY_LIMITS.artifactPlaintext, "KMS plaintext", KMS_UNAVAILABLE);
55
- }
56
- //#endregion
57
- //#region src/authorization/owner-signing.ts
58
- /**
59
- * Pure authorization of one returned Kernel owner-signing artifact.
60
- *
61
- * The protocol owns both exact wire shapes. This server owner adds only the
62
- * release policy: the request must use the first-runtime P-256 credential, the
63
- * artifact must be its one canonical JSON representation, its request hash must
64
- * bind the exact request, and its compact low-S signature must verify the exact
65
- * Kernel digest. It allocates and persists nothing.
66
- *
67
- * @author taek <leekt216@gmail.com>
68
- */
69
- const INVALID = "relay_request_invalid";
70
- /**
71
- * Returns the canonical artifact plaintext only after every request, key,
72
- * request-hash, and signature binding agrees.
73
- */
74
- function verifyKernelV4ReplayableInstallOwnerSigningArtifact(requestValue, artifactPlaintext) {
75
- try {
76
- const request = parseKernelV4ReplayableInstallOwnerSigningRequest(requestValue);
77
- if (request.signer.ownerCredential.kind !== "p256") throw new TypeError();
78
- const received = boundedText(artifactPlaintext, RELAY_LIMITS.artifactPlaintext, "owner signing artifact", INVALID);
79
- const artifact = parseOwnerSigningArtifact(JSON.parse(received));
80
- const canonical = serializeOwnerSigningArtifact(artifact);
81
- if (received !== canonical) throw new TypeError();
82
- if (artifact.requestHash !== hashOwnerSigningRequest(request)) throw new TypeError();
83
- if (!p256.verify(hexToBytes(artifact.signature.slice(2)), hexToBytes(request.expectedDigest.slice(2)), hexToBytes(request.signer.ownerCredential.publicKey.slice(2)), {
84
- format: "compact",
85
- lowS: true,
86
- prehash: false
87
- })) throw new TypeError();
88
- return canonical;
89
- } catch {
90
- return relayFailure(INVALID, "Kernel owner signing artifact is invalid");
91
- }
92
- }
93
- //#endregion
94
- //#region src/authorization/request.ts
95
- /**
96
- * Create and read an authorization request.
97
- *
98
- * The request record is immutable once created. Its decision is a separate
99
- * terminal record, so "was this decided?" has exactly one owner.
100
- *
101
- * @author taek <leekt216@gmail.com>
102
- */
103
- async function createAuthorizationRequest(input) {
104
- if (!isCodeChallengeS256(input.codeChallenge)) return relayFailure("relay_request_invalid", "codeChallenge must be an S256 challenge");
105
- if (!input.caller.redirectUris.includes(input.redirectUri)) return relayFailure("relay_forbidden", "redirectUri is not registered for this client");
106
- const createdAt = relayNow(input.clock);
107
- const record = Object.freeze({
108
- version: OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION,
109
- requestId: randomIdentifier(),
110
- clientId: input.caller.clientId,
111
- subject: input.caller.subject,
112
- redirectUri: input.redirectUri,
113
- codeChallenge: input.codeChallenge,
114
- requestedScope: input.requestedScope,
115
- createdAt,
116
- expiresAt: createdAt + input.requestTtlMs
117
- });
118
- await withRelayTransaction(input.store, async (transaction) => {
119
- if (!await transaction.insertAuthorizationRequest(record)) return relayFailure("relay_internal", "authorization request identifier is not unique");
120
- });
121
- return Object.freeze({
122
- requestId: record.requestId,
123
- expiresAt: record.expiresAt
124
- });
125
- }
126
- /**
127
- * Row-locked read of the request and its decision. Read paths take the same
128
- * exclusive lock as transitions.
129
- *
130
- * ponytail: one lock class for reads and writes; add a shared read mode only if
131
- * approval-screen traffic measurably contends.
132
- */
133
- async function readAuthorizationState(transaction, requestId, now, match) {
134
- const request = await transaction.lockAuthorizationRequest(requestId);
135
- if (!request || !match(request)) return relayFailure("relay_not_found", "authorization request does not exist");
136
- const decision = await transaction.lockAuthorizationDecision(requestId);
137
- return Object.freeze({
138
- requestId: request.requestId,
139
- clientId: request.clientId,
140
- redirectUri: request.redirectUri,
141
- requestedScope: request.requestedScope,
142
- expiresAt: request.expiresAt,
143
- expired: now >= request.expiresAt,
144
- decision: decision ? Object.freeze({
145
- outcome: decision.outcome,
146
- decidedAt: decision.decidedAt
147
- }) : null
148
- });
149
- }
150
- /** Read for the approving owner. */
151
- function fetchAuthorizationRequest(input) {
152
- const now = relayNow(input.clock);
153
- return withRelayTransaction(input.store, (transaction) => readAuthorizationState(transaction, input.requestId, now, (request) => request.subject === input.caller.subject));
154
- }
155
- //#endregion
156
- //#region src/authorization/scope.ts
157
- /**
158
- * Authoritative classification of one immutable stored authorization scope.
159
- *
160
- * The current server may approve an exact protocol PermissionRequest or the
161
- * one exact Kernel replayable-install request backed by a P-256 owner. Other
162
- * closed owner-signing requests are captured for projection and hashing but
163
- * remain reject-only. Unknown, malformed, and legacy scopes stay readable and
164
- * rejectable, but never authorize artifact creation.
165
- *
166
- * @author taek <leekt216@gmail.com>
167
- */
168
- function classifyStoredAuthorizationScope(requestedScope, requestId) {
169
- try {
170
- const parsed = JSON.parse(requestedScope);
171
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return Object.freeze({
172
- kind: "unverified",
173
- decision: "reject-only"
174
- });
175
- try {
176
- return Object.freeze({
177
- kind: "permission-request",
178
- decision: "approve-or-reject",
179
- request: parsePermissionRequest({
180
- ...parsed,
181
- requestId
182
- })
183
- });
184
- } catch {
185
- const request = parseOwnerSigningRequest(parsed);
186
- try {
187
- const kernelRequest = parseKernelV4ReplayableInstallOwnerSigningRequest(request);
188
- if (kernelRequest.signer.ownerCredential.kind === "p256") return Object.freeze({
189
- kind: "kernel-owner-signing-request",
190
- decision: "approve-or-reject",
191
- request: kernelRequest
192
- });
193
- } catch {}
194
- return Object.freeze({
195
- kind: "owner-signing-request",
196
- decision: "reject-only",
197
- request
198
- });
199
- }
200
- } catch {
201
- return Object.freeze({
202
- kind: "unverified",
203
- decision: "reject-only"
204
- });
205
- }
206
- }
207
- //#endregion
208
- //#region src/authorization/decision.ts
209
- /**
210
- * Authoritative approve/reject transition.
211
- *
212
- * ```text
213
- * state and owner the decision record owns "decided"; the request record
214
- * never mutates
215
- * persisted evidence the immutable requestedScope is reclassified from the
216
- * durable request before every approval attempt
217
- * resource occupied? a refused approval occupies nothing and performs no KMS
218
- * sealing; a rejection occupies only its decision row
219
- * retry positively safe? refused approval is read-only; a terminal outcome is
220
- * replayed by the native saga and rejected by this owner
221
- * transitions permission or verified Kernel owner signing:
222
- * undecided -> approved | rejected;
223
- * every reject-only scope: undecided -> rejected once
224
- * terminal both outcomes; a second decide fails relay_already_decided
225
- * crash/reload the decision, the code, and the sealed artifact commit in
226
- * one transaction on the row-locked request, so a crash
227
- * leaves the request undecided and nothing released
228
- * cleanup owner the relay transaction; refused approvals allocate nothing
229
- * ```
230
- *
231
- * Subject binding: the authoritative subject is recovered from the stored
232
- * authorization request by `requestId` and compared against the authenticated
233
- * owner. No wire field names the subject. A decision envelope that carries a
234
- * subject identifier is rejected as an unknown field by exact capture, because a
235
- * field that only *names* a subject is not cryptographically bound to it and can
236
- * never decide authority.
237
- *
238
- * @author taek <leekt216@gmail.com>
239
- */
240
- async function submitAuthorizationDecision(input) {
241
- const decidedAt = relayNow(input.clock);
242
- let approvedArtifact;
243
- if (input.command.outcome === "approved") {
244
- const state = await fetchAuthorizationRequest({
245
- store: input.store,
246
- clock: input.clock,
247
- caller: input.caller,
248
- requestId: input.requestId
249
- });
250
- if (state.decision !== null) return relayFailure("relay_already_decided", "authorization request is already decided");
251
- if (state.expired) return relayFailure("relay_expired", "authorization request expired");
252
- const scope = classifyStoredAuthorizationScope(state.requestedScope, state.requestId);
253
- if (scope.kind === "permission-request") approvedArtifact = input.command.artifact;
254
- else if (scope.kind === "kernel-owner-signing-request") approvedArtifact = verifyKernelV4ReplayableInstallOwnerSigningArtifact(scope.request, input.command.artifact);
255
- else return relayFailure("relay_request_invalid", "the authorization scope is reject-only");
256
- }
257
- let approved;
258
- if (input.command.outcome === "approved") {
259
- if (approvedArtifact === void 0) return relayFailure("relay_internal", "approved artifact was not authorized");
260
- const code = randomIdentifier();
261
- approved = Object.freeze({
262
- code,
263
- artifactId: randomIdentifier(),
264
- ciphertextRef: await sealArtifact(input.kms, approvedArtifact),
265
- codeRef: await sealArtifact(input.kms, code)
266
- });
267
- }
268
- const codeHash = approved ? await sha256Base64Url(approved.code) : void 0;
269
- const decision = Object.freeze({
270
- version: OAATH_AUTHORIZATION_DECISION_RECORD_VERSION,
271
- requestId: input.requestId,
272
- outcome: input.command.outcome,
273
- decidedAt,
274
- codeRef: approved?.codeRef ?? null,
275
- codeExpiresAt: approved ? decidedAt + input.codeTtlMs : null
276
- });
277
- const redirectUri = await withRelayTransaction(input.store, async (transaction) => {
278
- const request = await transaction.lockAuthorizationRequest(input.requestId);
279
- if (!request || request.subject !== input.caller.subject) return relayFailure("relay_not_found", "authorization request does not exist");
280
- if (await transaction.lockAuthorizationDecision(input.requestId)) return relayFailure("relay_already_decided", "authorization request is already decided");
281
- if (decidedAt >= request.expiresAt) return relayFailure("relay_expired", "authorization request expired");
282
- if (!await transaction.insertAuthorizationDecision(decision)) return relayFailure("relay_already_decided", "authorization request is already decided");
283
- if (approved && codeHash !== void 0) {
284
- if (!(await transaction.insertAuthorizationCode(Object.freeze({
285
- version: "oaath.authorization-code-record/v1",
286
- codeHash,
287
- requestId: request.requestId,
288
- clientId: request.clientId,
289
- redirectUri: request.redirectUri,
290
- codeChallenge: request.codeChallenge,
291
- artifactId: approved.artifactId,
292
- createdAt: decidedAt,
293
- expiresAt: decidedAt + input.codeTtlMs,
294
- consumedAt: null
295
- })) && await transaction.insertEncryptedArtifact(Object.freeze({
296
- version: "oaath.encrypted-artifact-record/v1",
297
- artifactId: approved.artifactId,
298
- requestId: request.requestId,
299
- clientId: request.clientId,
300
- ciphertextRef: approved.ciphertextRef,
301
- createdAt: decidedAt,
302
- claimedAt: null
303
- })))) return relayFailure("relay_internal", "released identifier is not unique");
304
- }
305
- return request.redirectUri;
306
- });
307
- if (approved && codeHash !== void 0) return Object.freeze({
308
- outcome: "approved",
309
- decidedAt,
310
- code: approved.code,
311
- artifactId: approved.artifactId,
312
- redirectUri,
313
- codeExpiresAt: decidedAt + input.codeTtlMs
314
- });
315
- return Object.freeze({
316
- outcome: "rejected",
317
- decidedAt
318
- });
319
- }
320
- //#endregion
321
- //#region src/native/decision.ts
322
- async function submitOwnerPhoneDecision(input) {
323
- if (input.caller.role !== "owner") return relayFailure("relay_forbidden", "caller may not act in the required role");
324
- try {
325
- const release = await submitAuthorizationDecision({
326
- store: input.store,
327
- clock: input.clock,
328
- kms: input.kms,
329
- caller: input.caller,
330
- requestId: input.operationId,
331
- command: input.command,
332
- codeTtlMs: input.codeTtlMs
333
- });
334
- return Object.freeze({
335
- operationId: input.operationId,
336
- outcome: release.outcome,
337
- decidedAt: release.decidedAt,
338
- settlement: "decided",
339
- release
340
- });
341
- } catch (error) {
342
- if (!(error instanceof OaathRelayError) || error.code !== "relay_already_decided") throw error;
343
- }
344
- const state = await fetchAuthorizationRequest({
345
- store: input.store,
346
- clock: input.clock,
347
- caller: input.caller,
348
- requestId: input.operationId
349
- });
350
- if (state.decision === null) return relayFailure("relay_internal", "decided request has no decision record");
351
- return Object.freeze({
352
- operationId: input.operationId,
353
- outcome: state.decision.outcome,
354
- decidedAt: state.decision.decidedAt,
355
- settlement: "replayed",
356
- release: null
357
- });
358
- }
359
- //#endregion
360
- //#region src/native/projection.ts
361
- /**
362
- * EXPERIMENTAL PREVIEW — authenticated owner-phone consent projection.
363
- *
364
- * This is a preview surface reached through `@oaath/server/native` and the
365
- * relay's preview HTTP routes (`GET /native/projections/{operationId}`). It has
366
- * no stability guarantee and no production qualification: real iOS, Apple
367
- * provisioning, Secure Enclave, and hosted operations are later work.
368
- *
369
- * What it owns: turning one stored authorization request into the projection
370
- * the owner's phone renders before deciding. It owns nothing about deciding;
371
- * the relay's decision use case stays the only decision owner.
372
- *
373
- * Two very different surfaces share this file, and only one of them is opaque:
374
- *
375
- * - The **push** projection (`operationId`, `displayPayload`, `expiresAt`)
376
- * transits Apple inside an APNs payload, so it stays opaque by design: no
377
- * permission, scope, client, chain, or account detail may ride it. The APNs
378
- * sender enforces that subset exactly.
379
- * - The **consent** projection returned here travels only the authenticated
380
- * relay → owner channel, so it deliberately carries the full authorization
381
- * request: the requesting client identity and the requested scope. The owner
382
- * must see exactly the authority they grant before approving.
383
- *
384
- * `displayPayload` is not a secret and not authority: it is a deterministic
385
- * digest of the request and its bound subject, so the owner can check that the
386
- * code on the phone is the code the browser shows. Holding it authorizes
387
- * nothing — deciding still requires the authenticated bound owner.
388
- *
389
- * @author taek <leekt216@gmail.com>
390
- */
391
- /** Bounded base64url match code length. 48 bits is plenty to compare by eye. */
392
- const NATIVE_DISPLAY_PAYLOAD_LENGTH = 8;
393
- /** Versioned consent envelope; the Swift decoder pins this exact value. */
394
- const OAATH_NATIVE_PROJECTION_VERSION = "oaath.native-projection/v4";
395
- const DISPLAY_DOMAIN = "oaath.native-display/v1:";
396
- /**
397
- * The stored scope is the requesting client's own JSON permission scope (the
398
- * SDK serializes a permission request without its `requestId`; the relay's
399
- * request id is the binding). An unparseable scope is not a failure: the owner
400
- * still gets the raw text to review, never a silent omission.
401
- */
402
- function projectCredential(credential) {
403
- if (credential.kind === "ecdsa") return Object.freeze({
404
- kind: "ecdsa",
405
- address: credential.address ?? ""
406
- });
407
- if (credential.kind === "p256") return Object.freeze({
408
- kind: "p256",
409
- publicKey: credential.publicKey ?? ""
410
- });
411
- return Object.freeze({
412
- kind: "webauthn",
413
- publicKey: credential.publicKey ?? "",
414
- authenticatorIdHash: credential.authenticatorIdHash ?? ""
415
- });
416
- }
417
- async function projectOwnerPhoneScope(requestedScope, operationId) {
418
- try {
419
- const classified = classifyStoredAuthorizationScope(requestedScope, operationId);
420
- if (classified.kind === "permission-request") {
421
- const request = classified.request;
422
- return Object.freeze({
423
- kind: "permission-request",
424
- decision: "approve-or-reject",
425
- application: Object.freeze({
426
- applicationId: request.application.applicationId,
427
- clientId: request.application.clientId,
428
- origin: request.application.origin,
429
- deviceFingerprint: (await sha256Base64Url(`${DISPLAY_DOMAIN}device:${request.application.deviceId}`)).slice(0, 8)
430
- }),
431
- account: Object.freeze({
432
- accountIndex: request.logicalAccount.accountIndex,
433
- kernelVersion: request.logicalAccount.kernelVersion,
434
- factoryRoute: request.logicalAccount.factoryRoute,
435
- entryPointVersion: request.logicalAccount.entryPoint.version,
436
- ownerCredential: projectCredential(request.logicalAccount.ownerCredential)
437
- }),
438
- operatorCredential: projectCredential(request.operatorCredential),
439
- sessionSigner: request.sessionSigner === null ? null : Object.freeze({
440
- mode: request.sessionSigner.mode,
441
- providerId: request.sessionSigner.providerId
442
- }),
443
- chainScope: request.chainScope,
444
- calls: Object.freeze(request.policy.calls.map((call) => Object.freeze({
445
- target: call.target,
446
- selector: call.selector,
447
- valueLimit: call.valueLimit,
448
- argumentEquals: Object.freeze(call.argumentEquals.map((rule) => Object.freeze({
449
- index: rule.index,
450
- value: rule.value
451
- })))
452
- }))),
453
- requestedAt: request.requestedAt,
454
- expiresAt: request.expiresAt,
455
- policyValidAfter: request.policy.validAfter,
456
- policyValidUntil: request.policy.validUntil,
457
- perChainOperationLimit: request.policy.perChainOperationLimit
458
- });
459
- }
460
- if (classified.kind === "kernel-owner-signing-request") return Object.freeze({
461
- kind: "owner-signing-request",
462
- decision: "approve-or-reject",
463
- requestHash: hashOwnerSigningRequest(classified.request),
464
- request: classified.request
465
- });
466
- if (classified.kind === "owner-signing-request") return Object.freeze({
467
- kind: "owner-signing-request",
468
- decision: "reject-only",
469
- requestHash: hashOwnerSigningRequest(classified.request),
470
- request: classified.request
471
- });
472
- return Object.freeze({
473
- kind: "raw",
474
- decision: "reject-only",
475
- text: requestedScope
476
- });
477
- } catch {
478
- return Object.freeze({
479
- kind: "raw",
480
- decision: "reject-only",
481
- text: requestedScope
482
- });
483
- }
484
- }
485
- /**
486
- * Projects a pending request for its bound owner. An expired or already decided
487
- * request is not projectable: there is nothing left to approve, so nothing is
488
- * ever pushed or rendered for it.
489
- */
490
- async function projectOwnerPhoneRequest(input) {
491
- if (input.caller.role !== "owner") return relayFailure("relay_forbidden", "caller may not act in the required role");
492
- const state = await fetchAuthorizationRequest({
493
- store: input.store,
494
- clock: input.clock,
495
- caller: input.caller,
496
- requestId: input.requestId
497
- });
498
- if (state.expired) return relayFailure("relay_expired", "authorization request expired");
499
- if (state.decision !== null) return relayFailure("relay_already_decided", "authorization request is already decided");
500
- const digest = await sha256Base64Url(`${DISPLAY_DOMAIN}${input.caller.subject}:${state.requestId}`);
501
- return Object.freeze({
502
- version: OAATH_NATIVE_PROJECTION_VERSION,
503
- operationId: state.requestId,
504
- displayPayload: digest.slice(0, 8),
505
- expiresAt: state.expiresAt,
506
- client: Object.freeze({
507
- clientId: state.clientId,
508
- redirectUri: state.redirectUri
509
- }),
510
- scope: await projectOwnerPhoneScope(state.requestedScope, state.requestId)
511
- });
512
- }
513
- //#endregion
514
- export { submitAuthorizationDecision as a, readAuthorizationState as c, withRelayTransaction as d, submitOwnerPhoneDecision as i, openArtifact as l, OAATH_NATIVE_PROJECTION_VERSION as n, createAuthorizationRequest as o, projectOwnerPhoneRequest as r, fetchAuthorizationRequest as s, NATIVE_DISPLAY_PAYLOAD_LENGTH as t, sealArtifact as u };
515
-
516
- //# sourceMappingURL=projection-dGIkE0rI.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"projection-dGIkE0rI.js","names":[],"sources":["../src/store/interface.ts","../src/artifact/encrypt.ts","../src/authorization/owner-signing.ts","../src/authorization/request.ts","../src/authorization/scope.ts","../src/authorization/decision.ts","../src/native/decision.ts","../src/native/projection.ts"],"sourcesContent":["/**\n * Durable relay transaction contract.\n *\n * State model owned by this contract:\n *\n * ```text\n * state and owner authorization request (immutable), decision (terminal),\n * code (one-shot), encrypted artifact (one-shot)\n * persisted evidence one row per record, one current schema version\n * resource occupied? yes: a decision row occupies its request; a consumed\n * code and a claimed artifact are terminal\n * retry positively safe? no: an ambiguous commit is never retried\n * transitions request -> decision(approved|rejected) once;\n * code: issued -> consumed once;\n * artifact: sealed -> claimed once\n * crash/reload every transition is decided inside one transaction on a\n * row locked for update; a crash leaves the prior state\n * cleanup owner the caller rolls back; the store releases its handle\n * ```\n *\n * Every `lock*` read must acquire an exclusive row lock for the life of the\n * transaction (`SELECT ... FOR UPDATE` semantics). Every one-shot writer\n * (`consumeAuthorizationCode`, `claimEncryptedArtifact`) must additionally\n * guard on the terminal column being null and report whether *this* call\n * performed the transition.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport type {\n AuthorizationCodeRecord,\n AuthorizationDecisionRecord,\n AuthorizationRequestRecord,\n CapabilityInvalidationRecord,\n EncryptedArtifactRecord,\n} from \"./records.js\";\n\nexport interface RelayTransaction {\n lockAuthorizationRequest(requestId: string): Promise<AuthorizationRequestRecord | undefined>;\n /** Returns false when the identifier already exists. */\n insertAuthorizationRequest(record: AuthorizationRequestRecord): Promise<boolean>;\n\n lockAuthorizationDecision(requestId: string): Promise<AuthorizationDecisionRecord | undefined>;\n /** Returns false when the request was already decided. */\n insertAuthorizationDecision(record: AuthorizationDecisionRecord): Promise<boolean>;\n\n lockAuthorizationCode(codeHash: string): Promise<AuthorizationCodeRecord | undefined>;\n insertAuthorizationCode(record: AuthorizationCodeRecord): Promise<boolean>;\n /** Returns true only when this call set `consumed_at`. */\n consumeAuthorizationCode(codeHash: string, consumedAt: number): Promise<boolean>;\n\n lockCapabilityInvalidation(grantId: string): Promise<CapabilityInvalidationRecord | undefined>;\n /** Returns false when the Grant's capability is already invalidated. */\n insertCapabilityInvalidation(record: CapabilityInvalidationRecord): Promise<boolean>;\n\n lockEncryptedArtifact(artifactId: string): Promise<EncryptedArtifactRecord | undefined>;\n insertEncryptedArtifact(record: EncryptedArtifactRecord): Promise<boolean>;\n /** Returns true only when this call set `claimed_at`. */\n claimEncryptedArtifact(artifactId: string, claimedAt: number): Promise<boolean>;\n\n /** Throws `relay_state_ambiguous` when the outcome cannot be proven. */\n commit(): Promise<void>;\n /** Never throws; rollback failure is a suppressed diagnostic. */\n rollback(): Promise<void>;\n}\n\nexport interface RelayStore {\n begin(): Promise<RelayTransaction>;\n close(): Promise<void>;\n}\n\n/**\n * Runs one transition inside one transaction. The body commits, or the\n * transaction is rolled back and the canonical failure is preserved.\n */\nexport async function withRelayTransaction<Value>(\n store: RelayStore,\n run: (transaction: RelayTransaction) => Promise<Value>,\n): Promise<Value> {\n const transaction = await store.begin();\n let result: Value;\n try {\n result = await run(transaction);\n } catch (error) {\n await transaction.rollback();\n throw error;\n }\n await transaction.commit();\n return result;\n}\n","/**\n * KMS sealing boundary. Plaintext never reaches the store.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport { relayFailure } from \"../relay/errors.js\";\nimport type { RelayKms } from \"../security/kms.js\";\nimport { boundedText, RELAY_LIMITS } from \"../store/records.js\";\n\nconst KMS_UNAVAILABLE = \"relay_kms_unavailable\" as const;\n\n/**\n * Encrypts an approval artifact and returns the opaque reference to store.\n * A port that echoes plaintext is rejected: the store must never hold it.\n */\nexport async function sealArtifact(kms: RelayKms, plaintext: string): Promise<string> {\n let sealed: unknown;\n try {\n sealed = await kms.encrypt(plaintext);\n } catch {\n return relayFailure(KMS_UNAVAILABLE, \"KMS encrypt failed\");\n }\n const ciphertextRef = boundedText(\n sealed,\n RELAY_LIMITS.ciphertextRef,\n \"KMS ciphertext reference\",\n KMS_UNAVAILABLE,\n );\n if (ciphertextRef.includes(plaintext)) {\n return relayFailure(KMS_UNAVAILABLE, \"KMS reference contains plaintext\");\n }\n return ciphertextRef;\n}\n\n/** Opens a reference this KMS previously sealed. Called only after the claim commits. */\nexport async function openArtifact(kms: RelayKms, ciphertextRef: string): Promise<string> {\n let opened: unknown;\n try {\n opened = await kms.decrypt(ciphertextRef);\n } catch {\n return relayFailure(KMS_UNAVAILABLE, \"KMS decrypt failed\");\n }\n return boundedText(opened, RELAY_LIMITS.artifactPlaintext, \"KMS plaintext\", KMS_UNAVAILABLE);\n}\n","/**\n * Pure authorization of one returned Kernel owner-signing artifact.\n *\n * The protocol owns both exact wire shapes. This server owner adds only the\n * release policy: the request must use the first-runtime P-256 credential, the\n * artifact must be its one canonical JSON representation, its request hash must\n * bind the exact request, and its compact low-S signature must verify the exact\n * Kernel digest. It allocates and persists nothing.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport { p256 } from \"@noble/curves/nist.js\";\nimport { hexToBytes } from \"@noble/hashes/utils.js\";\nimport {\n hashOwnerSigningRequest,\n parseKernelV4ReplayableInstallOwnerSigningRequest,\n parseOwnerSigningArtifact,\n serializeOwnerSigningArtifact,\n} from \"@oaath/protocol\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport { boundedText, RELAY_LIMITS } from \"../store/records.js\";\n\nconst INVALID = \"relay_request_invalid\" as const;\n\n/**\n * Returns the canonical artifact plaintext only after every request, key,\n * request-hash, and signature binding agrees.\n */\nexport function verifyKernelV4ReplayableInstallOwnerSigningArtifact(\n requestValue: unknown,\n artifactPlaintext: unknown,\n): string {\n try {\n const request = parseKernelV4ReplayableInstallOwnerSigningRequest(requestValue);\n if (request.signer.ownerCredential.kind !== \"p256\") throw new TypeError();\n\n const received = boundedText(\n artifactPlaintext,\n RELAY_LIMITS.artifactPlaintext,\n \"owner signing artifact\",\n INVALID,\n );\n const artifact = parseOwnerSigningArtifact(JSON.parse(received) as unknown);\n const canonical = serializeOwnerSigningArtifact(artifact);\n if (received !== canonical) throw new TypeError();\n if (artifact.requestHash !== hashOwnerSigningRequest(request)) throw new TypeError();\n\n const verified = p256.verify(\n hexToBytes(artifact.signature.slice(2)),\n hexToBytes(request.expectedDigest.slice(2)),\n hexToBytes(request.signer.ownerCredential.publicKey.slice(2)),\n { format: \"compact\", lowS: true, prehash: false },\n );\n if (!verified) throw new TypeError();\n return canonical;\n } catch {\n return relayFailure(INVALID, \"Kernel owner signing artifact is invalid\");\n }\n}\n","/**\n * Create and read an authorization request.\n *\n * The request record is immutable once created. Its decision is a separate\n * terminal record, so \"was this decided?\" has exactly one owner.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport { type RelayClock, relayNow } from \"../clock.js\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport type { RelayCaller } from \"../security/authentication.js\";\nimport type { RelayStore, RelayTransaction } from \"../store/interface.js\";\nimport { withRelayTransaction } from \"../store/interface.js\";\nimport {\n type AuthorizationDecisionOutcome,\n type AuthorizationRequestRecord,\n OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION,\n} from \"../store/records.js\";\nimport { isCodeChallengeS256, randomIdentifier } from \"./challenge.js\";\n\nexport interface CreateAuthorizationRequestInput {\n readonly store: RelayStore;\n readonly clock: RelayClock;\n /** Authenticated `client` caller; it owns `clientId` and `subject`. */\n readonly caller: RelayCaller;\n readonly redirectUri: string;\n readonly codeChallenge: string;\n readonly requestedScope: string;\n readonly requestTtlMs: number;\n}\n\nexport interface CreatedAuthorizationRequest {\n readonly requestId: string;\n readonly expiresAt: number;\n}\n\nexport interface AuthorizationState {\n readonly requestId: string;\n readonly clientId: string;\n readonly redirectUri: string;\n readonly requestedScope: string;\n readonly expiresAt: number;\n readonly expired: boolean;\n readonly decision: Readonly<{\n outcome: AuthorizationDecisionOutcome;\n decidedAt: number;\n }> | null;\n}\n\nexport async function createAuthorizationRequest(\n input: CreateAuthorizationRequestInput,\n): Promise<CreatedAuthorizationRequest> {\n if (!isCodeChallengeS256(input.codeChallenge)) {\n return relayFailure(\"relay_request_invalid\", \"codeChallenge must be an S256 challenge\");\n }\n // The deployment owns client registration, so only a redirect URI it bound to\n // this client may receive an authorization code.\n if (!input.caller.redirectUris.includes(input.redirectUri)) {\n return relayFailure(\"relay_forbidden\", \"redirectUri is not registered for this client\");\n }\n const createdAt = relayNow(input.clock);\n const record: AuthorizationRequestRecord = Object.freeze({\n version: OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION,\n requestId: randomIdentifier(),\n clientId: input.caller.clientId,\n subject: input.caller.subject,\n redirectUri: input.redirectUri,\n codeChallenge: input.codeChallenge,\n requestedScope: input.requestedScope,\n createdAt,\n expiresAt: createdAt + input.requestTtlMs,\n });\n await withRelayTransaction(input.store, async (transaction) => {\n if (!(await transaction.insertAuthorizationRequest(record))) {\n // 256 bits of CSPRNG output collided, or the store contradicts itself.\n return relayFailure(\"relay_internal\", \"authorization request identifier is not unique\");\n }\n });\n return Object.freeze({ requestId: record.requestId, expiresAt: record.expiresAt });\n}\n\n/**\n * Row-locked read of the request and its decision. Read paths take the same\n * exclusive lock as transitions.\n *\n * ponytail: one lock class for reads and writes; add a shared read mode only if\n * approval-screen traffic measurably contends.\n */\nexport async function readAuthorizationState(\n transaction: RelayTransaction,\n requestId: string,\n now: number,\n match: (request: AuthorizationRequestRecord) => boolean,\n): Promise<AuthorizationState> {\n const request = await transaction.lockAuthorizationRequest(requestId);\n // A caller that is not entitled to this request learns only that it is absent.\n if (!request || !match(request)) {\n return relayFailure(\"relay_not_found\", \"authorization request does not exist\");\n }\n const decision = await transaction.lockAuthorizationDecision(requestId);\n return Object.freeze({\n requestId: request.requestId,\n clientId: request.clientId,\n redirectUri: request.redirectUri,\n requestedScope: request.requestedScope,\n expiresAt: request.expiresAt,\n expired: now >= request.expiresAt,\n decision: decision\n ? Object.freeze({ outcome: decision.outcome, decidedAt: decision.decidedAt })\n : null,\n });\n}\n\nexport interface FetchAuthorizationRequestInput {\n readonly store: RelayStore;\n readonly clock: RelayClock;\n /** Authenticated `owner` caller; only the bound subject may read the request. */\n readonly caller: RelayCaller;\n readonly requestId: string;\n}\n\n/** Read for the approving owner. */\nexport function fetchAuthorizationRequest(\n input: FetchAuthorizationRequestInput,\n): Promise<AuthorizationState> {\n const now = relayNow(input.clock);\n return withRelayTransaction(input.store, (transaction) =>\n readAuthorizationState(\n transaction,\n input.requestId,\n now,\n (request) => request.subject === input.caller.subject,\n ),\n );\n}\n","/**\n * Authoritative classification of one immutable stored authorization scope.\n *\n * The current server may approve an exact protocol PermissionRequest or the\n * one exact Kernel replayable-install request backed by a P-256 owner. Other\n * closed owner-signing requests are captured for projection and hashing but\n * remain reject-only. Unknown, malformed, and legacy scopes stay readable and\n * rejectable, but never authorize artifact creation.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport {\n type KernelV4ReplayableInstallOwnerSigningRequest,\n type OwnerSigningRequest,\n type PermissionRequest,\n parseKernelV4ReplayableInstallOwnerSigningRequest,\n parseOwnerSigningRequest,\n parsePermissionRequest,\n} from \"@oaath/protocol\";\n\nexport type StoredAuthorizationScope =\n | Readonly<{\n kind: \"permission-request\";\n decision: \"approve-or-reject\";\n request: Readonly<PermissionRequest>;\n }>\n | Readonly<{\n kind: \"kernel-owner-signing-request\";\n decision: \"approve-or-reject\";\n request: Readonly<KernelV4ReplayableInstallOwnerSigningRequest>;\n }>\n | Readonly<{\n kind: \"owner-signing-request\";\n decision: \"reject-only\";\n request: Readonly<OwnerSigningRequest>;\n }>\n | Readonly<{ kind: \"unverified\"; decision: \"reject-only\" }>;\n\nexport function classifyStoredAuthorizationScope(\n requestedScope: string,\n requestId: string,\n): StoredAuthorizationScope {\n try {\n const parsed: unknown = JSON.parse(requestedScope);\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return Object.freeze({ kind: \"unverified\", decision: \"reject-only\" });\n }\n try {\n return Object.freeze({\n kind: \"permission-request\",\n decision: \"approve-or-reject\",\n request: parsePermissionRequest({ ...parsed, requestId }),\n });\n } catch {\n const request = parseOwnerSigningRequest(parsed);\n try {\n const kernelRequest = parseKernelV4ReplayableInstallOwnerSigningRequest(request);\n if (kernelRequest.signer.ownerCredential.kind === \"p256\") {\n return Object.freeze({\n kind: \"kernel-owner-signing-request\",\n decision: \"approve-or-reject\",\n request: kernelRequest,\n });\n }\n } catch {\n // The generic closed request remains readable but reject-only.\n }\n return Object.freeze({\n kind: \"owner-signing-request\",\n decision: \"reject-only\",\n request,\n });\n }\n } catch {\n return Object.freeze({ kind: \"unverified\", decision: \"reject-only\" });\n }\n}\n","/**\n * Authoritative approve/reject transition.\n *\n * ```text\n * state and owner the decision record owns \"decided\"; the request record\n * never mutates\n * persisted evidence the immutable requestedScope is reclassified from the\n * durable request before every approval attempt\n * resource occupied? a refused approval occupies nothing and performs no KMS\n * sealing; a rejection occupies only its decision row\n * retry positively safe? refused approval is read-only; a terminal outcome is\n * replayed by the native saga and rejected by this owner\n * transitions permission or verified Kernel owner signing:\n * undecided -> approved | rejected;\n * every reject-only scope: undecided -> rejected once\n * terminal both outcomes; a second decide fails relay_already_decided\n * crash/reload the decision, the code, and the sealed artifact commit in\n * one transaction on the row-locked request, so a crash\n * leaves the request undecided and nothing released\n * cleanup owner the relay transaction; refused approvals allocate nothing\n * ```\n *\n * Subject binding: the authoritative subject is recovered from the stored\n * authorization request by `requestId` and compared against the authenticated\n * owner. No wire field names the subject. A decision envelope that carries a\n * subject identifier is rejected as an unknown field by exact capture, because a\n * field that only *names* a subject is not cryptographically bound to it and can\n * never decide authority.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport { sealArtifact } from \"../artifact/encrypt.js\";\nimport { type RelayClock, relayNow } from \"../clock.js\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport type { RelayCaller } from \"../security/authentication.js\";\nimport type { RelayKms } from \"../security/kms.js\";\nimport type { RelayStore } from \"../store/interface.js\";\nimport { withRelayTransaction } from \"../store/interface.js\";\nimport {\n OAATH_AUTHORIZATION_CODE_RECORD_VERSION,\n OAATH_AUTHORIZATION_DECISION_RECORD_VERSION,\n OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION,\n} from \"../store/records.js\";\nimport { randomIdentifier, sha256Base64Url } from \"./challenge.js\";\nimport { verifyKernelV4ReplayableInstallOwnerSigningArtifact } from \"./owner-signing.js\";\nimport { fetchAuthorizationRequest } from \"./request.js\";\nimport { classifyStoredAuthorizationScope } from \"./scope.js\";\n\nexport type AuthorizationDecisionCommand =\n /** The owner approves and hands over the artifact the client will claim once. */\n Readonly<{ outcome: \"approved\"; artifact: string }> | Readonly<{ outcome: \"rejected\" }>;\n\nexport interface SubmitAuthorizationDecisionInput {\n readonly store: RelayStore;\n readonly clock: RelayClock;\n readonly kms: RelayKms;\n /** Authenticated `owner` caller; only the bound subject may decide. */\n readonly caller: RelayCaller;\n readonly requestId: string;\n readonly command: AuthorizationDecisionCommand;\n readonly codeTtlMs: number;\n}\n\nexport type SubmittedAuthorizationDecision =\n | Readonly<{\n outcome: \"approved\";\n decidedAt: number;\n /** Released exactly once, here. Only its SHA-256 is stored. */\n code: string;\n artifactId: string;\n redirectUri: string;\n codeExpiresAt: number;\n }>\n | Readonly<{ outcome: \"rejected\"; decidedAt: number }>;\n\nexport async function submitAuthorizationDecision(\n input: SubmitAuthorizationDecisionInput,\n): Promise<SubmittedAuthorizationDecision> {\n const decidedAt = relayNow(input.clock);\n let approvedArtifact: string | undefined;\n\n // Approval is the artifact-creating transition, so the shared decision owner\n // admits it only for an exact scope this server currently permits to release.\n // Refusal happens before KMS sealing or any durable decision/code/artifact.\n if (input.command.outcome === \"approved\") {\n const state = await fetchAuthorizationRequest({\n store: input.store,\n clock: input.clock,\n caller: input.caller,\n requestId: input.requestId,\n });\n // Preserve the transaction owner's existing precedence: the native saga\n // must recover an earlier terminal outcome without reclassifying its scope.\n if (state.decision !== null) {\n return relayFailure(\"relay_already_decided\", \"authorization request is already decided\");\n }\n if (state.expired) {\n return relayFailure(\"relay_expired\", \"authorization request expired\");\n }\n const scope = classifyStoredAuthorizationScope(state.requestedScope, state.requestId);\n if (scope.kind === \"permission-request\") {\n approvedArtifact = input.command.artifact;\n } else if (scope.kind === \"kernel-owner-signing-request\") {\n approvedArtifact = verifyKernelV4ReplayableInstallOwnerSigningArtifact(\n scope.request,\n input.command.artifact,\n );\n } else {\n return relayFailure(\"relay_request_invalid\", \"the authorization scope is reject-only\");\n }\n }\n\n // Seal before the transaction: the store only ever receives references, and\n // an uncommitted decision leaves nothing but unreferenced ciphertexts. The\n // code is sealed too so the authenticated client can pick it up later; PKCE\n // still guards its consumption.\n let approved:\n | Readonly<{ code: string; artifactId: string; ciphertextRef: string; codeRef: string }>\n | undefined;\n if (input.command.outcome === \"approved\") {\n if (approvedArtifact === undefined) {\n return relayFailure(\"relay_internal\", \"approved artifact was not authorized\");\n }\n const code = randomIdentifier();\n approved = Object.freeze({\n code,\n artifactId: randomIdentifier(),\n ciphertextRef: await sealArtifact(input.kms, approvedArtifact),\n codeRef: await sealArtifact(input.kms, code),\n });\n }\n const codeHash = approved ? await sha256Base64Url(approved.code) : undefined;\n const decision = Object.freeze({\n version: OAATH_AUTHORIZATION_DECISION_RECORD_VERSION,\n requestId: input.requestId,\n outcome: input.command.outcome,\n decidedAt,\n codeRef: approved?.codeRef ?? null,\n codeExpiresAt: approved ? decidedAt + input.codeTtlMs : null,\n } as const);\n\n const redirectUri = await withRelayTransaction(input.store, async (transaction) => {\n const request = await transaction.lockAuthorizationRequest(input.requestId);\n // The stored request owns the subject. A mismatch is reported as absence so\n // the endpoint is not an existence oracle for another subject's request.\n if (!request || request.subject !== input.caller.subject) {\n return relayFailure(\"relay_not_found\", \"authorization request does not exist\");\n }\n if (await transaction.lockAuthorizationDecision(input.requestId)) {\n return relayFailure(\"relay_already_decided\", \"authorization request is already decided\");\n }\n if (decidedAt >= request.expiresAt) {\n return relayFailure(\"relay_expired\", \"authorization request expired\");\n }\n if (!(await transaction.insertAuthorizationDecision(decision))) {\n return relayFailure(\"relay_already_decided\", \"authorization request is already decided\");\n }\n if (approved && codeHash !== undefined) {\n const inserted =\n (await transaction.insertAuthorizationCode(\n Object.freeze({\n version: OAATH_AUTHORIZATION_CODE_RECORD_VERSION,\n codeHash,\n requestId: request.requestId,\n clientId: request.clientId,\n redirectUri: request.redirectUri,\n codeChallenge: request.codeChallenge,\n artifactId: approved.artifactId,\n createdAt: decidedAt,\n expiresAt: decidedAt + input.codeTtlMs,\n consumedAt: null,\n }),\n )) &&\n (await transaction.insertEncryptedArtifact(\n Object.freeze({\n version: OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION,\n artifactId: approved.artifactId,\n requestId: request.requestId,\n clientId: request.clientId,\n ciphertextRef: approved.ciphertextRef,\n createdAt: decidedAt,\n claimedAt: null,\n }),\n ));\n if (!inserted) {\n return relayFailure(\"relay_internal\", \"released identifier is not unique\");\n }\n }\n return request.redirectUri;\n });\n\n if (approved && codeHash !== undefined) {\n return Object.freeze({\n outcome: \"approved\",\n decidedAt,\n code: approved.code,\n artifactId: approved.artifactId,\n redirectUri,\n codeExpiresAt: decidedAt + input.codeTtlMs,\n });\n }\n return Object.freeze({ outcome: \"rejected\", decidedAt });\n}\n","/**\n * EXPERIMENTAL PREVIEW — approve/reject saga keyed by the stable operation id.\n *\n * ```text\n * state and owner the relay's authorization decision record. This saga\n * owns no decision state of its own and keeps nothing in\n * memory; it routes to `submitAuthorizationDecision`.\n * persisted evidence one terminal decision row per request\n * resource occupied? yes: the decision occupies its request, and the\n * one-time code and artifact are released exactly once\n * retry positively safe? yes for the saga call, because a repeated call never\n * decides again: it answers the stored outcome\n * transitions undecided -> approved | rejected once, then replay-only\n * crash/reload the outcome is durable before the caller sees it, so a\n * phone that never saw its response retries and is\n * answered from the store, on any process\n * cleanup owner the relay transaction; nothing extra is allocated here\n * ```\n *\n * A second decide answers the *stored* outcome, not the resubmitted command: if\n * a rejected request is retried as approved, the answer is `rejected`. Compare\n * `outcome` against what you sent when that matters.\n *\n * The one-time code and encrypted artifact are released to the deciding call\n * only. A replay reports the outcome and no release material, because releasing\n * it twice is exactly what one-shot semantics forbid.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport type {\n AuthorizationDecisionCommand,\n SubmittedAuthorizationDecision,\n} from \"../authorization/decision.js\";\nimport { submitAuthorizationDecision } from \"../authorization/decision.js\";\nimport { fetchAuthorizationRequest } from \"../authorization/request.js\";\nimport type { RelayClock } from \"../clock.js\";\nimport { OaathRelayError, relayFailure } from \"../relay/errors.js\";\nimport type { RelayCaller } from \"../security/authentication.js\";\nimport type { RelayKms } from \"../security/kms.js\";\nimport type { RelayStore } from \"../store/interface.js\";\nimport type { AuthorizationDecisionOutcome } from \"../store/records.js\";\n\nexport interface SubmitOwnerPhoneDecisionInput {\n readonly store: RelayStore;\n readonly clock: RelayClock;\n readonly kms: RelayKms;\n /** Authenticated `owner` caller; only the bound subject may decide. */\n readonly caller: RelayCaller;\n /** `operationId` from `projectOwnerPhoneRequest`. */\n readonly operationId: string;\n readonly command: AuthorizationDecisionCommand;\n readonly codeTtlMs: number;\n}\n\nexport interface OwnerPhoneDecision {\n readonly operationId: string;\n /** The stored outcome, which on a replay may differ from the command sent. */\n readonly outcome: AuthorizationDecisionOutcome;\n readonly decidedAt: number;\n /** `decided` performed the transition; `replayed` answered the stored one. */\n readonly settlement: \"decided\" | \"replayed\";\n /** One-time release material, on the deciding call only. */\n readonly release: SubmittedAuthorizationDecision | null;\n}\n\nexport async function submitOwnerPhoneDecision(\n input: SubmitOwnerPhoneDecisionInput,\n): Promise<OwnerPhoneDecision> {\n if (input.caller.role !== \"owner\") {\n return relayFailure(\"relay_forbidden\", \"caller may not act in the required role\");\n }\n try {\n const release = await submitAuthorizationDecision({\n store: input.store,\n clock: input.clock,\n kms: input.kms,\n caller: input.caller,\n requestId: input.operationId,\n command: input.command,\n codeTtlMs: input.codeTtlMs,\n });\n return Object.freeze({\n operationId: input.operationId,\n outcome: release.outcome,\n decidedAt: release.decidedAt,\n settlement: \"decided\",\n release,\n });\n } catch (error) {\n // Only \"already decided\" is idempotent. Ambiguous, expired, unauthorized,\n // and unreadable stay failures: an unproven commit never becomes an answer.\n if (!(error instanceof OaathRelayError) || error.code !== \"relay_already_decided\") throw error;\n }\n const state = await fetchAuthorizationRequest({\n store: input.store,\n clock: input.clock,\n caller: input.caller,\n requestId: input.operationId,\n });\n if (state.decision === null) {\n return relayFailure(\"relay_internal\", \"decided request has no decision record\");\n }\n return Object.freeze({\n operationId: input.operationId,\n outcome: state.decision.outcome,\n decidedAt: state.decision.decidedAt,\n settlement: \"replayed\",\n release: null,\n });\n}\n","/**\n * EXPERIMENTAL PREVIEW — authenticated owner-phone consent projection.\n *\n * This is a preview surface reached through `@oaath/server/native` and the\n * relay's preview HTTP routes (`GET /native/projections/{operationId}`). It has\n * no stability guarantee and no production qualification: real iOS, Apple\n * provisioning, Secure Enclave, and hosted operations are later work.\n *\n * What it owns: turning one stored authorization request into the projection\n * the owner's phone renders before deciding. It owns nothing about deciding;\n * the relay's decision use case stays the only decision owner.\n *\n * Two very different surfaces share this file, and only one of them is opaque:\n *\n * - The **push** projection (`operationId`, `displayPayload`, `expiresAt`)\n * transits Apple inside an APNs payload, so it stays opaque by design: no\n * permission, scope, client, chain, or account detail may ride it. The APNs\n * sender enforces that subset exactly.\n * - The **consent** projection returned here travels only the authenticated\n * relay → owner channel, so it deliberately carries the full authorization\n * request: the requesting client identity and the requested scope. The owner\n * must see exactly the authority they grant before approving.\n *\n * `displayPayload` is not a secret and not authority: it is a deterministic\n * digest of the request and its bound subject, so the owner can check that the\n * code on the phone is the code the browser shows. Holding it authorizes\n * nothing — deciding still requires the authenticated bound owner.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport {\n hashOwnerSigningRequest,\n type KernelV4ReplayableInstallOwnerSigningRequest,\n type OwnerSigningRequest,\n} from \"@oaath/protocol\";\nimport { sha256Base64Url } from \"../authorization/challenge.js\";\nimport { fetchAuthorizationRequest } from \"../authorization/request.js\";\nimport { classifyStoredAuthorizationScope } from \"../authorization/scope.js\";\nimport type { RelayClock } from \"../clock.js\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport type { RelayCaller } from \"../security/authentication.js\";\nimport type { RelayStore } from \"../store/interface.js\";\n\n/** Bounded base64url match code length. 48 bits is plenty to compare by eye. */\nexport const NATIVE_DISPLAY_PAYLOAD_LENGTH = 8;\n\n/** Versioned consent envelope; the Swift decoder pins this exact value. */\nexport const OAATH_NATIVE_PROJECTION_VERSION = \"oaath.native-projection/v4\" as const;\n\nconst DISPLAY_DOMAIN = \"oaath.native-display/v1:\";\n\n/**\n * Whether the phone may offer approval for one projected scope. Permission\n * requests and exact Kernel replayable-install P-256 signing are approvable;\n * every other owner-signing, unknown, and malformed scope remains reject-only.\n */\nexport type OwnerPhoneDecisionCapability = \"approve-or-reject\" | \"reject-only\";\n\n/** One credential identity as the owner reviews it: public material only. */\nexport type OwnerPhoneCredentialProjection =\n | Readonly<{ kind: \"ecdsa\"; address: string }>\n | Readonly<{ kind: \"p256\"; publicKey: string }>\n | Readonly<{ kind: \"webauthn\"; publicKey: string; authenticatorIdHash: string }>;\n\n/**\n * The requested scope as the phone renders it. When the stored scope parses as\n * an `@oaath/protocol` permission request, every fact that determines who\n * receives authority, over which account, and under what limits is projected\n * structurally. A valid owner-signing request is also projected in full with\n * its protocol-owned hash; only exact Kernel replayable-install P-256 signing\n * is approvable. Anything else is returned as explicitly labeled raw text.\n */\nexport type OwnerPhoneScopeProjection =\n | Readonly<{\n kind: \"permission-request\";\n decision: \"approve-or-reject\";\n /** The application identity the signed request binds, verbatim. */\n application: Readonly<{\n applicationId: string;\n clientId: string;\n origin: string;\n /** Opaque device identity, fingerprinted: raw ids mean nothing to an owner. */\n deviceFingerprint: string;\n }>;\n /** The logical account this authority acts for. */\n account: Readonly<{\n accountIndex: string;\n kernelVersion: string;\n factoryRoute: string;\n entryPointVersion: string;\n ownerCredential: OwnerPhoneCredentialProjection;\n }>;\n /** The session credential that receives the scoped authority. */\n operatorCredential: OwnerPhoneCredentialProjection;\n /**\n * Where the session key lives: null for frontend custody (the page's\n * own non-extractable key), or the remote trust model the owner is\n * asked to approve — the request hash binds it, so approving this\n * display approves exactly this custody.\n */\n sessionSigner: Readonly<{\n mode: \"application_backend\" | \"oaath_hosted\";\n providerId: string;\n }> | null;\n chainScope: \"all\";\n calls: readonly Readonly<{\n target: string;\n selector: string;\n valueLimit: string;\n argumentEquals: readonly Readonly<{ index: number; value: string }>[];\n }>[];\n requestedAt: number;\n /** The permission request's own expiry, as the requesting client stated it. */\n expiresAt: number;\n /** The policy's on-chain validity window; inclusive, in Unix seconds. */\n policyValidAfter: number;\n policyValidUntil: number | null;\n perChainOperationLimit: number;\n }>\n | Readonly<{\n kind: \"owner-signing-request\";\n decision: \"approve-or-reject\";\n /** Canonical hash binding every captured request fact. */\n requestHash: `0x${string}`;\n /** Exact Kernel request independently refined by the protocol owner. */\n request: Readonly<KernelV4ReplayableInstallOwnerSigningRequest>;\n }>\n | Readonly<{\n kind: \"owner-signing-request\";\n decision: \"reject-only\";\n /** Canonical hash binding every captured request fact. */\n requestHash: `0x${string}`;\n /** Full immutable protocol request for independent device review. */\n request: Readonly<OwnerSigningRequest>;\n }>\n | Readonly<{ kind: \"raw\"; decision: \"reject-only\"; text: string }>;\n\nexport interface OwnerPhoneRequestProjection {\n readonly version: typeof OAATH_NATIVE_PROJECTION_VERSION;\n /**\n * Stable across every projection of the same request, and the key the\n * approve/reject saga is keyed by. It is the relay's own opaque request\n * identifier: one authoritative id, no second mapping table to lose.\n */\n readonly operationId: string;\n /** Bounded opaque match code, the only variable an APNs payload may carry. */\n readonly displayPayload: string;\n /** The stored request's expiry, in epoch milliseconds. */\n readonly expiresAt: number;\n /** The requesting client, exactly as the stored request binds it. */\n readonly client: Readonly<{ clientId: string; redirectUri: string }>;\n readonly scope: OwnerPhoneScopeProjection;\n}\n\n/**\n * The opaque subset of the projection that may transit Apple inside an APNs\n * payload. The sender exact-captures precisely these three fields, so consent\n * detail can never ride a notification.\n */\nexport type OwnerPhonePushProjection = Pick<\n OwnerPhoneRequestProjection,\n \"operationId\" | \"displayPayload\" | \"expiresAt\"\n>;\n\nexport interface ProjectOwnerPhoneRequestInput {\n readonly store: RelayStore;\n readonly clock: RelayClock;\n /** Authenticated `owner` caller; only the bound subject may be projected. */\n readonly caller: RelayCaller;\n readonly requestId: string;\n}\n\n/**\n * The stored scope is the requesting client's own JSON permission scope (the\n * SDK serializes a permission request without its `requestId`; the relay's\n * request id is the binding). An unparseable scope is not a failure: the owner\n * still gets the raw text to review, never a silent omission.\n */\nfunction projectCredential(\n credential: Readonly<{\n kind: \"ecdsa\" | \"p256\" | \"webauthn\";\n address?: string;\n publicKey?: string;\n authenticatorIdHash?: string;\n }>,\n): OwnerPhoneCredentialProjection {\n if (credential.kind === \"ecdsa\") {\n return Object.freeze({ kind: \"ecdsa\", address: credential.address ?? \"\" });\n }\n if (credential.kind === \"p256\") {\n return Object.freeze({ kind: \"p256\", publicKey: credential.publicKey ?? \"\" });\n }\n return Object.freeze({\n kind: \"webauthn\",\n publicKey: credential.publicKey ?? \"\",\n authenticatorIdHash: credential.authenticatorIdHash ?? \"\",\n });\n}\n\nexport async function projectOwnerPhoneScope(\n requestedScope: string,\n operationId: string,\n): Promise<OwnerPhoneScopeProjection> {\n try {\n const classified = classifyStoredAuthorizationScope(requestedScope, operationId);\n if (classified.kind === \"permission-request\") {\n const request = classified.request;\n return Object.freeze({\n kind: \"permission-request\",\n decision: \"approve-or-reject\",\n application: Object.freeze({\n applicationId: request.application.applicationId,\n clientId: request.application.clientId,\n origin: request.application.origin,\n deviceFingerprint: (\n await sha256Base64Url(`${DISPLAY_DOMAIN}device:${request.application.deviceId}`)\n ).slice(0, NATIVE_DISPLAY_PAYLOAD_LENGTH),\n }),\n account: Object.freeze({\n accountIndex: request.logicalAccount.accountIndex,\n kernelVersion: request.logicalAccount.kernelVersion,\n factoryRoute: request.logicalAccount.factoryRoute,\n entryPointVersion: request.logicalAccount.entryPoint.version,\n ownerCredential: projectCredential(request.logicalAccount.ownerCredential),\n }),\n operatorCredential: projectCredential(request.operatorCredential),\n sessionSigner:\n request.sessionSigner === null\n ? null\n : Object.freeze({\n mode: request.sessionSigner.mode,\n providerId: request.sessionSigner.providerId,\n }),\n chainScope: request.chainScope,\n calls: Object.freeze(\n request.policy.calls.map((call) =>\n Object.freeze({\n target: call.target,\n selector: call.selector,\n valueLimit: call.valueLimit,\n argumentEquals: Object.freeze(\n call.argumentEquals.map((rule) =>\n Object.freeze({ index: rule.index, value: rule.value }),\n ),\n ),\n }),\n ),\n ),\n requestedAt: request.requestedAt,\n expiresAt: request.expiresAt,\n policyValidAfter: request.policy.validAfter,\n policyValidUntil: request.policy.validUntil,\n perChainOperationLimit: request.policy.perChainOperationLimit,\n });\n }\n if (classified.kind === \"kernel-owner-signing-request\") {\n return Object.freeze({\n kind: \"owner-signing-request\",\n decision: \"approve-or-reject\",\n requestHash: hashOwnerSigningRequest(classified.request),\n request: classified.request,\n });\n }\n if (classified.kind === \"owner-signing-request\") {\n return Object.freeze({\n kind: \"owner-signing-request\",\n decision: \"reject-only\",\n requestHash: hashOwnerSigningRequest(classified.request),\n request: classified.request,\n });\n }\n return Object.freeze({ kind: \"raw\", decision: \"reject-only\", text: requestedScope });\n } catch {\n return Object.freeze({ kind: \"raw\", decision: \"reject-only\", text: requestedScope });\n }\n}\n\n/**\n * Projects a pending request for its bound owner. An expired or already decided\n * request is not projectable: there is nothing left to approve, so nothing is\n * ever pushed or rendered for it.\n */\nexport async function projectOwnerPhoneRequest(\n input: ProjectOwnerPhoneRequestInput,\n): Promise<OwnerPhoneRequestProjection> {\n if (input.caller.role !== \"owner\") {\n return relayFailure(\"relay_forbidden\", \"caller may not act in the required role\");\n }\n const state = await fetchAuthorizationRequest({\n store: input.store,\n clock: input.clock,\n caller: input.caller,\n requestId: input.requestId,\n });\n if (state.expired) {\n return relayFailure(\"relay_expired\", \"authorization request expired\");\n }\n if (state.decision !== null) {\n return relayFailure(\"relay_already_decided\", \"authorization request is already decided\");\n }\n const digest = await sha256Base64Url(\n `${DISPLAY_DOMAIN}${input.caller.subject}:${state.requestId}`,\n );\n return Object.freeze({\n version: OAATH_NATIVE_PROJECTION_VERSION,\n operationId: state.requestId,\n displayPayload: digest.slice(0, NATIVE_DISPLAY_PAYLOAD_LENGTH),\n expiresAt: state.expiresAt,\n client: Object.freeze({ clientId: state.clientId, redirectUri: state.redirectUri }),\n scope: await projectOwnerPhoneScope(state.requestedScope, state.requestId),\n });\n}\n"],"mappings":"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"}