@oxyhq/contracts 0.25.0 → 0.26.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.
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hubAuthorizeResultSchema = exports.hubAuthorizeRequestSchema = exports.hubActivateRequestSchema = exports.hubClaimRequestSchema = exports.hubSessionSchema = exports.browserHubRevokeResponseSchema = exports.browserHubErrorSchema = exports.browserHubResolveResponseSchema = exports.browserHubHandleResponseSchema = exports.browserHubHandleRequestSchema = exports.browserHubHandleSchema = exports.BROWSER_HUB_HANDLE_TTL_MS = exports.BROWSER_HUB_COOKIE_ATTRIBUTES = exports.BROWSER_HUB_COOKIE_NAME = void 0;
4
+ const zod_1 = require("zod");
5
+ const deviceDirectory_1 = require("./deviceDirectory");
6
+ /**
7
+ * The browser DeviceSession hub at `auth.oxy.so` (issue #937, Phase 5, ADR 0003).
8
+ *
9
+ * Two wire surfaces live in this file and they are deliberately different
10
+ * shapes, because they answer to different callers:
11
+ *
12
+ * - The `browserHub*` schemas below the first divider are the API's
13
+ * (`api.oxy.so/session/browser-hub/*`), spoken ONLY by the IdP's own
14
+ * server/edge layer. They carry the raw handle and a bearer, so nothing that
15
+ * speaks them may run in a browser.
16
+ * - The `hub*` schemas below the second divider are the EDGE's
17
+ * (`auth.oxy.so/hub/*`), spoken by the IdP SPA. They carry neither: the
18
+ * handle stays in an `HttpOnly` cookie the script cannot read, and the
19
+ * device-wide bearer stays at the edge.
20
+ *
21
+ * That split is the whole point of the phase. A single schema covering both
22
+ * would let a refactor move a credential across the boundary without any type
23
+ * changing shape.
24
+ *
25
+ * ## What is NOT reopened here
26
+ *
27
+ * Relying-party origins remain zero-cookie: they keep `{deviceId,
28
+ * deviceSecret}` + `POST /session/device/token` and set no cookie of any kind.
29
+ * `auth.oxy.so` alone holds a handle, first-party only. There is no
30
+ * refresh-token family and no bootstrap hop.
31
+ */
32
+ /* -------------------------------------------------------------------------- */
33
+ /* The cookie */
34
+ /* -------------------------------------------------------------------------- */
35
+ /**
36
+ * The one cookie name, `__Host-` prefixed.
37
+ *
38
+ * The prefix is not decoration: a browser refuses to store a `__Host-` cookie
39
+ * that carries a `Domain` attribute or a `Path` other than `/`, or that arrives
40
+ * without `Secure`. So the name itself is what makes "bound to `auth.oxy.so`
41
+ * alone, readable by no other `oxy.so` host" enforced by the client rather than
42
+ * merely intended by the server — including against a compromised sibling
43
+ * subdomain, which is the specific attack `Domain=.oxy.so` would have opened.
44
+ */
45
+ exports.BROWSER_HUB_COOKIE_NAME = '__Host-oxy-device';
46
+ /**
47
+ * The exact attribute set the edge sets, in the order it writes them.
48
+ *
49
+ * Held as data rather than as a template string so a test can assert the set
50
+ * rather than a rendered line, and so removing one attribute is a diff to a
51
+ * named constant instead of an edit inside a string literal.
52
+ *
53
+ * `Max-Age` is deliberately NOT in this list — see
54
+ * {@link BROWSER_HUB_HANDLE_TTL_MS}, which the edge appends. Everything here is
55
+ * a SECURITY attribute and none of them is ever conditional.
56
+ */
57
+ exports.BROWSER_HUB_COOKIE_ATTRIBUTES = ['Secure', 'HttpOnly', 'SameSite=Lax', 'Path=/'];
58
+ /**
59
+ * How long a hub handle lives, server-side and in the cookie alike.
60
+ *
61
+ * Thirty days. The cookie's `Max-Age` is derived from this same constant so the
62
+ * two cannot disagree: a cookie outliving its credential produces a browser that
63
+ * believes it is signed in and is refused on every call, and a credential
64
+ * outliving its cookie leaves an un-addressable row alive on the server.
65
+ *
66
+ * A hub handle is NOT a session cookie (one with no `Max-Age`, discarded when
67
+ * the browser closes). It cannot be: the thing it identifies is the browser
68
+ * PROFILE's device session, and a device session that evaporates when the user
69
+ * quits their browser would send them back to a QR scan every morning — the
70
+ * exact failure ADR 0003 exists to remove.
71
+ */
72
+ exports.BROWSER_HUB_HANDLE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
73
+ /* -------------------------------------------------------------------------- */
74
+ /* api.oxy.so/session/browser-hub/* — spoken by the edge, never a browser */
75
+ /* -------------------------------------------------------------------------- */
76
+ /**
77
+ * The raw handle, as it travels between the edge and the API.
78
+ *
79
+ * An opaque base64url random value and NOTHING else — no token, no user id, no
80
+ * device id, no account id, no serialized state. The server stores only
81
+ * `sha256(handle)`, so a dump of the column cannot address a browser.
82
+ *
83
+ * The minimum length is a floor against an empty or truncated value reaching a
84
+ * hash comparison, not a statement about the entropy: that is fixed by the
85
+ * issuer (`BROWSER_HUB_HANDLE_BYTES` in the API), and a handle is the only
86
+ * credential in this flow, so it is 256 bits.
87
+ */
88
+ exports.browserHubHandleSchema = zod_1.z.string().min(32);
89
+ /** Request body of every handle-presenting API endpoint. */
90
+ exports.browserHubHandleRequestSchema = zod_1.z.object({
91
+ handle: exports.browserHubHandleSchema,
92
+ });
93
+ /**
94
+ * Response of `POST /session/browser-hub/establish` and `.../rotate`.
95
+ *
96
+ * The raw handle is returned exactly ONCE, to the edge, which puts it straight
97
+ * into the cookie and keeps no copy. It is never logged, never re-readable, and
98
+ * never reaches the browser's script context.
99
+ */
100
+ exports.browserHubHandleResponseSchema = zod_1.z.object({
101
+ handle: exports.browserHubHandleSchema,
102
+ expiresAt: zod_1.z.string(),
103
+ });
104
+ /**
105
+ * Response of `POST /session/browser-hub/resolve` — the browser's device
106
+ * session, resolved from the handle alone.
107
+ *
108
+ * Carries a bearer because the edge needs one to run the authorize lane on the
109
+ * browser's behalf. It is the ordinary short-lived access token of the device's
110
+ * active context, and it stops at the edge.
111
+ */
112
+ exports.browserHubResolveResponseSchema = zod_1.z.object({
113
+ accessToken: zod_1.z.string().min(1),
114
+ expiresAt: zod_1.z.string(),
115
+ directory: deviceDirectory_1.deviceDirectorySchema,
116
+ });
117
+ /**
118
+ * Why a handle did not resolve. A CLOSED set, and deliberately coarse.
119
+ *
120
+ * - `invalid_handle` — unknown, expired, or revoked. One code for all
121
+ * three: distinguishing them would tell a caller
122
+ * holding a guessed value whether it ever existed.
123
+ * - `no_active_session` — the handle is good and the device has nothing live
124
+ * to mint for. The credential is NOT revoked; the
125
+ * browser re-authenticates and keeps its cookie.
126
+ */
127
+ exports.browserHubErrorSchema = zod_1.z.enum(['invalid_handle', 'no_active_session']);
128
+ /** Response of `POST /session/browser-hub/revoke`. Idempotent by construction. */
129
+ exports.browserHubRevokeResponseSchema = zod_1.z.object({
130
+ revoked: zod_1.z.boolean(),
131
+ });
132
+ /* -------------------------------------------------------------------------- */
133
+ /* auth.oxy.so/hub/* — spoken by the IdP SPA */
134
+ /* -------------------------------------------------------------------------- */
135
+ /**
136
+ * What the SPA is allowed to know about the hub session.
137
+ *
138
+ * `signed_out` covers "no cookie", "cookie present but the handle no longer
139
+ * resolves" and "resolved, but the device has nothing live" — from the script's
140
+ * side those are one state, and collapsing them here is what stops a UI from
141
+ * branching on a distinction it must not act on.
142
+ *
143
+ * `active` carries the DIRECTORY and no credential. A switcher renders from it;
144
+ * nothing in it can be spent against the API. The bearer that produced it never
145
+ * left the edge.
146
+ */
147
+ exports.hubSessionSchema = zod_1.z.discriminatedUnion('status', [
148
+ zod_1.z.object({ status: zod_1.z.literal('signed_out') }),
149
+ zod_1.z.object({ status: zod_1.z.literal('active'), directory: deviceDirectory_1.deviceDirectorySchema }),
150
+ ]);
151
+ /** Request body of `POST /hub/claim` — the Commons approval lane's handoff. */
152
+ exports.hubClaimRequestSchema = zod_1.z.object({
153
+ /**
154
+ * The secret `sessionToken` of an approved `AuthSession`, held only by the
155
+ * page that created it. The edge spends it server-side: the access token and
156
+ * device secret the claim yields are consumed to establish the hub and are
157
+ * then discarded, so neither is ever returned to the script.
158
+ */
159
+ sessionToken: zod_1.z.string().min(1),
160
+ });
161
+ /** Request body of `POST /hub/activate` — pick the globally active context. */
162
+ exports.hubActivateRequestSchema = zod_1.z.object({
163
+ contextId: zod_1.z.string().min(1),
164
+ });
165
+ /**
166
+ * Request body of `POST /hub/authorize` — a later official origin joining.
167
+ *
168
+ * There is no `prompt` field, and that is not an omission. `'none'` is absent
169
+ * from `buildOAuthAuthorizeUrl`'s union in `@oxyhq/core` precisely so a silent
170
+ * loop cannot be rebuilt in one line, and this endpoint would be the second
171
+ * place to rebuild it. A caller that needs a login or consent prompt gets one
172
+ * by not passing `approve`.
173
+ */
174
+ exports.hubAuthorizeRequestSchema = zod_1.z.object({
175
+ clientId: zod_1.z.string().min(1),
176
+ redirectUri: zod_1.z.string().url(),
177
+ state: zod_1.z.string().optional(),
178
+ codeChallenge: zod_1.z.string().min(1),
179
+ /** S256 only. `plain` is refused before the request leaves the edge. */
180
+ codeChallengeMethod: zod_1.z.literal('S256'),
181
+ scope: zod_1.z.string().optional(),
182
+ /**
183
+ * The user's explicit answer on the consent screen.
184
+ *
185
+ * Absent or `false` means "tell me whether consent is needed"; the edge then
186
+ * returns `consent_required` and mints nothing. Only `true` may mint, and only
187
+ * the consent screen's own button sends it — which is why the decision of
188
+ * WHETHER consent is required is re-read from the server on both passes rather
189
+ * than remembered from the first.
190
+ */
191
+ approve: zod_1.z.boolean().optional(),
192
+ });
193
+ /**
194
+ * Result of `POST /hub/authorize`.
195
+ *
196
+ * `signed_out` is the honest answer for a browser with no hub session: the SPA
197
+ * runs the ordinary Commons-first authentication and tries again. It is never a
198
+ * redirect the edge performs on the browser's behalf — no automatic chain
199
+ * across Oxy origins, and nothing here can be hidden inside an iframe.
200
+ */
201
+ exports.hubAuthorizeResultSchema = zod_1.z.discriminatedUnion('status', [
202
+ zod_1.z.object({ status: zod_1.z.literal('signed_out') }),
203
+ zod_1.z.object({
204
+ status: zod_1.z.literal('consent_required'),
205
+ reason: zod_1.z.enum(['new', 'scope_changed']),
206
+ userConsentScopes: zod_1.z.array(zod_1.z.string()).optional(),
207
+ }),
208
+ zod_1.z.object({
209
+ status: zod_1.z.literal('code'),
210
+ code: zod_1.z.string().min(1),
211
+ state: zod_1.z.string().nullable(),
212
+ redirectUri: zod_1.z.string(),
213
+ expiresIn: zod_1.z.number().int().positive(),
214
+ }),
215
+ ]);
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deviceDirectorySyncSchema = exports.deviceActivateResponseSchema = exports.deviceActivateRequestSchema = exports.deviceDirectorySchema = exports.devicePrincipalSchema = exports.deviceAccountContextSchema = exports.deviceDirectoryProfileSchema = exports.deviceContextRelationshipSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const accountGraph_1 = require("./accountGraph");
6
+ const deviceSession_1 = require("./deviceSession");
7
+ const userResponse_1 = require("./userResponse");
8
+ /**
9
+ * The canonical device directory — the ONE server-authoritative read model an
10
+ * account switcher renders (issue #937, ADR 0002).
11
+ *
12
+ * It replaces the client-side union of `DeviceSessionState.accounts[]` with a
13
+ * separately fetched `AccountNode[]` graph. That union cannot be correct on a
14
+ * device holding more than one person: the client only ever holds ONE caller's
15
+ * account graph, so it cannot enumerate what the OTHER principals may act as.
16
+ * Switchability is an authorization question, so the server answers it.
17
+ *
18
+ * The directory is deterministic and revision-bound: two reads at the same
19
+ * `revision` describe the same device state, and `revision` only advances on a
20
+ * real mutation (an idempotent activation advances nothing — ADR 0002).
21
+ */
22
+ /**
23
+ * How a principal reaches an account.
24
+ *
25
+ * Mirrors `AccountRelationship` in `@oxyhq/core`'s account graph rather than
26
+ * inventing a second vocabulary for the same fact:
27
+ * - `self` — the principal's own personal account (`principal.userId === accountId`)
28
+ * - `owner` — the principal owns this account
29
+ * - `member` — the principal holds a membership granting `account:act_as`
30
+ */
31
+ exports.deviceContextRelationshipSchema = zod_1.z.enum(['self', 'owner', 'member']);
32
+ /**
33
+ * Sanitized display metadata for a principal or an account context.
34
+ *
35
+ * Deliberately NOT `userResponseSchema`: a switcher needs a handle, a name, an
36
+ * avatar and the accent the row is drawn in, and the directory is read by every
37
+ * app on the device — so it carries the minimum that renders a row and nothing
38
+ * that would make it a general profile feed. `name.displayName` stays OPTIONAL;
39
+ * consumers fall back to the handle (`getNormalizedUserHandle`), never to a
40
+ * synthesized name.
41
+ *
42
+ * `color` is here and `email` is not, and the line between them is what the
43
+ * field is FOR rather than how sensitive it looks. An accent is a property of
44
+ * drawing the row — without it every non-active row falls back to the ambient
45
+ * theme accent and a device holding two people renders them identically
46
+ * (issue #961). An address is somebody's contact detail, and the `@handle` the
47
+ * secondary line already shows fills the same slot, so putting one in a payload
48
+ * every installed app reads would widen the profile for no rendering gain.
49
+ */
50
+ exports.deviceDirectoryProfileSchema = zod_1.z.object({
51
+ id: zod_1.z.string().min(1),
52
+ username: zod_1.z.string(),
53
+ name: userResponse_1.userNameSchema.optional(),
54
+ avatar: zod_1.z.string().nullable().optional(),
55
+ /**
56
+ * Named Bloom color preset (e.g. `"blue"`), or null when the account has
57
+ * none. Same shape as `userResponseSchema.color` — one spelling of one fact,
58
+ * so a consumer that themes a row from either source reads the same values.
59
+ */
60
+ color: zod_1.z.string().nullable().optional(),
61
+ });
62
+ /**
63
+ * One `principal acting as account` pair — the globally switchable unit.
64
+ *
65
+ * `id` is the identifier `POST /session/device/activate` takes. It names the
66
+ * PAIR, not the account: the same `accountId` legitimately appears under two
67
+ * principals on a shared device, and those are different sessions, permissions,
68
+ * audit actors and revocation paths.
69
+ *
70
+ * `onDevice` is `false` for a context the principal may act as but has never
71
+ * activated here — it exists as a row so it has a stable id to activate, and
72
+ * its delegated session is minted on first activation rather than eagerly for
73
+ * every organization the principal belongs to.
74
+ *
75
+ * `available` is `principalLive && (personal || live account:act_as)`, and the
76
+ * principal clause is not decoration: a principal whose own personal session
77
+ * has died makes EVERY context of theirs unavailable, including delegated ones
78
+ * whose sessions are perfectly alive. Activation verifies the principal's
79
+ * personal session (ADR 0002 step 3), so a client that models availability as
80
+ * "delegated context has a live session ⇒ activatable" will be refused by the
81
+ * server and the context healed away.
82
+ *
83
+ * A managed account whose membership was revoked is returned with
84
+ * `available: false` rather than silently omitted, so the UI can explain a row
85
+ * disappearing instead of just dropping it.
86
+ *
87
+ * (An earlier version of this comment described `available` as the
88
+ * `account:act_as` verdict alone. It was written before the server existed and
89
+ * was weaker than the code that shipped — the kind of wrong statement nothing
90
+ * recomputes, so it is spelled out here in full.)
91
+ */
92
+ exports.deviceAccountContextSchema = zod_1.z.object({
93
+ id: zod_1.z.string().min(1),
94
+ accountId: zod_1.z.string().min(1),
95
+ kind: accountGraph_1.accountKindSchema,
96
+ relationship: exports.deviceContextRelationshipSchema,
97
+ account: exports.deviceDirectoryProfileSchema,
98
+ onDevice: zod_1.z.boolean(),
99
+ available: zod_1.z.boolean(),
100
+ active: zod_1.z.boolean(),
101
+ lastUsedAt: zod_1.z.number().nullable(),
102
+ });
103
+ /**
104
+ * A human who authenticated onto this device.
105
+ *
106
+ * Never an organization, project, channel or bot — those are subjects a
107
+ * principal acts as, and they appear only in `contexts`. `authuser` is the
108
+ * Google-style signed-in-human slot and belongs HERE, not to an account: adding
109
+ * an organization must never consume one.
110
+ */
111
+ exports.devicePrincipalSchema = zod_1.z.object({
112
+ id: zod_1.z.string().min(1),
113
+ userId: zod_1.z.string().min(1),
114
+ authuser: zod_1.z.number().int().nonnegative(),
115
+ user: exports.deviceDirectoryProfileSchema,
116
+ contexts: zod_1.z.array(exports.deviceAccountContextSchema),
117
+ });
118
+ /**
119
+ * Response of `GET /session/device/directory`.
120
+ *
121
+ * `activeContextId` is the authority for what every official app in
122
+ * `sessionMode: 'account'` renders. It is null when the device has no active
123
+ * context — a real state (every context removed, or an active context healed
124
+ * away), not an error.
125
+ */
126
+ exports.deviceDirectorySchema = zod_1.z.object({
127
+ deviceId: zod_1.z.string().min(1),
128
+ revision: zod_1.z.number().int().nonnegative(),
129
+ activeContextId: zod_1.z.string().nullable(),
130
+ principals: zod_1.z.array(exports.devicePrincipalSchema),
131
+ updatedAt: zod_1.z.number(),
132
+ });
133
+ /**
134
+ * Request body of `POST /session/device/activate`.
135
+ *
136
+ * `contextId`, never `accountId`: an account id cannot name a context on a
137
+ * device where two people can both reach the same organization, and resolving
138
+ * that ambiguity server-side would mean guessing inside an authorization path.
139
+ */
140
+ exports.deviceActivateRequestSchema = zod_1.z.object({
141
+ contextId: zod_1.z.string().min(1),
142
+ });
143
+ /**
144
+ * Response of `POST /session/device/activate`.
145
+ *
146
+ * Carries the post-transition directory AND the bearer for the newly active
147
+ * context, because the client's ordering invariant is
148
+ * `commit token → reset caches → publish snapshot → notify` (ADR 0002). Handing
149
+ * the directory back without the token would force a second round trip in the
150
+ * middle of that sequence, which is exactly where a component would render the
151
+ * new subject while still holding the previous subject's bearer.
152
+ *
153
+ * `activeToken` is null when the activation legitimately produced no bearer for
154
+ * THIS caller — an identity-pinned client, or a caller whose application is not
155
+ * entitled to a token for the new context. It is never an error signal.
156
+ */
157
+ exports.deviceActivateResponseSchema = zod_1.z.object({
158
+ directory: exports.deviceDirectorySchema,
159
+ activeToken: deviceSession_1.activeTokenSchema.nullable(),
160
+ });
161
+ /**
162
+ * Response of the CONTEXT-aware removals — `POST /session/device/signout` with
163
+ * `{ contextId }` (one `principal → account` pair) or `{ principalId }` (one
164
+ * person and every context they reach, and nobody else's).
165
+ *
166
+ * It carries BOTH halves because a removal elects a replacement active context,
167
+ * so both the directory and the flat compatibility projection move in the same
168
+ * transition — and a client that learned only one of them would render one
169
+ * half of a device that no longer exists.
170
+ *
171
+ * This is deliberately NOT {@link deviceSessionSyncSchema} and must never be
172
+ * merged into it. A zod object strips unknown keys, so `{directory, state,
173
+ * activeToken}` parses cleanly as `{state, activeToken}` — silently dropping the
174
+ * directory. One schema covering both shapes would therefore make "the server
175
+ * stopped sending the directory" and "this endpoint never sends one"
176
+ * indistinguishable at the parse, on the exact path that decides which identity
177
+ * the app is running as. Two schemas fail closed instead: `directory` is
178
+ * required here, so a directory-less payload is refused outright.
179
+ *
180
+ * `activeToken` is null on the same terms as everywhere else — an identity-
181
+ * pinned caller, or one not entitled to a bearer for the newly-elected context —
182
+ * and null is also the honest answer when the removal left the device with no
183
+ * active context at all.
184
+ */
185
+ exports.deviceDirectorySyncSchema = zod_1.z.object({
186
+ directory: exports.deviceDirectorySchema,
187
+ state: deviceSession_1.deviceSessionStateSchema,
188
+ activeToken: deviceSession_1.activeTokenSchema.nullable(),
189
+ });
package/dist/cjs/index.js CHANGED
@@ -14,8 +14,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.appInterestInputSchema = exports.appEndorsementInputSchema = exports.recommendationResponseSchema = exports.recommendationItemSchema = exports.recommendationCountSchema = exports.recommendationRequestSchema = exports.recommendationSignalWeightsSchema = exports.recommendationBoostSchema = exports.recommendationExcludeTypeSchema = exports.oxyUserInvalidationEventSchema = exports.isPublishedOxyUserChangeReason = exports.OXY_PUBLISHED_USER_CHANGE_REASONS = exports.OXY_USER_CHANGE_REASONS = exports.OXY_USER_INVALIDATION_CHANNEL = exports.inboxEmailPushDataSchema = exports.INBOX_EMAIL_PUSH_TYPE = exports.INBOX_EMAIL_PUSH_CHANNEL = exports.IDENTITY_APPROVAL_PUSH_CHANNEL = exports.commonsDenyReasonSchema = exports.COMMONS_DENY_REASONS = exports.sessionStatusSchema = exports.publicApplicationSchema = exports.applicationTypeSchema = exports.safeParseContract = exports.resolveUserId = exports.deviceLinkedSessionsResponseSchema = exports.deviceLinkedSessionSchema = exports.currentUserResponseSchema = exports.userProfileUpdateSchema = exports.userResponseSchema = exports.themePreferenceSchema = exports.userRelationshipSchema = exports.userNameSchema = exports.createAccountRequestSchema = exports.SELECTABLE_ACCOUNT_CATEGORY_IDS = exports.RETIRED_ACCOUNT_CATEGORY_IDS = exports.newlyAddedRetiredCategories = exports.MAX_ACCOUNT_CATEGORIES = exports.kindAcceptsAccountCategories = exports.isSelectableAccountCategoryId = exports.accountCategoryIdSchema = exports.accountCategoriesSchema = exports.ACCOUNT_CATEGORY_KINDS = exports.ACCOUNT_CATEGORY_IDS = exports.isActAsEligibleKind = exports.isAccountKind = exports.childAccountKindSchema = exports.CHILD_ACCOUNT_KINDS = exports.accountKindSchema = exports.ACCOUNT_KINDS = void 0;
15
15
  exports.reputationBalanceBreakdownSchema = exports.reputationTransactionSchema = exports.reputationInfluenceContextSchema = exports.reputationDisputeStatusSchema = exports.reputationTargetEntityTypeSchema = exports.trustTierSchema = exports.reputationTransactionStatusSchema = exports.reputationCategorySchema = exports.REPUTATION_INFLUENCE_CONTEXTS = exports.REPUTATION_DISPUTE_STATUSES = exports.REPUTATION_TARGET_ENTITY_TYPES = exports.TRUST_TIERS = exports.REPUTATION_TRANSACTION_STATUSES = exports.REPUTATION_CATEGORIES = exports.credentialVerifyResultSchema = exports.credentialListResultSchema = exports.credentialIssueResultSchema = exports.verifiableCredentialResponseSchema = exports.credentialRecordSchema = exports.vouchResultSchema = exports.personhoodStatusResultSchema = exports.personhoodBreakdownSchema = exports.personhoodVouchRecordSchema = exports.validationVoteResultSchema = exports.validationRequestSummarySchema = exports.validationOpenResultSchema = exports.validationOpenRequestSchema = exports.validationVerdictRecordSchema = exports.realLifeAttestationResultSchema = exports.realLifeAttestationRecordSchema = exports.signedPublicCardSchema = exports.publicCardSchema = exports.logPageResponseSchema = exports.chainHeadResponseSchema = exports.oxySignedRecordTypeSchema = exports.exportBundleSchema = exports.exportAttestationSchema = exports.authMethodsResponseSchema = exports.authMethodEntrySchema = exports.domainVerificationInstructionsSchema = exports.domainVerificationRequestSchema = exports.verifiedDomainSchema = exports.signedRecordEnvelopeSchema = exports.didDocumentSchema = exports.didServiceSchema = exports.verificationMethodSchema = exports.appAffinityEventsIngestSchema = exports.appAffinityEventSchema = exports.appAffinityEventTypeSchema = exports.appUserSignalIngestSchema = void 0;
