@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.
- package/README.md +239 -5
- package/dist/apns.d.ts +2 -2
- package/dist/apns.js +2 -2
- package/dist/{challenge-AGTmUfj2.js → challenge-DmpGSogj.js} +2 -2
- package/dist/{challenge-AGTmUfj2.js.map → challenge-DmpGSogj.js.map} +1 -1
- package/dist/inbox-B80EUVht.js +249 -0
- package/dist/inbox-B80EUVht.js.map +1 -0
- package/dist/index.d.ts +9 -255
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +631 -48
- package/dist/index.js.map +1 -1
- package/dist/interface-DHpwP0wr.d.ts +245 -0
- package/dist/interface-DHpwP0wr.d.ts.map +1 -0
- package/dist/kernel.d.ts +42 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +80 -0
- package/dist/kernel.js.map +1 -0
- package/dist/kms-BWkywdJM.d.ts +18 -0
- package/dist/kms-BWkywdJM.d.ts.map +1 -0
- package/dist/native.d.ts +3 -30
- package/dist/native.js +115 -2
- package/dist/native.js.map +1 -0
- package/dist/postgres.d.ts +38 -3
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +301 -14
- package/dist/postgres.js.map +1 -1
- package/dist/projection-B7IB9irs.d.ts +228 -0
- package/dist/projection-B7IB9irs.d.ts.map +1 -0
- package/dist/{records-Bgr-9PUh.js → records-BQNDxbPg.js} +32 -8
- package/dist/records-BQNDxbPg.js.map +1 -0
- package/dist/records-BUvqmp0D.js +70 -0
- package/dist/records-BUvqmp0D.js.map +1 -0
- package/dist/service-Cf4f-9KP.js +706 -0
- package/dist/service-Cf4f-9KP.js.map +1 -0
- package/dist/service-J51bOVe6.d.ts +483 -0
- package/dist/service-J51bOVe6.d.ts.map +1 -0
- package/package.json +39 -8
- package/LICENSE +0 -201
- package/dist/authentication-HUYWk5Y2.d.ts +0 -46
- package/dist/authentication-HUYWk5Y2.d.ts.map +0 -1
- package/dist/decision-dEe9n108.d.ts +0 -53
- package/dist/decision-dEe9n108.d.ts.map +0 -1
- package/dist/interface-WwvwWs8e.d.ts +0 -114
- package/dist/interface-WwvwWs8e.d.ts.map +0 -1
- package/dist/native.d.ts.map +0 -1
- package/dist/projection-dGIkE0rI.js +0 -516
- package/dist/projection-dGIkE0rI.js.map +0 -1
- package/dist/projection-g6k_CCUK.d.ts +0 -136
- package/dist/projection-g6k_CCUK.d.ts.map +0 -1
- 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"}
|
package/dist/kernel.d.ts
ADDED
|
@@ -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
|
|
2
|
-
import {
|
|
3
|
-
|
|
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 {
|
|
2
|
-
|
|
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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"}
|