@oxyhq/contracts 0.14.0 → 0.14.2
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/deviceSession.js +42 -1
- package/dist/cjs/identity.js +3 -1
- package/dist/cjs/index.js +43 -1
- package/dist/cjs/updates.js +250 -0
- package/dist/cjs/webauthn.js +63 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/deviceSession.js +41 -0
- package/dist/esm/identity.js +3 -1
- package/dist/esm/index.js +15 -1
- package/dist/esm/updates.js +247 -0
- package/dist/esm/webauthn.js +60 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/deviceSession.d.ts +42 -0
- package/dist/types/identity.d.ts +7 -2
- package/dist/types/index.d.ts +6 -2
- package/dist/types/updates.d.ts +535 -0
- package/dist/types/webauthn.d.ts +83 -0
- package/package.json +1 -1
|
@@ -69,3 +69,44 @@ export const deviceHubTicketRedeemResponseSchema = z.object({
|
|
|
69
69
|
deviceId: z.string().min(1),
|
|
70
70
|
deviceSecret: z.string().min(1),
|
|
71
71
|
});
|
|
72
|
+
/* -------------------------------------------------------------------------- */
|
|
73
|
+
/* Instant cross-app session sync (token-free socket signal) */
|
|
74
|
+
/* -------------------------------------------------------------------------- */
|
|
75
|
+
/**
|
|
76
|
+
* Name of the token-free Socket.IO event emitted to room `user:<userId>` on
|
|
77
|
+
* every DeviceSession mutation that changes what is signed in for that user.
|
|
78
|
+
*
|
|
79
|
+
* This is a pure SIGNAL — it carries NO access token, NO deviceSecret and NO
|
|
80
|
+
* account bodies. A client that receives it re-fetches its authenticated
|
|
81
|
+
* session/account state (`GET /session/device/state`, `GET /accounts`). Unlike
|
|
82
|
+
* `session_state` (scoped to `device:<deviceId>`, i.e. a single origin), this
|
|
83
|
+
* reaches ALL of a user's connected sockets across their devices/origins so
|
|
84
|
+
* every Oxy app reflects an add / switch / signout instantly.
|
|
85
|
+
*/
|
|
86
|
+
export const SESSION_ACCOUNTS_CHANGED_EVENT = 'session_accounts_changed';
|
|
87
|
+
/**
|
|
88
|
+
* Why the signed-in set changed:
|
|
89
|
+
* - `login` — a brand-new session was minted for the user (QR / cross-app authorize)
|
|
90
|
+
* - `add` — an account was registered onto a device set
|
|
91
|
+
* - `switch` — the active account on a device changed
|
|
92
|
+
* - `signout` — one or all accounts were signed out of a device
|
|
93
|
+
* - `revoke` — a dead/revoked account was healed out of a device set
|
|
94
|
+
*/
|
|
95
|
+
export const sessionAccountsChangedReasonSchema = z.enum([
|
|
96
|
+
'login',
|
|
97
|
+
'add',
|
|
98
|
+
'switch',
|
|
99
|
+
'signout',
|
|
100
|
+
'revoke',
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
103
|
+
* Payload of {@link SESSION_ACCOUNTS_CHANGED_EVENT}. `revision` is the mutated
|
|
104
|
+
* DeviceSession revision for device-scoped reasons (`add`/`switch`/`signout`/
|
|
105
|
+
* `revoke`); for `login` (no device mutation at emit time) it is `0`. The
|
|
106
|
+
* payload is deliberately minimal and secret-free — the client refetches.
|
|
107
|
+
*/
|
|
108
|
+
export const sessionAccountsChangedEventSchema = z.object({
|
|
109
|
+
userId: z.string(),
|
|
110
|
+
revision: z.number().int().nonnegative(),
|
|
111
|
+
reason: sessionAccountsChangedReasonSchema,
|
|
112
|
+
});
|
package/dist/esm/identity.js
CHANGED
|
@@ -187,9 +187,11 @@ export const domainVerificationInstructionsSchema = z.object({
|
|
|
187
187
|
}),
|
|
188
188
|
});
|
|
189
189
|
export const authMethodEntrySchema = z.object({
|
|
190
|
-
type: z.enum(['identity', 'password', 'google', 'apple', 'github']),
|
|
190
|
+
type: z.enum(['identity', 'password', 'google', 'apple', 'github', 'webauthn']),
|
|
191
191
|
linkedAt: z.union([z.string(), z.date()]),
|
|
192
192
|
verificationMethodId: z.string().optional(),
|
|
193
|
+
credentialId: z.string().optional(),
|
|
194
|
+
name: z.string().optional(),
|
|
193
195
|
});
|
|
194
196
|
export const authMethodsResponseSchema = z.object({
|
|
195
197
|
did: z.string(),
|
package/dist/esm/index.js
CHANGED
|
@@ -38,7 +38,21 @@ credentialRecordSchema, verifiableCredentialResponseSchema, credentialIssueResul
|
|
|
38
38
|
export {
|
|
39
39
|
// Schemas
|
|
40
40
|
linkPreviewSchema, linkPreviewBatchRequestSchema, linkPreviewBatchResponseSchema, linkPreviewResponseSchema, } from './links.js';
|
|
41
|
-
export { sessionAccountSchema, deviceSessionStateSchema, activeTokenSchema, deviceSessionSyncSchema, deviceTokenMintRequestSchema, deviceTokenMintResponseSchema, deviceHubTicketIssueRequestSchema, deviceHubTicketIssueResponseSchema, deviceHubTicketRedeemRequestSchema, deviceHubTicketRedeemResponseSchema, } from './deviceSession.js';
|
|
41
|
+
export { sessionAccountSchema, deviceSessionStateSchema, activeTokenSchema, deviceSessionSyncSchema, deviceTokenMintRequestSchema, deviceTokenMintResponseSchema, deviceHubTicketIssueRequestSchema, deviceHubTicketIssueResponseSchema, deviceHubTicketRedeemRequestSchema, deviceHubTicketRedeemResponseSchema, SESSION_ACCOUNTS_CHANGED_EVENT, sessionAccountsChangedReasonSchema, sessionAccountsChangedEventSchema, } from './deviceSession.js';
|
|
42
42
|
export {
|
|
43
43
|
// Schemas
|
|
44
44
|
loginResultSchema, } from './deviceBoot.js';
|
|
45
|
+
export {
|
|
46
|
+
// Shared primitives
|
|
47
|
+
updatePlatformSchema, updateStatusSchema, updateAssetStatusSchema, sha256HexSchema, channelNameSchema, runtimeVersionSchema, rolloutPercentSchema,
|
|
48
|
+
// Assets: init + complete
|
|
49
|
+
assetInitItemSchema, assetInitRequestSchema, assetUploadTicketSchema, assetInitResponseSchema, assetCompleteRequestSchema, assetCompleteResultItemSchema, assetCompleteResponseSchema,
|
|
50
|
+
// Create update
|
|
51
|
+
updateAssetRefSchema, createUpdateRequestSchema,
|
|
52
|
+
// Read models
|
|
53
|
+
updateSchema, createUpdateResponseSchema, rollbackToEmbeddedEntrySchema, channelSchema, channelListResponseSchema, updateListResponseSchema,
|
|
54
|
+
// Rollback / promote / rollout
|
|
55
|
+
rollbackRequestSchema, rollbackToEmbeddedRequestSchema, promoteRequestSchema, updateRolloutPatchSchema, } from './updates.js';
|
|
56
|
+
export {
|
|
57
|
+
// Schemas
|
|
58
|
+
webauthnRegisterOptionsRequestSchema, webauthnLoginOptionsRequestSchema, webauthnRegisterVerifyRequestSchema, webauthnLoginVerifyRequestSchema, } from './webauthn.js';
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oxy Updates (self-hosted expo-updates protocol) — publish/admin API contracts.
|
|
3
|
+
*
|
|
4
|
+
* SINGLE SOURCE OF TRUTH for the wire shape of the AUTHENTICATED publish/admin
|
|
5
|
+
* surface that the `oxy-ship` CLI and the console Updates tab call. The PUBLIC
|
|
6
|
+
* manifest endpoint (`GET /updates/v1/apps/:clientId/manifest`) speaks the
|
|
7
|
+
* expo-updates v1 protocol verbatim (multipart/mixed, signed) and is therefore
|
|
8
|
+
* NOT modelled here — its shape is dictated by the Expo spec, not by us.
|
|
9
|
+
*
|
|
10
|
+
* The API validates its OUTPUT against these schemas; every consumer (the ship
|
|
11
|
+
* CLI, the console hook) validates its INPUT against the same definitions, so
|
|
12
|
+
* producer and consumers cannot drift.
|
|
13
|
+
*
|
|
14
|
+
* Domain model (mirrors the Mongoose models in `@oxyhq/api`):
|
|
15
|
+
* - A `channel` (e.g. `production`, `preview`, `pr-123`) is a named release
|
|
16
|
+
* track for one application.
|
|
17
|
+
* - An `update` is one published bundle for a single `(channel, runtimeVersion,
|
|
18
|
+
* platform)`. Its `updateId` is a UUIDv4 (the client parses it as a UUID).
|
|
19
|
+
* The HEAD of a track is the newest `published` update for that tuple.
|
|
20
|
+
* - An `asset` is content-addressed by its `sha256`; assets are shared across
|
|
21
|
+
* updates and applications (an unchanged JS bundle is uploaded once).
|
|
22
|
+
*
|
|
23
|
+
* Platform-agnostic — zod only, no react/react-native/expo. ESM-safe (no
|
|
24
|
+
* `require()`).
|
|
25
|
+
*/
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
/* -------------------------------------------------------------------------- */
|
|
28
|
+
/* Shared primitives */
|
|
29
|
+
/* -------------------------------------------------------------------------- */
|
|
30
|
+
/** The two platforms the expo-updates protocol addresses. */
|
|
31
|
+
export const updatePlatformSchema = z.enum(['ios', 'android']);
|
|
32
|
+
/** Lifecycle of a single published update row. */
|
|
33
|
+
export const updateStatusSchema = z.enum(['published', 'superseded', 'rolled_back']);
|
|
34
|
+
/** Upload lifecycle of a content-addressed asset. */
|
|
35
|
+
export const updateAssetStatusSchema = z.enum(['pending', 'uploaded']);
|
|
36
|
+
/** Lowercase-hex SHA-256 content hash (64 hex chars). */
|
|
37
|
+
export const sha256HexSchema = z
|
|
38
|
+
.string()
|
|
39
|
+
.regex(/^[a-f0-9]{64}$/, 'sha256 must be 64 lowercase hex characters');
|
|
40
|
+
/**
|
|
41
|
+
* A channel name. Constrained to a URL/path-safe slug so it can appear in the
|
|
42
|
+
* `expo-channel-name` header and be used CI-friendly for `pr-<n>` tracks. No
|
|
43
|
+
* colon (BullMQ/id safety) and no slash (path safety).
|
|
44
|
+
*/
|
|
45
|
+
export const channelNameSchema = z
|
|
46
|
+
.string()
|
|
47
|
+
.min(1)
|
|
48
|
+
.max(100)
|
|
49
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, 'channel name must be a URL-safe slug');
|
|
50
|
+
/** A runtime version string (expo-updates `runtimeVersion`, e.g. an appVersion). */
|
|
51
|
+
export const runtimeVersionSchema = z.string().min(1).max(255);
|
|
52
|
+
/** A rollout percentage: 0 rolls out to nobody, 100 to everybody. */
|
|
53
|
+
export const rolloutPercentSchema = z.number().int().min(0).max(100);
|
|
54
|
+
/* -------------------------------------------------------------------------- */
|
|
55
|
+
/* Assets: init + complete */
|
|
56
|
+
/* -------------------------------------------------------------------------- */
|
|
57
|
+
/**
|
|
58
|
+
* One asset the client intends to upload as part of an update. `sha256` is the
|
|
59
|
+
* content hash (dedup key); `contentType` and `size` describe the object the
|
|
60
|
+
* server will accept at the presigned URL.
|
|
61
|
+
*/
|
|
62
|
+
export const assetInitItemSchema = z.object({
|
|
63
|
+
sha256: sha256HexSchema,
|
|
64
|
+
contentType: z.string().min(1).max(255),
|
|
65
|
+
size: z.number().int().positive(),
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* `POST /updates/v1/assets/init` request. Declares the full asset set of an
|
|
69
|
+
* update; the server replies with a presigned PUT for every asset it does NOT
|
|
70
|
+
* already hold (content-addressed dedup — unchanged assets are never re-uploaded).
|
|
71
|
+
*/
|
|
72
|
+
export const assetInitRequestSchema = z.object({
|
|
73
|
+
applicationId: z.string().min(1),
|
|
74
|
+
assets: z.array(assetInitItemSchema).min(1).max(2000),
|
|
75
|
+
});
|
|
76
|
+
/** One presigned upload the client must PUT its bytes to before completing. */
|
|
77
|
+
export const assetUploadTicketSchema = z.object({
|
|
78
|
+
sha256: sha256HexSchema,
|
|
79
|
+
/** Presigned S3 PUT URL. The client PUTs the exact bytes here. */
|
|
80
|
+
uploadUrl: z.string(),
|
|
81
|
+
/** The S3 key the object will live at (`public/updates/assets/<sha256>`). */
|
|
82
|
+
storageKey: z.string(),
|
|
83
|
+
/** The Content-Type the presigned URL was signed for; echo it on the PUT. */
|
|
84
|
+
contentType: z.string(),
|
|
85
|
+
/**
|
|
86
|
+
* The Cache-Control the presigned URL was signed for; the client MUST send it
|
|
87
|
+
* verbatim on the PUT so the SigV4 signature matches and the stored object
|
|
88
|
+
* carries the long immutable cache header (assets are content-addressed).
|
|
89
|
+
*/
|
|
90
|
+
cacheControl: z.string(),
|
|
91
|
+
});
|
|
92
|
+
/**
|
|
93
|
+
* `POST /updates/v1/assets/init` response. `missing` holds a presigned upload
|
|
94
|
+
* for each asset the server does not yet have; `existing` lists the sha256s it
|
|
95
|
+
* already holds (the client skips those).
|
|
96
|
+
*/
|
|
97
|
+
export const assetInitResponseSchema = z.object({
|
|
98
|
+
missing: z.array(assetUploadTicketSchema),
|
|
99
|
+
existing: z.array(sha256HexSchema),
|
|
100
|
+
});
|
|
101
|
+
/**
|
|
102
|
+
* `POST /updates/v1/assets/complete` request. Sent after the client has PUT
|
|
103
|
+
* every `missing` asset. The server HEADs each object and flips it to
|
|
104
|
+
* `uploaded`; an object that is absent or size-mismatched is rejected.
|
|
105
|
+
*/
|
|
106
|
+
export const assetCompleteRequestSchema = z.object({
|
|
107
|
+
applicationId: z.string().min(1),
|
|
108
|
+
sha256s: z.array(sha256HexSchema).min(1).max(2000),
|
|
109
|
+
});
|
|
110
|
+
/** Per-asset verification outcome from `assets/complete`. */
|
|
111
|
+
export const assetCompleteResultItemSchema = z.object({
|
|
112
|
+
sha256: sha256HexSchema,
|
|
113
|
+
status: updateAssetStatusSchema,
|
|
114
|
+
size: z.number().int().nonnegative(),
|
|
115
|
+
});
|
|
116
|
+
export const assetCompleteResponseSchema = z.object({
|
|
117
|
+
assets: z.array(assetCompleteResultItemSchema),
|
|
118
|
+
});
|
|
119
|
+
/* -------------------------------------------------------------------------- */
|
|
120
|
+
/* Create update */
|
|
121
|
+
/* -------------------------------------------------------------------------- */
|
|
122
|
+
/**
|
|
123
|
+
* A single asset reference inside a create-update request. `key` is the
|
|
124
|
+
* expo-export asset key (the md5-basename the client uses to look the asset up
|
|
125
|
+
* and skip embedded ones); `fileExtension` is the suggested on-disk extension.
|
|
126
|
+
*/
|
|
127
|
+
export const updateAssetRefSchema = z.object({
|
|
128
|
+
sha256: sha256HexSchema,
|
|
129
|
+
/** expo-export asset key (md5 basename) — how app code references the asset. */
|
|
130
|
+
key: z.string().min(1),
|
|
131
|
+
contentType: z.string().min(1),
|
|
132
|
+
/** Suggested file extension including the leading dot (e.g. `.js`, `.png`). */
|
|
133
|
+
fileExtension: z.string().optional(),
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* `POST /updates/v1/updates` request. Publishes one bundle for one platform to a
|
|
137
|
+
* channel (the channel is created on demand — CI-friendly for `pr-<n>`). All
|
|
138
|
+
* referenced assets MUST already be `uploaded` (via init/complete). `extra`
|
|
139
|
+
* MUST carry `expoClient` so `Constants.expoConfig` works after an OTA update.
|
|
140
|
+
*/
|
|
141
|
+
export const createUpdateRequestSchema = z.object({
|
|
142
|
+
applicationId: z.string().min(1),
|
|
143
|
+
channel: channelNameSchema,
|
|
144
|
+
runtimeVersion: runtimeVersionSchema,
|
|
145
|
+
platform: updatePlatformSchema,
|
|
146
|
+
/** The launch (entry-point) asset — its `fileExtension` is ignored by clients. */
|
|
147
|
+
launchAsset: updateAssetRefSchema,
|
|
148
|
+
assets: z.array(updateAssetRefSchema),
|
|
149
|
+
/**
|
|
150
|
+
* Opaque `extra` blob embedded verbatim in the signed manifest. MUST contain
|
|
151
|
+
* `expoClient` (the public expo config) so `Constants.expoConfig` resolves
|
|
152
|
+
* after an OTA update; MAY carry other third-party config.
|
|
153
|
+
*/
|
|
154
|
+
extra: z
|
|
155
|
+
.object({ expoClient: z.record(z.string(), z.unknown()) })
|
|
156
|
+
.catchall(z.unknown()),
|
|
157
|
+
/** String→string metadata dict; filtered client-side via manifest filters. */
|
|
158
|
+
metadata: z.record(z.string(), z.string()).optional(),
|
|
159
|
+
/** Initial rollout percentage (default 100 — full rollout). */
|
|
160
|
+
rolloutPercent: rolloutPercentSchema.optional(),
|
|
161
|
+
/** Git commit the bundle was built from (audit / console display). */
|
|
162
|
+
gitCommit: z.string().max(100).optional(),
|
|
163
|
+
/** Git branch the bundle was built from (audit / console display). */
|
|
164
|
+
gitBranch: z.string().max(200).optional(),
|
|
165
|
+
/** Human-readable publish message (console display). */
|
|
166
|
+
message: z.string().max(500).optional(),
|
|
167
|
+
});
|
|
168
|
+
export const updateSchema = z.object({
|
|
169
|
+
id: z.string(),
|
|
170
|
+
applicationId: z.string(),
|
|
171
|
+
channel: z.string(),
|
|
172
|
+
runtimeVersion: z.string(),
|
|
173
|
+
platform: updatePlatformSchema,
|
|
174
|
+
status: updateStatusSchema,
|
|
175
|
+
rolloutPercent: rolloutPercentSchema,
|
|
176
|
+
launchAssetSha256: sha256HexSchema,
|
|
177
|
+
assetSha256s: z.array(sha256HexSchema),
|
|
178
|
+
gitCommit: z.string().optional(),
|
|
179
|
+
gitBranch: z.string().optional(),
|
|
180
|
+
message: z.string().optional(),
|
|
181
|
+
promotedFromUpdateId: z.string().optional(),
|
|
182
|
+
createdAt: z.string(),
|
|
183
|
+
updatedAt: z.string(),
|
|
184
|
+
});
|
|
185
|
+
export const createUpdateResponseSchema = z.object({
|
|
186
|
+
update: updateSchema,
|
|
187
|
+
});
|
|
188
|
+
export const rollbackToEmbeddedEntrySchema = z.object({
|
|
189
|
+
runtimeVersion: z.string(),
|
|
190
|
+
platform: updatePlatformSchema,
|
|
191
|
+
commitTime: z.string(),
|
|
192
|
+
});
|
|
193
|
+
export const channelSchema = z.object({
|
|
194
|
+
id: z.string(),
|
|
195
|
+
applicationId: z.string(),
|
|
196
|
+
name: z.string(),
|
|
197
|
+
rollbacksToEmbedded: z.array(rollbackToEmbeddedEntrySchema),
|
|
198
|
+
createdAt: z.string(),
|
|
199
|
+
updatedAt: z.string(),
|
|
200
|
+
});
|
|
201
|
+
export const channelListResponseSchema = z.object({
|
|
202
|
+
channels: z.array(channelSchema),
|
|
203
|
+
});
|
|
204
|
+
export const updateListResponseSchema = z.object({
|
|
205
|
+
updates: z.array(updateSchema),
|
|
206
|
+
});
|
|
207
|
+
/* -------------------------------------------------------------------------- */
|
|
208
|
+
/* Rollback / rollback-to-embedded / promote / rollout */
|
|
209
|
+
/* -------------------------------------------------------------------------- */
|
|
210
|
+
/**
|
|
211
|
+
* `POST /updates/v1/channels/:channel/rollback` request. Marks the current head
|
|
212
|
+
* for `(runtimeVersion, platform)` `rolled_back` so the previous published
|
|
213
|
+
* update (if any) becomes head again. Nothing is deleted.
|
|
214
|
+
*/
|
|
215
|
+
export const rollbackRequestSchema = z.object({
|
|
216
|
+
applicationId: z.string().min(1),
|
|
217
|
+
runtimeVersion: runtimeVersionSchema,
|
|
218
|
+
platform: updatePlatformSchema,
|
|
219
|
+
});
|
|
220
|
+
/**
|
|
221
|
+
* `POST /updates/v1/channels/:channel/rollback-to-embedded` request. Records a
|
|
222
|
+
* `rollBackToEmbedded` directive so clients on this `(runtimeVersion, platform)`
|
|
223
|
+
* fall back to the update embedded in their binary.
|
|
224
|
+
*/
|
|
225
|
+
export const rollbackToEmbeddedRequestSchema = z.object({
|
|
226
|
+
applicationId: z.string().min(1),
|
|
227
|
+
runtimeVersion: runtimeVersionSchema,
|
|
228
|
+
platform: updatePlatformSchema,
|
|
229
|
+
});
|
|
230
|
+
/**
|
|
231
|
+
* `POST /updates/v1/channels/:channel/promote` request. Promotes an existing
|
|
232
|
+
* update (by `updateId`) into the target channel by creating a NEW update (new
|
|
233
|
+
* UUID) pointing at the SAME assets. `toChannel` defaults to the path channel.
|
|
234
|
+
*/
|
|
235
|
+
export const promoteRequestSchema = z.object({
|
|
236
|
+
applicationId: z.string().min(1),
|
|
237
|
+
updateId: z.string().min(1),
|
|
238
|
+
/** Target channel to promote into. Defaults to the path `:channel`. */
|
|
239
|
+
toChannel: channelNameSchema.optional(),
|
|
240
|
+
/** Rollout percentage for the promoted update (default 100). */
|
|
241
|
+
rolloutPercent: rolloutPercentSchema.optional(),
|
|
242
|
+
});
|
|
243
|
+
/** `PATCH /updates/v1/updates/:updateId` request — adjust a rollout in place. */
|
|
244
|
+
export const updateRolloutPatchSchema = z.object({
|
|
245
|
+
applicationId: z.string().min(1),
|
|
246
|
+
rolloutPercent: rolloutPercentSchema,
|
|
247
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuthn / passkey ceremony contracts (Fase B/b1).
|
|
3
|
+
*
|
|
4
|
+
* These schemas describe ONLY the outer Oxy envelope that wraps a WebAuthn
|
|
5
|
+
* ceremony request — the username the client is registering/authenticating as,
|
|
6
|
+
* plus the device-session options every first-party sign-in accepts. The browser
|
|
7
|
+
* `RegistrationResponseJSON` / `AuthenticationResponseJSON` payloads are NOT
|
|
8
|
+
* mirrored here: they are validated by `@simplewebauthn/server` inside the route
|
|
9
|
+
* (`verifyRegistrationResponse` / `verifyAuthenticationResponse`), which is the
|
|
10
|
+
* single source of truth for their structure. Re-encoding them in Zod would just
|
|
11
|
+
* create a second, drift-prone definition of a shape we do not own.
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
/**
|
|
15
|
+
* Device-session options shared by every first-party sign-in body
|
|
16
|
+
* (`deviceName`/`deviceFingerprint`/`deviceId`). Mirrors what
|
|
17
|
+
* `sessionCreateOptionsFromBody` reads in the API so a WebAuthn login/verify can
|
|
18
|
+
* name and pin its resulting session exactly like `/auth/login` or `/auth/verify`.
|
|
19
|
+
*/
|
|
20
|
+
const deviceSessionEnvelope = {
|
|
21
|
+
deviceName: z.string().trim().min(1).max(120).optional(),
|
|
22
|
+
deviceFingerprint: z.string().trim().min(1).max(256).optional(),
|
|
23
|
+
deviceId: z.string().trim().min(1).max(256).optional(),
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* `POST /webauthn/register/options` — request registration options. With a bearer
|
|
27
|
+
* token the caller links a passkey to their signed-in account and `username` is
|
|
28
|
+
* ignored; without one it is a prospective signup and `username` is the desired
|
|
29
|
+
* (not-yet-created) handle.
|
|
30
|
+
*/
|
|
31
|
+
export const webauthnRegisterOptionsRequestSchema = z.object({
|
|
32
|
+
username: z.string().trim().min(1).max(60).optional(),
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* `POST /webauthn/login/options` — request authentication options. When
|
|
36
|
+
* `username` is present the server scopes `allowCredentials` to that user's
|
|
37
|
+
* passkeys (username-first); when omitted it returns an empty allow-list for the
|
|
38
|
+
* usernameless / discoverable-credential flow (the default).
|
|
39
|
+
*/
|
|
40
|
+
export const webauthnLoginOptionsRequestSchema = z.object({
|
|
41
|
+
username: z.string().trim().min(1).max(60).optional(),
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* `POST /webauthn/register/verify` — the outer envelope. The browser
|
|
45
|
+
* `RegistrationResponseJSON` travels alongside these fields under `response` and
|
|
46
|
+
* is validated by `@simplewebauthn/server`, not here. `username` is required only
|
|
47
|
+
* for the prospective-signup branch (no bearer); the linking branch ignores it.
|
|
48
|
+
*/
|
|
49
|
+
export const webauthnRegisterVerifyRequestSchema = z.object({
|
|
50
|
+
username: z.string().trim().min(1).max(60).optional(),
|
|
51
|
+
...deviceSessionEnvelope,
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* `POST /webauthn/login/verify` — the outer envelope. The browser
|
|
55
|
+
* `AuthenticationResponseJSON` travels alongside these fields under `response`
|
|
56
|
+
* and is validated by `@simplewebauthn/server`, not here.
|
|
57
|
+
*/
|
|
58
|
+
export const webauthnLoginVerifyRequestSchema = z.object({
|
|
59
|
+
...deviceSessionEnvelope,
|
|
60
|
+
});
|