16
16
  exports.reverseModerationEffectSchema = exports.finalizeModerationDecisionSchema = exports.moderationDecisionEventSchema = exports.moderationPolicyVersionsSchema = exports.moderationDecisionEventSubjectSchema = exports.moderationFindingSchema = exports.applicationModerationStandingSchema = exports.identityBindingStatusSchema = exports.identityBindingTypeSchema = exports.personhoodStatusSchema = exports.contributionTierSchema = exports.conductStandingSchema = exports.conductStrikeStatusSchema = exports.moderationEffectSkipReasonSchema = exports.moderationEffectStatusSchema = exports.moderationEffectTypeSchema = exports.moderationDecisionStatusSchema = exports.moderationAttributionSchema = exports.moderationFindingScopeSchema = exports.moderationSeveritySchema = exports.APPLICATION_MODERATION_STANDINGS = exports.IDENTITY_BINDING_STATUSES = exports.IDENTITY_BINDING_TYPES = exports.PERSONHOOD_STATUSES = exports.CONTRIBUTION_TIERS = exports.CONDUCT_STANDINGS = exports.CONDUCT_STRIKE_STATUSES = exports.MODERATION_EFFECT_SKIP_REASONS = exports.MODERATION_EFFECT_STATUSES = exports.MODERATION_EFFECT_TYPES = exports.MODERATION_DECISION_STATUSES = exports.MODERATION_ATTRIBUTIONS = exports.MODERATION_FINDING_SCOPES = exports.MODERATION_SEVERITIES = exports.isFullReputationBalance = exports.reverseReputationTransactionSchema = exports.upsertReputationRuleSchema = exports.resolveReputationDisputeSchema = exports.createReputationDisputeSchema = exports.awardReputationSchema = exports.reverseReputationTransactionResultSchema = exports.reputationInfluenceResultSchema = exports.reputationLeaderboardEntrySchema = exports.reputationLeaderboardUserSchema = exports.reputationRuleSchema = exports.reputationDisputeSchema = exports.reputationBalanceSchema = exports.reputationBalanceSummarySchema = exports.reputationReliabilitySchema = exports.reputationInfluenceSchema = void 0;
