@wtfalch/auth 0.13.0 → 0.15.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/dist/auth.d.ts +24 -3
- package/dist/auth.js +20 -0
- package/dist/namespace-session.js +49 -10
- package/package.json +1 -1
package/dist/auth.d.ts
CHANGED
|
@@ -131,15 +131,21 @@ export interface Completed {
|
|
|
131
131
|
location: string;
|
|
132
132
|
cookies: SetCookie[];
|
|
133
133
|
}
|
|
134
|
-
/**
|
|
135
|
-
|
|
134
|
+
/**
|
|
135
|
+
* A person signed in without the browser driving the flow -- from a link, a
|
|
136
|
+
* password reset, or headless credentials. `E` is whichever entry point's
|
|
137
|
+
* own error vocabulary applies: `ResetError` for a link or reset code (the
|
|
138
|
+
* default, so `followLink` and `resetPassword` are unchanged), `SignInError`
|
|
139
|
+
* for a password.
|
|
140
|
+
*/
|
|
141
|
+
export type SignedIn<E = ResetError> = {
|
|
136
142
|
ok: true;
|
|
137
143
|
location: string;
|
|
138
144
|
cookies: SetCookie[];
|
|
139
145
|
hosted?: true;
|
|
140
146
|
} | {
|
|
141
147
|
ok: false;
|
|
142
|
-
error:
|
|
148
|
+
error: E;
|
|
143
149
|
message: string;
|
|
144
150
|
};
|
|
145
151
|
export interface QrSignInStart {
|
|
@@ -202,6 +208,21 @@ export interface Auth {
|
|
|
202
208
|
code: string;
|
|
203
209
|
next?: string;
|
|
204
210
|
}, requestHeaders?: Headers): Promise<SignedIn>;
|
|
211
|
+
/**
|
|
212
|
+
* `signIn` as data, for a caller with no browser to carry through the OIDC
|
|
213
|
+
* redirect -- a CLI, a desktop app, or a server signing in on a person's
|
|
214
|
+
* behalf. There is no `authRequestId` to receive, because this runs its
|
|
215
|
+
* own transaction and finishes it itself: `ok: true` already carries the
|
|
216
|
+
* session, with no `complete()` call left to make. The same two checks as
|
|
217
|
+
* `followLink` apply -- an `ok: true` with an empty `cookies` means the
|
|
218
|
+
* issuer handed off to hosted login, and the cookie name is fixed, so the
|
|
219
|
+
* caller decides where the session goes.
|
|
220
|
+
*/
|
|
221
|
+
signInWithPassword(input: {
|
|
222
|
+
email: string;
|
|
223
|
+
password: string;
|
|
224
|
+
next?: string;
|
|
225
|
+
}, requestHeaders?: Headers): Promise<SignedIn<SignInError>>;
|
|
205
226
|
/** Emails a fresh verification link. Says nothing about whether the address exists. */
|
|
206
227
|
resendVerification(email: string, next?: string): Promise<void>;
|
|
207
228
|
/** Emails a password-reset link to {basePath}/reset?userId=&code=. Says nothing about whether the address exists. */
|
package/dist/auth.js
CHANGED
|
@@ -541,6 +541,25 @@ export function createAuth(input) {
|
|
|
541
541
|
return { ok: false, error: signInError(error) };
|
|
542
542
|
}
|
|
543
543
|
};
|
|
544
|
+
const signInWithPassword = async ({ email, password, next }, requestHeaders) => {
|
|
545
|
+
try {
|
|
546
|
+
const flow = await serverFlow();
|
|
547
|
+
const completed = await finishOrHandoff(() => broker().signIn({ authRequestId: flow.id, email, password }), flow, safeNextPath(next, options().afterLogin), requestHeaders);
|
|
548
|
+
emit({ type: 'signIn', email });
|
|
549
|
+
return { ok: true, ...completed };
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
const code = signInError(error);
|
|
553
|
+
// `signInError` collapses everything the broker can say into one of
|
|
554
|
+
// four codes; only a real `BrokerError` carries a sentence worth
|
|
555
|
+
// showing; a transient failure gets the same generic message every
|
|
556
|
+
// other headless entry point uses for `unavailable`.
|
|
557
|
+
const message = code !== 'unavailable' && error instanceof BrokerError
|
|
558
|
+
? (error.detail ?? error.error)
|
|
559
|
+
: 'the sign-in service did not answer';
|
|
560
|
+
return { ok: false, error: code, message };
|
|
561
|
+
}
|
|
562
|
+
};
|
|
544
563
|
const signUp = async ({ authRequestId, ...user }) => {
|
|
545
564
|
try {
|
|
546
565
|
const redirectTo = await broker().signUp({ authRequestId, ...user });
|
|
@@ -814,6 +833,7 @@ export function createAuth(input) {
|
|
|
814
833
|
endSession,
|
|
815
834
|
authRequest,
|
|
816
835
|
signIn,
|
|
836
|
+
signInWithPassword,
|
|
817
837
|
signUp,
|
|
818
838
|
startPasskeyRegistration,
|
|
819
839
|
finishPasskeyRegistration,
|
|
@@ -248,11 +248,30 @@ export function namespaceSessions(deps) {
|
|
|
248
248
|
.catch(() => { });
|
|
249
249
|
return stillValid ? leased : null;
|
|
250
250
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
let committed;
|
|
252
|
+
try {
|
|
253
|
+
committed = await store().advance(key, generation, {
|
|
254
|
+
payload: await seal(key, fresh),
|
|
255
|
+
expiresAt: now() + options().sessionMaxAge,
|
|
256
|
+
leaseUntil: null,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
// The commit itself threw (a store outage): the lease is still held.
|
|
261
|
+
// Best-effort release it, as the still-valid path above already does,
|
|
262
|
+
// so the session does not stay locked out for up to LEASE_SECONDS, and
|
|
263
|
+
// revoke a rotated refresh token that was never stored.
|
|
264
|
+
await store()
|
|
265
|
+
.advance(key, generation, {
|
|
266
|
+
payload: record.payload,
|
|
267
|
+
expiresAt: record.expiresAt,
|
|
268
|
+
leaseUntil: null,
|
|
269
|
+
})
|
|
270
|
+
.catch(() => { });
|
|
271
|
+
if (fresh.refreshToken !== tokens.rt)
|
|
272
|
+
revokeToken(fresh.refreshToken);
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
256
275
|
if (!committed) {
|
|
257
276
|
// Revoked while we asked, or our lease ran out and another request took
|
|
258
277
|
// over. These tokens are kept nowhere, so they are not left live.
|
|
@@ -351,11 +370,31 @@ export function namespaceSessions(deps) {
|
|
|
351
370
|
// does for the same case.
|
|
352
371
|
throw error;
|
|
353
372
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
373
|
+
let committed;
|
|
374
|
+
try {
|
|
375
|
+
committed = await store().advance(key, generation, {
|
|
376
|
+
payload: await seal(key, fresh),
|
|
377
|
+
expiresAt: now() + options().sessionMaxAge,
|
|
378
|
+
leaseUntil: null,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
// The commit itself threw (a store outage): the lease is still held.
|
|
383
|
+
// Best-effort release it, as the still-valid path in `refreshLeased`
|
|
384
|
+
// does, so the session does not stay locked out for up to
|
|
385
|
+
// LEASE_SECONDS, and revoke a rotated refresh token that was never
|
|
386
|
+
// stored.
|
|
387
|
+
await store()
|
|
388
|
+
.advance(key, generation, {
|
|
389
|
+
payload: record.payload,
|
|
390
|
+
expiresAt: record.expiresAt,
|
|
391
|
+
leaseUntil: null,
|
|
392
|
+
})
|
|
393
|
+
.catch(() => { });
|
|
394
|
+
if (fresh.refreshToken !== tokens.rt)
|
|
395
|
+
revokeToken(fresh.refreshToken);
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
359
398
|
if (!committed) {
|
|
360
399
|
// Revoked while the grant was in flight, or the lease ran out and
|
|
361
400
|
// another request took over. The fresh tokens are kept nowhere, so
|
package/package.json
CHANGED