@wtfalch/auth 0.19.0 → 0.20.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 +9 -0
- package/dist/auth.d.ts +12 -0
- package/dist/auth.js +23 -4
- package/dist/broker.d.ts +13 -0
- package/dist/broker.js +4 -0
- package/dist/config.d.ts +5 -0
- package/dist/next.d.ts +4 -0
- package/dist/next.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,15 @@ Auth-request lookup failures lead to the configured error page with
|
|
|
94
94
|
`auth_error=request` or `auth_error=unavailable`. That page should show a
|
|
95
95
|
retry link; it must not automatically restart authorization.
|
|
96
96
|
|
|
97
|
+
When a sign-in fails after it started (reasons such as `state`, `unavailable`,
|
|
98
|
+
`exchange`, `denied`, `organization`, `cookie`), the error URL is
|
|
99
|
+
`<onError>?auth_error=<reason>&next=<path>`. `next` is the page the person came
|
|
100
|
+
for, always a path starting with one `/`, URL-encoded as a single parameter, and
|
|
101
|
+
present only when the sealed value is a safe same-origin path. An `onError` that
|
|
102
|
+
already has a query string keeps it. `expired` and `request` carry no `next`:
|
|
103
|
+
there is no transaction to read it from. Use it for the retry link
|
|
104
|
+
(`/auth/start?next=<path>`); a page that ignores it behaves as before.
|
|
105
|
+
|
|
97
106
|
## In a browser app
|
|
98
107
|
|
|
99
108
|
`@wtfalch/auth/browser` is the other half: a public PKCE client for an app that
|
package/dist/auth.d.ts
CHANGED
|
@@ -278,6 +278,7 @@ export interface Auth {
|
|
|
278
278
|
}): Promise<{
|
|
279
279
|
userId: string;
|
|
280
280
|
invited: boolean;
|
|
281
|
+
personalEmailStored: boolean;
|
|
281
282
|
}>;
|
|
282
283
|
handle(request: Request): Promise<Response>;
|
|
283
284
|
/**
|
|
@@ -331,6 +332,17 @@ export interface Auth {
|
|
|
331
332
|
userId: string;
|
|
332
333
|
state: string;
|
|
333
334
|
}>;
|
|
335
|
+
/**
|
|
336
|
+
* An admin's reset for somebody in this app's own organisation whom
|
|
337
|
+
* `requestPasswordReset` cannot otherwise get back in -- returns a fresh
|
|
338
|
+
* one-time password the caller is trusted to deliver some other way,
|
|
339
|
+
* rather than mailing it. Refused for a user outside this app's
|
|
340
|
+
* organisation.
|
|
341
|
+
*/
|
|
342
|
+
adminResetPassword(userId: string): Promise<{
|
|
343
|
+
userId: string;
|
|
344
|
+
password: string;
|
|
345
|
+
}>;
|
|
334
346
|
/**
|
|
335
347
|
* Namespace mode: end every session this person has in this service, on
|
|
336
348
|
* every replica, at their next request. Deactivating someone does this too.
|
package/dist/auth.js
CHANGED
|
@@ -159,9 +159,9 @@ export function createAuth(input) {
|
|
|
159
159
|
return failure('expired', options().namespace ? [] : [cleared]);
|
|
160
160
|
const params = new URL(callbackUrl).searchParams;
|
|
161
161
|
if (params.get('state') !== transaction.st)
|
|
162
|
-
return failure('state', options().namespace ? [] : [cleared]);
|
|
162
|
+
return failure('state', options().namespace ? [] : [cleared], transaction.nx);
|
|
163
163
|
if (options().namespace && !(await sessions.bindingActive()))
|
|
164
|
-
return failure('unavailable', [cleared]);
|
|
164
|
+
return failure('unavailable', [cleared], transaction.nx);
|
|
165
165
|
// A silent pass the issuer refused (login_required, interaction_required,
|
|
166
166
|
// consent_required, or any other error) is not a failure: sign in the normal
|
|
167
167
|
// way, once. `done` is already set, so `/start` will not go silent again.
|
|
@@ -181,7 +181,7 @@ export function createAuth(input) {
|
|
|
181
181
|
session = await sessionCookie(tokens, requestHeaders);
|
|
182
182
|
}
|
|
183
183
|
catch (error) {
|
|
184
|
-
return failure(reasonOf(error), [cleared]);
|
|
184
|
+
return failure(reasonOf(error), [cleared], transaction.nx);
|
|
185
185
|
}
|
|
186
186
|
await replacePreviousSession(cookieHeader);
|
|
187
187
|
return {
|
|
@@ -539,6 +539,20 @@ export function createAuth(input) {
|
|
|
539
539
|
emit({ type: 'invite', email: input.email, userId: result.userId, invited: result.invited });
|
|
540
540
|
return result;
|
|
541
541
|
};
|
|
542
|
+
/**
|
|
543
|
+
* An admin's reset for somebody in this app's own organisation whom
|
|
544
|
+
* `requestPasswordReset` cannot otherwise get back in -- nobody at the
|
|
545
|
+
* mailbox a personal address or the login address itself would reach.
|
|
546
|
+
* Refused for a user outside this app's organisation, exactly as
|
|
547
|
+
* `setPersonActive` is. The password is a one-time one: the person must
|
|
548
|
+
* choose their own at next sign-in, same as an invite's. Audited with the
|
|
549
|
+
* target's id and never the password itself.
|
|
550
|
+
*/
|
|
551
|
+
const adminResetPassword = async (userId) => {
|
|
552
|
+
const result = await broker().adminResetPassword(userId);
|
|
553
|
+
emit({ type: 'adminResetPassword', userId: result.userId });
|
|
554
|
+
return result;
|
|
555
|
+
};
|
|
542
556
|
const resetPassword = async ({ userId, code, password, next, }, requestHeaders) => {
|
|
543
557
|
try {
|
|
544
558
|
const flow = await serverFlow();
|
|
@@ -891,9 +905,13 @@ export function createAuth(input) {
|
|
|
891
905
|
cookies,
|
|
892
906
|
};
|
|
893
907
|
};
|
|
894
|
-
|
|
908
|
+
/** `next`, when it is a safe same-origin path, rides on the error URL so the error page can offer a retry to it. */
|
|
909
|
+
const failure = (reason, cookies, next) => {
|
|
895
910
|
const url = new URL(options().onError, options().appUrl);
|
|
896
911
|
url.searchParams.set('auth_error', reason);
|
|
912
|
+
const retry = safeNextPath(next, '');
|
|
913
|
+
if (retry)
|
|
914
|
+
url.searchParams.set('next', retry);
|
|
897
915
|
return { location: url.href, cookies };
|
|
898
916
|
};
|
|
899
917
|
/** `failure`'s location on its own, for a caller that redirects rather than answering. */
|
|
@@ -927,6 +945,7 @@ export function createAuth(input) {
|
|
|
927
945
|
errorUrl,
|
|
928
946
|
people,
|
|
929
947
|
setPersonActive,
|
|
948
|
+
adminResetPassword,
|
|
930
949
|
revokeSessions,
|
|
931
950
|
sessions: listSessions,
|
|
932
951
|
endSession,
|
package/dist/broker.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface NewUser {
|
|
|
21
21
|
* `password_change_required`, then `setNewPassword`).
|
|
22
22
|
*/
|
|
23
23
|
changeRequired?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Where this person's reset links go instead of their own login address --
|
|
26
|
+
* for somebody who cannot yet open the mailbox that address names. See
|
|
27
|
+
* `Auth['adminResetPassword']` for the person this does not cover.
|
|
28
|
+
*/
|
|
29
|
+
personalEmail?: string;
|
|
24
30
|
}
|
|
25
31
|
/** One of the organisation's people, as the sign-in service reports them. */
|
|
26
32
|
export interface Person {
|
|
@@ -64,6 +70,11 @@ export declare class Broker {
|
|
|
64
70
|
userId: string;
|
|
65
71
|
state: string;
|
|
66
72
|
}>;
|
|
73
|
+
/** An admin's reset for somebody requestPasswordReset cannot get back in: see `Auth['adminResetPassword']`. */
|
|
74
|
+
adminResetPassword(userId: string): Promise<{
|
|
75
|
+
userId: string;
|
|
76
|
+
password: string;
|
|
77
|
+
}>;
|
|
67
78
|
authRequest(id: string): Promise<AuthRequest>;
|
|
68
79
|
signIn(input: {
|
|
69
80
|
authRequestId: string;
|
|
@@ -119,9 +130,11 @@ export declare class Broker {
|
|
|
119
130
|
givenName: string;
|
|
120
131
|
familyName: string;
|
|
121
132
|
next: string;
|
|
133
|
+
personalEmail?: string;
|
|
122
134
|
}): Promise<{
|
|
123
135
|
userId: string;
|
|
124
136
|
invited: boolean;
|
|
137
|
+
personalEmailStored: boolean;
|
|
125
138
|
}>;
|
|
126
139
|
sendLink(email: string, next: string): Promise<unknown>;
|
|
127
140
|
sendReset(email: string, next: string): Promise<unknown>;
|
package/dist/broker.js
CHANGED
|
@@ -67,6 +67,10 @@ export class Broker {
|
|
|
67
67
|
setPersonActive(userId, active) {
|
|
68
68
|
return this.call('POST', active ? '/people/reactivate' : '/people/deactivate', { userId });
|
|
69
69
|
}
|
|
70
|
+
/** An admin's reset for somebody requestPasswordReset cannot get back in: see `Auth['adminResetPassword']`. */
|
|
71
|
+
adminResetPassword(userId) {
|
|
72
|
+
return this.call('POST', '/people/reset-password', { userId });
|
|
73
|
+
}
|
|
70
74
|
authRequest(id) {
|
|
71
75
|
return this.call('GET', '/auth-request', { id });
|
|
72
76
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export type AuthEvent = {
|
|
|
19
19
|
email: string;
|
|
20
20
|
userId: string;
|
|
21
21
|
invited: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Never the password: an audit record of who was reset, not a copy of the credential. */
|
|
24
|
+
| {
|
|
25
|
+
type: 'adminResetPassword';
|
|
26
|
+
userId: string;
|
|
22
27
|
};
|
|
23
28
|
/**
|
|
24
29
|
* Why `accessToken` refused: no session (no cookie in cookie mode, no live
|
package/dist/next.d.ts
CHANGED
|
@@ -95,14 +95,18 @@ export interface NextAuth {
|
|
|
95
95
|
givenName: string;
|
|
96
96
|
familyName: string;
|
|
97
97
|
next?: string;
|
|
98
|
+
personalEmail?: string;
|
|
98
99
|
}) => Promise<{
|
|
99
100
|
userId: string;
|
|
100
101
|
invited: boolean;
|
|
102
|
+
personalEmailStored: boolean;
|
|
101
103
|
}>;
|
|
102
104
|
/** The organisation's people, for an operator surface. Scoped to this app's organisation by the service. */
|
|
103
105
|
people: Auth['people'];
|
|
104
106
|
/** Deactivate or reactivate somebody in this app's organisation. */
|
|
105
107
|
setPersonActive: Auth['setPersonActive'];
|
|
108
|
+
/** An admin's reset for somebody in this app's organisation whom requestPasswordReset cannot otherwise get back in. */
|
|
109
|
+
adminResetPassword: Auth['adminResetPassword'];
|
|
106
110
|
/** The devices page: the signed-in person's own live sessions, newest first. Namespace mode only. */
|
|
107
111
|
sessions: () => Promise<SessionRecord[]>;
|
|
108
112
|
/**
|
package/dist/next.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Sign in against a self-hosted ZITADEL issuer: a server session for a Next.js app, and a browser client with silent single sign-on across subdomains.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|