17
- exports.assetCompleteResponseSchema = exports.assetCompleteResultItemSchema = exports.assetCompleteRequestSchema = exports.assetInitResponseSchema = exports.assetUploadTicketSchema = exports.assetInitRequestSchema = exports.assetInitItemSchema = exports.rolloutPercentSchema = exports.runtimeVersionSchema = exports.channelNameSchema = exports.sha256HexSchema = exports.updateAssetStatusSchema = exports.updateStatusSchema = exports.updatePlatformSchema = exports.backupStatusResponseSchema = exports.backupUploadRequestSchema = exports.encryptedBackupEnvelopeSchema = exports.backupLookupIdSchema = exports.rotateKeyCompleteResponseSchema = exports.rotateKeyCompleteRequestSchema = exports.rotateKeyChallengeResponseSchema = exports.loginResultSchema = exports.sessionAccountsChangedEventSchema = exports.sessionAccountsChangedReasonSchema = exports.SESSION_ACCOUNTS_CHANGED_EVENT = exports.deviceBackgroundTokenResponseSchema = exports.deviceBackgroundTokenRequestSchema = exports.deviceBackgroundCredentialResponseSchema = exports.deviceTokenMintResponseSchema = exports.deviceTokenMintRequestSchema = exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = exports.linkPreviewResponseSchema = exports.linkPreviewBatchResponseSchema = exports.linkPreviewBatchRequestSchema = exports.linkPreviewSchema = exports.applicationModerationTrustSchema = exports.reputationContextualInfluenceSchema = exports.reputationReviewingSchema = exports.reputationReportingSchema = exports.reputationConductSchema = exports.reputationContributionSchema = exports.reputationPersonhoodSchema = exports.identityBindingSchema = exports.registerIdentityBindingSchema = exports.reverseModerationEffectResultSchema = exports.applyModerationDecisionResultSchema = exports.moderationEffectSchema = void 0;
18
- exports.transparencyCheckpointListSchema = exports.transparencyInclusionProofSchema = exports.transparencyCheckpointSchema = exports.transparencyAnchorSchema = exports.transparencyCheckpointSignatureSchema = exports.deviceTransferDenyResponseSchema = exports.deviceTransferApproveResponseSchema = exports.deviceTransferApproveRequestSchema = exports.deviceTransferInfoResponseSchema = exports.deviceTransferInitResponseSchema = exports.deviceTransferInitRequestSchema = exports.devicePairingStatusSchema = exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = exports.updateRolloutPatchSchema = exports.promoteRequestSchema = exports.rollbackToEmbeddedRequestSchema = exports.rollbackRequestSchema = exports.updateListResponseSchema = exports.channelListResponseSchema = exports.channelSchema = exports.rollbackToEmbeddedEntrySchema = exports.createUpdateResponseSchema = exports.updateSchema = exports.createUpdateRequestSchema = exports.updateAssetRefSchema = void 0;
17
+ exports.hubActivateRequestSchema = exports.hubClaimRequestSchema = exports.hubSessionSchema = exports.browserHubRevokeResponseSchema = exports.browserHubErrorSchema = exports.browserHubResolveResponseSchema = exports.browserHubHandleResponseSchema = exports.browserHubHandleRequestSchema = exports.browserHubHandleSchema = exports.BROWSER_HUB_HANDLE_TTL_MS = exports.BROWSER_HUB_COOKIE_ATTRIBUTES = exports.BROWSER_HUB_COOKIE_NAME = exports.oauthAuthorizeCodeResponseSchema = exports.oauthConsentDecisionSchema = exports.deviceDirectorySyncSchema = exports.deviceActivateResponseSchema = exports.deviceActivateRequestSchema = exports.deviceDirectorySchema = exports.devicePrincipalSchema = exports.deviceAccountContextSchema = exports.deviceDirectoryProfileSchema = exports.deviceContextRelationshipSchema = exports.sessionAccountsChangedEventSchema = exports.sessionAccountsChangedReasonSchema = exports.SESSION_ACCOUNTS_CHANGED_EVENT = exports.deviceBackgroundTokenResponseSchema = exports.deviceBackgroundTokenRequestSchema = exports.deviceBackgroundCredentialResponseSchema = exports.deviceTokenMintResponseSchema = exports.deviceTokenMintRequestSchema = exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = exports.linkPreviewResponseSchema = exports.linkPreviewBatchResponseSchema = exports.linkPreviewBatchRequestSchema = exports.linkPreviewSchema = exports.applicationModerationTrustSchema = exports.reputationContextualInfluenceSchema = exports.reputationReviewingSchema = exports.reputationReportingSchema = exports.reputationConductSchema = exports.reputationContributionSchema = exports.reputationPersonhoodSchema = exports.identityBindingSchema = exports.registerIdentityBindingSchema = exports.reverseModerationEffectResultSchema = exports.applyModerationDecisionResultSchema = exports.moderationEffectSchema = void 0;
18
+ exports.transparencyCheckpointSchema = exports.transparencyAnchorSchema = exports.transparencyCheckpointSignatureSchema = exports.deviceTransferDenyResponseSchema = exports.deviceTransferApproveResponseSchema = exports.deviceTransferApproveRequestSchema = exports.deviceTransferInfoResponseSchema = exports.deviceTransferInitResponseSchema = exports.deviceTransferInitRequestSchema = exports.devicePairingStatusSchema = exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = exports.updateRolloutPatchSchema = exports.promoteRequestSchema = exports.rollbackToEmbeddedRequestSchema = exports.rollbackRequestSchema = exports.updateListResponseSchema = exports.channelListResponseSchema = exports.channelSchema = exports.rollbackToEmbeddedEntrySchema = exports.createUpdateResponseSchema = exports.updateSchema = exports.createUpdateRequestSchema = exports.updateAssetRefSchema = exports.assetCompleteResponseSchema = exports.assetCompleteResultItemSchema = exports.assetCompleteRequestSchema = exports.assetInitResponseSchema = exports.assetUploadTicketSchema = exports.assetInitRequestSchema = exports.assetInitItemSchema = exports.rolloutPercentSchema = exports.runtimeVersionSchema = exports.channelNameSchema = exports.sha256HexSchema = exports.updateAssetStatusSchema = exports.updateStatusSchema = exports.updatePlatformSchema = exports.backupStatusResponseSchema = exports.backupUploadRequestSchema = exports.encryptedBackupEnvelopeSchema = exports.backupLookupIdSchema = exports.rotateKeyCompleteResponseSchema = exports.rotateKeyCompleteRequestSchema = exports.rotateKeyChallengeResponseSchema = exports.loginResultSchema = exports.hubAuthorizeResultSchema = exports.hubAuthorizeRequestSchema = void 0;
19
+ exports.transparencyCheckpointListSchema = exports.transparencyInclusionProofSchema = void 0;
19
20
  var accountGraph_1 = require("./accountGraph");
