@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.
- 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,228 @@
|
|
|
1
|
+
import { b as RelayClock, t as RelayStore } from "./interface-DHpwP0wr.js";
|
|
2
|
+
import { KernelReplayableInstallOwnerSigningRequest, KernelRevocationSigningRequest, OwnerSigningArtifact, OwnerSigningRequest, PermissionRequest, WorkspaceAccountContext } from "@oaath/protocol";
|
|
3
|
+
//#region src/security/authentication.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Deployment-owned client/device authentication port.
|
|
6
|
+
*
|
|
7
|
+
* The relay never parses credentials. It hands the request to the deployment,
|
|
8
|
+
* which returns the authenticated bindings or nothing. `clientId` and `subject`
|
|
9
|
+
* come only from here: wire input may never declare either.
|
|
10
|
+
*
|
|
11
|
+
* @author taek <leekt216@gmail.com>
|
|
12
|
+
*/
|
|
13
|
+
/** `client` is the requesting application; `owner` is the approving account owner. */
|
|
14
|
+
type RelayCallerRole = "client" | "owner";
|
|
15
|
+
interface RelayCaller {
|
|
16
|
+
readonly role: RelayCallerRole;
|
|
17
|
+
readonly clientId: string;
|
|
18
|
+
/** Pairwise user/device subject. */
|
|
19
|
+
readonly subject: string;
|
|
20
|
+
/**
|
|
21
|
+
* Redirect URIs the deployment registered for this client. An authorization
|
|
22
|
+
* code may only be requested for one of them. `owner` callers pass an empty
|
|
23
|
+
* list; they never receive a code at a redirect URI.
|
|
24
|
+
*/
|
|
25
|
+
readonly redirectUris: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* The application organization/audience the deployment binds this caller to,
|
|
28
|
+
* or null when the deployment declares none. Like `clientId` and `subject`
|
|
29
|
+
* it comes only from the authentication port; wire input may never declare
|
|
30
|
+
* it. It is captured onto each authorization request, and Grant reference
|
|
31
|
+
* verification later denies any assertion that does not match the captured
|
|
32
|
+
* value — a deployment without audiences therefore never verifies one.
|
|
33
|
+
*/
|
|
34
|
+
readonly organizationAudience: string | null;
|
|
35
|
+
}
|
|
36
|
+
interface RelayAuthentication {
|
|
37
|
+
/**
|
|
38
|
+
* Resolves to a `RelayCaller`-shaped value, or `null`/`undefined` when the
|
|
39
|
+
* caller is not authenticated. May throw `OaathRelayError` to choose a code.
|
|
40
|
+
*/
|
|
41
|
+
authenticate(request: Request): Promise<unknown>;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/native/permission-approval.d.ts
|
|
45
|
+
interface PreparedOwnerPhonePermissionApproval {
|
|
46
|
+
readonly signingRequest: Readonly<KernelReplayableInstallOwnerSigningRequest>;
|
|
47
|
+
/** Verifies and serializes the SDK's decision plus install approval; submits nothing. */
|
|
48
|
+
complete(artifact: Readonly<OwnerSigningArtifact>, decidedAt: number): Promise<string>;
|
|
49
|
+
}
|
|
50
|
+
interface OwnerPhonePermissionApprovals {
|
|
51
|
+
/** Recreate from the stored request and the deployment-owned install nonce. */
|
|
52
|
+
prepare(request: Readonly<PermissionRequest>): Promise<Readonly<PreparedOwnerPhonePermissionApproval>>;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/native/revocation.d.ts
|
|
56
|
+
interface OwnerPhoneRevocationScopeProjection {
|
|
57
|
+
readonly kind: "kernel-revocation";
|
|
58
|
+
readonly decision: "approve-or-reject";
|
|
59
|
+
/** Authenticated service commitment; the phone independently derives the operation digest. */
|
|
60
|
+
readonly requestHash: `0x${string}`;
|
|
61
|
+
readonly permission: OwnerPhonePermissionScopeProjection;
|
|
62
|
+
readonly install: Readonly<{
|
|
63
|
+
kind: "owner-signing-request";
|
|
64
|
+
decision: "approve-or-reject";
|
|
65
|
+
requestHash: `0x${string}`;
|
|
66
|
+
request: KernelRevocationSigningRequest["install"];
|
|
67
|
+
}>;
|
|
68
|
+
readonly effect: KernelRevocationSigningRequest["effect"];
|
|
69
|
+
readonly chainId: number;
|
|
70
|
+
readonly entryPoint: `0x${string}`;
|
|
71
|
+
readonly operation: KernelRevocationSigningRequest["operation"];
|
|
72
|
+
readonly expectedDigest: `0x${string}`;
|
|
73
|
+
}
|
|
74
|
+
/** A terminal owner decision acknowledges custody; it releases no OAuth code or signing artifact. */
|
|
75
|
+
interface OwnerPhoneRevocationDecision {
|
|
76
|
+
readonly version: "oaath.native-revocation-decision/v1";
|
|
77
|
+
readonly operationId: string;
|
|
78
|
+
readonly outcome: "approved" | "rejected";
|
|
79
|
+
readonly decidedAt: number;
|
|
80
|
+
readonly settlement: "decided" | "replayed";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Projects a stored request for its authenticated owner. The calling queue owns
|
|
84
|
+
* admission, current ownership, expiry and terminal-decision checks; this helper
|
|
85
|
+
* owns only consent bytes. It performs no signing, submission or observation.
|
|
86
|
+
*/
|
|
87
|
+
declare function projectOwnerPhoneRevocation(input: {
|
|
88
|
+
readonly operationId: string;
|
|
89
|
+
readonly ownerSubject: string;
|
|
90
|
+
readonly expiresAt: number;
|
|
91
|
+
readonly request: unknown;
|
|
92
|
+
}): Promise<OwnerPhoneRequestProjection>;
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/native/projection.d.ts
|
|
95
|
+
/** Versioned consent envelope; the Swift decoder pins this exact value. */
|
|
96
|
+
declare const OAATH_NATIVE_PROJECTION_VERSION: "oaath.native-projection/v7";
|
|
97
|
+
/** One credential identity as the owner reviews it: public material only. */
|
|
98
|
+
type OwnerPhoneCredentialProjection = Readonly<{
|
|
99
|
+
kind: "ecdsa";
|
|
100
|
+
address: string;
|
|
101
|
+
}> | Readonly<{
|
|
102
|
+
kind: "p256";
|
|
103
|
+
publicKey: string;
|
|
104
|
+
}> | Readonly<{
|
|
105
|
+
kind: "webauthn";
|
|
106
|
+
publicKey: string;
|
|
107
|
+
authenticatorIdHash: string;
|
|
108
|
+
}>;
|
|
109
|
+
/**
|
|
110
|
+
* The requested scope as the phone renders it. When the stored scope parses as
|
|
111
|
+
* an `@oaath/protocol` permission request, every fact that determines who
|
|
112
|
+
* receives authority, over which account, and under what limits is projected
|
|
113
|
+
* structurally. A valid owner-signing request is also projected in full with
|
|
114
|
+
* its protocol-owned hash; only exact Kernel replayable-install P-256 signing
|
|
115
|
+
* is approvable. Anything else is returned as explicitly labeled raw text.
|
|
116
|
+
*/
|
|
117
|
+
type OwnerPhonePermissionScopeProjection = Readonly<{
|
|
118
|
+
kind: "permission-request";
|
|
119
|
+
decision: "approve-or-reject";
|
|
120
|
+
/** Explicit request context, independent of later account selection. */
|
|
121
|
+
context: Readonly<WorkspaceAccountContext>;
|
|
122
|
+
/** The application identity the signed request binds, verbatim. */
|
|
123
|
+
application: Readonly<{
|
|
124
|
+
applicationId: string;
|
|
125
|
+
clientId: string;
|
|
126
|
+
origin: string;
|
|
127
|
+
/** Opaque device identity, fingerprinted: raw ids mean nothing to an owner. */
|
|
128
|
+
deviceFingerprint: string;
|
|
129
|
+
}>;
|
|
130
|
+
/** The logical account this authority acts for. */
|
|
131
|
+
account: Readonly<{
|
|
132
|
+
accountIndex: string;
|
|
133
|
+
kernelVersion: string;
|
|
134
|
+
factoryRoute: string;
|
|
135
|
+
entryPointVersion: string;
|
|
136
|
+
ownerCredential: OwnerPhoneCredentialProjection;
|
|
137
|
+
}>;
|
|
138
|
+
/** The session credential that receives the scoped authority. */
|
|
139
|
+
operatorCredential: OwnerPhoneCredentialProjection;
|
|
140
|
+
/**
|
|
141
|
+
* Where the session key lives: null for frontend custody (the page's
|
|
142
|
+
* own non-extractable key), or the remote trust model the owner is
|
|
143
|
+
* asked to approve — the request hash binds it, so approving this
|
|
144
|
+
* display approves exactly this custody.
|
|
145
|
+
*/
|
|
146
|
+
sessionSigner: Readonly<{
|
|
147
|
+
mode: "application_backend" | "oaath_hosted";
|
|
148
|
+
providerId: string;
|
|
149
|
+
}> | null;
|
|
150
|
+
chainScope: "all";
|
|
151
|
+
calls: readonly Readonly<{
|
|
152
|
+
target: string;
|
|
153
|
+
selector: string;
|
|
154
|
+
valueLimit: string;
|
|
155
|
+
argumentEquals: readonly Readonly<{
|
|
156
|
+
index: number;
|
|
157
|
+
value: string;
|
|
158
|
+
}>[];
|
|
159
|
+
}>[];
|
|
160
|
+
requestedAt: number;
|
|
161
|
+
/** The permission request's own expiry, as the requesting client stated it. */
|
|
162
|
+
expiresAt: number;
|
|
163
|
+
/** The policy's on-chain validity window; inclusive, in Unix seconds. */
|
|
164
|
+
policyValidAfter: number;
|
|
165
|
+
policyValidUntil: number | null;
|
|
166
|
+
perChainOperationLimit: number;
|
|
167
|
+
/** Window after which the count refills, or null for a lifetime cap. */
|
|
168
|
+
perChainOperationIntervalSeconds: number | null;
|
|
169
|
+
}>;
|
|
170
|
+
type OwnerPhoneScopeProjection = OwnerPhonePermissionScopeProjection | OwnerPhoneRevocationScopeProjection | Readonly<{
|
|
171
|
+
kind: "owner-signing-request";
|
|
172
|
+
decision: "approve-or-reject";
|
|
173
|
+
/** Canonical hash binding every captured request fact. */
|
|
174
|
+
requestHash: `0x${string}`;
|
|
175
|
+
/** Exact Kernel request independently refined by the protocol owner. */
|
|
176
|
+
request: Readonly<KernelReplayableInstallOwnerSigningRequest>;
|
|
177
|
+
}> | Readonly<{
|
|
178
|
+
kind: "owner-signing-request";
|
|
179
|
+
decision: "reject-only";
|
|
180
|
+
/** Canonical hash binding every captured request fact. */
|
|
181
|
+
requestHash: `0x${string}`;
|
|
182
|
+
/** Full immutable protocol request for independent device review. */
|
|
183
|
+
request: Readonly<OwnerSigningRequest>;
|
|
184
|
+
}> | Readonly<{
|
|
185
|
+
kind: "raw";
|
|
186
|
+
decision: "reject-only";
|
|
187
|
+
text: string;
|
|
188
|
+
}>;
|
|
189
|
+
interface OwnerPhoneRequestProjection {
|
|
190
|
+
readonly version: typeof OAATH_NATIVE_PROJECTION_VERSION;
|
|
191
|
+
/**
|
|
192
|
+
* Stable across every projection of the same request, and the key the
|
|
193
|
+
* approve/reject saga is keyed by. It is the relay's own opaque request
|
|
194
|
+
* identifier: one authoritative id, no second mapping table to lose.
|
|
195
|
+
*/
|
|
196
|
+
readonly operationId: string;
|
|
197
|
+
/** Bounded opaque match code, the only variable an APNs payload may carry. */
|
|
198
|
+
readonly displayPayload: string;
|
|
199
|
+
/** The stored request's expiry, in epoch milliseconds. */
|
|
200
|
+
readonly expiresAt: number;
|
|
201
|
+
/** The requesting client, exactly as the stored request binds it. */
|
|
202
|
+
readonly client: Readonly<{
|
|
203
|
+
clientId: string;
|
|
204
|
+
redirectUri: string | null;
|
|
205
|
+
}>;
|
|
206
|
+
readonly scope: OwnerPhoneScopeProjection;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The opaque subset of the projection that may transit Apple inside an APNs
|
|
210
|
+
* payload. The sender exact-captures precisely these three fields, so consent
|
|
211
|
+
* detail can never ride a notification.
|
|
212
|
+
*/
|
|
213
|
+
type OwnerPhonePushProjection = Pick<OwnerPhoneRequestProjection, "operationId" | "displayPayload" | "expiresAt">;
|
|
214
|
+
interface ProjectOwnerPhoneRequestInput {
|
|
215
|
+
readonly store: RelayStore;
|
|
216
|
+
readonly clock: RelayClock;
|
|
217
|
+
/** Authenticated `owner` caller; only the bound subject may be projected. */
|
|
218
|
+
readonly caller: RelayCaller;
|
|
219
|
+
readonly requestId: string;
|
|
220
|
+
}
|
|
221
|
+
declare function projectOwnerPhoneRequest(input: ProjectOwnerPhoneRequestInput): Promise<OwnerPhoneRequestProjection>;
|
|
222
|
+
/** The signing packet for the same consent, using the existing projection wire shape. */
|
|
223
|
+
declare function projectOwnerPhonePermissionSigning(input: ProjectOwnerPhoneRequestInput & {
|
|
224
|
+
readonly permissionApprovals?: OwnerPhonePermissionApprovals;
|
|
225
|
+
}): Promise<OwnerPhoneRequestProjection>;
|
|
226
|
+
//#endregion
|
|
227
|
+
export { ProjectOwnerPhoneRequestInput as a, OwnerPhoneRevocationDecision as c, OwnerPhonePermissionApprovals as d, PreparedOwnerPhonePermissionApproval as f, RelayCallerRole as h, OwnerPhoneScopeProjection as i, OwnerPhoneRevocationScopeProjection as l, RelayCaller as m, OwnerPhonePushProjection as n, projectOwnerPhonePermissionSigning as o, RelayAuthentication as p, OwnerPhoneRequestProjection as r, projectOwnerPhoneRequest as s, OAATH_NATIVE_PROJECTION_VERSION as t, projectOwnerPhoneRevocation as u };
|
|
228
|
+
//# sourceMappingURL=projection-B7IB9irs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection-B7IB9irs.d.ts","names":[],"sources":["../src/security/authentication.ts","../src/native/permission-approval.ts","../src/native/revocation.ts","../src/native/projection.ts"],"mappings":";;;;;;;;;;;;;KAoBY;UAIK;WACN,MAAM;WACN;;WAEA;;;;;;WAMA;;;;;;;;;WASA;;UAGM;;;;;EAKf,aAAa,SAAS,UAAU;;;;UCtCjB;WACN,gBAAgB,SAAS;;EAElC,SAAS,UAAU,SAAS,uBAAuB,oBAAoB;;UAGxD;;EAEf,QACE,SAAS,SAAS,qBACjB,QAAQ,SAAS;;;;UCPL;WACN;WACA;;WAEA;WACA,YAAY;WACZ,SAAS;IAChB;IACA;IACA;IACA,SAAS;;WAEF,QAAQ;WACR;WACA;WACA,WAAW;WACX;;;UAIM;WACN;WACA;WACA;WACA;WACA;;;;;;;iBAQW,4BAA4B;WACvC;WACA;WACA;WACA;IACP,QAAQ;;;;cCIC;;KAUD,iCACR;EAAW;EAAe;KAC1B;EAAW;EAAc;KACzB;EAAW;EAAkB;EAAmB;;;;;;;;;;KAUxC,sCAAsC;EAChD;EACA;;EAEA,SAAS,SAAS;;EAElB,aAAa;IACX;IACA;IACA;;IAEA;;;EAGF,SAAS;IACP;IACA;IACA;IACA;IACA,iBAAiB;;;EAGnB,oBAAoB;;;;;;;EAOpB,eAAe;IACb;IACA;;EAEF;EACA,gBAAgB;IACd;IACA;IACA;IACA,yBAAyB;MAAW;MAAe;;;EAErD;;EAEA;;EAEA;EACA;EACA;;EAEA;;KAGU,4BACR,sCACA,sCACA;EACE;EACA;;EAEA;;EAEA,SAAS,SAAS;KAEpB;EACE;EACA;;EAEA;;EAEA,SAAS,SAAS;KAEpB;EAAW;EAAa;EAAyB;;UAEpC;WACN,gBAAgB;;;;;;WAMhB;;WAEA;;WAEA;;WAEA,QAAQ;IAAW;IAAkB;;WACrC,OAAO;;;;;;;KAQN,2BAA2B,KACrC;UAIe;WACN,OAAO;WACP,OAAO;;WAEP,QAAQ;WACR;;iBA+JW,yBACpB,OAAO,gCACN,QAAQ;;iBAUW,mCACpB,OAAO;WACI,sBAAsB;IAEhC,QAAQ"}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import { exactRecord } from "@oaath/protocol";
|
|
1
|
+
import { captureBundlerRejection, captureValidationGasDiagnostic, exactRecord } from "@oaath/protocol";
|
|
2
2
|
//#region src/relay/errors.ts
|
|
3
3
|
/**
|
|
4
|
+
* Closed relay failure codes and their stable HTTP projection.
|
|
5
|
+
*
|
|
6
|
+
* Machine decisions and wire responses use these codes only. `Error.message`
|
|
7
|
+
* exists for local diagnostics and never reaches a response body.
|
|
8
|
+
*
|
|
9
|
+
* @author taek <leekt216@gmail.com>
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
4
12
|
* Status projection. `relay_state_ambiguous` is deliberately 500 and not 503,
|
|
5
13
|
* because 503 invites the retry that an ambiguous commit forbids.
|
|
6
14
|
*/
|
|
@@ -27,15 +35,19 @@ const RELAY_ERROR_STATUS = Object.freeze({
|
|
|
27
35
|
relay_internal: 500
|
|
28
36
|
});
|
|
29
37
|
var OaathRelayError = class extends Error {
|
|
38
|
+
bundlerRejection;
|
|
30
39
|
code;
|
|
31
|
-
|
|
40
|
+
diagnostic;
|
|
41
|
+
constructor(code, message, diagnostic = null, bundlerRejection = null) {
|
|
32
42
|
super(message);
|
|
33
43
|
this.name = "OaathRelayError";
|
|
34
44
|
this.code = code;
|
|
45
|
+
this.diagnostic = captureValidationGasDiagnostic(diagnostic);
|
|
46
|
+
this.bundlerRejection = code === "relay_chain_unavailable" ? captureBundlerRejection(bundlerRejection) : null;
|
|
35
47
|
}
|
|
36
48
|
};
|
|
37
|
-
function relayFailure(code, message) {
|
|
38
|
-
throw new OaathRelayError(code, message);
|
|
49
|
+
function relayFailure(code, message, diagnostic = null, bundlerRejection = null) {
|
|
50
|
+
throw new OaathRelayError(code, message, diagnostic, bundlerRejection);
|
|
39
51
|
}
|
|
40
52
|
/** Any non-relay throw is an unreadable internal failure, never caller-visible detail. */
|
|
41
53
|
function relayErrorCode(error) {
|
|
@@ -45,8 +57,14 @@ const RESPONSE_HEADERS = Object.freeze({
|
|
|
45
57
|
"content-type": "application/json; charset=utf-8",
|
|
46
58
|
"cache-control": "no-store"
|
|
47
59
|
});
|
|
48
|
-
function relayErrorResponse(code) {
|
|
49
|
-
|
|
60
|
+
function relayErrorResponse(code, diagnostic = null, bundlerRejection = null) {
|
|
61
|
+
const captured = code === "relay_chain_unavailable" ? captureValidationGasDiagnostic(diagnostic) : null;
|
|
62
|
+
const rejection = code === "relay_chain_unavailable" ? captureBundlerRejection(bundlerRejection) : null;
|
|
63
|
+
return jsonResponse(RELAY_ERROR_STATUS[code], { error: {
|
|
64
|
+
code,
|
|
65
|
+
...captured === null ? {} : { diagnostic: captured },
|
|
66
|
+
...rejection === null ? {} : { bundlerRejection: rejection }
|
|
67
|
+
} });
|
|
50
68
|
}
|
|
51
69
|
function jsonResponse(status, body) {
|
|
52
70
|
return new Response(JSON.stringify(body), {
|
|
@@ -72,7 +90,7 @@ function jsonResponse(status, body) {
|
|
|
72
90
|
*
|
|
73
91
|
* @author taek <leekt216@gmail.com>
|
|
74
92
|
*/
|
|
75
|
-
const OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION = "oaath.authorization-request-record/
|
|
93
|
+
const OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION = "oaath.authorization-request-record/v2";
|
|
76
94
|
const OAATH_AUTHORIZATION_DECISION_RECORD_VERSION = "oaath.authorization-decision-record/v1";
|
|
77
95
|
const OAATH_AUTHORIZATION_CODE_RECORD_VERSION = "oaath.authorization-code-record/v1";
|
|
78
96
|
const OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION = "oaath.encrypted-artifact-record/v1";
|
|
@@ -140,6 +158,9 @@ function parseAuthorizationRequestRecord(value) {
|
|
|
140
158
|
"requestId",
|
|
141
159
|
"clientId",
|
|
142
160
|
"subject",
|
|
161
|
+
"ownerDeviceId",
|
|
162
|
+
"ownerSubject",
|
|
163
|
+
"organizationAudience",
|
|
143
164
|
"redirectUri",
|
|
144
165
|
"codeChallenge",
|
|
145
166
|
"requestedScope",
|
|
@@ -151,6 +172,9 @@ function parseAuthorizationRequestRecord(value) {
|
|
|
151
172
|
requestId: canonicalIdentifier(record.requestId, "requestId", UNREADABLE),
|
|
152
173
|
clientId: canonicalIdentifier(record.clientId, "clientId", UNREADABLE),
|
|
153
174
|
subject: canonicalIdentifier(record.subject, "subject", UNREADABLE),
|
|
175
|
+
ownerDeviceId: canonicalIdentifier(record.ownerDeviceId, "ownerDeviceId", UNREADABLE),
|
|
176
|
+
ownerSubject: canonicalIdentifier(record.ownerSubject, "ownerSubject", UNREADABLE),
|
|
177
|
+
organizationAudience: record.organizationAudience === null ? null : canonicalIdentifier(record.organizationAudience, "organizationAudience", UNREADABLE),
|
|
154
178
|
redirectUri: boundedText(record.redirectUri, RELAY_LIMITS.redirectUri, "redirectUri", UNREADABLE),
|
|
155
179
|
codeChallenge: canonicalIdentifier(record.codeChallenge, "codeChallenge", UNREADABLE),
|
|
156
180
|
requestedScope: boundedText(record.requestedScope, RELAY_LIMITS.requestedScope, "requestedScope", UNREADABLE),
|
|
@@ -228,4 +252,4 @@ function parseEncryptedArtifactRecord(value) {
|
|
|
228
252
|
//#endregion
|
|
229
253
|
export { jsonResponse as _, OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION as a, relayFailure as b, canonicalIdentifier as c, parseAuthorizationRequestRecord as d, parseCapabilityInvalidationRecord as f, RELAY_ERROR_STATUS as g, OaathRelayError as h, OAATH_CAPABILITY_INVALIDATION_RECORD_VERSION as i, parseAuthorizationCodeRecord as l, timestamp as m, OAATH_AUTHORIZATION_DECISION_RECORD_VERSION as n, RELAY_LIMITS as o, parseEncryptedArtifactRecord as p, OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION as r, boundedText as s, OAATH_AUTHORIZATION_CODE_RECORD_VERSION as t, parseAuthorizationDecisionRecord as u, relayErrorCode as v, relayErrorResponse as y };
|
|
230
254
|
|
|
231
|
-
//# sourceMappingURL=records-
|
|
255
|
+
//# sourceMappingURL=records-BQNDxbPg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records-BQNDxbPg.js","names":[],"sources":["../src/relay/errors.ts","../src/store/records.ts"],"sourcesContent":["/**\n * Closed relay failure codes and their stable HTTP projection.\n *\n * Machine decisions and wire responses use these codes only. `Error.message`\n * exists for local diagnostics and never reaches a response body.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport {\n type BundlerRejection,\n captureBundlerRejection,\n captureValidationGasDiagnostic,\n type ValidationGasDiagnostic,\n} from \"@oaath/protocol\";\n\nexport type RelayErrorCode =\n /** Wire input is missing, malformed, oversized, or contains unknown fields. */\n | \"relay_request_invalid\"\n /** The deployment authentication port did not authenticate the caller. */\n | \"relay_unauthenticated\"\n /** Authenticated caller may not act in the required role. */\n | \"relay_forbidden\"\n /** Route, or a record the caller is entitled to see, does not exist. */\n | \"relay_not_found\"\n /** Route exists with a different method. */\n | \"relay_method_not_allowed\"\n /** The authorization request or code is past its injected-clock expiry. */\n | \"relay_expired\"\n /** The authorization request already reached a terminal decision. */\n | \"relay_already_decided\"\n /** The one-time authorization code was already consumed. */\n | \"relay_code_already_consumed\"\n /** The one-time encrypted artifact was already claimed. */\n | \"relay_artifact_already_claimed\"\n /**\n * Authorization code redemption failed. One code for every reason it can\n * fail — unknown code, another client's code, wrong redirect URI, wrong PKCE\n * verifier — so the endpoint never confirms that a guessed code was correct.\n * A code that existed is burned.\n */\n | \"relay_code_invalid\"\n /** The optional deployment limiter rejected the call. */\n | \"relay_rate_limited\"\n /** A durable record could not be read as the current schema version. */\n | \"relay_record_unreadable\"\n /** The KMS port was unavailable or returned an unusable result. */\n | \"relay_kms_unavailable\"\n /** The store failed before any state change could have been committed. */\n | \"relay_store_unavailable\"\n /**\n * The store could not prove whether a transition committed. The caller must\n * treat the transition as neither applied nor retryable: an ambiguous commit\n * never authorizes resubmission.\n */\n | \"relay_state_ambiguous\"\n /**\n * EXPERIMENTAL PREVIEW (`@oaath/server/apns`): the constructed APNs payload\n * exceeded Apple's 4096-byte limit, so nothing was sent.\n */\n | \"relay_apns_payload_too_large\"\n /**\n * EXPERIMENTAL PREVIEW (`@oaath/server/apns`): the injected Apple credential\n * capability is not a usable P-256 signing key, key id, team id, or topic.\n */\n | \"relay_apns_credentials_invalid\"\n /** A deployment-injected chain execution port failed or answered unusably. */\n | \"relay_chain_unavailable\"\n /** The Grant's capability is durably invalidated; nothing more executes here. */\n | \"relay_capability_invalidated\"\n /** An invariant the relay owns was violated. */\n | \"relay_internal\";\n\n/**\n * Status projection. `relay_state_ambiguous` is deliberately 500 and not 503,\n * because 503 invites the retry that an ambiguous commit forbids.\n */\nexport const RELAY_ERROR_STATUS: Readonly<Record<RelayErrorCode, number>> = Object.freeze({\n relay_request_invalid: 400,\n relay_unauthenticated: 401,\n relay_forbidden: 403,\n relay_not_found: 404,\n relay_method_not_allowed: 405,\n relay_expired: 410,\n relay_already_decided: 409,\n relay_code_already_consumed: 409,\n relay_artifact_already_claimed: 409,\n relay_code_invalid: 400,\n relay_rate_limited: 429,\n relay_record_unreadable: 500,\n relay_kms_unavailable: 503,\n relay_store_unavailable: 503,\n relay_state_ambiguous: 500,\n // The preview codes have no route. They are projected only so the union stays\n // closed and one owner keeps every code's status.\n relay_apns_payload_too_large: 500,\n relay_apns_credentials_invalid: 500,\n relay_chain_unavailable: 503,\n relay_capability_invalidated: 409,\n relay_internal: 500,\n});\n\nexport class OaathRelayError extends Error {\n readonly bundlerRejection: Readonly<BundlerRejection> | null;\n readonly code: RelayErrorCode;\n readonly diagnostic: Readonly<ValidationGasDiagnostic> | null;\n\n constructor(\n code: RelayErrorCode,\n message: string,\n diagnostic: Readonly<ValidationGasDiagnostic> | null = null,\n bundlerRejection: Readonly<BundlerRejection> | null = null,\n ) {\n super(message);\n this.name = \"OaathRelayError\";\n this.code = code;\n this.diagnostic = captureValidationGasDiagnostic(diagnostic);\n this.bundlerRejection =\n code === \"relay_chain_unavailable\" ? captureBundlerRejection(bundlerRejection) : null;\n }\n}\n\nexport function relayFailure(\n code: RelayErrorCode,\n message: string,\n diagnostic: Readonly<ValidationGasDiagnostic> | null = null,\n bundlerRejection: Readonly<BundlerRejection> | null = null,\n): never {\n throw new OaathRelayError(code, message, diagnostic, bundlerRejection);\n}\n\n/** Any non-relay throw is an unreadable internal failure, never caller-visible detail. */\nexport function relayErrorCode(error: unknown): RelayErrorCode {\n return error instanceof OaathRelayError ? error.code : \"relay_internal\";\n}\n\nconst RESPONSE_HEADERS: Readonly<Record<string, string>> = Object.freeze({\n \"content-type\": \"application/json; charset=utf-8\",\n \"cache-control\": \"no-store\",\n});\n\nexport function relayErrorResponse(\n code: RelayErrorCode,\n diagnostic: Readonly<ValidationGasDiagnostic> | null = null,\n bundlerRejection: Readonly<BundlerRejection> | null = null,\n): Response {\n const captured =\n code === \"relay_chain_unavailable\" ? captureValidationGasDiagnostic(diagnostic) : null;\n const rejection =\n code === \"relay_chain_unavailable\" ? captureBundlerRejection(bundlerRejection) : null;\n return jsonResponse(RELAY_ERROR_STATUS[code], {\n error: {\n code,\n ...(captured === null ? {} : { diagnostic: captured }),\n ...(rejection === null ? {} : { bundlerRejection: rejection }),\n },\n });\n}\n\nexport function jsonResponse(status: number, body: unknown): Response {\n return new Response(JSON.stringify(body), { status, headers: RESPONSE_HEADERS });\n}\n","/**\n * Exact current-version durable relay records.\n *\n * Durable storage is a trust boundary: every read is captured into an exact,\n * owned, immutable record before typed code consumes it. There is exactly one\n * current version per record and no reader for any older version.\n *\n * These shapes are relay-owned for now. `@oaath/protocol` owns the wire\n * `AuthorizationRequest`, `AuthorizationDecision`, and `AuthorizationCode`\n * contracts; adopting them here is a follow-up because the units differ (this\n * store keeps epoch milliseconds, the protocol speaks whole seconds) and the\n * code digest differs (base64url WebCrypto here, `0x` hex there). Once adopted,\n * this file should keep only the store envelope around those records.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport { type CaptureContext, exactRecord } from \"@oaath/protocol\";\nimport { type RelayErrorCode, relayFailure } from \"../relay/errors.js\";\n\nexport const OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION =\n \"oaath.authorization-request-record/v2\" as const;\nexport const OAATH_AUTHORIZATION_DECISION_RECORD_VERSION =\n \"oaath.authorization-decision-record/v1\" as const;\nexport const OAATH_AUTHORIZATION_CODE_RECORD_VERSION =\n \"oaath.authorization-code-record/v1\" as const;\nexport const OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION =\n \"oaath.encrypted-artifact-record/v1\" as const;\nexport const OAATH_CAPABILITY_INVALIDATION_RECORD_VERSION =\n \"oaath.capability-invalidation-record/v1\" as const;\n\n/** Bounded field limits owned here and shared with wire capture. */\nexport const RELAY_LIMITS = Object.freeze({\n identifier: 256,\n redirectUri: 2048,\n codeChallenge: 128,\n codeVerifier: 128,\n requestedScope: 8192,\n artifactPlaintext: 32_768,\n ciphertextRef: 65_536,\n});\n\nconst MAX_TIMESTAMP = Number.MAX_SAFE_INTEGER;\n\n/** Deployment-selected device reference and its authenticated approving subject. */\nexport interface AuthorizationOwnerRoute {\n readonly ownerDeviceId: string;\n readonly ownerSubject: string;\n}\n\nexport interface AuthorizationRequestRecord extends AuthorizationOwnerRoute {\n readonly version: typeof OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION;\n readonly requestId: string;\n /** Client bound by the deployment authentication port, never by wire input. */\n readonly clientId: string;\n /** Requesting member's subject, independent of the approving device. */\n readonly subject: string;\n /**\n * Organization/audience bound by the authentication port at creation time,\n * or null when the deployment declares none. Grant reference verification\n * compares assertions against this captured value.\n */\n readonly organizationAudience: string | null;\n readonly redirectUri: string;\n /** PKCE S256 challenge; the verifier never reaches the store. */\n readonly codeChallenge: string;\n /** Opaque owner-reviewed scope payload. Migrates to @oaath/protocol. */\n readonly requestedScope: string;\n readonly createdAt: number;\n readonly expiresAt: number;\n}\n\nexport type AuthorizationDecisionOutcome = \"approved\" | \"rejected\";\n\n/**\n * Terminal decision. Its existence is the single authoritative fact that an\n * authorization request was decided; the request record never mutates.\n */\nexport interface AuthorizationDecisionRecord {\n readonly version: typeof OAATH_AUTHORIZATION_DECISION_RECORD_VERSION;\n readonly requestId: string;\n readonly outcome: AuthorizationDecisionOutcome;\n readonly decidedAt: number;\n /**\n * KMS-sealed copy of the released code for authenticated client pickup, and\n * its expiry. The relay mints the code itself, so holding a sealed copy adds\n * no new trust; PKCE still guards consumption. Both are null exactly when\n * the outcome is a rejection.\n */\n readonly codeRef: string | null;\n readonly codeExpiresAt: number | null;\n}\n\nexport interface AuthorizationCodeRecord {\n readonly version: typeof OAATH_AUTHORIZATION_CODE_RECORD_VERSION;\n /** SHA-256 (base64url) of the released code. The code itself is never stored. */\n readonly codeHash: string;\n readonly requestId: string;\n /** Binding snapshot taken at issue time so consume needs exactly one row lock. */\n readonly clientId: string;\n readonly redirectUri: string;\n readonly codeChallenge: string;\n readonly artifactId: string;\n readonly createdAt: number;\n readonly expiresAt: number;\n /** Set exactly once. A non-null value is terminal. */\n readonly consumedAt: number | null;\n}\n\nexport interface EncryptedArtifactRecord {\n readonly version: typeof OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION;\n readonly artifactId: string;\n readonly requestId: string;\n readonly clientId: string;\n /** Opaque KMS reference. Plaintext never reaches the store. */\n readonly ciphertextRef: string;\n readonly createdAt: number;\n /** Set exactly once. A non-null value is terminal. */\n readonly claimedAt: number | null;\n}\n\n/**\n * One durable capability invalidation: from the moment this record commits,\n * the relay's chain execution routes refuse every submission and usage read\n * for the Grant, so the recorded evidence states an enforced fact — \"this\n * service will no longer act for this capability\" — not a bare digest.\n * Consuming the on-chain install nonce is the chain-local revocation\n * operation's job and stays separate evidence.\n */\nexport interface CapabilityInvalidationRecord {\n readonly version: typeof OAATH_CAPABILITY_INVALIDATION_RECORD_VERSION;\n readonly grantId: string;\n /** The authenticated client that recorded the invalidation. */\n readonly clientId: string;\n readonly capabilityHash: string;\n readonly invalidatedAt: number;\n}\n\nexport function boundedText(\n value: unknown,\n maximum: number,\n label: string,\n code: RelayErrorCode,\n): string {\n if (typeof value !== \"string\" || value.length < 1 || value.length > maximum) {\n return relayFailure(code, `${label} must be a bounded non-empty string`);\n }\n // Control characters would corrupt logs and downstream headers.\n for (let index = 0; index < value.length; index += 1) {\n const unit = value.charCodeAt(index);\n if (unit < 0x20 || unit === 0x7f) {\n return relayFailure(code, `${label} must not contain control characters`);\n }\n }\n return value;\n}\n\nexport function canonicalIdentifier(value: unknown, label: string, code: RelayErrorCode): string {\n const text = boundedText(value, RELAY_LIMITS.identifier, label, code);\n if (!/^[A-Za-z0-9._~-]+$/u.test(text)) {\n return relayFailure(code, `${label} must be URL-safe`);\n }\n return text;\n}\n\nexport function timestamp(value: unknown, label: string, code: RelayErrorCode): number {\n if (\n typeof value !== \"number\" ||\n !Number.isSafeInteger(value) ||\n Object.is(value, -0) ||\n value < 0 ||\n value > MAX_TIMESTAMP\n ) {\n return relayFailure(code, `${label} must be a non-negative safe integer`);\n }\n return value;\n}\n\nfunction nullableTimestamp(value: unknown, label: string, code: RelayErrorCode): number | null {\n return value === null ? null : timestamp(value, label, code);\n}\n\nconst UNREADABLE: RelayErrorCode = \"relay_record_unreadable\";\n\nfunction captured(value: unknown, keys: readonly string[], label: string): Record<string, unknown> {\n const context: CaptureContext = new WeakSet();\n return exactRecord(value, keys, label, context, (message) => relayFailure(UNREADABLE, message));\n}\n\nfunction version<Value extends string>(value: unknown, expected: Value, label: string): Value {\n if (value !== expected) {\n return relayFailure(UNREADABLE, `${label} version is unsupported`);\n }\n return expected;\n}\n\nexport function parseCapabilityInvalidationRecord(value: unknown): CapabilityInvalidationRecord {\n const record = captured(\n value,\n [\"version\", \"grantId\", \"clientId\", \"capabilityHash\", \"invalidatedAt\"],\n \"capability invalidation record\",\n );\n const capabilityHash = boundedText(record.capabilityHash, 66, \"capabilityHash\", UNREADABLE);\n if (!/^0x[0-9a-f]{64}$/u.test(capabilityHash)) {\n return relayFailure(UNREADABLE, \"capabilityHash must be a lowercase 32-byte hash\");\n }\n return Object.freeze({\n version: version(\n record.version,\n OAATH_CAPABILITY_INVALIDATION_RECORD_VERSION,\n \"capability invalidation record\",\n ),\n grantId: canonicalIdentifier(record.grantId, \"grantId\", UNREADABLE),\n clientId: canonicalIdentifier(record.clientId, \"clientId\", UNREADABLE),\n capabilityHash,\n invalidatedAt: timestamp(record.invalidatedAt, \"invalidatedAt\", UNREADABLE),\n });\n}\n\nexport function parseAuthorizationRequestRecord(value: unknown): AuthorizationRequestRecord {\n const record = captured(\n value,\n [\n \"version\",\n \"requestId\",\n \"clientId\",\n \"subject\",\n \"ownerDeviceId\",\n \"ownerSubject\",\n \"organizationAudience\",\n \"redirectUri\",\n \"codeChallenge\",\n \"requestedScope\",\n \"createdAt\",\n \"expiresAt\",\n ],\n \"authorization request record\",\n );\n return Object.freeze({\n version: version(\n record.version,\n OAATH_AUTHORIZATION_REQUEST_RECORD_VERSION,\n \"authorization request record\",\n ),\n requestId: canonicalIdentifier(record.requestId, \"requestId\", UNREADABLE),\n clientId: canonicalIdentifier(record.clientId, \"clientId\", UNREADABLE),\n subject: canonicalIdentifier(record.subject, \"subject\", UNREADABLE),\n ownerDeviceId: canonicalIdentifier(record.ownerDeviceId, \"ownerDeviceId\", UNREADABLE),\n ownerSubject: canonicalIdentifier(record.ownerSubject, \"ownerSubject\", UNREADABLE),\n organizationAudience:\n record.organizationAudience === null\n ? null\n : canonicalIdentifier(record.organizationAudience, \"organizationAudience\", UNREADABLE),\n redirectUri: boundedText(\n record.redirectUri,\n RELAY_LIMITS.redirectUri,\n \"redirectUri\",\n UNREADABLE,\n ),\n codeChallenge: canonicalIdentifier(record.codeChallenge, \"codeChallenge\", UNREADABLE),\n requestedScope: boundedText(\n record.requestedScope,\n RELAY_LIMITS.requestedScope,\n \"requestedScope\",\n UNREADABLE,\n ),\n createdAt: timestamp(record.createdAt, \"createdAt\", UNREADABLE),\n expiresAt: timestamp(record.expiresAt, \"expiresAt\", UNREADABLE),\n });\n}\n\nexport function parseAuthorizationDecisionRecord(value: unknown): AuthorizationDecisionRecord {\n const record = captured(\n value,\n [\"version\", \"requestId\", \"outcome\", \"decidedAt\", \"codeRef\", \"codeExpiresAt\"],\n \"authorization decision record\",\n );\n if (record.outcome !== \"approved\" && record.outcome !== \"rejected\") {\n return relayFailure(UNREADABLE, \"decision outcome is unsupported\");\n }\n // The sealed code exists exactly when a code was released: an approval with\n // no pickup copy or a rejection carrying one is an unreadable record.\n if ((record.outcome === \"approved\") !== (record.codeRef !== null)) {\n return relayFailure(UNREADABLE, \"decision code reference does not match the outcome\");\n }\n if ((record.codeRef === null) !== (record.codeExpiresAt === null)) {\n return relayFailure(UNREADABLE, \"decision code expiry does not match its reference\");\n }\n return Object.freeze({\n version: version(\n record.version,\n OAATH_AUTHORIZATION_DECISION_RECORD_VERSION,\n \"authorization decision record\",\n ),\n requestId: canonicalIdentifier(record.requestId, \"requestId\", UNREADABLE),\n outcome: record.outcome,\n decidedAt: timestamp(record.decidedAt, \"decidedAt\", UNREADABLE),\n codeRef:\n record.codeRef === null\n ? null\n : boundedText(record.codeRef, RELAY_LIMITS.ciphertextRef, \"codeRef\", UNREADABLE),\n codeExpiresAt:\n record.codeExpiresAt === null\n ? null\n : timestamp(record.codeExpiresAt, \"codeExpiresAt\", UNREADABLE),\n });\n}\n\nexport function parseAuthorizationCodeRecord(value: unknown): AuthorizationCodeRecord {\n const record = captured(\n value,\n [\n \"version\",\n \"codeHash\",\n \"requestId\",\n \"clientId\",\n \"redirectUri\",\n \"codeChallenge\",\n \"artifactId\",\n \"createdAt\",\n \"expiresAt\",\n \"consumedAt\",\n ],\n \"authorization code record\",\n );\n return Object.freeze({\n version: version(\n record.version,\n OAATH_AUTHORIZATION_CODE_RECORD_VERSION,\n \"authorization code record\",\n ),\n codeHash: canonicalIdentifier(record.codeHash, \"codeHash\", UNREADABLE),\n requestId: canonicalIdentifier(record.requestId, \"requestId\", UNREADABLE),\n clientId: canonicalIdentifier(record.clientId, \"clientId\", UNREADABLE),\n redirectUri: boundedText(\n record.redirectUri,\n RELAY_LIMITS.redirectUri,\n \"redirectUri\",\n UNREADABLE,\n ),\n codeChallenge: canonicalIdentifier(record.codeChallenge, \"codeChallenge\", UNREADABLE),\n artifactId: canonicalIdentifier(record.artifactId, \"artifactId\", UNREADABLE),\n createdAt: timestamp(record.createdAt, \"createdAt\", UNREADABLE),\n expiresAt: timestamp(record.expiresAt, \"expiresAt\", UNREADABLE),\n consumedAt: nullableTimestamp(record.consumedAt, \"consumedAt\", UNREADABLE),\n });\n}\n\nexport function parseEncryptedArtifactRecord(value: unknown): EncryptedArtifactRecord {\n const record = captured(\n value,\n [\"version\", \"artifactId\", \"requestId\", \"clientId\", \"ciphertextRef\", \"createdAt\", \"claimedAt\"],\n \"encrypted artifact record\",\n );\n return Object.freeze({\n version: version(\n record.version,\n OAATH_ENCRYPTED_ARTIFACT_RECORD_VERSION,\n \"encrypted artifact record\",\n ),\n artifactId: canonicalIdentifier(record.artifactId, \"artifactId\", UNREADABLE),\n requestId: canonicalIdentifier(record.requestId, \"requestId\", UNREADABLE),\n clientId: canonicalIdentifier(record.clientId, \"clientId\", UNREADABLE),\n ciphertextRef: boundedText(\n record.ciphertextRef,\n RELAY_LIMITS.ciphertextRef,\n \"ciphertextRef\",\n UNREADABLE,\n ),\n createdAt: timestamp(record.createdAt, \"createdAt\", UNREADABLE),\n claimedAt: nullableTimestamp(record.claimedAt, \"claimedAt\", UNREADABLE),\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { b as relayFailure, c as canonicalIdentifier, m as timestamp, o as RELAY_LIMITS, s as boundedText } from "./records-BQNDxbPg.js";
|
|
2
|
+
import { exactRecord, parseKernelRevocationSigningRequest } from "@oaath/protocol";
|
|
3
|
+
//#region src/revocation/records.ts
|
|
4
|
+
/** Current immutable revocation request and terminal phone decision. No submission evidence. */
|
|
5
|
+
const OAATH_REVOCATION_REQUEST_RECORD_VERSION = "oaath.revocation-request-record/v1";
|
|
6
|
+
const OAATH_REVOCATION_DECISION_RECORD_VERSION = "oaath.revocation-decision-record/v1";
|
|
7
|
+
/** Separates owner operations from authorization request IDs on the shared phone route. */
|
|
8
|
+
const REVOCATION_OPERATION_PREFIX = "revocation-";
|
|
9
|
+
const UNREADABLE = "relay_record_unreadable";
|
|
10
|
+
const fail = (message) => relayFailure(UNREADABLE, message);
|
|
11
|
+
function capture(value, keys) {
|
|
12
|
+
return exactRecord(value, keys, "revocation record", /* @__PURE__ */ new WeakSet(), fail);
|
|
13
|
+
}
|
|
14
|
+
function operationId(value) {
|
|
15
|
+
const id = canonicalIdentifier(value, "operationId", UNREADABLE);
|
|
16
|
+
if (!id.startsWith("revocation-") || id.length > 64) return fail("revocation operation ID is invalid");
|
|
17
|
+
return id;
|
|
18
|
+
}
|
|
19
|
+
function parseRevocationRequestRecord(value) {
|
|
20
|
+
const record = capture(value, [
|
|
21
|
+
"version",
|
|
22
|
+
"operationId",
|
|
23
|
+
"ownerDeviceId",
|
|
24
|
+
"ownerSubject",
|
|
25
|
+
"createdAt",
|
|
26
|
+
"expiresAt",
|
|
27
|
+
"signingRequest"
|
|
28
|
+
]);
|
|
29
|
+
if (record.version !== "oaath.revocation-request-record/v1") return fail("revocation request version is unsupported");
|
|
30
|
+
const createdAt = timestamp(record.createdAt, "createdAt", UNREADABLE);
|
|
31
|
+
const expiresAt = timestamp(record.expiresAt, "expiresAt", UNREADABLE);
|
|
32
|
+
if (expiresAt < createdAt) return fail("revocation request expiry precedes creation");
|
|
33
|
+
let signingRequest;
|
|
34
|
+
try {
|
|
35
|
+
signingRequest = parseKernelRevocationSigningRequest(record.signingRequest);
|
|
36
|
+
} catch {
|
|
37
|
+
return fail("stored revocation signing request is unreadable");
|
|
38
|
+
}
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
version: record.version,
|
|
41
|
+
operationId: operationId(record.operationId),
|
|
42
|
+
ownerDeviceId: canonicalIdentifier(record.ownerDeviceId, "ownerDeviceId", UNREADABLE),
|
|
43
|
+
ownerSubject: canonicalIdentifier(record.ownerSubject, "ownerSubject", UNREADABLE),
|
|
44
|
+
createdAt,
|
|
45
|
+
expiresAt,
|
|
46
|
+
signingRequest
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function parseRevocationDecisionRecord(value) {
|
|
50
|
+
const record = capture(value, [
|
|
51
|
+
"version",
|
|
52
|
+
"operationId",
|
|
53
|
+
"outcome",
|
|
54
|
+
"decidedAt",
|
|
55
|
+
"artifactRef"
|
|
56
|
+
]);
|
|
57
|
+
if (record.version !== "oaath.revocation-decision-record/v1" || record.outcome !== "approved" && record.outcome !== "rejected") return fail("revocation decision is unsupported");
|
|
58
|
+
if (record.outcome === "rejected" !== (record.artifactRef === null)) return fail("revocation decision contradicts artifact custody");
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
version: record.version,
|
|
61
|
+
operationId: operationId(record.operationId),
|
|
62
|
+
outcome: record.outcome,
|
|
63
|
+
decidedAt: timestamp(record.decidedAt, "decidedAt", UNREADABLE),
|
|
64
|
+
artifactRef: record.artifactRef === null ? null : boundedText(record.artifactRef, RELAY_LIMITS.ciphertextRef, "artifactRef", UNREADABLE)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { parseRevocationRequestRecord as a, parseRevocationDecisionRecord as i, OAATH_REVOCATION_REQUEST_RECORD_VERSION as n, REVOCATION_OPERATION_PREFIX as r, OAATH_REVOCATION_DECISION_RECORD_VERSION as t };
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=records-BUvqmp0D.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records-BUvqmp0D.js","names":[],"sources":["../src/revocation/records.ts"],"sourcesContent":["/** Current immutable revocation request and terminal phone decision. No submission evidence. */\nimport {\n exactRecord,\n type KernelRevocationSigningRequest,\n parseKernelRevocationSigningRequest,\n} from \"@oaath/protocol\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport { boundedText, canonicalIdentifier, RELAY_LIMITS, timestamp } from \"../store/records.js\";\n\nexport const OAATH_REVOCATION_REQUEST_RECORD_VERSION =\n \"oaath.revocation-request-record/v1\" as const;\nexport const OAATH_REVOCATION_DECISION_RECORD_VERSION =\n \"oaath.revocation-decision-record/v1\" as const;\n/** Separates owner operations from authorization request IDs on the shared phone route. */\nexport const REVOCATION_OPERATION_PREFIX = \"revocation-\";\n\nexport interface RevocationRequestRecord {\n readonly version: typeof OAATH_REVOCATION_REQUEST_RECORD_VERSION;\n readonly operationId: string;\n readonly ownerDeviceId: string;\n readonly ownerSubject: string;\n readonly createdAt: number;\n readonly expiresAt: number;\n readonly signingRequest: Readonly<KernelRevocationSigningRequest>;\n}\nexport interface RevocationDecisionRecord {\n readonly version: typeof OAATH_REVOCATION_DECISION_RECORD_VERSION;\n readonly operationId: string;\n readonly outcome: \"approved\" | \"rejected\";\n readonly decidedAt: number;\n /** Sealed canonical phone artifact; null exactly for rejection. Never an OAuth release. */\n readonly artifactRef: string | null;\n}\nconst UNREADABLE = \"relay_record_unreadable\" as const;\nconst fail = (message: string): never => relayFailure(UNREADABLE, message);\nfunction capture(value: unknown, keys: readonly string[]): Record<string, unknown> {\n return exactRecord(value, keys, \"revocation record\", new WeakSet(), fail);\n}\nfunction operationId(value: unknown): string {\n const id = canonicalIdentifier(value, \"operationId\", UNREADABLE);\n if (!id.startsWith(REVOCATION_OPERATION_PREFIX) || id.length > 64)\n return fail(\"revocation operation ID is invalid\");\n return id;\n}\nexport function parseRevocationRequestRecord(value: unknown): Readonly<RevocationRequestRecord> {\n const record = capture(value, [\n \"version\",\n \"operationId\",\n \"ownerDeviceId\",\n \"ownerSubject\",\n \"createdAt\",\n \"expiresAt\",\n \"signingRequest\",\n ]);\n if (record.version !== OAATH_REVOCATION_REQUEST_RECORD_VERSION)\n return fail(\"revocation request version is unsupported\");\n const createdAt = timestamp(record.createdAt, \"createdAt\", UNREADABLE);\n const expiresAt = timestamp(record.expiresAt, \"expiresAt\", UNREADABLE);\n if (expiresAt < createdAt) return fail(\"revocation request expiry precedes creation\");\n let signingRequest: Readonly<KernelRevocationSigningRequest>;\n try {\n signingRequest = parseKernelRevocationSigningRequest(record.signingRequest);\n } catch {\n return fail(\"stored revocation signing request is unreadable\");\n }\n return Object.freeze({\n version: record.version,\n operationId: operationId(record.operationId),\n ownerDeviceId: canonicalIdentifier(record.ownerDeviceId, \"ownerDeviceId\", UNREADABLE),\n ownerSubject: canonicalIdentifier(record.ownerSubject, \"ownerSubject\", UNREADABLE),\n createdAt,\n expiresAt,\n signingRequest,\n });\n}\nexport function parseRevocationDecisionRecord(value: unknown): Readonly<RevocationDecisionRecord> {\n const record = capture(value, [\"version\", \"operationId\", \"outcome\", \"decidedAt\", \"artifactRef\"]);\n if (\n record.version !== OAATH_REVOCATION_DECISION_RECORD_VERSION ||\n (record.outcome !== \"approved\" && record.outcome !== \"rejected\")\n )\n return fail(\"revocation decision is unsupported\");\n if ((record.outcome === \"rejected\") !== (record.artifactRef === null))\n return fail(\"revocation decision contradicts artifact custody\");\n return Object.freeze({\n version: record.version,\n operationId: operationId(record.operationId),\n outcome: record.outcome,\n decidedAt: timestamp(record.decidedAt, \"decidedAt\", UNREADABLE),\n artifactRef:\n record.artifactRef === null\n ? null\n : boundedText(record.artifactRef, RELAY_LIMITS.ciphertextRef, \"artifactRef\", UNREADABLE),\n });\n}\n"],"mappings":";;;;AASA,MAAa,0CACX;AACF,MAAa,2CACX;;AAEF,MAAa,8BAA8B;AAmB3C,MAAM,aAAa;AACnB,MAAM,QAAQ,YAA2B,aAAa,YAAY,OAAO;AACzE,SAAS,QAAQ,OAAgB,MAAkD;CACjF,OAAO,YAAY,OAAO,MAAM,qCAAqB,IAAI,QAAQ,GAAG,IAAI;AAC1E;AACA,SAAS,YAAY,OAAwB;CAC3C,MAAM,KAAK,oBAAoB,OAAO,eAAe,UAAU;CAC/D,IAAI,CAAC,GAAG,WAAA,aAAsC,KAAK,GAAG,SAAS,IAC7D,OAAO,KAAK,oCAAoC;CAClD,OAAO;AACT;AACA,SAAgB,6BAA6B,OAAmD;CAC9F,MAAM,SAAS,QAAQ,OAAO;EAC5B;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,IAAI,OAAO,YAAA,sCACT,OAAO,KAAK,2CAA2C;CACzD,MAAM,YAAY,UAAU,OAAO,WAAW,aAAa,UAAU;CACrE,MAAM,YAAY,UAAU,OAAO,WAAW,aAAa,UAAU;CACrE,IAAI,YAAY,WAAW,OAAO,KAAK,6CAA6C;CACpF,IAAI;CACJ,IAAI;EACF,iBAAiB,oCAAoC,OAAO,cAAc;CAC5E,QAAQ;EACN,OAAO,KAAK,iDAAiD;CAC/D;CACA,OAAO,OAAO,OAAO;EACnB,SAAS,OAAO;EAChB,aAAa,YAAY,OAAO,WAAW;EAC3C,eAAe,oBAAoB,OAAO,eAAe,iBAAiB,UAAU;EACpF,cAAc,oBAAoB,OAAO,cAAc,gBAAgB,UAAU;EACjF;EACA;EACA;CACF,CAAC;AACH;AACA,SAAgB,8BAA8B,OAAoD;CAChG,MAAM,SAAS,QAAQ,OAAO;EAAC;EAAW;EAAe;EAAW;EAAa;CAAa,CAAC;CAC/F,IACE,OAAO,YAAA,yCACN,OAAO,YAAY,cAAc,OAAO,YAAY,YAErD,OAAO,KAAK,oCAAoC;CAClD,IAAK,OAAO,YAAY,gBAAiB,OAAO,gBAAgB,OAC9D,OAAO,KAAK,kDAAkD;CAChE,OAAO,OAAO,OAAO;EACnB,SAAS,OAAO;EAChB,aAAa,YAAY,OAAO,WAAW;EAC3C,SAAS,OAAO;EAChB,WAAW,UAAU,OAAO,WAAW,aAAa,UAAU;EAC9D,aACE,OAAO,gBAAgB,OACnB,OACA,YAAY,OAAO,aAAa,aAAa,eAAe,eAAe,UAAU;CAC7F,CAAC;AACH"}
|