@omelhorsite/sdk 0.3.0 → 0.4.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 +113 -131
- package/dist/index.js +1049 -1246
- package/dist/types/auth/device.d.ts +1 -1
- package/dist/types/auth/index.d.ts +2 -2
- package/dist/types/auth/tokens.d.ts +15 -15
- package/dist/types/client.d.ts +10 -10
- package/dist/types/errors.d.ts +12 -15
- package/dist/types/http.d.ts +74 -118
- package/dist/types/index.d.ts +2 -2
- package/dist/types/internal/attachments.d.ts +17 -0
- package/dist/types/internal/helpers.d.ts +9 -0
- package/dist/types/listing.d.ts +90 -0
- package/dist/types/local/qr.d.ts +1 -1
- package/dist/types/local/wordlist.d.ts +2 -3
- package/dist/types/resources/account.d.ts +19 -19
- package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
- package/dist/types/resources/admin/chests.d.ts +51 -0
- package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
- package/dist/types/resources/admin/identities.d.ts +99 -0
- package/dist/types/resources/admin/index.d.ts +77 -0
- package/dist/types/resources/admin/jobs.d.ts +97 -0
- package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
- package/dist/types/resources/admin/notepads.d.ts +49 -0
- package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
- package/dist/types/resources/admin/quotas.d.ts +114 -0
- package/dist/types/resources/admin/shortLinks.d.ts +201 -0
- package/dist/types/resources/admin/types.d.ts +222 -0
- package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
- package/dist/types/resources/admin.d.ts +17 -13
- package/dist/types/resources/auth/index.d.ts +1 -1
- package/dist/types/resources/auth/passkeys.d.ts +127 -163
- package/dist/types/resources/auth/sessions.d.ts +116 -161
- package/dist/types/resources/chests.d.ts +27 -31
- package/dist/types/resources/content/analysis.d.ts +62 -0
- package/dist/types/resources/content/blogs.d.ts +471 -0
- package/dist/types/resources/content/feedbacks.d.ts +263 -0
- package/dist/types/resources/content/index.d.ts +52 -0
- package/dist/types/resources/content/intel/articles.d.ts +230 -0
- package/dist/types/resources/content/intel/config.d.ts +135 -0
- package/dist/types/resources/content/intel/index.d.ts +53 -0
- package/dist/types/resources/content/intel/items.d.ts +91 -0
- package/dist/types/resources/content/intel/reports.d.ts +108 -0
- package/dist/types/resources/content/intel/scripts.d.ts +152 -0
- package/dist/types/resources/content/intel/sources.d.ts +236 -0
- package/dist/types/resources/content/intel/stats.d.ts +105 -0
- package/dist/types/resources/content/intel/types.d.ts +86 -0
- package/dist/types/resources/content/jokes.d.ts +93 -0
- package/dist/types/resources/content/notifications.d.ts +147 -0
- package/dist/types/resources/content/serviceUsages.d.ts +73 -0
- package/dist/types/resources/content/servicesStatus.d.ts +194 -0
- package/dist/types/resources/content/siteConfig.d.ts +43 -0
- package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
- package/dist/types/resources/content.d.ts +41 -236
- package/dist/types/resources/dynamicQrs.d.ts +29 -45
- package/dist/types/resources/forms.d.ts +37 -58
- package/dist/types/resources/jobs.d.ts +35 -44
- package/dist/types/resources/library/annotations.d.ts +162 -0
- package/dist/types/resources/library/books.d.ts +534 -0
- package/dist/types/resources/library/chat.d.ts +259 -0
- package/dist/types/resources/library/index.d.ts +39 -0
- package/dist/types/resources/library/shelves.d.ts +237 -0
- package/dist/types/resources/library/types.d.ts +120 -0
- package/dist/types/resources/library.d.ts +20 -22
- package/dist/types/resources/media.d.ts +48 -61
- package/dist/types/resources/movies/addons.d.ts +388 -0
- package/dist/types/resources/movies/collections.d.ts +352 -0
- package/dist/types/resources/movies/index.d.ts +29 -0
- package/dist/types/resources/movies/types.d.ts +50 -0
- package/dist/types/resources/movies/watchProgress.d.ts +277 -0
- package/dist/types/resources/movies.d.ts +27 -53
- package/dist/types/resources/music/artists.d.ts +182 -246
- package/dist/types/resources/music/imports.d.ts +190 -218
- package/dist/types/resources/music/index.d.ts +8 -7
- package/dist/types/resources/music/playlists.d.ts +85 -117
- package/dist/types/resources/music/social.d.ts +153 -228
- package/dist/types/resources/music/songs.d.ts +165 -227
- package/dist/types/resources/realtime.d.ts +75 -88
- package/dist/types/resources/shortLinks.d.ts +38 -47
- package/dist/types/resources/social/groupChats.d.ts +458 -0
- package/dist/types/resources/social/index.d.ts +19 -0
- package/dist/types/resources/social/messages.d.ts +424 -0
- package/dist/types/resources/social/relationships.d.ts +322 -0
- package/dist/types/resources/social/types.d.ts +37 -0
- package/dist/types/resources/social.d.ts +21 -3
- package/dist/types/resources/storage/upload.d.ts +42 -56
- package/dist/types/resources/storage.d.ts +94 -109
- package/dist/types/resources/tickets.d.ts +19 -10
- package/dist/types/resources/tools/backgroundRemoval.d.ts +11 -13
- package/dist/types/resources/tools/captions.d.ts +107 -135
- package/dist/types/resources/tools/upscale.d.ts +12 -16
- package/dist/types/types.d.ts +29 -38
- package/package.json +1 -1
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* | `POST /authentication_options` | **none** | **20/min per IP** |
|
|
14
14
|
* | `POST /authentication` | **none** | **20/min per IP** |
|
|
15
15
|
*
|
|
16
|
-
* The last two share ONE bucket, not one each:
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* The last two share ONE bucket, not one each: the throttle matches every
|
|
17
|
+
* path that starts with `/webauthn_credentials/authentication`, and
|
|
18
|
+
* `/authentication_options` starts with that string. Twenty POSTs a minute
|
|
19
19
|
* from an IP covers both halves of every login attempt, so a NATed office gets
|
|
20
20
|
* ten sign-ins a minute between them. Budget accordingly, and see the note on
|
|
21
21
|
* {@link PasskeysNamespace.authenticate} for why neither of those two methods
|
|
@@ -29,16 +29,12 @@
|
|
|
29
29
|
* say so in those words. It says `500`, or it says "Passkey could not be
|
|
30
30
|
* verified", two minutes after the user pressed their fingerprint.
|
|
31
31
|
*
|
|
32
|
-
* The SDK's one body-shaped rewrite is the null sentinel, and
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* every method here passes the caller's object straight to `http.post` with no
|
|
39
|
-
* copy, no reshaping, and no key filtering. `test/passkeys.test.ts` pins the
|
|
40
|
-
* serialised body byte for byte, including a `null` that must survive as
|
|
41
|
-
* `null`.
|
|
32
|
+
* The SDK's one body-shaped rewrite is the null sentinel, and it is written
|
|
33
|
+
* into query strings only, never into a JSON body. A ceremony travels in the
|
|
34
|
+
* BODY, and every method here passes the caller's object straight to
|
|
35
|
+
* `http.post` with no copy, no reshaping, and no key filtering: a `null`
|
|
36
|
+
* inside survives as `null`. `test/passkeys.test.ts` pins the serialised body
|
|
37
|
+
* byte for byte.
|
|
42
38
|
*
|
|
43
39
|
* The corollary is that nothing here normalises for you either. If your
|
|
44
40
|
* platform is loose about base64 padding, run the credential through
|
|
@@ -51,15 +47,14 @@
|
|
|
51
47
|
*
|
|
52
48
|
* It never calls `navigator.credentials` and never touches a native passkey
|
|
53
49
|
* module. That is the host's job, in the host's runtime, and it is the half
|
|
54
|
-
* that differs between
|
|
50
|
+
* that differs between runtimes:
|
|
55
51
|
*
|
|
56
52
|
* - browser: `@simplewebauthn/browser`'s `startRegistration` /
|
|
57
53
|
* `startAuthentication`, which take `optionsJSON` in the exact shape
|
|
58
54
|
* {@link PasskeyRegistrationOptions} / {@link PasskeyAuthenticationOptions}
|
|
59
55
|
* describe and hand back the exact shape the credential types describe;
|
|
60
|
-
* - React Native: `react-native-passkeys`, loaded lazily because
|
|
61
|
-
*
|
|
62
|
-
* (Expo Go, a stale dev client);
|
|
56
|
+
* - React Native: `react-native-passkeys`, best loaded lazily because it
|
|
57
|
+
* throws at import time when the native module is not linked;
|
|
63
58
|
* - Bun / a Worker: there is no authenticator. `list` and `remove` work,
|
|
64
59
|
* the ceremonies cannot.
|
|
65
60
|
*
|
|
@@ -69,41 +64,35 @@ import { Resource } from "../../http";
|
|
|
69
64
|
import type { AccountSession } from "../account";
|
|
70
65
|
import type { BaseRecord, Id, RequestOptions, Timestamp } from "../../types";
|
|
71
66
|
/**
|
|
72
|
-
* One registered passkey
|
|
73
|
-
*
|
|
74
|
-
* The
|
|
75
|
-
* `
|
|
76
|
-
* `
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* `
|
|
84
|
-
*
|
|
85
|
-
* phone" unless the user typed a nickname.
|
|
86
|
-
*
|
|
87
|
-
* `id` is a STRING (`create_table :webauthn_credentials, id: :string`), like
|
|
88
|
-
* users and sessions and unlike songs and playlists. Never do arithmetic on it.
|
|
67
|
+
* One registered passkey.
|
|
68
|
+
*
|
|
69
|
+
* The payload is exactly `id`, `created_at`, `updated_at`, `nickname`,
|
|
70
|
+
* `last_used_at`, and it is the same five fields whether it came from
|
|
71
|
+
* `POST /registration` or from `GET /`.
|
|
72
|
+
*
|
|
73
|
+
* Nothing about the credential itself is ever rendered. The credential id,
|
|
74
|
+
* the public key and the sign counter stay on the server; there is no field
|
|
75
|
+
* here that identifies the authenticator, so a UI cannot say "your YubiKey"
|
|
76
|
+
* or "this phone" unless the user typed a nickname.
|
|
77
|
+
*
|
|
78
|
+
* `id` is a STRING, like users and sessions and unlike songs and playlists.
|
|
79
|
+
* Never do arithmetic on it.
|
|
89
80
|
*/
|
|
90
81
|
export interface Passkey extends BaseRecord {
|
|
91
82
|
/**
|
|
92
83
|
* User-chosen label, or `null`.
|
|
93
84
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* the wrong thing; read the answer instead.
|
|
85
|
+
* The server trims it and stores a blank as `null`, so `" "` comes back as
|
|
86
|
+
* `null`, not as the spaces that were sent. A UI that echoes what it just
|
|
87
|
+
* submitted will show the wrong thing; read the answer instead.
|
|
98
88
|
*/
|
|
99
89
|
readonly nickname: string | null;
|
|
100
90
|
/**
|
|
101
91
|
* When this passkey last completed a sign-in, or `null` if it never has.
|
|
102
92
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* edit: the record has no update route.
|
|
93
|
+
* Every sign-in also bumps `updated_at`, so a passkey's `updated_at` tracks
|
|
94
|
+
* its last use rather than its last edit. There is no edit: the record has
|
|
95
|
+
* no update route.
|
|
107
96
|
*/
|
|
108
97
|
readonly last_used_at: Timestamp | null;
|
|
109
98
|
}
|
|
@@ -111,16 +100,15 @@ export interface Passkey extends BaseRecord {
|
|
|
111
100
|
* What `POST /webauthn_credentials/authentication` answers with: the session,
|
|
112
101
|
* plus the token, exactly as `POST /sessions` answers a password sign-in.
|
|
113
102
|
*
|
|
114
|
-
*
|
|
115
|
-
* is an {@link AccountSession} with a `token`. That token is the new
|
|
103
|
+
* This is an {@link AccountSession} with a `token`. That token is the new
|
|
116
104
|
* credential, and it is shown ONCE. Persist it here or lose it.
|
|
117
105
|
*
|
|
118
|
-
* A browser gets the same token a second way, as an httpOnly cookie
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
106
|
+
* A browser gets the same token a second way, as an httpOnly cookie set
|
|
107
|
+
* alongside the body. That is why a cookie-mode client can ignore `token`
|
|
108
|
+
* entirely and still be signed in after the post-login reload: it
|
|
109
|
+
* authenticates by cookie and deliberately never stores a token where a
|
|
110
|
+
* script could read it. A token-mode client does the opposite and reads
|
|
111
|
+
* `token`.
|
|
124
112
|
*/
|
|
125
113
|
export interface PasskeySession extends AccountSession {
|
|
126
114
|
/** The bearer token for the new session. Rendered once, here, and never again. */
|
|
@@ -129,10 +117,8 @@ export interface PasskeySession extends AccountSession {
|
|
|
129
117
|
/**
|
|
130
118
|
* An `ArrayBuffer` after the only encoding this API speaks: unpadded base64url.
|
|
131
119
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* (`WebAuthn::Encoders::Base64UrlEncoder#encode`) has its `=` padding chomped
|
|
135
|
-
* off, and everything it reads back it decodes the same way.
|
|
120
|
+
* The server uses it in BOTH directions: everything it sends has its `=`
|
|
121
|
+
* padding chomped off, and everything it reads back it decodes the same way.
|
|
136
122
|
*
|
|
137
123
|
* A type alias, not a branded type, because a brand would force every caller to
|
|
138
124
|
* cast the strings their platform just handed them and would buy nothing: the
|
|
@@ -153,24 +139,21 @@ export type PasskeyTransport = "usb" | "nfc" | "ble" | "smart-card" | "hybrid" |
|
|
|
153
139
|
* One credential the ceremony should exclude (registration) or allow
|
|
154
140
|
* (authentication).
|
|
155
141
|
*
|
|
156
|
-
* The server
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* platform reports it.
|
|
142
|
+
* The server emits `type` and `id` and NEVER `transports`, so a descriptor
|
|
143
|
+
* that arrives from this API has exactly two keys. `transports` is here for
|
|
144
|
+
* the other direction, where a platform reports it.
|
|
160
145
|
*/
|
|
161
146
|
export interface PasskeyCredentialDescriptor {
|
|
162
147
|
readonly type: "public-key";
|
|
163
|
-
/** The credential id, base64url.
|
|
148
|
+
/** The credential id, base64url. */
|
|
164
149
|
readonly id: PasskeyBase64Url;
|
|
165
150
|
readonly transports?: readonly PasskeyTransport[];
|
|
166
151
|
}
|
|
167
|
-
/** The relying party
|
|
152
|
+
/** The relying party: `"O Melhor Site"` at `omelhorsite.pt`. */
|
|
168
153
|
export interface PasskeyRelyingParty {
|
|
169
154
|
readonly name: string;
|
|
170
155
|
/**
|
|
171
|
-
* The registrable domain that owns the credential. Always sent
|
|
172
|
-
* initializer sets `rp_id`, and `RPEntity` falls back to it when the caller
|
|
173
|
-
* gives none.
|
|
156
|
+
* The registrable domain that owns the credential. Always sent.
|
|
174
157
|
*
|
|
175
158
|
* It is the domain of the UI, NOT of the API. Passkeys minted here are scoped
|
|
176
159
|
* to `omelhorsite.pt` and its subdomains; `backend.omelhorsite.pt` serves the
|
|
@@ -181,9 +164,9 @@ export interface PasskeyRelyingParty {
|
|
|
181
164
|
/**
|
|
182
165
|
* The account the passkey will belong to.
|
|
183
166
|
*
|
|
184
|
-
* `id` is
|
|
185
|
-
*
|
|
186
|
-
* {@link PasskeysNamespace.registrationOptions}. It is NOT `
|
|
167
|
+
* `id` is a server-minted opaque handle that exists for exactly this purpose,
|
|
168
|
+
* assigned lazily on the first call to
|
|
169
|
+
* {@link PasskeysNamespace.registrationOptions}. It is NOT the user's `id`, and the
|
|
187
170
|
* distinction is the point: the value is stored on the authenticator, is
|
|
188
171
|
* readable by anyone who gets hold of the device, and must therefore say
|
|
189
172
|
* nothing about the account. `name` and `displayName`, by contrast, ARE the
|
|
@@ -211,26 +194,23 @@ export interface PasskeyAuthenticatorSelection {
|
|
|
211
194
|
}
|
|
212
195
|
/**
|
|
213
196
|
* `POST /webauthn_credentials/registration_options` - the arguments for
|
|
214
|
-
* `navigator.credentials.create()`, camelCased and base64url-encoded
|
|
215
|
-
* gem's `JSONSerializer`.
|
|
197
|
+
* `navigator.credentials.create()`, camelCased and base64url-encoded.
|
|
216
198
|
*
|
|
217
|
-
* Every optional field below is optional because
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* `WebAuthn::PublicKeyCredential::CreationOptions`:
|
|
199
|
+
* Every optional field below is optional because the server drops a falsy
|
|
200
|
+
* attribute, not because it sometimes omits a field on purpose. What it
|
|
201
|
+
* actually sends:
|
|
221
202
|
*
|
|
222
203
|
* - `challenge`, `timeout` (120000), `extensions` (`{}`), `rp`, `user`,
|
|
223
204
|
* `pubKeyCredParams` (ES256, PS256, RS256), `authenticatorSelection`
|
|
224
205
|
* (`residentKey` and `userVerification`, both `"preferred"`), and
|
|
225
206
|
* `excludeCredentials`, which is `[]` for an account with no passkeys yet
|
|
226
207
|
* rather than absent;
|
|
227
|
-
* - NOT `attestation`. The
|
|
228
|
-
*
|
|
208
|
+
* - NOT `attestation`. The key is absent, so the platform applies its own
|
|
209
|
+
* default (`"none"`).
|
|
229
210
|
*
|
|
230
211
|
* Pass this object to the platform whole. Do not rebuild it field by field
|
|
231
|
-
* unless your platform needs you to:
|
|
232
|
-
*
|
|
233
|
-
* property of `react-native-passkeys` rather than of WebAuthn.
|
|
212
|
+
* unless your platform needs you to: `react-native-passkeys` does, because its
|
|
213
|
+
* native Record decoder trips on `extensions: {}`.
|
|
234
214
|
*/
|
|
235
215
|
export interface PasskeyRegistrationOptions {
|
|
236
216
|
readonly challenge: PasskeyBase64Url;
|
|
@@ -257,15 +237,14 @@ export interface PasskeyRegistrationOptions {
|
|
|
257
237
|
* `options` from `POST /webauthn_credentials/authentication_options` - the
|
|
258
238
|
* arguments for `navigator.credentials.get()`.
|
|
259
239
|
*
|
|
260
|
-
* `allowCredentials` is always present and always `[]`: the
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* OS offers whatever passkeys it holds for the domain and the user picks an
|
|
240
|
+
* `allowCredentials` is always present and always `[]`: the server sends no
|
|
241
|
+
* allow list, and spells that as `[]` rather than omitting the key. An empty
|
|
242
|
+
* allow list IS the feature - it makes the ceremony discoverable, so the OS
|
|
243
|
+
* offers whatever passkeys it holds for the domain and the user picks an
|
|
265
244
|
* account. That is why sign-in needs no email field.
|
|
266
245
|
*
|
|
267
|
-
* `rpId` is always present too
|
|
268
|
-
*
|
|
246
|
+
* `rpId` is always present too, which matters because `react-native-passkeys`
|
|
247
|
+
* requires it.
|
|
269
248
|
*/
|
|
270
249
|
export interface PasskeyAuthenticationOptions {
|
|
271
250
|
readonly challenge: PasskeyBase64Url;
|
|
@@ -282,10 +261,10 @@ export interface PasskeyAuthenticationOptions {
|
|
|
282
261
|
* the ceremony arguments plus the handle that identifies the challenge.
|
|
283
262
|
*
|
|
284
263
|
* The handle exists because the login ceremony has no session to key a
|
|
285
|
-
* challenge against. The server
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
264
|
+
* challenge against. The server keeps the challenge under the handle and hands
|
|
265
|
+
* you the handle; you give it back with the assertion. Treat it as a
|
|
266
|
+
* single-use nonce: it is a random UUID, it lives two minutes, and
|
|
267
|
+
* {@link PasskeysNamespace.authenticate} spends it.
|
|
289
268
|
*/
|
|
290
269
|
export interface PasskeyAuthenticationChallenge {
|
|
291
270
|
readonly handle: string;
|
|
@@ -295,14 +274,11 @@ export interface PasskeyAuthenticationChallenge {
|
|
|
295
274
|
* What the authenticator produced during registration, ready to be posted back.
|
|
296
275
|
*
|
|
297
276
|
* `id` and `rawId` are BOTH required, and both must be the same credential id
|
|
298
|
-
* in base64url. This is not belt-and-braces
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* does not catch it and you get a `500`.
|
|
304
|
-
*
|
|
305
|
-
* The gem reads `clientDataJSON`, `attestationObject` and `transports` from
|
|
277
|
+
* in base64url. This is not belt-and-braces: the server decodes each one
|
|
278
|
+
* separately and compares the BYTES. Send them out of step and you get a
|
|
279
|
+
* `500`, not a `400` - see the warning on {@link PasskeysNamespace.register}.
|
|
280
|
+
*
|
|
281
|
+
* The server reads `clientDataJSON`, `attestationObject` and `transports` from
|
|
306
282
|
* `response` and ignores anything else in it, so a browser's `getPublicKey()` /
|
|
307
283
|
* `publicKey` extras are harmless. They are also pointless bytes on a phone's
|
|
308
284
|
* connection.
|
|
@@ -328,8 +304,8 @@ export interface PasskeyRegistrationCredential {
|
|
|
328
304
|
* consequence for getting it wrong.
|
|
329
305
|
*
|
|
330
306
|
* `userHandle` is what a discoverable ceremony returns as the account the user
|
|
331
|
-
* picked, and this server does NOT read it: it looks the credential up by
|
|
332
|
-
*
|
|
307
|
+
* picked, and this server does NOT read it: it looks the credential up by its
|
|
308
|
+
* id instead. Send it anyway if the platform gave you one, but do
|
|
333
309
|
* not synthesise one, and never treat its absence as a failure.
|
|
334
310
|
*/
|
|
335
311
|
export interface PasskeyAssertionCredential {
|
|
@@ -351,8 +327,8 @@ export interface RegisterPasskeyInput {
|
|
|
351
327
|
/** The attestation the platform just produced. Sent verbatim. */
|
|
352
328
|
readonly credential: PasskeyRegistrationCredential;
|
|
353
329
|
/**
|
|
354
|
-
* Optional label. Blank and whitespace-only strings are stored as `null
|
|
355
|
-
*
|
|
330
|
+
* Optional label. Blank and whitespace-only strings are stored as `null`,
|
|
331
|
+
* so there is no point sending `" "` to clear
|
|
356
332
|
* anything - there is nothing to clear, the record cannot be updated.
|
|
357
333
|
*/
|
|
358
334
|
readonly nickname?: string;
|
|
@@ -373,15 +349,15 @@ export interface AuthenticatePasskeyInput {
|
|
|
373
349
|
* ## Who needs this
|
|
374
350
|
*
|
|
375
351
|
* A browser does not. `@simplewebauthn/browser` already emits unpadded
|
|
376
|
-
* base64url on both sides,
|
|
377
|
-
*
|
|
352
|
+
* base64url on both sides, so a browser credential can be posted straight
|
|
353
|
+
* through.
|
|
378
354
|
*
|
|
379
355
|
* React Native does. Each platform re-encodes in its own native layer on the
|
|
380
356
|
* way out of the OS, and they do not agree with each other about padding; iOS
|
|
381
357
|
* in particular re-pads on the way in and strips on the way out. That would be
|
|
382
|
-
* harmless if the server were lenient, and on most fields it is -
|
|
383
|
-
*
|
|
384
|
-
*
|
|
358
|
+
* harmless if the server were lenient, and on most fields it is - it re-pads a
|
|
359
|
+
* short string and accepts either alphabet. It is NOT lenient about one
|
|
360
|
+
* thing: it decodes `id` and `rawId` SEPARATELY and demands
|
|
385
361
|
* identical bytes, so a platform that pads one and not the other loses the
|
|
386
362
|
* ceremony at the very last step, with a 500 and nothing in the message.
|
|
387
363
|
*
|
|
@@ -414,9 +390,9 @@ export declare function isPasskeyBase64Url(value: unknown): value is PasskeyBase
|
|
|
414
390
|
* thing that must not happen behind a caller's back.
|
|
415
391
|
*
|
|
416
392
|
* Drops `getPublicKey`, `publicKey`, `getPublicKeyAlgorithm` and
|
|
417
|
-
* `getAuthenticatorData` from `response`: `
|
|
418
|
-
*
|
|
419
|
-
*
|
|
393
|
+
* `getAuthenticatorData` from `response`: the server reads `clientDataJSON`,
|
|
394
|
+
* `attestationObject` and `transports` and nothing else, and the dropped
|
|
395
|
+
* fields are large.
|
|
420
396
|
*
|
|
421
397
|
* @throws {TypeError} through {@link passkeyBase64Url} when a required field is
|
|
422
398
|
* missing or not base64.
|
|
@@ -441,22 +417,18 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
441
417
|
* `GET /webauthn_credentials` - every passkey of the signed-in user, newest
|
|
442
418
|
* first.
|
|
443
419
|
*
|
|
444
|
-
* Returns a plain array, and that is the whole story:
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* `reject_unknown_filter_keys!` is not in the chain and a filter is silently
|
|
450
|
-
* ignored rather than rejected with a `400`. The web frontend does exactly
|
|
451
|
-
* that today: `PasskeyService.list` accepts a `ListFilters` argument and
|
|
452
|
-
* forwards it, and it has never had any effect.
|
|
420
|
+
* Returns a plain array, and that is the whole story: unlike almost every
|
|
421
|
+
* other index in this API it has NO list DSL, NO `modifiers[page]`, NO
|
|
422
|
+
* `search` / `exact_search`, and NO `ETag` / `304`. Sending those parameters
|
|
423
|
+
* is not an error either: a filter is silently ignored rather than rejected
|
|
424
|
+
* with a `400`.
|
|
453
425
|
*
|
|
454
426
|
* Hence no `Paginated` and no `PageParams` here. An account's passkey count
|
|
455
427
|
* is bounded by how many devices a person owns, and the server would return
|
|
456
428
|
* all of them regardless of what was asked.
|
|
457
429
|
*
|
|
458
|
-
* The order is
|
|
459
|
-
*
|
|
430
|
+
* The order is newest-created first, so it does not change when a passkey
|
|
431
|
+
* is used.
|
|
460
432
|
*
|
|
461
433
|
* Requires a credential. General authenticated ceiling, 600/min.
|
|
462
434
|
*
|
|
@@ -466,11 +438,9 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
466
438
|
/**
|
|
467
439
|
* `DELETE /webauthn_credentials/:id` - removes one passkey. `204`, no body.
|
|
468
440
|
*
|
|
469
|
-
* The lookup is scoped to the caller
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
* through, but only for a passkey they can already find, which by that
|
|
473
|
-
* scoping means their own.
|
|
441
|
+
* The lookup is scoped to the caller, so somebody else's id is a `404` and
|
|
442
|
+
* never a `403`: the endpoint does not confirm that the id exists. An
|
|
443
|
+
* administrator is no exception.
|
|
474
444
|
*
|
|
475
445
|
* Nothing stops the last passkey being removed. There is no "you would lock
|
|
476
446
|
* yourself out" guard, because passwords and OAuth identities are still
|
|
@@ -493,19 +463,18 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
493
463
|
*
|
|
494
464
|
* Two side effects, both of which matter:
|
|
495
465
|
*
|
|
496
|
-
* 1. **It assigns
|
|
497
|
-
* never touched passkeys gets one minted here
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
* (`webauthn:reg:<user id>`). Calling this twice for one account
|
|
466
|
+
* 1. **It assigns the account's WebAuthn user handle on first use.** An
|
|
467
|
+
* account that has never touched passkeys gets one minted here. That
|
|
468
|
+
* handle is then permanent and is what every passkey on this account is
|
|
469
|
+
* bound to.
|
|
470
|
+
* 2. **It caches a challenge under a key that is per USER, not per call.**
|
|
471
|
+
* Calling this twice for one account
|
|
503
472
|
* OVERWRITES the first challenge, so two registrations in flight at once
|
|
504
473
|
* means the older one fails verification with "Registration challenge
|
|
505
474
|
* expired" even though nothing expired. Do not call it speculatively, do
|
|
506
475
|
* not call it to warm a screen, and do not let a button fire it twice.
|
|
507
476
|
*
|
|
508
|
-
* The challenge lives **two minutes
|
|
477
|
+
* The challenge lives **two minutes**, and the `timeout`
|
|
509
478
|
* inside the options is also 120000. Those are the same number, and the cache
|
|
510
479
|
* clock starts BEFORE the response is even sent, so a user who lets the OS
|
|
511
480
|
* sheet sit open for its full advertised timeout arrives after the challenge
|
|
@@ -513,7 +482,7 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
513
482
|
*
|
|
514
483
|
* Requires a credential. General authenticated ceiling, 600/min.
|
|
515
484
|
*
|
|
516
|
-
* An empty JSON object is sent as the body. The
|
|
485
|
+
* An empty JSON object is sent as the body. The server reads nothing from
|
|
517
486
|
* it; it is there so the request carries `Content-Type: application/json`
|
|
518
487
|
* like every other POST in this API rather than arriving bodyless.
|
|
519
488
|
*/
|
|
@@ -523,26 +492,22 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
523
492
|
* the new {@link Passkey}.
|
|
524
493
|
*
|
|
525
494
|
* `input.credential` is posted VERBATIM. Nothing here copies it, reshapes it,
|
|
526
|
-
* filters its keys or rewrites a `null` inside it, because the server
|
|
527
|
-
*
|
|
528
|
-
* the gem, which then checks a signature over those exact bytes.
|
|
495
|
+
* filters its keys or rewrites a `null` inside it, because the server checks
|
|
496
|
+
* a signature over those exact bytes.
|
|
529
497
|
*
|
|
530
498
|
* ## The failure modes, and which of them are honest
|
|
531
499
|
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
* `500`:
|
|
500
|
+
* A verification failure is a `400`. Some of what can go wrong here is not
|
|
501
|
+
* treated as one, and reaches you as a `500`:
|
|
535
502
|
*
|
|
536
503
|
* - `type` that is not exactly `"public-key"`, or an `id` and `rawId` whose
|
|
537
|
-
* decoded bytes differ
|
|
538
|
-
*
|
|
539
|
-
* and slip past the rescue;
|
|
540
|
-
* - `rawId` absent or `null`: the decoder calls `end_with?` on it and raises
|
|
541
|
-
* `NoMethodError`. See {@link normalizePasskeyRegistrationCredential},
|
|
504
|
+
* decoded bytes differ;
|
|
505
|
+
* - `rawId` absent or `null`. See {@link normalizePasskeyRegistrationCredential},
|
|
542
506
|
* which fills `rawId` from `id` for the Android case where the platform
|
|
543
|
-
* really does send `null
|
|
544
|
-
*
|
|
545
|
-
*
|
|
507
|
+
* really does send `null`.
|
|
508
|
+
*
|
|
509
|
+
* `credential` absent altogether is the one malformed shape that is still a
|
|
510
|
+
* `400`.
|
|
546
511
|
*
|
|
547
512
|
* So a `500` from this endpoint is a malformed payload, not an outage, and it
|
|
548
513
|
* is the single most likely thing to be wrong on a client that hand-builds
|
|
@@ -561,10 +526,9 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
561
526
|
* again."` when more than two minutes passed since
|
|
562
527
|
* {@link registrationOptions}, or when a second call to it overwrote this
|
|
563
528
|
* ceremony's challenge.
|
|
564
|
-
* @throws {OmsApiError} 400 when verification fails, or when the
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
* it.
|
|
529
|
+
* @throws {OmsApiError} 400 when verification fails, or when the passkey
|
|
530
|
+
* cannot be stored - which in practice means this authenticator is already
|
|
531
|
+
* registered and `excludeCredentials` did not stop it.
|
|
568
532
|
*/
|
|
569
533
|
register(input: RegisterPasskeyInput, options?: RequestOptions): Promise<Passkey>;
|
|
570
534
|
/**
|
|
@@ -572,9 +536,9 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
572
536
|
* returns the challenge handle plus the arguments for
|
|
573
537
|
* `navigator.credentials.get()`.
|
|
574
538
|
*
|
|
575
|
-
* **Send this with NO credential.** The route
|
|
576
|
-
*
|
|
577
|
-
*
|
|
539
|
+
* **Send this with NO credential.** The route accepts anonymous callers,
|
|
540
|
+
* and the whole point is that there is no session yet. The transport always
|
|
541
|
+
* attaches `Authorization` when the client
|
|
578
542
|
* holds a token, and the caller cannot strip it per request, so a client that
|
|
579
543
|
* might be signed in should build an anonymous one for the login flow.
|
|
580
544
|
*
|
|
@@ -590,7 +554,7 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
590
554
|
*
|
|
591
555
|
* Retrying is off here, which deviates from the transport's default of
|
|
592
556
|
* replaying a `429`. The reason is the clock rather than safety: the replay
|
|
593
|
-
* sleeps out `Retry-After`, which
|
|
557
|
+
* sleeps out `Retry-After`, which the server sets from a one-minute window,
|
|
594
558
|
* and then hands back a challenge that lives two minutes and still has an OS
|
|
595
559
|
* sheet and a second rate-limited request ahead of it. Waiting silently
|
|
596
560
|
* inside the SDK is likelier to produce a login that dies at the last step
|
|
@@ -613,16 +577,16 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
613
577
|
*
|
|
614
578
|
* ## An assertion is spent exactly once, so nothing here is replayed
|
|
615
579
|
*
|
|
616
|
-
* The
|
|
617
|
-
*
|
|
618
|
-
*
|
|
580
|
+
* The server discards the challenge BEFORE it verifies anything. Once a
|
|
581
|
+
* request has reached it the handle is gone whatever the outcome, so a
|
|
582
|
+
* second attempt carrying the same body answers `401 "Login
|
|
619
583
|
* challenge expired."` and reports the wrong cause for whatever actually
|
|
620
584
|
* went wrong.
|
|
621
585
|
*
|
|
622
586
|
* The transport's default policy already declines to replay a `POST` after a
|
|
623
587
|
* torn connection or a `5xx`, which is exactly right for that reason. The one
|
|
624
588
|
* outcome it WOULD replay is a `429`, and this method turns that off as well.
|
|
625
|
-
* A `429`
|
|
589
|
+
* A `429` is answered before the request is performed, so it genuinely did
|
|
626
590
|
* not spend the handle; but `Retry-After` is set from a one-minute window,
|
|
627
591
|
* the challenge lives two minutes, and the OS sheet has already eaten part of
|
|
628
592
|
* that. Sleeping through the rate limit inside the SDK converts it into an
|
|
@@ -642,10 +606,10 @@ export declare class PasskeysNamespace extends Resource {
|
|
|
642
606
|
* server has never seen (`"Unknown passkey."`); a deactivated account
|
|
643
607
|
* (`"This account is deactivated."`); a failed signature, wrong origin or
|
|
644
608
|
* wrong challenge (`"Passkey could not be verified."`); and a sign counter
|
|
645
|
-
* that did not advance, which the
|
|
646
|
-
*
|
|
647
|
-
* keychain report a counter of `0` forever, and `
|
|
648
|
-
*
|
|
609
|
+
* that did not advance, which the server treats as a cloned authenticator
|
|
610
|
+
* and reports as that same message. Note that passkeys synced through a
|
|
611
|
+
* keychain report a counter of `0` forever, and `0` against `0` is let
|
|
612
|
+
* through, so that last case does not fire for them.
|
|
649
613
|
* @throws {OmsQuotaError} 429 when the shared per-IP bucket is spent.
|
|
650
614
|
*/
|
|
651
615
|
authenticate(input: AuthenticatePasskeyInput, options?: RequestOptions): Promise<PasskeySession>;
|