@wtfalch/auth 0.4.0 → 0.5.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 +5 -0
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +20 -8
- package/dist/browser.d.ts +12 -0
- package/dist/browser.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,6 +87,11 @@ stops one subdomain writing a session the next one trusts. Single sign-on comes
|
|
|
87
87
|
from the **issuer's** session instead — each origin takes its own token from
|
|
88
88
|
it, and the person is asked for credentials once.
|
|
89
89
|
|
|
90
|
+
`silentSignInAvailable()` is what a caller draws from: with no session it
|
|
91
|
+
cannot otherwise tell *about to leave for the issuer* from *asked already, and
|
|
92
|
+
here we are*, and the first case paints a sign-in screen at somebody on their
|
|
93
|
+
way to being signed in.
|
|
94
|
+
|
|
90
95
|
**Every silent attempt happens at most once per tab**, because an issuer
|
|
91
96
|
answering `login_required` returns the person to a page whose load would ask
|
|
92
97
|
again. `forget()` sets the same mark, so a sign-out does not undo itself.
|
package/dist/auth.d.ts
CHANGED
|
@@ -86,6 +86,26 @@ export interface Auth {
|
|
|
86
86
|
verify(request: Request): Promise<Response>;
|
|
87
87
|
/** GET {basePath}/link?sessionId=&code= : the sign-in link from an email, on any device. */
|
|
88
88
|
link(request: Request): Promise<Response>;
|
|
89
|
+
/**
|
|
90
|
+
* The same link as data, for a caller that is not the browser holding it.
|
|
91
|
+
*
|
|
92
|
+
* `link` answers a browser: it redirects and sets a cookie. A client with no
|
|
93
|
+
* browser of its own -- a desktop app, a CLI -- cannot be signed in that way,
|
|
94
|
+
* and its server was otherwise left to call `link` and scrape `Set-Cookie`
|
|
95
|
+
* off the Response, which is reaching around this interface for something it
|
|
96
|
+
* should be able to ask for. `complete` and `resetPassword` already return
|
|
97
|
+
* the session this way; this is the third of the same shape.
|
|
98
|
+
*
|
|
99
|
+
* The cookies come back unset. Whoever calls this decides where the session
|
|
100
|
+
* goes, and for a headless client that is deliberately not the browser: the
|
|
101
|
+
* cookie name is fixed, so setting it would overwrite whatever website
|
|
102
|
+
* session the person already had in the tab they clicked from.
|
|
103
|
+
*/
|
|
104
|
+
followLink(input: {
|
|
105
|
+
sessionId: string;
|
|
106
|
+
code: string;
|
|
107
|
+
next?: string;
|
|
108
|
+
}): Promise<SignedIn>;
|
|
89
109
|
/** Emails a fresh verification link. Says nothing about whether the address exists. */
|
|
90
110
|
resendVerification(email: string, next?: string): Promise<void>;
|
|
91
111
|
/** Emails a password-reset link to {basePath}/reset?userId=&code=. Says nothing about whether the address exists. */
|
package/dist/auth.js
CHANGED
|
@@ -258,20 +258,31 @@ export function createAuth(input) {
|
|
|
258
258
|
return { ok: false, error: 'unavailable', message: 'the sign-in service did not answer' };
|
|
259
259
|
}
|
|
260
260
|
};
|
|
261
|
+
const followLink = async ({ sessionId, code, next, }) => {
|
|
262
|
+
try {
|
|
263
|
+
const flow = await serverFlow();
|
|
264
|
+
const completed = await finishOrHandoff(() => broker().followLink({ authRequestId: flow.id, sessionId, code }), flow, safeNextPath(next, options().afterLogin));
|
|
265
|
+
return { ok: true, ...completed };
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (error instanceof BrokerError && error.status < 500) {
|
|
269
|
+
return { ok: false, error: 'invalid_code', message: error.detail ?? error.error };
|
|
270
|
+
}
|
|
271
|
+
return { ok: false, error: 'unavailable', message: 'the sign-in service did not answer' };
|
|
272
|
+
}
|
|
273
|
+
};
|
|
261
274
|
const link = async (request) => {
|
|
262
275
|
const params = new URL(request.url).searchParams;
|
|
263
276
|
const sessionId = params.get('sessionId');
|
|
264
277
|
const code = params.get('code');
|
|
265
278
|
if (!sessionId || !code)
|
|
266
279
|
return failed('link', []);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return failed('link', []);
|
|
274
|
-
}
|
|
280
|
+
const result = await followLink({
|
|
281
|
+
sessionId,
|
|
282
|
+
code,
|
|
283
|
+
next: params.get('next') ?? undefined,
|
|
284
|
+
});
|
|
285
|
+
return result.ok ? redirect(result.location, result.cookies) : failed('link', []);
|
|
275
286
|
};
|
|
276
287
|
const signIn = async ({ authRequestId, email, password, }) => {
|
|
277
288
|
try {
|
|
@@ -333,6 +344,7 @@ export function createAuth(input) {
|
|
|
333
344
|
logout,
|
|
334
345
|
verify,
|
|
335
346
|
link,
|
|
347
|
+
followLink,
|
|
336
348
|
resendVerification,
|
|
337
349
|
requestPasswordReset,
|
|
338
350
|
resetPassword,
|
package/dist/browser.d.ts
CHANGED
|
@@ -81,6 +81,18 @@ export interface BrowserAuth {
|
|
|
81
81
|
/** The session this tab holds, or null. Expiry is checked with a minute of
|
|
82
82
|
* slack: a token that dies mid-request is worse than one renewed early. */
|
|
83
83
|
currentSession(): BrowserSession | null;
|
|
84
|
+
/**
|
|
85
|
+
* Whether a silent attempt is still available in this tab: no session held,
|
|
86
|
+
* and none tried.
|
|
87
|
+
*
|
|
88
|
+
* A caller needs this to know what to *draw*. "No session" alone cannot
|
|
89
|
+
* tell "about to leave for the issuer" from "asked already, and here we
|
|
90
|
+
* are" -- and drawing a sign-in screen in the first case shows somebody a
|
|
91
|
+
* screen saying they are not signed in, for as long as the redirect takes,
|
|
92
|
+
* on their way to being let in. It was a third of a second on the estate's
|
|
93
|
+
* mail client, and it was the thing people noticed.
|
|
94
|
+
*/
|
|
95
|
+
silentSignInAvailable(): boolean;
|
|
84
96
|
/**
|
|
85
97
|
* Ask the issuer to authorise without interacting, if it has not been asked
|
|
86
98
|
* in this tab already. Returns false when there was nothing to try — a
|
package/dist/browser.js
CHANGED
|
@@ -116,12 +116,12 @@ export function createBrowserAuth(options) {
|
|
|
116
116
|
return null;
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
|
+
const silentSignInAvailable = () => !currentSession() && !store.get(TRIED);
|
|
119
120
|
return {
|
|
120
121
|
currentSession,
|
|
122
|
+
silentSignInAvailable,
|
|
121
123
|
async trySilentSignIn(returnTo = window.location.pathname) {
|
|
122
|
-
if (
|
|
123
|
-
return false;
|
|
124
|
-
if (store.get(TRIED))
|
|
124
|
+
if (!silentSignInAvailable())
|
|
125
125
|
return false;
|
|
126
126
|
store.set(TRIED, '1');
|
|
127
127
|
await authorize('none', returnTo);
|
package/package.json
CHANGED