@spacelr/sdk 0.7.0 → 0.8.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 +50 -0
- package/dist/index.d.mts +162 -1
- package/dist/index.d.ts +162 -1
- package/dist/index.js +88 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +88 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,56 @@ Notes:
|
|
|
77
77
|
- Enabling/configuring cold-tier on a collection is an **admin** operation and
|
|
78
78
|
is not part of this client SDK.
|
|
79
79
|
|
|
80
|
+
## Passkey authentication (WebAuthn)
|
|
81
|
+
|
|
82
|
+
The SDK never touches `navigator.credentials` and does not depend on
|
|
83
|
+
`@simplewebauthn` in any way (no runtime, dev, or peer dependency). It only
|
|
84
|
+
performs the HTTP `begin`/`verify` round trips against the gateway. The
|
|
85
|
+
browser WebAuthn ceremony itself — calling `navigator.credentials.get()` /
|
|
86
|
+
`navigator.credentials.create()` — is the caller's responsibility, typically
|
|
87
|
+
via the optional `@simplewebauthn/browser` package:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm install @simplewebauthn/browser # optional, only needed for the ceremony
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { startAuthentication, startRegistration } from '@simplewebauthn/browser';
|
|
95
|
+
|
|
96
|
+
// --- Login ---
|
|
97
|
+
const opts = await spacelr.auth.beginPasskeyLogin(email);
|
|
98
|
+
// The SDK vendors its own self-contained WebAuthn JSON types (see
|
|
99
|
+
// libs/sdk/src/types/auth.ts) so it never depends on `@simplewebauthn`.
|
|
100
|
+
// Because of that, an `as` cast is ALWAYS required here — `@simplewebauthn/browser`
|
|
101
|
+
// v13's `optionsJSON` parameter uses its own DOM literal-union/named types, which
|
|
102
|
+
// the vendored types don't nominally match even though they're structurally identical.
|
|
103
|
+
const assertion = await startAuthentication({ optionsJSON: opts as never });
|
|
104
|
+
await spacelr.auth.verifyPasskeyLogin(assertion); // tokens stored, 'authenticated' emitted
|
|
105
|
+
|
|
106
|
+
// --- Register (while already signed in) ---
|
|
107
|
+
const regOpts = await spacelr.auth.beginPasskeyRegistration();
|
|
108
|
+
const attestation = await startRegistration({ optionsJSON: regOpts as never }); // cast required, see above
|
|
109
|
+
await spacelr.auth.verifyPasskeyRegistration(attestation, 'My Laptop');
|
|
110
|
+
|
|
111
|
+
// --- Manage registered credentials ---
|
|
112
|
+
const creds = await spacelr.auth.listPasskeys();
|
|
113
|
+
await spacelr.auth.renamePasskey(creds[0].credentialId, 'Renamed');
|
|
114
|
+
await spacelr.auth.deletePasskey(creds[0].credentialId);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Notes:
|
|
118
|
+
- `beginPasskeyLogin` / `verifyPasskeyLogin` complete the passkey login flow;
|
|
119
|
+
on success tokens are stored and the SDK emits `authenticated`, the same as
|
|
120
|
+
`auth.login()`.
|
|
121
|
+
- `beginPasskeyRegistration` / `verifyPasskeyRegistration` register a new
|
|
122
|
+
passkey for the current session; they don't emit auth events (the caller is
|
|
123
|
+
already signed in).
|
|
124
|
+
- `listPasskeys`, `renamePasskey`, and `deletePasskey` manage the current
|
|
125
|
+
user's registered credentials.
|
|
126
|
+
- `@simplewebauthn/browser` is entirely optional and only relevant to browser
|
|
127
|
+
consumers performing the ceremony — Node/server usage of the SDK never
|
|
128
|
+
needs it.
|
|
129
|
+
|
|
80
130
|
## Requirements
|
|
81
131
|
|
|
82
132
|
- Node.js >= 18
|
package/dist/index.d.mts
CHANGED
|
@@ -146,6 +146,120 @@ interface TwoFactorVerifyParams {
|
|
|
146
146
|
token: string;
|
|
147
147
|
code: string;
|
|
148
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Vendored WebAuthn JSON interfaces.
|
|
151
|
+
*
|
|
152
|
+
* These are SDK-owned structural interfaces (no `@simplewebauthn` import and
|
|
153
|
+
* no new dependency in `libs/sdk/package.json`), faithful to the W3C WebAuthn
|
|
154
|
+
* JSON serialization — the same shape as `@simplewebauthn/browser`'s
|
|
155
|
+
* `PublicKeyCredentialRequestOptionsJSON` / `AuthenticationResponseJSON` /
|
|
156
|
+
* `PublicKeyCredentialCreationOptionsJSON` / `RegistrationResponseJSON`.
|
|
157
|
+
* A browser caller passes these values into `@simplewebauthn/browser`'s
|
|
158
|
+
* `startAuthentication({ optionsJSON })` / `startRegistration({ optionsJSON })`.
|
|
159
|
+
* Because these are structurally-equivalent SDK-owned types rather than
|
|
160
|
+
* `@simplewebauthn`'s own types, an `as` cast at that call boundary is ALWAYS
|
|
161
|
+
* required — not only when the installed `@simplewebauthn` version differs.
|
|
162
|
+
* `@simplewebauthn/browser` v13's `optionsJSON` parameter is typed with DOM
|
|
163
|
+
* literal-union/named types (e.g. `PublicKeyCredentialRequestOptionsJSON`),
|
|
164
|
+
* which these vendored interfaces do not nominally match even when
|
|
165
|
+
* structurally identical, so TypeScript will not accept them without a cast.
|
|
166
|
+
*/
|
|
167
|
+
/** A public key credential descriptor in its JSON wire form. */
|
|
168
|
+
interface PasskeyCredentialDescriptorJSON {
|
|
169
|
+
id: string;
|
|
170
|
+
type: string;
|
|
171
|
+
transports?: string[];
|
|
172
|
+
}
|
|
173
|
+
/** WebAuthn assertion (`navigator.credentials.get`) request options, JSON form. */
|
|
174
|
+
interface PasskeyAuthenticationOptionsJSON {
|
|
175
|
+
challenge: string;
|
|
176
|
+
rpId?: string;
|
|
177
|
+
timeout?: number;
|
|
178
|
+
userVerification?: string;
|
|
179
|
+
allowCredentials?: PasskeyCredentialDescriptorJSON[];
|
|
180
|
+
extensions?: Record<string, unknown>;
|
|
181
|
+
}
|
|
182
|
+
/** WebAuthn assertion (`navigator.credentials.get`) response, JSON form. */
|
|
183
|
+
interface PasskeyAuthenticationResponseJSON {
|
|
184
|
+
id: string;
|
|
185
|
+
rawId: string;
|
|
186
|
+
type: string;
|
|
187
|
+
response: {
|
|
188
|
+
clientDataJSON: string;
|
|
189
|
+
authenticatorData: string;
|
|
190
|
+
signature: string;
|
|
191
|
+
userHandle?: string;
|
|
192
|
+
};
|
|
193
|
+
clientExtensionResults?: Record<string, unknown>;
|
|
194
|
+
authenticatorAttachment?: string;
|
|
195
|
+
}
|
|
196
|
+
/** WebAuthn attestation (`navigator.credentials.create`) request options, JSON form. */
|
|
197
|
+
interface PasskeyRegistrationOptionsJSON {
|
|
198
|
+
challenge: string;
|
|
199
|
+
rp: {
|
|
200
|
+
id?: string;
|
|
201
|
+
name: string;
|
|
202
|
+
};
|
|
203
|
+
user: {
|
|
204
|
+
id: string;
|
|
205
|
+
name: string;
|
|
206
|
+
displayName: string;
|
|
207
|
+
};
|
|
208
|
+
pubKeyCredParams: Array<{
|
|
209
|
+
type: string;
|
|
210
|
+
alg: number;
|
|
211
|
+
}>;
|
|
212
|
+
timeout?: number;
|
|
213
|
+
excludeCredentials?: PasskeyCredentialDescriptorJSON[];
|
|
214
|
+
authenticatorSelection?: Record<string, unknown>;
|
|
215
|
+
attestation?: string;
|
|
216
|
+
extensions?: Record<string, unknown>;
|
|
217
|
+
}
|
|
218
|
+
/** WebAuthn attestation (`navigator.credentials.create`) response, JSON form. */
|
|
219
|
+
interface PasskeyRegistrationResponseJSON {
|
|
220
|
+
id: string;
|
|
221
|
+
rawId: string;
|
|
222
|
+
type: string;
|
|
223
|
+
response: {
|
|
224
|
+
clientDataJSON: string;
|
|
225
|
+
attestationObject: string;
|
|
226
|
+
transports?: string[];
|
|
227
|
+
};
|
|
228
|
+
clientExtensionResults?: Record<string, unknown>;
|
|
229
|
+
authenticatorAttachment?: string;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Response returned by the passkey login flow.
|
|
233
|
+
*
|
|
234
|
+
* This is intentionally a SEPARATE type from `LoginResponse`, not an alias:
|
|
235
|
+
* the gateway's passkey mapper drops `user.sub`/`user.username`, returning
|
|
236
|
+
* only `{ id, email, roles }`, so `LoginResponse` (which requires `user.sub`)
|
|
237
|
+
* would not structurally match.
|
|
238
|
+
*/
|
|
239
|
+
interface PasskeyLoginResponse {
|
|
240
|
+
user: {
|
|
241
|
+
id: string;
|
|
242
|
+
email: string;
|
|
243
|
+
roles: string[];
|
|
244
|
+
};
|
|
245
|
+
access_token: string;
|
|
246
|
+
refresh_token?: string;
|
|
247
|
+
expires_in?: number;
|
|
248
|
+
}
|
|
249
|
+
/** Result of verifying a passkey registration attestation. */
|
|
250
|
+
interface PasskeyRegistrationResult {
|
|
251
|
+
verified: boolean;
|
|
252
|
+
}
|
|
253
|
+
/** A registered passkey credential, as returned by the credential-list endpoint. */
|
|
254
|
+
interface PasskeyCredential {
|
|
255
|
+
credentialId: string;
|
|
256
|
+
label?: string;
|
|
257
|
+
deviceType?: string;
|
|
258
|
+
backedUp?: boolean;
|
|
259
|
+
transports?: string[];
|
|
260
|
+
createdAt: string;
|
|
261
|
+
lastUsedAt?: string;
|
|
262
|
+
}
|
|
149
263
|
|
|
150
264
|
interface TokenStorage {
|
|
151
265
|
getTokens(): Promise<StoredTokens | null>;
|
|
@@ -292,6 +406,14 @@ interface HttpRequestOptions {
|
|
|
292
406
|
path: string;
|
|
293
407
|
body?: unknown;
|
|
294
408
|
authenticated?: boolean;
|
|
409
|
+
/**
|
|
410
|
+
* Suppress the 401 auto-recovery (forceRefresh + emitAuthLost) for this
|
|
411
|
+
* request. Used by logout: a 401 there means the session is already gone,
|
|
412
|
+
* so triggering a refresh/auth-lost would recurse (auth-lost handlers that
|
|
413
|
+
* call logout re-fire the same 401). The token is still sent so the server
|
|
414
|
+
* can revoke it when it is valid.
|
|
415
|
+
*/
|
|
416
|
+
skipAuthRefresh?: boolean;
|
|
295
417
|
headers?: Record<string, string>;
|
|
296
418
|
query?: Record<string, string | number | undefined>;
|
|
297
419
|
/** Send cookies cross-origin (credentials: 'include'). Defaults to true for /auth/ paths. */
|
|
@@ -703,6 +825,45 @@ declare class AuthModule {
|
|
|
703
825
|
* Call this after catching SpacelrTwoFactorRequiredError from login().
|
|
704
826
|
*/
|
|
705
827
|
verifyTwoFactor(params: TwoFactorVerifyParams): Promise<LoginResponse>;
|
|
828
|
+
/**
|
|
829
|
+
* Fetch WebAuthn assertion options to start a passkey login.
|
|
830
|
+
* Pass `email` to scope the challenge to a known user's registered
|
|
831
|
+
* credentials; omit it for a discoverable-credential (usernameless) flow.
|
|
832
|
+
*/
|
|
833
|
+
beginPasskeyLogin(email?: string): Promise<PasskeyAuthenticationOptionsJSON>;
|
|
834
|
+
/**
|
|
835
|
+
* Verify a WebAuthn assertion produced by `navigator.credentials.get()`
|
|
836
|
+
* against the options returned by `beginPasskeyLogin()`, completing login.
|
|
837
|
+
*/
|
|
838
|
+
verifyPasskeyLogin(response: PasskeyAuthenticationResponseJSON): Promise<PasskeyLoginResponse>;
|
|
839
|
+
/**
|
|
840
|
+
* Fetch WebAuthn attestation options to register a new passkey for the
|
|
841
|
+
* currently authenticated user.
|
|
842
|
+
*/
|
|
843
|
+
beginPasskeyRegistration(): Promise<PasskeyRegistrationOptionsJSON>;
|
|
844
|
+
/**
|
|
845
|
+
* Verify a WebAuthn attestation produced by `navigator.credentials.create()`
|
|
846
|
+
* against the options returned by `beginPasskeyRegistration()`, saving the
|
|
847
|
+
* new credential under the current user. This does not issue tokens or
|
|
848
|
+
* change auth state — the caller is already authenticated.
|
|
849
|
+
*/
|
|
850
|
+
verifyPasskeyRegistration(response: PasskeyRegistrationResponseJSON, label?: string): Promise<PasskeyRegistrationResult>;
|
|
851
|
+
/**
|
|
852
|
+
* List the WebAuthn passkey credentials registered for the current user.
|
|
853
|
+
*/
|
|
854
|
+
listPasskeys(): Promise<PasskeyCredential[]>;
|
|
855
|
+
/**
|
|
856
|
+
* Rename a registered passkey credential (e.g. "My Laptop").
|
|
857
|
+
*/
|
|
858
|
+
renamePasskey(credentialId: string, label: string): Promise<{
|
|
859
|
+
renamed: true;
|
|
860
|
+
}>;
|
|
861
|
+
/**
|
|
862
|
+
* Delete a registered passkey credential.
|
|
863
|
+
*/
|
|
864
|
+
deletePasskey(credentialId: string): Promise<{
|
|
865
|
+
deleted: true;
|
|
866
|
+
}>;
|
|
706
867
|
private storeTokensFromLogin;
|
|
707
868
|
private storeTokensFromRegister;
|
|
708
869
|
}
|
|
@@ -1582,4 +1743,4 @@ interface SpacelrClient {
|
|
|
1582
1743
|
}
|
|
1583
1744
|
declare function createClient(config: SpacelrClientConfig): SpacelrClient;
|
|
1584
1745
|
|
|
1585
|
-
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
|
|
1746
|
+
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PasskeyAuthenticationOptionsJSON, type PasskeyAuthenticationResponseJSON, type PasskeyCredential, type PasskeyCredentialDescriptorJSON, type PasskeyLoginResponse, type PasskeyRegistrationOptionsJSON, type PasskeyRegistrationResponseJSON, type PasskeyRegistrationResult, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
|
package/dist/index.d.ts
CHANGED
|
@@ -146,6 +146,120 @@ interface TwoFactorVerifyParams {
|
|
|
146
146
|
token: string;
|
|
147
147
|
code: string;
|
|
148
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Vendored WebAuthn JSON interfaces.
|
|
151
|
+
*
|
|
152
|
+
* These are SDK-owned structural interfaces (no `@simplewebauthn` import and
|
|
153
|
+
* no new dependency in `libs/sdk/package.json`), faithful to the W3C WebAuthn
|
|
154
|
+
* JSON serialization — the same shape as `@simplewebauthn/browser`'s
|
|
155
|
+
* `PublicKeyCredentialRequestOptionsJSON` / `AuthenticationResponseJSON` /
|
|
156
|
+
* `PublicKeyCredentialCreationOptionsJSON` / `RegistrationResponseJSON`.
|
|
157
|
+
* A browser caller passes these values into `@simplewebauthn/browser`'s
|
|
158
|
+
* `startAuthentication({ optionsJSON })` / `startRegistration({ optionsJSON })`.
|
|
159
|
+
* Because these are structurally-equivalent SDK-owned types rather than
|
|
160
|
+
* `@simplewebauthn`'s own types, an `as` cast at that call boundary is ALWAYS
|
|
161
|
+
* required — not only when the installed `@simplewebauthn` version differs.
|
|
162
|
+
* `@simplewebauthn/browser` v13's `optionsJSON` parameter is typed with DOM
|
|
163
|
+
* literal-union/named types (e.g. `PublicKeyCredentialRequestOptionsJSON`),
|
|
164
|
+
* which these vendored interfaces do not nominally match even when
|
|
165
|
+
* structurally identical, so TypeScript will not accept them without a cast.
|
|
166
|
+
*/
|
|
167
|
+
/** A public key credential descriptor in its JSON wire form. */
|
|
168
|
+
interface PasskeyCredentialDescriptorJSON {
|
|
169
|
+
id: string;
|
|
170
|
+
type: string;
|
|
171
|
+
transports?: string[];
|
|
172
|
+
}
|
|
173
|
+
/** WebAuthn assertion (`navigator.credentials.get`) request options, JSON form. */
|
|
174
|
+
interface PasskeyAuthenticationOptionsJSON {
|
|
175
|
+
challenge: string;
|
|
176
|
+
rpId?: string;
|
|
177
|
+
timeout?: number;
|
|
178
|
+
userVerification?: string;
|
|
179
|
+
allowCredentials?: PasskeyCredentialDescriptorJSON[];
|
|
180
|
+
extensions?: Record<string, unknown>;
|
|
181
|
+
}
|
|
182
|
+
/** WebAuthn assertion (`navigator.credentials.get`) response, JSON form. */
|
|
183
|
+
interface PasskeyAuthenticationResponseJSON {
|
|
184
|
+
id: string;
|
|
185
|
+
rawId: string;
|
|
186
|
+
type: string;
|
|
187
|
+
response: {
|
|
188
|
+
clientDataJSON: string;
|
|
189
|
+
authenticatorData: string;
|
|
190
|
+
signature: string;
|
|
191
|
+
userHandle?: string;
|
|
192
|
+
};
|
|
193
|
+
clientExtensionResults?: Record<string, unknown>;
|
|
194
|
+
authenticatorAttachment?: string;
|
|
195
|
+
}
|
|
196
|
+
/** WebAuthn attestation (`navigator.credentials.create`) request options, JSON form. */
|
|
197
|
+
interface PasskeyRegistrationOptionsJSON {
|
|
198
|
+
challenge: string;
|
|
199
|
+
rp: {
|
|
200
|
+
id?: string;
|
|
201
|
+
name: string;
|
|
202
|
+
};
|
|
203
|
+
user: {
|
|
204
|
+
id: string;
|
|
205
|
+
name: string;
|
|
206
|
+
displayName: string;
|
|
207
|
+
};
|
|
208
|
+
pubKeyCredParams: Array<{
|
|
209
|
+
type: string;
|
|
210
|
+
alg: number;
|
|
211
|
+
}>;
|
|
212
|
+
timeout?: number;
|
|
213
|
+
excludeCredentials?: PasskeyCredentialDescriptorJSON[];
|
|
214
|
+
authenticatorSelection?: Record<string, unknown>;
|
|
215
|
+
attestation?: string;
|
|
216
|
+
extensions?: Record<string, unknown>;
|
|
217
|
+
}
|
|
218
|
+
/** WebAuthn attestation (`navigator.credentials.create`) response, JSON form. */
|
|
219
|
+
interface PasskeyRegistrationResponseJSON {
|
|
220
|
+
id: string;
|
|
221
|
+
rawId: string;
|
|
222
|
+
type: string;
|
|
223
|
+
response: {
|
|
224
|
+
clientDataJSON: string;
|
|
225
|
+
attestationObject: string;
|
|
226
|
+
transports?: string[];
|
|
227
|
+
};
|
|
228
|
+
clientExtensionResults?: Record<string, unknown>;
|
|
229
|
+
authenticatorAttachment?: string;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Response returned by the passkey login flow.
|
|
233
|
+
*
|
|
234
|
+
* This is intentionally a SEPARATE type from `LoginResponse`, not an alias:
|
|
235
|
+
* the gateway's passkey mapper drops `user.sub`/`user.username`, returning
|
|
236
|
+
* only `{ id, email, roles }`, so `LoginResponse` (which requires `user.sub`)
|
|
237
|
+
* would not structurally match.
|
|
238
|
+
*/
|
|
239
|
+
interface PasskeyLoginResponse {
|
|
240
|
+
user: {
|
|
241
|
+
id: string;
|
|
242
|
+
email: string;
|
|
243
|
+
roles: string[];
|
|
244
|
+
};
|
|
245
|
+
access_token: string;
|
|
246
|
+
refresh_token?: string;
|
|
247
|
+
expires_in?: number;
|
|
248
|
+
}
|
|
249
|
+
/** Result of verifying a passkey registration attestation. */
|
|
250
|
+
interface PasskeyRegistrationResult {
|
|
251
|
+
verified: boolean;
|
|
252
|
+
}
|
|
253
|
+
/** A registered passkey credential, as returned by the credential-list endpoint. */
|
|
254
|
+
interface PasskeyCredential {
|
|
255
|
+
credentialId: string;
|
|
256
|
+
label?: string;
|
|
257
|
+
deviceType?: string;
|
|
258
|
+
backedUp?: boolean;
|
|
259
|
+
transports?: string[];
|
|
260
|
+
createdAt: string;
|
|
261
|
+
lastUsedAt?: string;
|
|
262
|
+
}
|
|
149
263
|
|
|
150
264
|
interface TokenStorage {
|
|
151
265
|
getTokens(): Promise<StoredTokens | null>;
|
|
@@ -292,6 +406,14 @@ interface HttpRequestOptions {
|
|
|
292
406
|
path: string;
|
|
293
407
|
body?: unknown;
|
|
294
408
|
authenticated?: boolean;
|
|
409
|
+
/**
|
|
410
|
+
* Suppress the 401 auto-recovery (forceRefresh + emitAuthLost) for this
|
|
411
|
+
* request. Used by logout: a 401 there means the session is already gone,
|
|
412
|
+
* so triggering a refresh/auth-lost would recurse (auth-lost handlers that
|
|
413
|
+
* call logout re-fire the same 401). The token is still sent so the server
|
|
414
|
+
* can revoke it when it is valid.
|
|
415
|
+
*/
|
|
416
|
+
skipAuthRefresh?: boolean;
|
|
295
417
|
headers?: Record<string, string>;
|
|
296
418
|
query?: Record<string, string | number | undefined>;
|
|
297
419
|
/** Send cookies cross-origin (credentials: 'include'). Defaults to true for /auth/ paths. */
|
|
@@ -703,6 +825,45 @@ declare class AuthModule {
|
|
|
703
825
|
* Call this after catching SpacelrTwoFactorRequiredError from login().
|
|
704
826
|
*/
|
|
705
827
|
verifyTwoFactor(params: TwoFactorVerifyParams): Promise<LoginResponse>;
|
|
828
|
+
/**
|
|
829
|
+
* Fetch WebAuthn assertion options to start a passkey login.
|
|
830
|
+
* Pass `email` to scope the challenge to a known user's registered
|
|
831
|
+
* credentials; omit it for a discoverable-credential (usernameless) flow.
|
|
832
|
+
*/
|
|
833
|
+
beginPasskeyLogin(email?: string): Promise<PasskeyAuthenticationOptionsJSON>;
|
|
834
|
+
/**
|
|
835
|
+
* Verify a WebAuthn assertion produced by `navigator.credentials.get()`
|
|
836
|
+
* against the options returned by `beginPasskeyLogin()`, completing login.
|
|
837
|
+
*/
|
|
838
|
+
verifyPasskeyLogin(response: PasskeyAuthenticationResponseJSON): Promise<PasskeyLoginResponse>;
|
|
839
|
+
/**
|
|
840
|
+
* Fetch WebAuthn attestation options to register a new passkey for the
|
|
841
|
+
* currently authenticated user.
|
|
842
|
+
*/
|
|
843
|
+
beginPasskeyRegistration(): Promise<PasskeyRegistrationOptionsJSON>;
|
|
844
|
+
/**
|
|
845
|
+
* Verify a WebAuthn attestation produced by `navigator.credentials.create()`
|
|
846
|
+
* against the options returned by `beginPasskeyRegistration()`, saving the
|
|
847
|
+
* new credential under the current user. This does not issue tokens or
|
|
848
|
+
* change auth state — the caller is already authenticated.
|
|
849
|
+
*/
|
|
850
|
+
verifyPasskeyRegistration(response: PasskeyRegistrationResponseJSON, label?: string): Promise<PasskeyRegistrationResult>;
|
|
851
|
+
/**
|
|
852
|
+
* List the WebAuthn passkey credentials registered for the current user.
|
|
853
|
+
*/
|
|
854
|
+
listPasskeys(): Promise<PasskeyCredential[]>;
|
|
855
|
+
/**
|
|
856
|
+
* Rename a registered passkey credential (e.g. "My Laptop").
|
|
857
|
+
*/
|
|
858
|
+
renamePasskey(credentialId: string, label: string): Promise<{
|
|
859
|
+
renamed: true;
|
|
860
|
+
}>;
|
|
861
|
+
/**
|
|
862
|
+
* Delete a registered passkey credential.
|
|
863
|
+
*/
|
|
864
|
+
deletePasskey(credentialId: string): Promise<{
|
|
865
|
+
deleted: true;
|
|
866
|
+
}>;
|
|
706
867
|
private storeTokensFromLogin;
|
|
707
868
|
private storeTokensFromRegister;
|
|
708
869
|
}
|
|
@@ -1582,4 +1743,4 @@ interface SpacelrClient {
|
|
|
1582
1743
|
}
|
|
1583
1744
|
declare function createClient(config: SpacelrClientConfig): SpacelrClient;
|
|
1584
1745
|
|
|
1585
|
-
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
|
|
1746
|
+
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PasskeyAuthenticationOptionsJSON, type PasskeyAuthenticationResponseJSON, type PasskeyCredential, type PasskeyCredentialDescriptorJSON, type PasskeyLoginResponse, type PasskeyRegistrationOptionsJSON, type PasskeyRegistrationResponseJSON, type PasskeyRegistrationResult, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
|
package/dist/index.js
CHANGED
|
@@ -158,7 +158,7 @@ var HttpClient = class {
|
|
|
158
158
|
});
|
|
159
159
|
const responseBody = await this.parseResponse(response);
|
|
160
160
|
if (!response.ok) {
|
|
161
|
-
if (options.authenticated && response.status === 401) {
|
|
161
|
+
if (options.authenticated && response.status === 401 && !options.skipAuthRefresh) {
|
|
162
162
|
if (await this.recoverFromAuthFailure(isRetry)) {
|
|
163
163
|
return this.requestWithRetry(options, true);
|
|
164
164
|
}
|
|
@@ -1438,7 +1438,11 @@ var AuthModule = class {
|
|
|
1438
1438
|
await this.http.request({
|
|
1439
1439
|
method: "POST",
|
|
1440
1440
|
path: "/auth/logout",
|
|
1441
|
-
authenticated: true
|
|
1441
|
+
authenticated: true,
|
|
1442
|
+
// A 401 here means the session is already gone — do not trigger the
|
|
1443
|
+
// refresh/auth-lost recovery, or an auth-lost handler that calls
|
|
1444
|
+
// logout would recurse into an endless 401 loop.
|
|
1445
|
+
skipAuthRefresh: true
|
|
1442
1446
|
});
|
|
1443
1447
|
} catch {
|
|
1444
1448
|
}
|
|
@@ -1590,6 +1594,88 @@ var AuthModule = class {
|
|
|
1590
1594
|
await this.storeTokensFromLogin(response);
|
|
1591
1595
|
return response;
|
|
1592
1596
|
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Fetch WebAuthn assertion options to start a passkey login.
|
|
1599
|
+
* Pass `email` to scope the challenge to a known user's registered
|
|
1600
|
+
* credentials; omit it for a discoverable-credential (usernameless) flow.
|
|
1601
|
+
*/
|
|
1602
|
+
async beginPasskeyLogin(email) {
|
|
1603
|
+
return this.http.request({
|
|
1604
|
+
method: "POST",
|
|
1605
|
+
path: "/auth/passkey/login/options",
|
|
1606
|
+
body: email ? { email } : {}
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Verify a WebAuthn assertion produced by `navigator.credentials.get()`
|
|
1611
|
+
* against the options returned by `beginPasskeyLogin()`, completing login.
|
|
1612
|
+
*/
|
|
1613
|
+
async verifyPasskeyLogin(response) {
|
|
1614
|
+
const result = await this.http.request({
|
|
1615
|
+
method: "POST",
|
|
1616
|
+
path: "/auth/passkey/login/verify",
|
|
1617
|
+
body: { response }
|
|
1618
|
+
});
|
|
1619
|
+
await this.storeTokensFromLogin(result);
|
|
1620
|
+
return result;
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Fetch WebAuthn attestation options to register a new passkey for the
|
|
1624
|
+
* currently authenticated user.
|
|
1625
|
+
*/
|
|
1626
|
+
async beginPasskeyRegistration() {
|
|
1627
|
+
return this.http.request({
|
|
1628
|
+
method: "POST",
|
|
1629
|
+
path: "/auth/passkey/register/options",
|
|
1630
|
+
authenticated: true,
|
|
1631
|
+
body: {}
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Verify a WebAuthn attestation produced by `navigator.credentials.create()`
|
|
1636
|
+
* against the options returned by `beginPasskeyRegistration()`, saving the
|
|
1637
|
+
* new credential under the current user. This does not issue tokens or
|
|
1638
|
+
* change auth state — the caller is already authenticated.
|
|
1639
|
+
*/
|
|
1640
|
+
async verifyPasskeyRegistration(response, label) {
|
|
1641
|
+
return this.http.request({
|
|
1642
|
+
method: "POST",
|
|
1643
|
+
path: "/auth/passkey/register/verify",
|
|
1644
|
+
authenticated: true,
|
|
1645
|
+
body: { response, label }
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* List the WebAuthn passkey credentials registered for the current user.
|
|
1650
|
+
*/
|
|
1651
|
+
async listPasskeys() {
|
|
1652
|
+
return this.http.request({
|
|
1653
|
+
method: "GET",
|
|
1654
|
+
path: "/auth/passkey/credentials",
|
|
1655
|
+
authenticated: true
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Rename a registered passkey credential (e.g. "My Laptop").
|
|
1660
|
+
*/
|
|
1661
|
+
async renamePasskey(credentialId, label) {
|
|
1662
|
+
return this.http.request({
|
|
1663
|
+
method: "PATCH",
|
|
1664
|
+
path: `/auth/passkey/credentials/${encodeURIComponent(credentialId)}`,
|
|
1665
|
+
authenticated: true,
|
|
1666
|
+
body: { label }
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Delete a registered passkey credential.
|
|
1671
|
+
*/
|
|
1672
|
+
async deletePasskey(credentialId) {
|
|
1673
|
+
return this.http.request({
|
|
1674
|
+
method: "DELETE",
|
|
1675
|
+
path: `/auth/passkey/credentials/${encodeURIComponent(credentialId)}`,
|
|
1676
|
+
authenticated: true
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1593
1679
|
async storeTokensFromLogin(response) {
|
|
1594
1680
|
const expiresAt = response.expires_in ? Math.floor(Date.now() / 1e3) + response.expires_in : void 0;
|
|
1595
1681
|
await this.tokenManager.setTokens({
|