20
21
  Object.defineProperty(exports, "ACCOUNT_KINDS", { enumerable: true, get: function () { return accountGraph_1.ACCOUNT_KINDS; } });
21
22
  Object.defineProperty(exports, "accountKindSchema", { enumerable: true, get: function () { return accountGraph_1.accountKindSchema; } });
@@ -234,6 +235,33 @@ Object.defineProperty(exports, "deviceBackgroundTokenResponseSchema", { enumerab
234
235
  Object.defineProperty(exports, "SESSION_ACCOUNTS_CHANGED_EVENT", { enumerable: true, get: function () { return deviceSession_1.SESSION_ACCOUNTS_CHANGED_EVENT; } });
235
236
  Object.defineProperty(exports, "sessionAccountsChangedReasonSchema", { enumerable: true, get: function () { return deviceSession_1.sessionAccountsChangedReasonSchema; } });
236
237
  Object.defineProperty(exports, "sessionAccountsChangedEventSchema", { enumerable: true, get: function () { return deviceSession_1.sessionAccountsChangedEventSchema; } });
238
+ var deviceDirectory_1 = require("./deviceDirectory");
239
+ Object.defineProperty(exports, "deviceContextRelationshipSchema", { enumerable: true, get: function () { return deviceDirectory_1.deviceContextRelationshipSchema; } });
240
+ Object.defineProperty(exports, "deviceDirectoryProfileSchema", { enumerable: true, get: function () { return deviceDirectory_1.deviceDirectoryProfileSchema; } });
241
+ Object.defineProperty(exports, "deviceAccountContextSchema", { enumerable: true, get: function () { return deviceDirectory_1.deviceAccountContextSchema; } });
242
+ Object.defineProperty(exports, "devicePrincipalSchema", { enumerable: true, get: function () { return deviceDirectory_1.devicePrincipalSchema; } });
243
+ Object.defineProperty(exports, "deviceDirectorySchema", { enumerable: true, get: function () { return deviceDirectory_1.deviceDirectorySchema; } });
244
+ Object.defineProperty(exports, "deviceActivateRequestSchema", { enumerable: true, get: function () { return deviceDirectory_1.deviceActivateRequestSchema; } });
245
+ Object.defineProperty(exports, "deviceActivateResponseSchema", { enumerable: true, get: function () { return deviceDirectory_1.deviceActivateResponseSchema; } });
246
+ Object.defineProperty(exports, "deviceDirectorySyncSchema", { enumerable: true, get: function () { return deviceDirectory_1.deviceDirectorySyncSchema; } });
247
+ var oauth_1 = require("./oauth");
248
+ Object.defineProperty(exports, "oauthConsentDecisionSchema", { enumerable: true, get: function () { return oauth_1.oauthConsentDecisionSchema; } });
249
+ Object.defineProperty(exports, "oauthAuthorizeCodeResponseSchema", { enumerable: true, get: function () { return oauth_1.oauthAuthorizeCodeResponseSchema; } });
250
+ var browserHub_1 = require("./browserHub");
251
+ Object.defineProperty(exports, "BROWSER_HUB_COOKIE_NAME", { enumerable: true, get: function () { return browserHub_1.BROWSER_HUB_COOKIE_NAME; } });
252
+ Object.defineProperty(exports, "BROWSER_HUB_COOKIE_ATTRIBUTES", { enumerable: true, get: function () { return browserHub_1.BROWSER_HUB_COOKIE_ATTRIBUTES; } });
253
+ Object.defineProperty(exports, "BROWSER_HUB_HANDLE_TTL_MS", { enumerable: true, get: function () { return browserHub_1.BROWSER_HUB_HANDLE_TTL_MS; } });
254
+ Object.defineProperty(exports, "browserHubHandleSchema", { enumerable: true, get: function () { return browserHub_1.browserHubHandleSchema; } });
255
+ Object.defineProperty(exports, "browserHubHandleRequestSchema", { enumerable: true, get: function () { return browserHub_1.browserHubHandleRequestSchema; } });
256
+ Object.defineProperty(exports, "browserHubHandleResponseSchema", { enumerable: true, get: function () { return browserHub_1.browserHubHandleResponseSchema; } });
257
+ Object.defineProperty(exports, "browserHubResolveResponseSchema", { enumerable: true, get: function () { return browserHub_1.browserHubResolveResponseSchema; } });
258
+ Object.defineProperty(exports, "browserHubErrorSchema", { enumerable: true, get: function () { return browserHub_1.browserHubErrorSchema; } });
259
+ Object.defineProperty(exports, "browserHubRevokeResponseSchema", { enumerable: true, get: function () { return browserHub_1.browserHubRevokeResponseSchema; } });
260
+ Object.defineProperty(exports, "hubSessionSchema", { enumerable: true, get: function () { return browserHub_1.hubSessionSchema; } });
261
+ Object.defineProperty(exports, "hubClaimRequestSchema", { enumerable: true, get: function () { return browserHub_1.hubClaimRequestSchema; } });
262
+ Object.defineProperty(exports, "hubActivateRequestSchema", { enumerable: true, get: function () { return browserHub_1.hubActivateRequestSchema; } });
263
+ Object.defineProperty(exports, "hubAuthorizeRequestSchema", { enumerable: true, get: function () { return browserHub_1.hubAuthorizeRequestSchema; } });
264
+ Object.defineProperty(exports, "hubAuthorizeResultSchema", { enumerable: true, get: function () { return browserHub_1.hubAuthorizeResultSchema; } });
237
265
  var deviceBoot_1 = require("./deviceBoot");
238
266
  // Schemas
239
267
  Object.defineProperty(exports, "loginResultSchema", { enumerable: true, get: function () { return deviceBoot_1.loginResultSchema; } });
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.oauthAuthorizeCodeResponseSchema = exports.oauthConsentDecisionSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * The two `/auth/oauth/*` responses the browser hub's edge layer reads.
7
+ *
8
+ * They existed on the wire long before this file — `GET /auth/oauth/consent`
9
+ * and `POST /auth/oauth/authorize` are the surface `auth.oxy.so` has always
10
+ * driven with a bearer. What is new (issue #937 Phase 5) is a SECOND consumer
11
+ * that is not the SPA: the IdP's edge layer runs both calls server-side so the
12
+ * device-wide bearer never enters the browser's script context. A shape read by
13
+ * two independently deployed consumers is a contract, so it is written down
14
+ * once here and validated on both sides rather than transcribed into the edge.
15
+ *
16
+ * These are NOT the RFC 6749 token/userinfo responses. Those two speak flat
17
+ * OAuth/OIDC on the wire and are the one place in the API that does not use the
18
+ * `{ data }` envelope; these two are ordinary internal API responses that happen
19
+ * to be about OAuth.
20
+ */
21
+ /**
22
+ * Server-authoritative answer to "must this user be shown a consent screen".
23
+ *
24
+ * Discriminated on `consentRequired` so the two arms cannot be confused by a
25
+ * consumer that reads `reason` first: `trusted`/`granted` are reasons NOT to
26
+ * ask, `new`/`scope_changed` are reasons to ask, and a flat object would let a
27
+ * typo in one produce a plausible value of the other.
28
+ *
29
+ * - `trusted` — the application is first-party/internal/system/official
30
+ * by the REGISTRY's verdict (`isTrustedApplication`), and
31
+ * the request names no scope over the user's own follow
32
+ * graph. Never inferred from a hostname.
33
+ * - `granted` — a prior `AppGrant` already covers every requested scope.
34
+ * - `scope_changed` — a prior grant exists and is missing one.
35
+ * - `new` — no prior grant.
36
+ *
37
+ * `userConsentScopes` names the scopes that FORCED the screen, so the consent UI
38
+ * can say which one it is asking about. Present only on the `true` arm, and only
39
+ * when such a scope exists — a trusted app asked for one is still asked.
40
+ */
41
+ exports.oauthConsentDecisionSchema = zod_1.z.discriminatedUnion('consentRequired', [
42
+ zod_1.z.object({
43
+ consentRequired: zod_1.z.literal(false),
44
+ reason: zod_1.z.enum(['trusted', 'granted']),
45
+ }),
46
+ zod_1.z.object({
47
+ consentRequired: zod_1.z.literal(true),
48
+ reason: zod_1.z.enum(['new', 'scope_changed']),
49
+ userConsentScopes: zod_1.z.array(zod_1.z.string()).optional(),
50
+ }),
51
+ ]);
52
+ /**
53
+ * A minted authorization code.
54
+ *
55
+ * `state` is echoed back as the caller sent it and is `null` when they sent
56
+ * none — never omitted, so a consumer cannot read "the server dropped my state"
57
+ * as "I sent none". `redirectUri` is echoed for the same reason the code is
58
+ * bound to it server-side: the caller must be able to see that the value the
59
+ * code was issued against is the one it registered.
60
+ */
61
+ exports.oauthAuthorizeCodeResponseSchema = zod_1.z.object({
62
+ code: zod_1.z.string().min(1),
63
+ state: zod_1.z.string().nullable(),
64
+ redirectUri: zod_1.z.string(),
65
+ expiresIn: zod_1.z.number().int().positive(),
66
+ });