@softwarepatterns/am 0.0.2 → 0.2.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.
- package/README.md +114 -76
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +104 -284
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/types/auth.d.ts +88 -279
- package/dist/types/types.d.ts +16 -5
- package/package.json +19 -4
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ type UserId = `uid${string}`;
|
|
|
57
57
|
type AccountId = `acc${string}`;
|
|
58
58
|
/** Identifier for a membership linking a user to an account. */
|
|
59
59
|
type MembershipId = `mbr${string}`;
|
|
60
|
+
/** Identifier for an application (namespace for users and clients). */
|
|
61
|
+
type ApplicationId = `app${string}`;
|
|
60
62
|
/**
|
|
61
63
|
* Possible statuses for an account.
|
|
62
64
|
*
|
|
@@ -80,6 +82,8 @@ type AccountStatus = "active" | "trial" | "past_due" | "suspended" | "closed";
|
|
|
80
82
|
*/
|
|
81
83
|
type AccountResource = {
|
|
82
84
|
id: AccountId;
|
|
85
|
+
/** Parent application ID - accounts always belong to an application */
|
|
86
|
+
parentId: ApplicationId;
|
|
83
87
|
/** Display name chosen by the account owner */
|
|
84
88
|
name: string | null;
|
|
85
89
|
/** URL to the account's avatar image */
|
|
@@ -98,14 +102,17 @@ type AccountResource = {
|
|
|
98
102
|
* - suspended: Access restricted due to billing or policy
|
|
99
103
|
* - closed: Permanently closed
|
|
100
104
|
*/
|
|
101
|
-
type UserStatus = "active" | "
|
|
105
|
+
type UserStatus = "active" | "disabled" | "suspended" | "deleted";
|
|
102
106
|
/**
|
|
103
107
|
* A reference to a user. Will not be deleted even due to GDPR requests or account closures,
|
|
104
108
|
* to maintain referential integrity in audit logs and historical records.
|
|
109
|
+
*
|
|
110
|
+
* Users belong to an Application. They access Accounts via Memberships.
|
|
105
111
|
*/
|
|
106
112
|
type UserResource = {
|
|
107
113
|
id: UserId;
|
|
108
|
-
|
|
114
|
+
/** The application this user belongs to */
|
|
115
|
+
applicationId: ApplicationId;
|
|
109
116
|
status: UserStatus;
|
|
110
117
|
preferredMembershipId: MembershipId | null;
|
|
111
118
|
};
|
|
@@ -174,17 +181,21 @@ type SessionTokens = {
|
|
|
174
181
|
type SessionProfile = UserResource & {
|
|
175
182
|
identity: UserIdentity | null;
|
|
176
183
|
emailCredentials: EmailCredential[];
|
|
177
|
-
memberships: Membership
|
|
178
|
-
|
|
184
|
+
memberships: (Membership & {
|
|
185
|
+
account: AccountResource;
|
|
186
|
+
})[];
|
|
187
|
+
/** Currently active membership in the accessToken */
|
|
179
188
|
activeMembership: Membership | null;
|
|
180
189
|
lastUpdatedAt: number;
|
|
190
|
+
/** Convenience: the account ID from the active membership (for navigation) */
|
|
191
|
+
accountId: AccountId | null;
|
|
181
192
|
};
|
|
182
193
|
/**
|
|
183
194
|
* Combined authentication state containing tokens and optional profile.
|
|
184
195
|
*/
|
|
185
196
|
type Authentication = {
|
|
186
197
|
tokens: SessionTokens;
|
|
187
|
-
profile: SessionProfile
|
|
198
|
+
profile: SessionProfile;
|
|
188
199
|
};
|
|
189
200
|
/**
|
|
190
201
|
* Result of checkEmail() indicating whether the email is registered.
|
|
@@ -213,34 +224,21 @@ type Config = {
|
|
|
213
224
|
baseUrl: string;
|
|
214
225
|
earlyRefreshMs: number;
|
|
215
226
|
fetchFn: FetchFn;
|
|
216
|
-
onRefresh?: (tokens: SessionTokens) => void | Promise<void>;
|
|
217
|
-
onProfileRefetch?: (profile: SessionProfile) => void | Promise<void>;
|
|
218
|
-
onUnauthenticated?: (e: AuthError) => void | Promise<void>;
|
|
219
227
|
profileStorageKey: string;
|
|
220
228
|
storage: StorageConfig;
|
|
221
229
|
tokensStorageKey: string;
|
|
222
230
|
};
|
|
231
|
+
type AuthEventMap = {
|
|
232
|
+
refresh: SessionTokens;
|
|
233
|
+
profileChange: SessionProfile;
|
|
234
|
+
unauthenticated: AuthError;
|
|
235
|
+
sessionChange: AuthSession | null;
|
|
236
|
+
};
|
|
223
237
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* Always thrown on non-2xx responses from auth endpoints. Contains structured ProblemDetails
|
|
227
|
-
* from the server when available.
|
|
228
|
-
*
|
|
229
|
-
* Most 400 errors will also contain `invalidParams` for parameters that caused
|
|
230
|
-
* the error, which can be used to display field-level validation messages.
|
|
238
|
+
* AuthError represents structured authentication failures from Accountmaker endpoints.
|
|
231
239
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* Note that HTTP error codes are distinctly:
|
|
236
|
-
* - 400: Client error (bad request, invalid input, etc.)
|
|
237
|
-
* - 401: Unauthenticated (we don't know who you are)
|
|
238
|
-
* - 402: Payment required (e.g. billing issue)
|
|
239
|
-
* - 403: Unauthorized (we know who you are, but you don't have permission)
|
|
240
|
-
* - 404: Not found
|
|
241
|
-
* - 409: Conflict (email already registered, user already invited, etc.)
|
|
242
|
-
* - 429: Too many requests (rate limiting)
|
|
243
|
-
* - 500: Internal server error (server's fault)
|
|
240
|
+
* AuthError wraps RFC 7807 Problem Details. invalidParams may be present for field-level validation.
|
|
241
|
+
* Network failures throw other error types.
|
|
244
242
|
*
|
|
245
243
|
* Also note that the `type` field often contains a URI that points to documentation about the
|
|
246
244
|
* specific error type, including how to resolve it, code samples, and links to the RFCs or other
|
|
@@ -263,6 +261,16 @@ type Config = {
|
|
|
263
261
|
* }
|
|
264
262
|
* }
|
|
265
263
|
* ```
|
|
264
|
+
*
|
|
265
|
+
* Note that HTTP error codes are distinctly:
|
|
266
|
+
* - 400: Client error (bad request, invalid input, etc.)
|
|
267
|
+
* - 401: Unauthenticated (we don't know who you are)
|
|
268
|
+
* - 402: Payment required (e.g. billing issue)
|
|
269
|
+
* - 403: Unauthorized (we know who you are, but you don't have permission)
|
|
270
|
+
* - 404: Not found
|
|
271
|
+
* - 409: Conflict (email already registered, user already invited, etc.)
|
|
272
|
+
* - 429: Too many requests (rate limiting)
|
|
273
|
+
* - 500: Internal server error (server's fault)
|
|
266
274
|
*/
|
|
267
275
|
declare class AuthError extends Error {
|
|
268
276
|
readonly problem: ProblemDetails;
|
|
@@ -275,45 +283,20 @@ declare class AuthError extends Error {
|
|
|
275
283
|
get invalidParams(): ProblemDetails["invalidParams"] | undefined;
|
|
276
284
|
}
|
|
277
285
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* It contains the current access token, user profile, and methods to perform authorized
|
|
281
|
-
* requests.
|
|
286
|
+
* AuthSession represents an authenticated user with automatic token refresh and persisted state.
|
|
282
287
|
*
|
|
283
|
-
*
|
|
284
|
-
* - session.fetch() will always use a valid access token (refreshing automatically)
|
|
285
|
-
* - All non-2xx responses throw AuthError
|
|
286
|
-
* - Tokens and profile are automatically persisted to storage (if configured)
|
|
288
|
+
* AuthSession owns tokens, profile data, refresh logic, and authenticated requests.
|
|
287
289
|
*/
|
|
288
290
|
declare class AuthSession {
|
|
289
291
|
constructor(initial: Authentication, config: Partial<Config>);
|
|
290
|
-
/**
|
|
291
|
-
* Restores an AuthSession from persisted storage. Returns null if no valid session
|
|
292
|
-
* is found.
|
|
293
|
-
*
|
|
294
|
-
* @example
|
|
295
|
-
* ```ts
|
|
296
|
-
* const session = AuthSession.restoreSession();
|
|
297
|
-
* if (session) {
|
|
298
|
-
* console.log("Restored session for user:", session.profile);
|
|
299
|
-
* } else {
|
|
300
|
-
* console.log("No valid session found.");
|
|
301
|
-
* }
|
|
302
|
-
* ```
|
|
303
|
-
*/
|
|
304
|
-
static restoreSession(config?: Partial<Config>): AuthSession | null;
|
|
305
292
|
/**
|
|
306
293
|
* Removes all persisted data (tokens, profile) from storage, and prevents future
|
|
307
|
-
*
|
|
294
|
+
* refreshes of token and profile data. Does NOT clear current token or profile data from the
|
|
308
295
|
* session memory, but effectively deactivates the session for future use.
|
|
309
296
|
*/
|
|
310
297
|
clear(): void;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
idToken(): string | undefined;
|
|
314
|
-
expiresIn(): number;
|
|
315
|
-
expiresAt(): Date;
|
|
316
|
-
profile(): SessionProfile | null;
|
|
298
|
+
get tokens(): SessionTokens;
|
|
299
|
+
get profile(): SessionProfile;
|
|
317
300
|
toJSON(): Authentication;
|
|
318
301
|
/**
|
|
319
302
|
* Creates an AuthSession from existing authentication data. Useful for restoring
|
|
@@ -326,38 +309,31 @@ declare class AuthSession {
|
|
|
326
309
|
*/
|
|
327
310
|
isExpired(): boolean;
|
|
328
311
|
/**
|
|
329
|
-
*
|
|
330
|
-
* session's access token. Any service can validate the token against Am's public
|
|
331
|
-
* keys at https://api.accountmaker.com/.well-known/jwks.json?client_id={clientId}
|
|
332
|
-
*
|
|
333
|
-
* Automatically:
|
|
334
|
-
* - Adds Authorization header
|
|
335
|
-
* - Refreshes token if expired
|
|
336
|
-
* - Retries once on 401 if refresh succeeds
|
|
312
|
+
* Performs standard fetch() with a Bearer token and automatic refresh.
|
|
337
313
|
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
* If the error is a RFC 7807 Problem Details response, the AuthError.problem
|
|
341
|
-
* will contain the full details.
|
|
314
|
+
* Returns Response, does not parse the body, does not throw for HTTP status codes.
|
|
315
|
+
* Throws AuthError when refresh fails, trows runtime errors on network failure.
|
|
342
316
|
*
|
|
343
317
|
* @example
|
|
344
318
|
* ```ts
|
|
345
|
-
* const res = await session.fetch(
|
|
319
|
+
* const res = await session.fetch("/api/projects");
|
|
346
320
|
* const projects = await res.json();
|
|
347
321
|
* ```
|
|
348
322
|
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
323
|
+
* Any service can validate tokens using:
|
|
324
|
+
* https://api.accountmaker.com/.well-known/jwks.json?client_id={clientId}
|
|
351
325
|
*/
|
|
352
|
-
fetch(url: string | URL, init?: RequestInit): Promise<
|
|
326
|
+
fetch(url: string | URL, init?: RequestInit): Promise<Response>;
|
|
353
327
|
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
328
|
+
* Replaces the access token using the refresh token.
|
|
329
|
+
*
|
|
330
|
+
* It is called automatically by all other methods when the access token is expired or near expiry.
|
|
357
331
|
*/
|
|
358
332
|
refresh(): Promise<void>;
|
|
359
333
|
/**
|
|
360
|
-
*
|
|
334
|
+
* refetchProfile() replaces the cached profile with server state.
|
|
335
|
+
*
|
|
336
|
+
* Concurrent calls are deduplicated.
|
|
361
337
|
*
|
|
362
338
|
* @example
|
|
363
339
|
* ```ts
|
|
@@ -370,29 +346,19 @@ declare class AuthSession {
|
|
|
370
346
|
*/
|
|
371
347
|
refetchProfile(): Promise<void>;
|
|
372
348
|
/**
|
|
373
|
-
*
|
|
374
|
-
* called by the currently authenticated user or an account admin.
|
|
349
|
+
* Requests a verification email for the current user.
|
|
375
350
|
*
|
|
376
351
|
* Throws AuthError on network errors, etc.
|
|
377
352
|
* @throws AuthError
|
|
378
353
|
*/
|
|
379
354
|
sendVerificationEmail(): Promise<void>;
|
|
380
|
-
/**
|
|
381
|
-
* Fetches a user by ID.
|
|
382
|
-
*
|
|
383
|
-
* Throws AuthError on invalid user ID, network errors, etc.
|
|
384
|
-
* @throws AuthError
|
|
385
|
-
*/
|
|
386
|
-
user(id: UserId): Promise<UserResource>;
|
|
387
355
|
}
|
|
388
356
|
/**
|
|
389
|
-
*
|
|
390
|
-
* invite acceptance, and other unauthenticated actions.
|
|
357
|
+
* Am runs authentication flows and produces AuthSession.
|
|
391
358
|
*
|
|
392
|
-
*
|
|
393
|
-
* for all subsequent authenticated requests.
|
|
359
|
+
* Use Am before a session exists (sign-in, sign-up, magic link, invites, password reset, CSRF).
|
|
394
360
|
*
|
|
395
|
-
*
|
|
361
|
+
* @example
|
|
396
362
|
* ```ts
|
|
397
363
|
* const am = new Am();
|
|
398
364
|
* const session = await am.signIn({ email: 'user@example.com', password: 'secret' });
|
|
@@ -400,68 +366,55 @@ declare class AuthSession {
|
|
|
400
366
|
* ```
|
|
401
367
|
*/
|
|
402
368
|
declare class Am {
|
|
403
|
-
private options;
|
|
404
369
|
constructor(config?: Partial<Config>);
|
|
405
370
|
/**
|
|
406
|
-
*
|
|
407
|
-
*
|
|
371
|
+
* session returns the current AuthSession or null.
|
|
372
|
+
*
|
|
373
|
+
* Use restoreSession() to load from storage.
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```ts
|
|
377
|
+
* const session = am.session;
|
|
378
|
+
* if (!session) throw new Error("Not authenticated");
|
|
379
|
+
* ```
|
|
408
380
|
*/
|
|
409
|
-
|
|
381
|
+
get session(): AuthSession | null;
|
|
410
382
|
/**
|
|
411
|
-
*
|
|
412
|
-
*
|
|
383
|
+
* Constructs AuthSession from existing tokens and profile.
|
|
384
|
+
*
|
|
385
|
+
* Use this after a server-side auth handshake or custom persistence.
|
|
413
386
|
*/
|
|
414
|
-
|
|
387
|
+
createSession(initial: Authentication): AuthSession;
|
|
415
388
|
/**
|
|
416
|
-
*
|
|
417
|
-
* containing fresh tokens and profile. Tokens are automatically persisted (if
|
|
418
|
-
* storage is enabled).
|
|
389
|
+
* restoreSession loads AuthSession from storage or returns null.
|
|
419
390
|
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
391
|
+
* Invalid or partial stored data is cleared.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```ts
|
|
395
|
+
* const am = new Am({ storage: 'localStorage' });
|
|
396
|
+
* const session = am.restoreSession();
|
|
397
|
+
* if (session) session.fetch("/api/me");
|
|
398
|
+
* ```
|
|
422
399
|
*/
|
|
423
|
-
|
|
424
|
-
clientId: ClientId;
|
|
425
|
-
token: string;
|
|
426
|
-
}, config?: Partial<Config>): Promise<AuthSession>;
|
|
400
|
+
restoreSession(): AuthSession | null;
|
|
427
401
|
/**
|
|
428
|
-
*
|
|
429
|
-
|
|
430
|
-
|
|
402
|
+
* Subscribes to auth events and returns an unsubscribe function.
|
|
403
|
+
*/
|
|
404
|
+
on<K extends keyof AuthEventMap>(event: K, fn: (v: AuthEventMap[K]) => void): () => boolean;
|
|
405
|
+
/**
|
|
406
|
+
* acceptInvite exchanges an invite token for a fresh AuthSession.
|
|
431
407
|
*
|
|
432
|
-
* Throws AuthError on invalid or
|
|
433
|
-
* @throws AuthError
|
|
408
|
+
* Throws AuthError on invalid, expired, or already-used tokens.
|
|
434
409
|
*/
|
|
435
410
|
acceptInvite(query: {
|
|
436
411
|
clientId: ClientId;
|
|
437
412
|
token: string;
|
|
438
413
|
}): Promise<AuthSession>;
|
|
439
414
|
/**
|
|
440
|
-
*
|
|
441
|
-
* email is associated with an active account, and what login methods are preferred and
|
|
442
|
-
* available for that user. This can be used to enforce login expierences for enterprise SSO or
|
|
443
|
-
* to prefer passwordless login methods.
|
|
444
|
-
*
|
|
445
|
-
* Throws AuthError on invalid client ID, network errors, etc.
|
|
446
|
-
* @throws AuthError
|
|
447
|
-
*/
|
|
448
|
-
static checkEmail(body: {
|
|
449
|
-
clientId: ClientId;
|
|
450
|
-
email: string;
|
|
451
|
-
csrfToken?: string;
|
|
452
|
-
}, config?: Partial<Config>): Promise<{
|
|
453
|
-
status: EmailCheckStatus;
|
|
454
|
-
preferred: LoginMethod[];
|
|
455
|
-
available: LoginMethod[];
|
|
456
|
-
}>;
|
|
457
|
-
/**
|
|
458
|
-
* Checks the status of an email address for authentication purposes. Indicates whether the
|
|
459
|
-
* email is associated with an active account, and what login methods are preferred and
|
|
460
|
-
* available for that user. This can be used to enforce login expierences for enterprise SSO or
|
|
461
|
-
* to prefer passwordless login methods.
|
|
415
|
+
* checkEmail returns how an email should authenticate for this client.
|
|
462
416
|
*
|
|
463
|
-
*
|
|
464
|
-
* @throws AuthError
|
|
417
|
+
* Use this to choose password vs magic link vs SSO before rendering a login form.
|
|
465
418
|
*/
|
|
466
419
|
checkEmail(body: {
|
|
467
420
|
clientId: ClientId;
|
|
@@ -473,66 +426,25 @@ declare class Am {
|
|
|
473
426
|
available: LoginMethod[];
|
|
474
427
|
}>;
|
|
475
428
|
/**
|
|
476
|
-
*
|
|
477
|
-
* to get the signed token to include in the form. When the form is submitted, the server
|
|
478
|
-
* will verify the signed token against the session cookie. This prevents a certain class
|
|
479
|
-
* of CSRF attacks that rely on being able to read values from the target site.
|
|
480
|
-
*/
|
|
481
|
-
static csrfSession(config?: Partial<Config>): Promise<{
|
|
482
|
-
csrfToken: string;
|
|
483
|
-
}>;
|
|
484
|
-
/**
|
|
485
|
-
* When called, sets a httpOnly CSRF session cookie. Then when rendering a form, call csrfToken()
|
|
486
|
-
* to get the signed token to include in the form. When the form is submitted, the server
|
|
487
|
-
* will verify the signed token against the session cookie. This prevents a certain class
|
|
488
|
-
* of CSRF attacks that rely on being able to read values from the target site.
|
|
489
|
-
*/
|
|
490
|
-
csrfSession(): Promise<{
|
|
491
|
-
csrfToken: string;
|
|
492
|
-
}>;
|
|
493
|
-
/**
|
|
494
|
-
* Fetches a signed CSRF token for use in forms. Call csrfSession() first to set a httpOnly CSRF
|
|
495
|
-
* session cookie, then call this method to get the signed token, then include the token in your
|
|
496
|
-
* form submissions. When the form is submitted, the server will verify the signed token against
|
|
497
|
-
* the httpOnly session cookie. This prevents a certain class of CSRF attacks that rely on being
|
|
498
|
-
* able to read values from the target site.
|
|
429
|
+
* Sets the httpOnly CSRF cookie.
|
|
499
430
|
*
|
|
500
|
-
*
|
|
431
|
+
* Call csrfToken() next to fetch a signed token for form posts.
|
|
501
432
|
*/
|
|
502
|
-
|
|
433
|
+
csrfSession(): Promise<{
|
|
503
434
|
csrfToken: string;
|
|
504
435
|
}>;
|
|
505
436
|
/**
|
|
506
|
-
*
|
|
507
|
-
* session cookie, then call this method to get the signed token, then include the token in your
|
|
508
|
-
* form submissions. When the form is submitted, the server will verify the signed token against
|
|
509
|
-
* the httpOnly session cookie. This prevents a certain class of CSRF attacks that rely on being
|
|
510
|
-
* able to read values from the target site.
|
|
437
|
+
* Returns a signed CSRF token for form posts.
|
|
511
438
|
*
|
|
512
|
-
*
|
|
439
|
+
* Call csrfSession() first to set the CSRF cookie.
|
|
513
440
|
*/
|
|
514
441
|
csrfToken(): Promise<{
|
|
515
442
|
csrfToken: string;
|
|
516
443
|
}>;
|
|
517
444
|
/**
|
|
518
|
-
*
|
|
519
|
-
* Tokens are automatically persisted (if storage is enabled).
|
|
445
|
+
* Authenticates with email and password and returns a new AuthSession.
|
|
520
446
|
*
|
|
521
|
-
*
|
|
522
|
-
* @throws AuthError
|
|
523
|
-
*/
|
|
524
|
-
static signIn(body: {
|
|
525
|
-
clientId: ClientId;
|
|
526
|
-
email: string;
|
|
527
|
-
password: string;
|
|
528
|
-
csrfToken?: string;
|
|
529
|
-
}, config?: Partial<Config>): Promise<AuthSession>;
|
|
530
|
-
/**
|
|
531
|
-
* On success, returns an AuthSession containing fresh tokens and profile.
|
|
532
|
-
* Tokens are automatically persisted (if storage is enabled).
|
|
533
|
-
*
|
|
534
|
-
* Throws AuthError on invalid credentials, unverified email, etc.
|
|
535
|
-
* @throws AuthError
|
|
447
|
+
* Tokens and profile are persisted when storage is configured.
|
|
536
448
|
*/
|
|
537
449
|
signIn(body: {
|
|
538
450
|
clientId: ClientId;
|
|
@@ -541,62 +453,15 @@ declare class Am {
|
|
|
541
453
|
csrfToken?: string;
|
|
542
454
|
}): Promise<AuthSession>;
|
|
543
455
|
/**
|
|
544
|
-
* Authenticates
|
|
545
|
-
*
|
|
546
|
-
* On success, returns an AuthSession containing fresh tokens and profile.
|
|
547
|
-
* Tokens are automatically persisted (if storage is enabled).
|
|
548
|
-
*
|
|
549
|
-
* Throws AuthError on invalid or expired token, etc.
|
|
550
|
-
* @throws AuthError
|
|
551
|
-
*/
|
|
552
|
-
static signInWithToken(token: string, config?: Partial<Config>): Promise<AuthSession>;
|
|
553
|
-
/**
|
|
554
|
-
* Authenticates using a one-time token (e.g., magic link or invite token).
|
|
456
|
+
* Authenticates with a one-time token and returns a new AuthSession.
|
|
555
457
|
*
|
|
556
|
-
*
|
|
557
|
-
* Tokens are automatically persisted (if storage is enabled).
|
|
558
|
-
*
|
|
559
|
-
* Throws AuthError on invalid or expired token, etc.
|
|
560
|
-
* @throws AuthError
|
|
458
|
+
* Use this for magic links and similar one-time login flows.
|
|
561
459
|
*/
|
|
562
460
|
signInWithToken(token: string): Promise<AuthSession>;
|
|
563
461
|
/**
|
|
564
|
-
*
|
|
565
|
-
* persist tokens. Use AuthSession.refresh() to refresh and persist tokens in an
|
|
566
|
-
* existing session.
|
|
462
|
+
* Creates a new user and returns a new AuthSession.
|
|
567
463
|
*
|
|
568
|
-
*
|
|
569
|
-
* @throws AuthError
|
|
570
|
-
*/
|
|
571
|
-
static refresh(refreshToken: string, config?: Partial<Config>): Promise<SessionTokens>;
|
|
572
|
-
/**
|
|
573
|
-
* Manually refreshes session tokens using the provided refresh token. Does NOT
|
|
574
|
-
* persist tokens. Use AuthSession.refresh() to refresh and persist tokens in an
|
|
575
|
-
* existing session.
|
|
576
|
-
*
|
|
577
|
-
* Throws AuthError on invalid or expired refresh token, etc.
|
|
578
|
-
* @throws AuthError
|
|
579
|
-
*/
|
|
580
|
-
refresh(refreshToken: string): Promise<SessionTokens>;
|
|
581
|
-
/**
|
|
582
|
-
* Successful registration immediately authenticates the user and returns an
|
|
583
|
-
* AuthSession. Tokens are automatically persisted (if storage is enabled).
|
|
584
|
-
*
|
|
585
|
-
* Throws AuthError on invalid data, existing email, etc.
|
|
586
|
-
* @throws AuthError
|
|
587
|
-
*/
|
|
588
|
-
static signUp(body: {
|
|
589
|
-
clientId: ClientId;
|
|
590
|
-
email: string;
|
|
591
|
-
password: string;
|
|
592
|
-
csrfToken?: string;
|
|
593
|
-
}, config?: Partial<Config>): Promise<AuthSession>;
|
|
594
|
-
/**
|
|
595
|
-
* Successful registration immediately authenticates the user and returns an
|
|
596
|
-
* AuthSession. Tokens are automatically persisted (if storage is enabled).
|
|
597
|
-
*
|
|
598
|
-
* Throws AuthError on invalid data, existing email, etc.
|
|
599
|
-
* @throws AuthError
|
|
464
|
+
* Tokens and profile are persisted when storage is configured.
|
|
600
465
|
*/
|
|
601
466
|
signUp(body: {
|
|
602
467
|
clientId: ClientId;
|
|
@@ -605,45 +470,14 @@ declare class Am {
|
|
|
605
470
|
csrfToken?: string;
|
|
606
471
|
}): Promise<AuthSession>;
|
|
607
472
|
/**
|
|
608
|
-
*
|
|
609
|
-
* the user's password is updated.
|
|
610
|
-
*
|
|
611
|
-
* Throws AuthError on invalid or expired token, weak password, etc.
|
|
612
|
-
* @throws AuthError
|
|
613
|
-
*/
|
|
614
|
-
static resetPassword(body: {
|
|
615
|
-
token: string;
|
|
616
|
-
newPassword: string;
|
|
617
|
-
}, config?: Partial<Config>): Promise<void>;
|
|
618
|
-
/**
|
|
619
|
-
* Completes a password reset using the token received via email. On success,
|
|
620
|
-
* the user's password is updated.
|
|
621
|
-
*
|
|
622
|
-
* Throws AuthError on invalid or expired token, weak password, etc.
|
|
623
|
-
* @throws AuthError
|
|
473
|
+
* Sets a new password using a one-time reset token.
|
|
624
474
|
*/
|
|
625
475
|
resetPassword(body: {
|
|
626
476
|
token: string;
|
|
627
477
|
newPassword: string;
|
|
628
478
|
}): Promise<void>;
|
|
629
479
|
/**
|
|
630
|
-
* Sends a
|
|
631
|
-
* passwordless authentication.
|
|
632
|
-
*
|
|
633
|
-
* Throws AuthError on invalid email format, etc.
|
|
634
|
-
* @throws AuthError
|
|
635
|
-
*/
|
|
636
|
-
static sendMagicLink(body: {
|
|
637
|
-
clientId: ClientId;
|
|
638
|
-
email: string;
|
|
639
|
-
csrfToken?: string;
|
|
640
|
-
}, config?: Partial<Config>): Promise<void>;
|
|
641
|
-
/**
|
|
642
|
-
* Sends a magic link email to the specified address. A magic link allows
|
|
643
|
-
* passwordless authentication.
|
|
644
|
-
*
|
|
645
|
-
* Throws AuthError on invalid email format, etc.
|
|
646
|
-
* @throws AuthError
|
|
480
|
+
* Sends a one-time sign-in link to an email address.
|
|
647
481
|
*/
|
|
648
482
|
sendMagicLink(body: {
|
|
649
483
|
clientId: ClientId;
|
|
@@ -651,21 +485,7 @@ declare class Am {
|
|
|
651
485
|
csrfToken?: string;
|
|
652
486
|
}): Promise<void>;
|
|
653
487
|
/**
|
|
654
|
-
* Sends a password reset
|
|
655
|
-
*
|
|
656
|
-
* Throws AuthError on invalid email format, etc.
|
|
657
|
-
* @throws AuthError
|
|
658
|
-
*/
|
|
659
|
-
static sendPasswordReset(body: {
|
|
660
|
-
clientId: ClientId;
|
|
661
|
-
email: string;
|
|
662
|
-
csrfToken?: string;
|
|
663
|
-
}, config?: Partial<Config>): Promise<void>;
|
|
664
|
-
/**
|
|
665
|
-
* Sends a password reset email to the specified address.
|
|
666
|
-
*
|
|
667
|
-
* Throws AuthError on invalid email format, etc.
|
|
668
|
-
* @throws AuthError
|
|
488
|
+
* Sends a password reset link to an email address.
|
|
669
489
|
*/
|
|
670
490
|
sendPasswordReset(body: {
|
|
671
491
|
clientId: ClientId;
|
|
@@ -675,4 +495,4 @@ declare class Am {
|
|
|
675
495
|
}
|
|
676
496
|
|
|
677
497
|
export { Am, AuthError, AuthSession };
|
|
678
|
-
export type { AccountId, AccountResource, AccountStatus, Authentication, ClientId, EmailCheckStatus, EmailCredential, LoginMethod, Membership, MembershipId, MembershipRole, ProblemDetails, SessionProfile, SessionTokens, StorageLike, UserId, UserIdentity, UserResource, UserStatus };
|
|
498
|
+
export type { AccountId, AccountResource, AccountStatus, ApplicationId, Authentication, ClientId, EmailCheckStatus, EmailCredential, LoginMethod, Membership, MembershipId, MembershipRole, ProblemDetails, SessionProfile, SessionTokens, StorageLike, UserId, UserIdentity, UserResource, UserStatus };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const t=Symbol("
|
|
1
|
+
const t=Symbol("session_state"),e=Symbol("auth_session"),n=Symbol("auth_state"),r=Symbol("emitter");function s(t){return t?"localStorage"===t?function(){try{return"undefined"==typeof window?null:window.localStorage?window.localStorage:null}catch{return null}}():t:null}function o(t,e){return t?function(t){if(!t)return null;try{return JSON.parse(t)}catch{return null}}(t.getItem(e)):null}function i(t,e,n){if(t)try{t.setItem(e,JSON.stringify(n))}catch{}}function a(t,e){if(t)try{t.removeItem(e)}catch{}}function c(t,e,n){if(!t)return;const r=o(t,e),s=u(r)?r:null;null===r||s||a(t,e),s&&s.expiresAt>=n.expiresAt||i(t,e,n)}function l(t,e,n){if(!t)return;const r=o(t,e),s=f(r)?r:null;null===r||s||a(t,e),s&&s.lastUpdatedAt>=n.lastUpdatedAt||i(t,e,n)}function u(t){return!!t&&"string"==typeof t.accessToken&&"string"==typeof t.refreshToken&&"number"==typeof t.expiresAt&&"number"==typeof t.expiresIn&&"Bearer"===t.tokenType}function f(t){return!!t&&"string"==typeof t.id&&"string"==typeof t.applicationId&&"string"==typeof t.status&&"number"==typeof t.lastUpdatedAt&&("object"==typeof t.identity||null===t.identity)}function p(e){return e[t]}function h(t){return t[n]}function y(t,n){t[e]=n;const s=p(n);!function(t,e){t[r]=(t,n)=>{const r=h(e).listeners[t];if(r)for(const e of r)try{e(n)}catch{console.warn("Unhandled error in AuthEvent listener for event",t)}}}(s,t),g(s,"sessionChange",n)}function g(t,e,n){(0,t[r])(e,n)}function d(t){return t.replace(/_([a-z])/g,(t,e)=>e.toUpperCase())}function m(t){if(null===t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(t=>m(t));const e=t,n={};for(const t of Object.keys(e))Object.prototype.hasOwnProperty.call(e,t)&&(n[d(t)]=m(e[t]));return n}function w(t){return t.replace(/[A-Z]/g,t=>`_${t.toLowerCase()}`)}function b(t){if(null===t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(t=>b(t));const e=t,n={};for(const t of Object.keys(e))Object.prototype.hasOwnProperty.call(e,t)&&(n[w(t)]=b(e[t]));return n}class k extends Error{constructor(t){super(t.title),this.name="AuthError",this.problem=Object.freeze(t)}get type(){return this.problem.type}get title(){return this.problem.title}get status(){return this.problem.status}get code(){return this.problem.code}get detail(){return this.problem.detail}get invalidParams(){return this.problem.invalidParams}}const S={fetchFn:function(){const t=globalThis.fetch;return"function"==typeof t?t.bind(globalThis):async()=>{throw new Error("Missing fetch implementation. Provide config.fetchFn or use a runtime with global fetch.")}}(),baseUrl:"https://api.accountmaker.com",earlyRefreshMs:6e4,storage:null,tokensStorageKey:"am_tokens",profileStorageKey:"am_profile"};const P=async(t,e,n={})=>t.config.fetchFn(e,function(t,e){const n=new Headers(t.headers);return n.set("Authorization",`Bearer ${e}`),{...t,headers:n}}(n,t.tokens.accessToken)),A=async(t,e,n={})=>{U(t)&&await x(t);let r=await P(t,e,n);return 401!==r.status||t.cleared||(await x(t),r=await P(t,e,n)),r},j=async t=>{if(204===t.status)return;const e=m(await async function(t){const e=t.headers.get("Content-Type")||"";if(!e.includes("application/json")&&!e.includes("+json"))return null;try{return await t.json()}catch{return null}}(t));if(!t.ok)throw new k(function(t,e){return function(t){return(t.headers.get("Content-Type")||"").includes("application/problem+json")}(t)&&e&&"object"==typeof e&&"string"==typeof e.type&&"string"==typeof e.title&&"number"==typeof e.status?e:function(t,e){return{type:"about:blank",title:t.statusText||"Request failed",status:t.status,detail:"string"==typeof e?e:void 0}}(t,e)}(t,e));return e},T=async(t,e,n={})=>{const r=await A(t,e,n);return j(r)},O=async({fetchFn:t,baseUrl:e},n,r={})=>{const s=new URLSearchParams(b(r)).toString(),o=s?`${e}${n}?${s}`:`${e}${n}`,i=await t(o,{method:"GET",headers:{Accept:"application/json"}});return j(i)},$=async({fetchFn:t,baseUrl:e},n,r)=>{const s=await t(`${e}${n}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(b(r))});return j(s)},U=t=>{const e=Math.min(Math.max(t.config.earlyRefreshMs,0),3e5);return Date.now()>=t.tokens.expiresAt-e},x=async t=>{if(!t.cleared){t.refreshPromise||(t.refreshPromise=async function(t){const{fetchFn:e,baseUrl:n}=t.config,r=await e(`${n}/auth/refresh`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(b({refreshToken:t.tokens.refreshToken}))});let o;try{o=await j(r)}catch(e){throw!t.cleared&&K(e)&&g(t,"unauthenticated",e),e}const i=v(o);t.tokens=i;const a=s(t.config.storage);c(a,t.config.tokensStorageKey,i),g(t,"refresh",i)}(t));try{await t.refreshPromise}finally{t.refreshPromise=null}}},K=t=>t instanceof k&&401===t.status,v=t=>{const e="number"==typeof t.expiresIn?t.expiresIn:0;return{...t,expiresAt:Date.now()+1e3*e}},I=t=>({...t,lastUpdatedAt:Date.now()});async function C(t){const e=I(await(async(t,e,n={})=>{const r=new URLSearchParams(b(n)).toString(),s=t.config.baseUrl,o=r?`${s}${e}?${r}`:`${s}${e}`;return await T(t,o,{method:"GET",headers:{Accept:"application/json"}})})(t,"/auth/me",{}));t.profile=e;l(s(t.config.storage),t.config.profileStorageKey,e),g(t,"profileChange",e)}const E=t=>({tokens:v(t.tokens),profile:I(t.profile)});class J{constructor(e,n){const r={...S,...n},o={...e,config:r,refreshPromise:null,profilePromise:null,cleared:!1};!function(e,n){e[t]=n}(this,o);const i=s(r.storage);c(i,r.tokensStorageKey,o.tokens),o.profile&&l(i,r.profileStorageKey,o.profile)}clear(){const t=p(this);!function(t){const e=s(t.storage);a(e,t.profileStorageKey),a(e,t.tokensStorageKey)}(t.config),t.cleared=!0}get tokens(){return p(this).tokens}get profile(){return p(this).profile}toJSON(){const t=p(this);return{tokens:t.tokens,profile:t.profile}}static fromJSON(t,e){return new J(t,e)}isExpired(){return U(p(this))}async fetch(t,e={}){return A(p(this),t,e)}async refresh(){return x(p(this))}async refetchProfile(){const t=p(this);if(!t.cleared){t.profilePromise||(t.profilePromise=C(t));try{await t.profilePromise}finally{t.profilePromise=null}}}async sendVerificationEmail(){await(async(t,e,n)=>{const r=t.config.baseUrl;return await T(t,`${r}${e}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(b(n))})})(p(this),"/auth/send-verification-email",{})}}class N{constructor(t){var e,r;e=this,r={config:{...S,...t},listeners:{}},e[n]=r}get session(){return this[e]||null}createSession(t){const e=new J(t,h(this).config);return y(this,e),e}restoreSession(){const t=h(this).config,e=s(t.storage);if(!e)return null;const n=o(e,t.tokensStorageKey),r=u(n)?n:null,i=o(e,t.profileStorageKey),c=f(i)?i:null;if(!r||!c)return a(e,t.tokensStorageKey),a(e,t.profileStorageKey),null;const l=new J({tokens:r,profile:c},t);return y(this,l),l}on(t,e){const n=h(this).listeners,r=n[t]??(n[t]=new Set);return r.add(e),()=>r.delete(e)}async acceptInvite(t){const e=h(this).config,n=E(await O(e,"/auth/accept-invite",t)),r=new J(n,e);return y(this,r),r}async checkEmail(t){return $(h(this).config,"/auth/check-email",t)}async csrfSession(){return O(h(this).config,"/auth/csrf-session")}async csrfToken(){return O(h(this).config,"/auth/csrf-token")}async signIn(t){const e=h(this).config,n=E(await $(e,"/auth/sign-in",t)),r=new J(n,e);return y(this,r),r}async signInWithToken(t){const e=h(this).config,n=E(await O(e,"/auth/sign-in-with-token",{token:t})),r=new J(n,e);return y(this,r),r}async signUp(t){const e=h(this).config,n=E(await $(e,"/auth/sign-up",t)),r=new J(n,e);return y(this,r),r}async resetPassword(t){return $(h(this).config,"/auth/reset-password",t)}async sendMagicLink(t){return $(h(this).config,"/auth/send-magic-link",t)}async sendPasswordReset(t){return $(h(this).config,"/auth/send-password-reset",t)}}export{N as Am,k as AuthError,J as AuthSession};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|