@oaath/server 0.1.0 → 0.3.1

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
@@ -0,0 +1,245 @@
1
+ import { BundlerRejection, KernelRevocationSigningRequest, ValidationGasDiagnostic } from "@oaath/protocol";
2
+ //#region src/clock.d.ts
3
+ /**
4
+ * Injected clock. Expiry is never read from `Date.now()` directly, so tests and
5
+ * deployments own time.
6
+ *
7
+ * @author taek <leekt216@gmail.com>
8
+ */
9
+ interface RelayClock {
10
+ /** Milliseconds since the Unix epoch as a non-negative safe integer. */
11
+ now(): number;
12
+ }
13
+ //#endregion
14
+ //#region src/revocation/records.d.ts
15
+ declare const OAATH_REVOCATION_REQUEST_RECORD_VERSION: "oaath.revocation-request-record/v1";
16
+ declare const OAATH_REVOCATION_DECISION_RECORD_VERSION: "oaath.revocation-decision-record/v1";
17
+ interface RevocationRequestRecord {
18
+ readonly version: typeof OAATH_REVOCATION_REQUEST_RECORD_VERSION;
19
+ readonly operationId: string;
20
+ readonly ownerDeviceId: string;
21
+ readonly ownerSubject: string;
22
+ readonly createdAt: number;
23
+ readonly expiresAt: number;
24
+ readonly signingRequest: Readonly<KernelRevocationSigningRequest>;
25
+ }
26
+ interface RevocationDecisionRecord {
27
+ readonly version: typeof OAATH_REVOCATION_DECISION_RECORD_VERSION;
28
+ readonly operationId: string;
29
+ readonly outcome: "approved" | "rejected";
30
+ readonly decidedAt: number;
31
+ /** Sealed canonical phone artifact; null exactly for rejection. Never an OAuth release. */
32
+ readonly artifactRef: string | null;
33
+ }
34
+ //#endregion
35
+ //#region src/relay/errors.d.ts
36
+ type RelayErrorCode =
37
+ /** Wire input is missing, malformed, oversized, or contains unknown fields. */
38
+ "relay_request_invalid" |
39
+ /** The deployment authentication port did not authenticate the caller. */
40
+ "relay_unauthenticated" |
41
+ /** Authenticated caller may not act in the required role. */
42
+ "relay_forbidden" |
43
+ /** Route, or a record the caller is entitled to see, does not exist. */
44
+ "relay_not_found" |
45
+ /** Route exists with a different method. */
46
+ "relay_method_not_allowed" |
47
+ /** The authorization request or code is past its injected-clock expiry. */
48
+ "relay_expired" |
49
+ /** The authorization request already reached a terminal decision. */
50
+ "relay_already_decided" |
51
+ /** The one-time authorization code was already consumed. */
52
+ "relay_code_already_consumed" |
53
+ /** The one-time encrypted artifact was already claimed. */
54
+ "relay_artifact_already_claimed" |
55
+ /**
56
+ * Authorization code redemption failed. One code for every reason it can
57
+ * fail — unknown code, another client's code, wrong redirect URI, wrong PKCE
58
+ * verifier — so the endpoint never confirms that a guessed code was correct.
59
+ * A code that existed is burned.
60
+ */
61
+ "relay_code_invalid" |
62
+ /** The optional deployment limiter rejected the call. */
63
+ "relay_rate_limited" |
64
+ /** A durable record could not be read as the current schema version. */
65
+ "relay_record_unreadable" |
66
+ /** The KMS port was unavailable or returned an unusable result. */
67
+ "relay_kms_unavailable" |
68
+ /** The store failed before any state change could have been committed. */
69
+ "relay_store_unavailable" |
70
+ /**
71
+ * The store could not prove whether a transition committed. The caller must
72
+ * treat the transition as neither applied nor retryable: an ambiguous commit
73
+ * never authorizes resubmission.
74
+ */
75
+ "relay_state_ambiguous" |
76
+ /**
77
+ * EXPERIMENTAL PREVIEW (`@oaath/server/apns`): the constructed APNs payload
78
+ * exceeded Apple's 4096-byte limit, so nothing was sent.
79
+ */
80
+ "relay_apns_payload_too_large" |
81
+ /**
82
+ * EXPERIMENTAL PREVIEW (`@oaath/server/apns`): the injected Apple credential
83
+ * capability is not a usable P-256 signing key, key id, team id, or topic.
84
+ */
85
+ "relay_apns_credentials_invalid" |
86
+ /** A deployment-injected chain execution port failed or answered unusably. */
87
+ "relay_chain_unavailable" |
88
+ /** The Grant's capability is durably invalidated; nothing more executes here. */
89
+ "relay_capability_invalidated" |
90
+ /** An invariant the relay owns was violated. */
91
+ "relay_internal";
92
+ /**
93
+ * Status projection. `relay_state_ambiguous` is deliberately 500 and not 503,
94
+ * because 503 invites the retry that an ambiguous commit forbids.
95
+ */
96
+ declare const RELAY_ERROR_STATUS: Readonly<Record<RelayErrorCode, number>>;
97
+ declare class OaathRelayError extends Error {
98
+ readonly bundlerRejection: Readonly<BundlerRejection> | null;
99
+ readonly code: RelayErrorCode;
100
+ readonly diagnostic: Readonly<ValidationGasDiagnostic> | null;
101
+ constructor(code: RelayErrorCode, message: string, diagnostic?: Readonly<ValidationGasDiagnostic> | null, bundlerRejection?: Readonly<BundlerRejection> | null);
102
+ }
103
+ //#endregion
104
+ //#region src/store/records.d.ts
105
+ declare const OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION: "oaath.authorization-request-record/v2";
106
+ declare const OAATH_AUTHORIZATION_DECISION_RECORD_VERSION: "oaath.authorization-decision-record/v1";
107
+ declare const OAATH_AUTHORIZATION_CODE_RECORD_VERSION: "oaath.authorization-code-record/v1";
108
+ declare const OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION: "oaath.encrypted-artifact-record/v1";
109
+ declare const OAATH_CAPABILITY_INVALIDATION_RECORD_VERSION: "oaath.capability-invalidation-record/v1";
110
+ /** Deployment-selected device reference and its authenticated approving subject. */
111
+ interface AuthorizationOwnerRoute {
112
+ readonly ownerDeviceId: string;
113
+ readonly ownerSubject: string;
114
+ }
115
+ interface AuthorizationRequestRecord extends AuthorizationOwnerRoute {
116
+ readonly version: typeof OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION;
117
+ readonly requestId: string;
118
+ /** Client bound by the deployment authentication port, never by wire input. */
119
+ readonly clientId: string;
120
+ /** Requesting member's subject, independent of the approving device. */
121
+ readonly subject: string;
122
+ /**
123
+ * Organization/audience bound by the authentication port at creation time,
124
+ * or null when the deployment declares none. Grant reference verification
125
+ * compares assertions against this captured value.
126
+ */
127
+ readonly organizationAudience: string | null;
128
+ readonly redirectUri: string;
129
+ /** PKCE S256 challenge; the verifier never reaches the store. */
130
+ readonly codeChallenge: string;
131
+ /** Opaque owner-reviewed scope payload. Migrates to @oaath/protocol. */
132
+ readonly requestedScope: string;
133
+ readonly createdAt: number;
134
+ readonly expiresAt: number;
135
+ }
136
+ type AuthorizationDecisionOutcome = "approved" | "rejected";
137
+ /**
138
+ * Terminal decision. Its existence is the single authoritative fact that an
139
+ * authorization request was decided; the request record never mutates.
140
+ */
141
+ interface AuthorizationDecisionRecord {
142
+ readonly version: typeof OAATH_AUTHORIZATION_DECISION_RECORD_VERSION;
143
+ readonly requestId: string;
144
+ readonly outcome: AuthorizationDecisionOutcome;
145
+ readonly decidedAt: number;
146
+ /**
147
+ * KMS-sealed copy of the released code for authenticated client pickup, and
148
+ * its expiry. The relay mints the code itself, so holding a sealed copy adds
149
+ * no new trust; PKCE still guards consumption. Both are null exactly when
150
+ * the outcome is a rejection.
151
+ */
152
+ readonly codeRef: string | null;
153
+ readonly codeExpiresAt: number | null;
154
+ }
155
+ interface AuthorizationCodeRecord {
156
+ readonly version: typeof OAATH_AUTHORIZATION_CODE_RECORD_VERSION;
157
+ /** SHA-256 (base64url) of the released code. The code itself is never stored. */
158
+ readonly codeHash: string;
159
+ readonly requestId: string;
160
+ /** Binding snapshot taken at issue time so consume needs exactly one row lock. */
161
+ readonly clientId: string;
162
+ readonly redirectUri: string;
163
+ readonly codeChallenge: string;
164
+ readonly artifactId: string;
165
+ readonly createdAt: number;
166
+ readonly expiresAt: number;
167
+ /** Set exactly once. A non-null value is terminal. */
168
+ readonly consumedAt: number | null;
169
+ }
170
+ interface EncryptedArtifactRecord {
171
+ readonly version: typeof OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION;
172
+ readonly artifactId: string;
173
+ readonly requestId: string;
174
+ readonly clientId: string;
175
+ /** Opaque KMS reference. Plaintext never reaches the store. */
176
+ readonly ciphertextRef: string;
177
+ readonly createdAt: number;
178
+ /** Set exactly once. A non-null value is terminal. */
179
+ readonly claimedAt: number | null;
180
+ }
181
+ /**
182
+ * One durable capability invalidation: from the moment this record commits,
183
+ * the relay's chain execution routes refuse every submission and usage read
184
+ * for the Grant, so the recorded evidence states an enforced fact — "this
185
+ * service will no longer act for this capability" — not a bare digest.
186
+ * Consuming the on-chain install nonce is the chain-local revocation
187
+ * operation's job and stays separate evidence.
188
+ */
189
+ interface CapabilityInvalidationRecord {
190
+ readonly version: typeof OAATH_CAPABILITY_INVALIDATION_RECORD_VERSION;
191
+ readonly grantId: string;
192
+ /** The authenticated client that recorded the invalidation. */
193
+ readonly clientId: string;
194
+ readonly capabilityHash: string;
195
+ readonly invalidatedAt: number;
196
+ }
197
+ //#endregion
198
+ //#region src/store/interface.d.ts
199
+ /** Read-only discovery metadata. The existing request/decision records own pending state. */
200
+ interface PendingOwnerRequest {
201
+ readonly operationId: string;
202
+ readonly ownerSubject: string;
203
+ readonly expiresAt: number;
204
+ }
205
+ interface RelayTransaction {
206
+ /** Unexpired and undecided requests, ordered by expiry then ASCII operation ID. No reservation. */
207
+ listPendingOwnerRequests(ownerSubject: string, now: number, limit: number): Promise<readonly PendingOwnerRequest[]>;
208
+ /** Lock the original authorization request first to serialize grant/chain enqueue. */
209
+ lockLatestRevocationRequest(grantId: string, chainId: number): Promise<RevocationRequestRecord | undefined>;
210
+ /** Lock the immutable request before reading or writing its terminal decision. */
211
+ lockRevocationRequest(operationId: string): Promise<RevocationRequestRecord | undefined>;
212
+ insertRevocationRequest(record: RevocationRequestRecord): Promise<boolean>;
213
+ lockRevocationDecision(operationId: string): Promise<RevocationDecisionRecord | undefined>;
214
+ insertRevocationDecision(record: RevocationDecisionRecord): Promise<boolean>;
215
+ lockAuthorizationRequest(requestId: string): Promise<AuthorizationRequestRecord | undefined>;
216
+ /** Returns false when the identifier already exists. */
217
+ insertAuthorizationRequest(record: AuthorizationRequestRecord): Promise<boolean>;
218
+ lockAuthorizationDecision(requestId: string): Promise<AuthorizationDecisionRecord | undefined>;
219
+ /** Returns false when the request was already decided. */
220
+ insertAuthorizationDecision(record: AuthorizationDecisionRecord): Promise<boolean>;
221
+ lockAuthorizationCode(codeHash: string): Promise<AuthorizationCodeRecord | undefined>;
222
+ insertAuthorizationCode(record: AuthorizationCodeRecord): Promise<boolean>;
223
+ /** Returns true only when this call set `consumed_at`. */
224
+ consumeAuthorizationCode(codeHash: string, consumedAt: number): Promise<boolean>;
225
+ lockCapabilityInvalidation(grantId: string): Promise<CapabilityInvalidationRecord | undefined>;
226
+ /** Returns false when the Grant's capability is already invalidated. */
227
+ insertCapabilityInvalidation(record: CapabilityInvalidationRecord): Promise<boolean>;
228
+ lockEncryptedArtifact(artifactId: string): Promise<EncryptedArtifactRecord | undefined>;
229
+ /** Internal authority lookup; returns retained evidence even after client claim. Never releases it. */
230
+ lockEncryptedArtifactByRequestId(requestId: string): Promise<EncryptedArtifactRecord | undefined>;
231
+ insertEncryptedArtifact(record: EncryptedArtifactRecord): Promise<boolean>;
232
+ /** Returns true only when this call set `claimed_at`. */
233
+ claimEncryptedArtifact(artifactId: string, claimedAt: number): Promise<boolean>;
234
+ /** Throws `relay_state_ambiguous` when the outcome cannot be proven. */
235
+ commit(): Promise<void>;
236
+ /** Never throws; rollback failure is a suppressed diagnostic. */
237
+ rollback(): Promise<void>;
238
+ }
239
+ interface RelayStore {
240
+ begin(): Promise<RelayTransaction>;
241
+ close(): Promise<void>;
242
+ }
243
+ //#endregion
244
+ export { OAATH_REVOCATION_REQUEST_RECORD_VERSION as _, AuthorizationDecisionRecord as a, RelayClock as b, EncryptedArtifactRecord as c, OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION as d, OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION as f, OAATH_REVOCATION_DECISION_RECORD_VERSION as g, RelayErrorCode as h, AuthorizationDecisionOutcome as i, OAATH_AUTHORIZATION_CODE_RECORD_VERSION as l, RELAY_ERROR_STATUS as m, RelayTransaction as n, AuthorizationOwnerRoute as o, OaathRelayError as p, AuthorizationCodeRecord as r, AuthorizationRequestRecord as s, RelayStore as t, OAATH_AUTHORIZATION_DECISION_RECORD_VERSION as u, RevocationDecisionRecord as v, RevocationRequestRecord as y };
245
+ //# sourceMappingURL=interface-DHpwP0wr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface-DHpwP0wr.d.ts","names":[],"sources":["../src/clock.ts","../src/revocation/records.ts","../src/relay/errors.ts","../src/store/records.ts","../src/store/interface.ts"],"mappings":";;;;;;;;UAUiB;;EAEf;;;;cCHW;cAEA;UAKI;WACN,gBAAgB;WAChB;WACA;WACA;WACA;WACA;WACA,gBAAgB,SAAS;;UAEnB;WACN,gBAAgB;WAChB;WACA;WACA;;WAEA;;;;KCfC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6DC,oBAAoB,SAAS,OAAO;cAyBpC,wBAAwB;WAC1B,kBAAkB,SAAS;WAC3B,MAAM;WACN,YAAY,SAAS;EAE9B,YACE,MAAM,gBACN,iBACA,aAAY,SAAS,iCACrB,mBAAkB,SAAS;;;;cC3FlB;cAEA;cAEA;cAEA;cAEA;;UAiBI;WACN;WACA;;UAGM,mCAAmC;WACzC,gBAAgB;WAChB;;WAEA;;WAEA;;;;;;WAMA;WACA;;WAEA;;WAEA;WACA;WACA;;KAGC;;;;;UAMK;WACN,gBAAgB;WAChB;WACA,SAAS;WACT;;;;;;;WAOA;WACA;;UAGM;WACN,gBAAgB;;WAEhB;WACA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;UAGM;WACN,gBAAgB;WAChB;WACA;WACA;;WAEA;WACA;;WAEA;;;;;;;;;;UAWM;WACN,gBAAgB;WAChB;;WAEA;WACA;WACA;;;;;UC7FM;WACN;WACA;WACA;;UAGM;;EAEf,yBACE,sBACA,aACA,gBACC,iBAAiB;;EAEpB,4BACE,iBACA,kBACC,QAAQ;;EAEX,sBAAsB,sBAAsB,QAAQ;EACpD,wBAAwB,QAAQ,0BAA0B;EAC1D,uBAAuB,sBAAsB,QAAQ;EACrD,yBAAyB,QAAQ,2BAA2B;EAE5D,yBAAyB,oBAAoB,QAAQ;;EAErD,2BAA2B,QAAQ,6BAA6B;EAEhE,0BAA0B,oBAAoB,QAAQ;;EAEtD,4BAA4B,QAAQ,8BAA8B;EAElE,sBAAsB,mBAAmB,QAAQ;EACjD,wBAAwB,QAAQ,0BAA0B;;EAE1D,yBAAyB,kBAAkB,qBAAqB;EAEhE,2BAA2B,kBAAkB,QAAQ;;EAErD,6BAA6B,QAAQ,+BAA+B;EAEpE,sBAAsB,qBAAqB,QAAQ;;EAEnD,iCAAiC,oBAAoB,QAAQ;EAC7D,wBAAwB,QAAQ,0BAA0B;;EAE1D,uBAAuB,oBAAoB,oBAAoB;;EAG/D,UAAU;;EAEV,YAAY;;UAGG;EACf,SAAS,QAAQ;EACjB,SAAS"}
@@ -0,0 +1,42 @@
1
+ import { b as RelayClock, t as RelayStore } from "./interface-DHpwP0wr.js";
2
+ import { t as RelayKms } from "./kms-BWkywdJM.js";
3
+ import { OperationObserveResult, OperationObserverCapabilities, OperationStartResult, OperationStoreAdapter } from "@oaath/sdk/advanced";
4
+ import { PreparedUserOperation } from "@oaath/sdk/kernel";
5
+ //#region src/revocation/executor.d.ts
6
+ interface OwnerPhoneRevocationExecutorInput {
7
+ readonly store: RelayStore;
8
+ readonly kms: RelayKms;
9
+ readonly clock: RelayClock;
10
+ readonly operationId: string;
11
+ readonly operations: OperationStoreAdapter;
12
+ readonly observation: OperationObserverCapabilities;
13
+ /**
14
+ * Opens a session for this exact prepared operation and verified root signature.
15
+ * Opening must not send. Return { submit(): Promise<{ userOperationHash }>, close() }.
16
+ * Both opening and submit happen only after durable submission_attempted.
17
+ */
18
+ readonly submission: Readonly<{
19
+ openSubmission(prepared: Readonly<PreparedUserOperation>, signature: `0x${string}`): Promise<unknown>;
20
+ close(): Promise<void>;
21
+ }>;
22
+ }
23
+ /**
24
+ * Deployment execution capability, not a client/phone HTTP route. A terminal
25
+ * approved decision is custody; the existing Operation journal owns execution.
26
+ * Reload reconstructs exact bytes without quoting or preparing on chain. The
27
+ * journal is read before KMS/submission access. An attempted or terminal hash
28
+ * never opens another session, even after consent expiry. Observe reads only
29
+ * chain evidence and retains uncertainty. Close delegates retryable resource
30
+ * cleanup to the runner; the deployment owns relay store, pool and KMS lifetime.
31
+ */
32
+ interface OwnerPhoneRevocationExecutor {
33
+ readonly operationId: string;
34
+ readonly userOperationHash: `0x${string}`;
35
+ start(timeoutMs: number): Promise<OperationStartResult>;
36
+ observe(timeoutMs: number): Promise<OperationObserveResult>;
37
+ close(): Promise<void>;
38
+ }
39
+ declare function createOwnerPhoneRevocationExecutor(input: OwnerPhoneRevocationExecutorInput): Promise<Readonly<OwnerPhoneRevocationExecutor>>;
40
+ //#endregion
41
+ export { type OwnerPhoneRevocationExecutor, type OwnerPhoneRevocationExecutorInput, createOwnerPhoneRevocationExecutor };
42
+ //# sourceMappingURL=kernel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernel.d.ts","names":[],"sources":["../src/revocation/executor.ts"],"mappings":";;;;;UAmBiB;WACN,OAAO;WACP,KAAK;WACL,OAAO;WACP;WACA,YAAY;WACZ,aAAa;;;;;;WAMb,YAAY;IACnB,eACE,UAAU,SAAS,wBACnB,2BACC;IACH,SAAS;;;;;;;;;;;;UAaI;WACN;WACA;EACT,MAAM,oBAAoB,QAAQ;EAClC,QAAQ,oBAAoB,QAAQ;EACpC,SAAS;;iBAEW,mCACpB,OAAO,oCACN,QAAQ,SAAS"}
package/dist/kernel.js ADDED
@@ -0,0 +1,80 @@
1
+ import { b as relayFailure } from "./records-BQNDxbPg.js";
2
+ import { a as relayNow } from "./challenge-DmpGSogj.js";
3
+ import { S as withRelayTransaction, b as openArtifact, r as readRevocationState } from "./service-Cf4f-9KP.js";
4
+ import { parseOwnerSigningArtifact } from "@oaath/protocol";
5
+ import { OperationStore, createOperationObserver, createOperationRunner } from "@oaath/sdk/advanced";
6
+ import { restoreKernelPermissionRevocation } from "@oaath/sdk/kernel";
7
+ //#region src/revocation/executor.ts
8
+ /** Composes durable approved phone custody with the existing SDK execution owners. */
9
+ async function createOwnerPhoneRevocationExecutor(input) {
10
+ const { store, kms, clock, operationId, operations, observation, submission } = input;
11
+ const { request, decision } = await withRelayTransaction(store, (transaction) => readRevocationState(transaction, operationId));
12
+ if (decision?.outcome !== "approved" || decision.artifactRef === null) return relayFailure("relay_request_invalid", "revocation has no approved custody");
13
+ const artifactRef = decision.artifactRef;
14
+ const restored = await restoreKernelPermissionRevocation({ preparation: request.signingRequest });
15
+ const prepared = restored.prepared;
16
+ const key = Object.freeze({
17
+ grantId: prepared.grantId,
18
+ chainId: prepared.chainId,
19
+ kind: "revocation"
20
+ });
21
+ const journal = new OperationStore(operations);
22
+ const noEffect = async () => void 0;
23
+ const runner = createOperationRunner({
24
+ terminalBehavior: "reuse_same_kind",
25
+ requestHash: null,
26
+ store: journal,
27
+ observer: createOperationObserver(observation),
28
+ preparation: {
29
+ prepare: async () => prepared,
30
+ reserveOperation: noEffect,
31
+ releaseOperationReservation: noEffect,
32
+ authorizeOperation: async () => {
33
+ if (relayNow(clock) > request.expiresAt) return relayFailure("relay_expired", "revocation consent expired before submission");
34
+ },
35
+ abandonOperation: noEffect,
36
+ confirmOperationPublished: noEffect,
37
+ close: noEffect
38
+ },
39
+ submission: {
40
+ async openSubmission(snapshot) {
41
+ const artifact = parseOwnerSigningArtifact(JSON.parse(await openArtifact(kms, artifactRef)));
42
+ const signature = await restored.complete(artifact);
43
+ return submission.openSubmission(snapshot, signature);
44
+ },
45
+ close: () => submission.close()
46
+ }
47
+ });
48
+ function runInput(timeoutMs) {
49
+ const now = Math.floor(relayNow(clock) / 1e3);
50
+ return {
51
+ kind: "revocation",
52
+ key,
53
+ expectedUserOperationHash: prepared.userOperationHash,
54
+ preparedAt: now,
55
+ attemptedAt: now,
56
+ submittedAt: now,
57
+ observedAt: now,
58
+ timeoutMs
59
+ };
60
+ }
61
+ return Object.freeze({
62
+ operationId,
63
+ userOperationHash: prepared.userOperationHash,
64
+ async start(timeoutMs) {
65
+ const existing = await journal.getExact(key, prepared.userOperationHash);
66
+ if (existing && existing.value.state !== "prepared") return Object.freeze({
67
+ status: "started",
68
+ record: existing
69
+ });
70
+ if (relayNow(clock) > request.expiresAt) return relayFailure("relay_expired", "revocation consent expired before submission");
71
+ return runner.resumePreparedOperation(runInput(timeoutMs));
72
+ },
73
+ observe: (timeoutMs) => runner.observeOperation(runInput(timeoutMs)),
74
+ close: () => runner.close()
75
+ });
76
+ }
77
+ //#endregion
78
+ export { createOwnerPhoneRevocationExecutor };
79
+
80
+ //# sourceMappingURL=kernel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernel.js","names":[],"sources":["../src/revocation/executor.ts"],"sourcesContent":["/** Composes durable approved phone custody with the existing SDK execution owners. */\nimport { parseOwnerSigningArtifact } from \"@oaath/protocol\";\nimport {\n createOperationObserver,\n createOperationRunner,\n type OperationObserveResult,\n type OperationObserverCapabilities,\n type OperationStartResult,\n OperationStore,\n type OperationStoreAdapter,\n} from \"@oaath/sdk/advanced\";\nimport { type PreparedUserOperation, restoreKernelPermissionRevocation } from \"@oaath/sdk/kernel\";\nimport { openArtifact } from \"../artifact/encrypt.js\";\nimport { type RelayClock, relayNow } from \"../clock.js\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport type { RelayKms } from \"../security/kms.js\";\nimport { type RelayStore, withRelayTransaction } from \"../store/interface.js\";\nimport { readRevocationState } from \"./service.js\";\n\nexport interface OwnerPhoneRevocationExecutorInput {\n readonly store: RelayStore;\n readonly kms: RelayKms;\n readonly clock: RelayClock;\n readonly operationId: string;\n readonly operations: OperationStoreAdapter;\n readonly observation: OperationObserverCapabilities;\n /**\n * Opens a session for this exact prepared operation and verified root signature.\n * Opening must not send. Return { submit(): Promise<{ userOperationHash }>, close() }.\n * Both opening and submit happen only after durable submission_attempted.\n */\n readonly submission: Readonly<{\n openSubmission(\n prepared: Readonly<PreparedUserOperation>,\n signature: `0x${string}`,\n ): Promise<unknown>;\n close(): Promise<void>;\n }>;\n}\n\n/**\n * Deployment execution capability, not a client/phone HTTP route. A terminal\n * approved decision is custody; the existing Operation journal owns execution.\n * Reload reconstructs exact bytes without quoting or preparing on chain. The\n * journal is read before KMS/submission access. An attempted or terminal hash\n * never opens another session, even after consent expiry. Observe reads only\n * chain evidence and retains uncertainty. Close delegates retryable resource\n * cleanup to the runner; the deployment owns relay store, pool and KMS lifetime.\n */\nexport interface OwnerPhoneRevocationExecutor {\n readonly operationId: string;\n readonly userOperationHash: `0x${string}`;\n start(timeoutMs: number): Promise<OperationStartResult>;\n observe(timeoutMs: number): Promise<OperationObserveResult>;\n close(): Promise<void>;\n}\nexport async function createOwnerPhoneRevocationExecutor(\n input: OwnerPhoneRevocationExecutorInput,\n): Promise<Readonly<OwnerPhoneRevocationExecutor>> {\n const { store, kms, clock, operationId, operations, observation, submission } = input;\n const { request, decision } = await withRelayTransaction(store, (transaction) =>\n readRevocationState(transaction, operationId),\n );\n if (decision?.outcome !== \"approved\" || decision.artifactRef === null)\n return relayFailure(\"relay_request_invalid\", \"revocation has no approved custody\");\n const artifactRef = decision.artifactRef;\n const restored = await restoreKernelPermissionRevocation({ preparation: request.signingRequest });\n const prepared = restored.prepared;\n const key = Object.freeze({\n grantId: prepared.grantId,\n chainId: prepared.chainId,\n kind: \"revocation\" as const,\n });\n const journal = new OperationStore(operations);\n const noEffect = async () => undefined;\n const runner = createOperationRunner({\n terminalBehavior: \"reuse_same_kind\",\n requestHash: null,\n store: journal,\n observer: createOperationObserver(observation),\n preparation: {\n prepare: async () => prepared,\n reserveOperation: noEffect,\n releaseOperationReservation: noEffect,\n authorizeOperation: async () => {\n if (relayNow(clock) > request.expiresAt)\n return relayFailure(\"relay_expired\", \"revocation consent expired before submission\");\n },\n abandonOperation: noEffect,\n confirmOperationPublished: noEffect,\n close: noEffect,\n },\n submission: {\n async openSubmission(snapshot: PreparedUserOperation) {\n const artifact = parseOwnerSigningArtifact(\n JSON.parse(await openArtifact(kms, artifactRef)),\n );\n const signature = await restored.complete(artifact);\n return submission.openSubmission(snapshot, signature);\n },\n close: () => submission.close(),\n },\n });\n function runInput(timeoutMs: number) {\n const now = Math.floor(relayNow(clock) / 1000);\n return {\n kind: \"revocation\",\n key,\n expectedUserOperationHash: prepared.userOperationHash,\n preparedAt: now,\n attemptedAt: now,\n submittedAt: now,\n observedAt: now,\n timeoutMs,\n };\n }\n return Object.freeze({\n operationId,\n userOperationHash: prepared.userOperationHash,\n async start(timeoutMs: number) {\n const existing = await journal.getExact(key, prepared.userOperationHash);\n if (existing && existing.value.state !== \"prepared\")\n return Object.freeze({ status: \"started\" as const, record: existing });\n if (relayNow(clock) > request.expiresAt)\n return relayFailure(\"relay_expired\", \"revocation consent expired before submission\");\n return runner.resumePreparedOperation(runInput(timeoutMs));\n },\n observe: (timeoutMs: number) => runner.observeOperation(runInput(timeoutMs)),\n close: () => runner.close(),\n });\n}\n"],"mappings":";;;;;;;;AAwDA,eAAsB,mCACpB,OACiD;CACjD,MAAM,EAAE,OAAO,KAAK,OAAO,aAAa,YAAY,aAAa,eAAe;CAChF,MAAM,EAAE,SAAS,aAAa,MAAM,qBAAqB,QAAQ,gBAC/D,oBAAoB,aAAa,WAAW,CAC9C;CACA,IAAI,UAAU,YAAY,cAAc,SAAS,gBAAgB,MAC/D,OAAO,aAAa,yBAAyB,oCAAoC;CACnF,MAAM,cAAc,SAAS;CAC7B,MAAM,WAAW,MAAM,kCAAkC,EAAE,aAAa,QAAQ,eAAe,CAAC;CAChG,MAAM,WAAW,SAAS;CAC1B,MAAM,MAAM,OAAO,OAAO;EACxB,SAAS,SAAS;EAClB,SAAS,SAAS;EAClB,MAAM;CACR,CAAC;CACD,MAAM,UAAU,IAAI,eAAe,UAAU;CAC7C,MAAM,WAAW,YAAY,KAAA;CAC7B,MAAM,SAAS,sBAAsB;EACnC,kBAAkB;EAClB,aAAa;EACb,OAAO;EACP,UAAU,wBAAwB,WAAW;EAC7C,aAAa;GACX,SAAS,YAAY;GACrB,kBAAkB;GAClB,6BAA6B;GAC7B,oBAAoB,YAAY;IAC9B,IAAI,SAAS,KAAK,IAAI,QAAQ,WAC5B,OAAO,aAAa,iBAAiB,8CAA8C;GACvF;GACA,kBAAkB;GAClB,2BAA2B;GAC3B,OAAO;EACT;EACA,YAAY;GACV,MAAM,eAAe,UAAiC;IACpD,MAAM,WAAW,0BACf,KAAK,MAAM,MAAM,aAAa,KAAK,WAAW,CAAC,CACjD;IACA,MAAM,YAAY,MAAM,SAAS,SAAS,QAAQ;IAClD,OAAO,WAAW,eAAe,UAAU,SAAS;GACtD;GACA,aAAa,WAAW,MAAM;EAChC;CACF,CAAC;CACD,SAAS,SAAS,WAAmB;EACnC,MAAM,MAAM,KAAK,MAAM,SAAS,KAAK,IAAI,GAAI;EAC7C,OAAO;GACL,MAAM;GACN;GACA,2BAA2B,SAAS;GACpC,YAAY;GACZ,aAAa;GACb,aAAa;GACb,YAAY;GACZ;EACF;CACF;CACA,OAAO,OAAO,OAAO;EACnB;EACA,mBAAmB,SAAS;EAC5B,MAAM,MAAM,WAAmB;GAC7B,MAAM,WAAW,MAAM,QAAQ,SAAS,KAAK,SAAS,iBAAiB;GACvE,IAAI,YAAY,SAAS,MAAM,UAAU,YACvC,OAAO,OAAO,OAAO;IAAE,QAAQ;IAAoB,QAAQ;GAAS,CAAC;GACvE,IAAI,SAAS,KAAK,IAAI,QAAQ,WAC5B,OAAO,aAAa,iBAAiB,8CAA8C;GACrF,OAAO,OAAO,wBAAwB,SAAS,SAAS,CAAC;EAC3D;EACA,UAAU,cAAsB,OAAO,iBAAiB,SAAS,SAAS,CAAC;EAC3E,aAAa,OAAO,MAAM;CAC5B,CAAC;AACH"}
@@ -0,0 +1,18 @@
1
+ //#region src/security/kms.d.ts
2
+ /**
3
+ * Deployment-owned encryption port.
4
+ *
5
+ * The relay stores only the opaque reference this port returns. It never sees a
6
+ * key, and plaintext never reaches the store.
7
+ *
8
+ * @author taek <leekt216@gmail.com>
9
+ */
10
+ interface RelayKms {
11
+ /** Resolves to an opaque bounded string reference for the ciphertext. */
12
+ encrypt(plaintext: string): Promise<unknown>;
13
+ /** Resolves to the plaintext for a reference this port previously returned. */
14
+ decrypt(ciphertextRef: string): Promise<unknown>;
15
+ }
16
+ //#endregion
17
+ export { RelayKms as t };
18
+ //# sourceMappingURL=kms-BWkywdJM.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kms-BWkywdJM.d.ts","names":[],"sources":["../src/security/kms.ts"],"mappings":";;;;;;;;;UASiB;;EAEf,QAAQ,oBAAoB;;EAE5B,QAAQ,wBAAwB"}
package/dist/native.d.ts CHANGED
@@ -1,30 +1,3 @@
1
- import { i as RelayClock, n as RelayCaller } from "./authentication-HUYWk5Y2.js";
2
- import { i as AuthorizationDecisionOutcome, t as RelayStore } from "./interface-WwvwWs8e.js";
3
- import { a as OwnerPhoneScopeProjection, i as OwnerPhoneRequestProjection, n as OAATH_NATIVE_PROJECTION_VERSION, o as ProjectOwnerPhoneRequestInput, r as OwnerPhonePushProjection, s as projectOwnerPhoneRequest, t as NATIVE_DISPLAY_PAYLOAD_LENGTH } from "./projection-g6k_CCUK.js";
4
- import { a as RelayKms, r as SubmittedAuthorizationDecision, t as AuthorizationDecisionCommand } from "./decision-dEe9n108.js";
5
- //#region src/native/decision.d.ts
6
- interface SubmitOwnerPhoneDecisionInput {
7
- readonly store: RelayStore;
8
- readonly clock: RelayClock;
9
- readonly kms: RelayKms;
10
- /** Authenticated `owner` caller; only the bound subject may decide. */
11
- readonly caller: RelayCaller;
12
- /** `operationId` from `projectOwnerPhoneRequest`. */
13
- readonly operationId: string;
14
- readonly command: AuthorizationDecisionCommand;
15
- readonly codeTtlMs: number;
16
- }
17
- interface OwnerPhoneDecision {
18
- readonly operationId: string;
19
- /** The stored outcome, which on a replay may differ from the command sent. */
20
- readonly outcome: AuthorizationDecisionOutcome;
21
- readonly decidedAt: number;
22
- /** `decided` performed the transition; `replayed` answered the stored one. */
23
- readonly settlement: "decided" | "replayed";
24
- /** One-time release material, on the deciding call only. */
25
- readonly release: SubmittedAuthorizationDecision | null;
26
- }
27
- declare function submitOwnerPhoneDecision(input: SubmitOwnerPhoneDecisionInput): Promise<OwnerPhoneDecision>;
28
- //#endregion
29
- export { NATIVE_DISPLAY_PAYLOAD_LENGTH, OAATH_NATIVE_PROJECTION_VERSION, type OwnerPhoneDecision, type OwnerPhonePushProjection, type OwnerPhoneRequestProjection, type OwnerPhoneScopeProjection, type ProjectOwnerPhoneRequestInput, type SubmitOwnerPhoneDecisionInput, projectOwnerPhoneRequest, submitOwnerPhoneDecision };
30
- //# sourceMappingURL=native.d.ts.map
1
+ import { a as ProjectOwnerPhoneRequestInput, c as OwnerPhoneRevocationDecision, d as OwnerPhonePermissionApprovals, f as PreparedOwnerPhonePermissionApproval, i as OwnerPhoneScopeProjection, l as OwnerPhoneRevocationScopeProjection, n as OwnerPhonePushProjection, o as projectOwnerPhonePermissionSigning, r as OwnerPhoneRequestProjection, s as projectOwnerPhoneRequest, t as OAATH_NATIVE_PROJECTION_VERSION, u as projectOwnerPhoneRevocation } from "./projection-B7IB9irs.js";
2
+ import { A as listOwnerPhoneRequests, B as createMemoryOwnerDeviceCredentialStore, C as RequestOwnerPhoneRevocationInput, D as submitOwnerPhoneRevocationDecision, E as requestOwnerPhoneRevocation, F as OAATH_OWNER_DEVICE_CREDENTIAL_VERSION, I as OwnerDeviceAuthentication, L as OwnerDeviceCredentialRecord, M as OwnerPhoneDecision, N as SubmitOwnerPhoneDecisionInput, O as OAATH_NATIVE_INBOX_VERSION, P as submitOwnerPhoneDecision, R as OwnerDeviceCredentialStore, S as OwnerPhoneRevocationStatus, T as fetchOwnerPhoneRevocation, V as createOwnerDeviceAuthentication, j as NATIVE_DISPLAY_PAYLOAD_LENGTH, k as OwnerPhoneInbox, w as fetchClientPhoneRevocation, z as OwnerDeviceReference } from "./service-J51bOVe6.js";
3
+ export { NATIVE_DISPLAY_PAYLOAD_LENGTH, OAATH_NATIVE_INBOX_VERSION, OAATH_NATIVE_PROJECTION_VERSION, OAATH_OWNER_DEVICE_CREDENTIAL_VERSION, type OwnerDeviceAuthentication, type OwnerDeviceCredentialRecord, type OwnerDeviceCredentialStore, type OwnerDeviceReference, type OwnerPhoneDecision, type OwnerPhoneInbox, type OwnerPhonePermissionApprovals, type OwnerPhonePushProjection, type OwnerPhoneRequestProjection, type OwnerPhoneRevocationDecision, type OwnerPhoneRevocationScopeProjection, type OwnerPhoneRevocationStatus, type OwnerPhoneScopeProjection, type PreparedOwnerPhonePermissionApproval, type ProjectOwnerPhoneRequestInput, type RequestOwnerPhoneRevocationInput, type SubmitOwnerPhoneDecisionInput, createMemoryOwnerDeviceCredentialStore, createOwnerDeviceAuthentication, fetchClientPhoneRevocation, fetchOwnerPhoneRevocation, listOwnerPhoneRequests, projectOwnerPhonePermissionSigning, projectOwnerPhoneRequest, projectOwnerPhoneRevocation, requestOwnerPhoneRevocation, submitOwnerPhoneDecision, submitOwnerPhoneRevocationDecision };
package/dist/native.js CHANGED
@@ -1,2 +1,115 @@
1
- import { i as submitOwnerPhoneDecision, n as OAATH_NATIVE_PROJECTION_VERSION, r as projectOwnerPhoneRequest, t as NATIVE_DISPLAY_PAYLOAD_LENGTH } from "./projection-dGIkE0rI.js";
2
- export { NATIVE_DISPLAY_PAYLOAD_LENGTH, OAATH_NATIVE_PROJECTION_VERSION, projectOwnerPhoneRequest, submitOwnerPhoneDecision };
1
+ import { b as relayFailure, c as canonicalIdentifier } from "./records-BQNDxbPg.js";
2
+ import { n as randomIdentifier, r as sha256Base64Url } from "./challenge-DmpGSogj.js";
3
+ import { a as submitOwnerPhoneRevocationDecision, c as projectOwnerPhonePermissionSigning, h as NATIVE_DISPLAY_PAYLOAD_LENGTH, i as requestOwnerPhoneRevocation, l as projectOwnerPhoneRequest, n as fetchOwnerPhoneRevocation, o as projectOwnerPhoneRevocation, s as OAATH_NATIVE_PROJECTION_VERSION, t as fetchClientPhoneRevocation } from "./service-Cf4f-9KP.js";
4
+ import { n as listOwnerPhoneRequests, r as submitOwnerPhoneDecision, t as OAATH_NATIVE_INBOX_VERSION } from "./inbox-B80EUVht.js";
5
+ import { captureRecord, exactCapturedRecord } from "@oaath/protocol";
6
+ //#region src/native/authentication.ts
7
+ const OAATH_OWNER_DEVICE_CREDENTIAL_VERSION = "oaath.owner-device-credential/v1";
8
+ const CREDENTIAL = /^[A-Za-z0-9_-]{43}$/u;
9
+ function deviceReference(device) {
10
+ return Object.freeze({
11
+ workspaceId: canonicalIdentifier(device.workspaceId, "workspaceId", "relay_request_invalid"),
12
+ ownerDeviceId: canonicalIdentifier(device.ownerDeviceId, "ownerDeviceId", "relay_request_invalid")
13
+ });
14
+ }
15
+ function captureCredential(value) {
16
+ const code = "relay_record_unreadable";
17
+ const fail = (message) => relayFailure(code, message);
18
+ const record = exactCapturedRecord(captureRecord(value, "owner device credential", /* @__PURE__ */ new WeakSet(), fail), [
19
+ "version",
20
+ "credentialHash",
21
+ "workspaceId",
22
+ "ownerDeviceId",
23
+ "subject",
24
+ "revoked"
25
+ ], "owner device credential", fail);
26
+ if (record.version !== "oaath.owner-device-credential/v1" || typeof record.credentialHash !== "string" || !CREDENTIAL.test(record.credentialHash) || typeof record.revoked !== "boolean") return fail("owner device credential is invalid");
27
+ return Object.freeze({
28
+ version: OAATH_OWNER_DEVICE_CREDENTIAL_VERSION,
29
+ credentialHash: record.credentialHash,
30
+ workspaceId: canonicalIdentifier(record.workspaceId, "workspaceId", code),
31
+ ownerDeviceId: canonicalIdentifier(record.ownerDeviceId, "ownerDeviceId", code),
32
+ subject: canonicalIdentifier(record.subject, "subject", code),
33
+ revoked: record.revoked
34
+ });
35
+ }
36
+ /**
37
+ * One active -> revoked transition per credential hash, separate from account
38
+ * authority. Directory identity is checked at issuance and authentication;
39
+ * an issued credential can never be retargeted to a changed owner subject.
40
+ * No cache, operation lane, retries or owned connections. Recreated instances
41
+ * read the same stores. The deployment owns pool shutdown and pairing delivery.
42
+ * Explicit later issuance creates a new credential; it cannot revive an old one.
43
+ */
44
+ function createOwnerDeviceAuthentication(options) {
45
+ const { store, directory } = options;
46
+ async function enrolled(reference) {
47
+ return (await directory.read())?.directory.ownerDevices.find((device) => device.workspaceId === reference.workspaceId && device.ownerDeviceId === reference.ownerDeviceId);
48
+ }
49
+ return Object.freeze({
50
+ async issue(input) {
51
+ const reference = deviceReference(input);
52
+ const device = await enrolled(reference);
53
+ if (!device) return relayFailure("relay_not_found", "owner device is not enrolled");
54
+ const subject = canonicalIdentifier(device.subject, "owner subject", "relay_request_invalid");
55
+ const credential = randomIdentifier();
56
+ const record = Object.freeze({
57
+ version: OAATH_OWNER_DEVICE_CREDENTIAL_VERSION,
58
+ credentialHash: await sha256Base64Url(credential),
59
+ ...reference,
60
+ subject,
61
+ revoked: false
62
+ });
63
+ if (await store.insert(record) !== true) return relayFailure("relay_internal", "owner device credential was not issued");
64
+ return credential;
65
+ },
66
+ async authenticate(request) {
67
+ const header = request.headers.get("authorization");
68
+ if (!header?.startsWith("Bearer ")) return null;
69
+ const credential = header.slice(7);
70
+ if (!CREDENTIAL.test(credential)) return null;
71
+ const hash = await sha256Base64Url(credential);
72
+ const stored = await store.read(hash);
73
+ if (stored === null) return null;
74
+ const record = captureCredential(stored);
75
+ if (record.credentialHash !== hash) return relayFailure("relay_record_unreadable", "owner device credential binding differs");
76
+ if (record.revoked) return null;
77
+ const device = await enrolled(record);
78
+ if (!device || device.subject !== record.subject) return null;
79
+ return Object.freeze({
80
+ role: "owner",
81
+ clientId: record.ownerDeviceId,
82
+ subject: record.subject,
83
+ redirectUris: Object.freeze([]),
84
+ organizationAudience: null
85
+ });
86
+ },
87
+ revoke(device) {
88
+ return store.revoke(deviceReference(device));
89
+ }
90
+ });
91
+ }
92
+ /** Ephemeral deployment adapter; the authentication service owns no local cache. */
93
+ function createMemoryOwnerDeviceCredentialStore() {
94
+ const records = /* @__PURE__ */ new Map();
95
+ return {
96
+ async read(hash) {
97
+ return records.get(hash) ?? null;
98
+ },
99
+ async insert(record) {
100
+ if (records.has(record.credentialHash)) return false;
101
+ records.set(record.credentialHash, Object.freeze({ ...record }));
102
+ return true;
103
+ },
104
+ async revoke(device) {
105
+ for (const [hash, record] of records) if (record.workspaceId === device.workspaceId && record.ownerDeviceId === device.ownerDeviceId && !record.revoked) records.set(hash, Object.freeze({
106
+ ...record,
107
+ revoked: true
108
+ }));
109
+ }
110
+ };
111
+ }
112
+ //#endregion
113
+ export { NATIVE_DISPLAY_PAYLOAD_LENGTH, OAATH_NATIVE_INBOX_VERSION, OAATH_NATIVE_PROJECTION_VERSION, OAATH_OWNER_DEVICE_CREDENTIAL_VERSION, createMemoryOwnerDeviceCredentialStore, createOwnerDeviceAuthentication, fetchClientPhoneRevocation, fetchOwnerPhoneRevocation, listOwnerPhoneRequests, projectOwnerPhonePermissionSigning, projectOwnerPhoneRequest, projectOwnerPhoneRevocation, requestOwnerPhoneRevocation, submitOwnerPhoneDecision, submitOwnerPhoneRevocationDecision };
114
+
115
+ //# sourceMappingURL=native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native.js","names":[],"sources":["../src/native/authentication.ts"],"sourcesContent":["import { captureRecord, exactCapturedRecord } from \"@oaath/protocol\";\nimport { randomIdentifier, sha256Base64Url } from \"../authorization/challenge.js\";\nimport type { DirectoryOwnerDevice } from \"../directory/records.js\";\nimport type { ServiceDirectory } from \"../directory/service.js\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport type { RelayAuthentication, RelayCaller } from \"../security/authentication.js\";\nimport { canonicalIdentifier } from \"../store/records.js\";\n\nexport const OAATH_OWNER_DEVICE_CREDENTIAL_VERSION = \"oaath.owner-device-credential/v1\" as const;\nexport type OwnerDeviceReference = Pick<DirectoryOwnerDevice, \"workspaceId\" | \"ownerDeviceId\">;\n\n/** Authentication binding captured at issuance; contains no bearer or owner key. */\nexport interface OwnerDeviceCredentialRecord extends DirectoryOwnerDevice {\n readonly version: typeof OAATH_OWNER_DEVICE_CREDENTIAL_VERSION;\n readonly credentialHash: string;\n readonly revoked: boolean;\n}\n\nexport interface OwnerDeviceCredentialStore {\n /** Null means absent. Unreadable or unavailable storage must throw. */\n read(credentialHash: string): Promise<unknown>;\n /** Insert an immutable binding once. Never replace a revoked record. */\n insert(record: Readonly<OwnerDeviceCredentialRecord>): Promise<boolean>;\n /** Atomically revoke all credentials already issued for this device. Idempotent. */\n revoke(device: Readonly<OwnerDeviceReference>): Promise<void>;\n}\n\nexport interface OwnerDeviceAuthentication extends RelayAuthentication {\n authenticate(request: Request): Promise<Readonly<RelayCaller> | null>;\n /**\n * Deployment administration only, after authenticated pairing/enrollment.\n * Returns a fresh 32-byte base64url bearer after its hash is stored. A lost\n * write response throws; nothing is returned and no write is retried.\n */\n issue(device: Readonly<OwnerDeviceReference>): Promise<string>;\n /** Revokes relay access only. Does not uninstall permissions or delete keys/accounts. */\n revoke(device: Readonly<OwnerDeviceReference>): Promise<void>;\n}\n\nconst CREDENTIAL = /^[A-Za-z0-9_-]{43}$/u;\n\nfunction deviceReference(device: Readonly<OwnerDeviceReference>): OwnerDeviceReference {\n return Object.freeze({\n workspaceId: canonicalIdentifier(device.workspaceId, \"workspaceId\", \"relay_request_invalid\"),\n ownerDeviceId: canonicalIdentifier(\n device.ownerDeviceId,\n \"ownerDeviceId\",\n \"relay_request_invalid\",\n ),\n });\n}\n\nfunction captureCredential(value: unknown): Readonly<OwnerDeviceCredentialRecord> {\n const code = \"relay_record_unreadable\";\n const fail = (message: string): never => relayFailure(code, message);\n const record = exactCapturedRecord(\n captureRecord(value, \"owner device credential\", new WeakSet(), fail),\n [\"version\", \"credentialHash\", \"workspaceId\", \"ownerDeviceId\", \"subject\", \"revoked\"],\n \"owner device credential\",\n fail,\n );\n if (\n record.version !== OAATH_OWNER_DEVICE_CREDENTIAL_VERSION ||\n typeof record.credentialHash !== \"string\" ||\n !CREDENTIAL.test(record.credentialHash) ||\n typeof record.revoked !== \"boolean\"\n )\n return fail(\"owner device credential is invalid\");\n return Object.freeze({\n version: OAATH_OWNER_DEVICE_CREDENTIAL_VERSION,\n credentialHash: record.credentialHash,\n workspaceId: canonicalIdentifier(record.workspaceId, \"workspaceId\", code),\n ownerDeviceId: canonicalIdentifier(record.ownerDeviceId, \"ownerDeviceId\", code),\n subject: canonicalIdentifier(record.subject, \"subject\", code),\n revoked: record.revoked,\n });\n}\n\n/**\n * One active -> revoked transition per credential hash, separate from account\n * authority. Directory identity is checked at issuance and authentication;\n * an issued credential can never be retargeted to a changed owner subject.\n * No cache, operation lane, retries or owned connections. Recreated instances\n * read the same stores. The deployment owns pool shutdown and pairing delivery.\n * Explicit later issuance creates a new credential; it cannot revive an old one.\n */\nexport function createOwnerDeviceAuthentication(options: {\n readonly store: OwnerDeviceCredentialStore;\n readonly directory: Pick<ServiceDirectory, \"read\">;\n}): Readonly<OwnerDeviceAuthentication> {\n const { store, directory } = options;\n async function enrolled(reference: OwnerDeviceReference) {\n const snapshot = await directory.read();\n return snapshot?.directory.ownerDevices.find(\n (device) =>\n device.workspaceId === reference.workspaceId &&\n device.ownerDeviceId === reference.ownerDeviceId,\n );\n }\n return Object.freeze({\n async issue(input: Readonly<OwnerDeviceReference>): Promise<string> {\n const reference = deviceReference(input);\n const device = await enrolled(reference);\n if (!device) return relayFailure(\"relay_not_found\", \"owner device is not enrolled\");\n const subject = canonicalIdentifier(device.subject, \"owner subject\", \"relay_request_invalid\");\n const credential = randomIdentifier();\n const record = Object.freeze({\n version: OAATH_OWNER_DEVICE_CREDENTIAL_VERSION,\n credentialHash: await sha256Base64Url(credential),\n ...reference,\n subject,\n revoked: false,\n });\n if ((await store.insert(record)) !== true)\n return relayFailure(\"relay_internal\", \"owner device credential was not issued\");\n return credential;\n },\n async authenticate(request: Request): Promise<Readonly<RelayCaller> | null> {\n const header = request.headers.get(\"authorization\");\n if (!header?.startsWith(\"Bearer \")) return null;\n const credential = header.slice(7);\n if (!CREDENTIAL.test(credential)) return null;\n const hash = await sha256Base64Url(credential);\n const stored = await store.read(hash);\n if (stored === null) return null;\n const record = captureCredential(stored);\n if (record.credentialHash !== hash)\n return relayFailure(\"relay_record_unreadable\", \"owner device credential binding differs\");\n if (record.revoked) return null;\n const device = await enrolled(record);\n if (!device || device.subject !== record.subject) return null;\n return Object.freeze({\n role: \"owner\",\n clientId: record.ownerDeviceId,\n subject: record.subject,\n redirectUris: Object.freeze([]),\n organizationAudience: null,\n });\n },\n revoke(device: Readonly<OwnerDeviceReference>): Promise<void> {\n return store.revoke(deviceReference(device));\n },\n });\n}\n\n/** Ephemeral deployment adapter; the authentication service owns no local cache. */\nexport function createMemoryOwnerDeviceCredentialStore(): OwnerDeviceCredentialStore {\n const records = new Map<string, Readonly<OwnerDeviceCredentialRecord>>();\n return {\n async read(hash) {\n return records.get(hash) ?? null;\n },\n async insert(record) {\n if (records.has(record.credentialHash)) return false;\n records.set(record.credentialHash, Object.freeze({ ...record }));\n return true;\n },\n async revoke(device) {\n for (const [hash, record] of records) {\n if (\n record.workspaceId === device.workspaceId &&\n record.ownerDeviceId === device.ownerDeviceId &&\n !record.revoked\n )\n records.set(hash, Object.freeze({ ...record, revoked: true }));\n }\n },\n };\n}\n"],"mappings":"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"}