@byline/host-tanstack-start 6.7.2 → 6.8.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/admin-shell/admin-roles/container_module.css +1 -1
- package/dist/admin-shell/admin-roles/delete.js +1 -1
- package/dist/admin-shell/admin-users/container.js +181 -159
- package/dist/admin-shell/admin-users/container.module.js +2 -1
- package/dist/admin-shell/admin-users/container_module.css +5 -1
- package/dist/admin-shell/admin-users/delete.js +1 -1
- package/dist/integrations/byline-admin-services.js +2 -0
- package/dist/integrations/passkey-admin-services.d.ts +9 -0
- package/dist/integrations/passkey-admin-services.js +53 -0
- package/dist/integrations/session-coordination.d.ts +3 -1
- package/dist/integrations/session-coordination.js +3 -1
- package/dist/integrations/session-middleware.d.ts +3 -1
- package/dist/integrations/session-middleware.js +87 -79
- package/dist/integrations/sign-in-body.d.ts +3 -0
- package/dist/integrations/sign-in-body.js +19 -4
- package/dist/integrations/sign-in-middleware.js +30 -5
- package/dist/server-fns/auth/passkey-admission.d.ts +24 -0
- package/dist/server-fns/auth/passkey-admission.js +88 -0
- package/dist/server-fns/auth/passkeys.d.ts +96 -0
- package/dist/server-fns/auth/passkeys.js +238 -0
- package/dist/server-fns/auth/reconcile-locale.d.ts +20 -0
- package/dist/server-fns/auth/reconcile-locale.js +16 -0
- package/dist/server-fns/auth/sign-in.js +1 -15
- package/package.json +11 -11
- package/src/admin-shell/admin-roles/container.module.css +1 -1
- package/src/admin-shell/admin-roles/delete.tsx +1 -1
- package/src/admin-shell/admin-users/container.module.css +7 -1
- package/src/admin-shell/admin-users/container.tsx +22 -2
- package/src/admin-shell/admin-users/delete.tsx +1 -1
- package/src/integrations/byline-admin-services.ts +3 -0
- package/src/integrations/passkey-admin-services.ts +85 -0
- package/src/integrations/session-client.test.node.ts +73 -0
- package/src/integrations/session-coordination.ts +3 -1
- package/src/integrations/session-middleware.ts +86 -72
- package/src/integrations/sign-in-body.test.node.ts +31 -1
- package/src/integrations/sign-in-body.ts +27 -3
- package/src/integrations/sign-in-middleware.ts +42 -9
- package/src/server-fns/auth/passkey-admission.test.node.ts +173 -0
- package/src/server-fns/auth/passkey-admission.ts +125 -0
- package/src/server-fns/auth/passkeys.test.node.ts +247 -0
- package/src/server-fns/auth/passkeys.ts +252 -0
- package/src/server-fns/auth/reconcile-locale.ts +40 -0
- package/src/server-fns/auth/sign-in.ts +1 -32
|
@@ -6,89 +6,97 @@ import { renewAdminSession } from "../server-fns/auth/renew.js";
|
|
|
6
6
|
import { coordinateAuthAction, flagSessionChanged, observeSession, renewSingleFlight, requireUnchanged, sessionSnapshot } from "./session-coordination.js";
|
|
7
7
|
import { sessionRequestKind } from "./session-request-kind.js";
|
|
8
8
|
const outcomeHeader = 'x-byline-auth-boundary';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
const snapshot = sessionSnapshot();
|
|
18
|
-
requireUnchanged(snapshot);
|
|
19
|
-
return next({
|
|
20
|
-
sendContext: {
|
|
21
|
-
bylineExpectedSessionId: snapshot.sessionId
|
|
22
|
-
},
|
|
23
|
-
fetch: async (input, init)=>{
|
|
24
|
-
const reusable = !(input instanceof Request) && !(init?.body instanceof ReadableStream);
|
|
25
|
-
let response = await fetch(input, init);
|
|
26
|
-
if ('changed' === response.headers.get(outcomeHeader)) {
|
|
27
|
-
flagSessionChanged();
|
|
28
|
-
throw ERR_SESSION_CHANGED({
|
|
29
|
-
message: 'session changed'
|
|
30
|
-
});
|
|
9
|
+
function createAdminSessionMiddleware(coordinateCookies) {
|
|
10
|
+
return createMiddleware({
|
|
11
|
+
type: 'function'
|
|
12
|
+
}).client(async ({ next })=>{
|
|
13
|
+
if ("u" < typeof window) return next({
|
|
14
|
+
sendContext: {
|
|
15
|
+
bylineExpectedSessionId: null
|
|
31
16
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
17
|
+
});
|
|
18
|
+
const snapshot = sessionSnapshot();
|
|
19
|
+
requireUnchanged(snapshot);
|
|
20
|
+
return next({
|
|
21
|
+
sendContext: {
|
|
22
|
+
bylineExpectedSessionId: snapshot.sessionId
|
|
23
|
+
},
|
|
24
|
+
fetch: async (input, init)=>{
|
|
25
|
+
const reusable = !(input instanceof Request) && !(init?.body instanceof ReadableStream);
|
|
26
|
+
const send = ()=>coordinateCookies ? coordinateAuthAction(async ()=>{
|
|
27
|
+
requireUnchanged(snapshot);
|
|
28
|
+
return fetch(input, init);
|
|
29
|
+
}) : fetch(input, init);
|
|
30
|
+
let response = await send();
|
|
31
|
+
if ('changed' === response.headers.get(outcomeHeader)) {
|
|
32
|
+
flagSessionChanged();
|
|
33
|
+
throw ERR_SESSION_CHANGED({
|
|
34
|
+
message: 'session changed'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if ('renew' === response.headers.get(outcomeHeader)) {
|
|
38
|
+
requireUnchanged(snapshot);
|
|
39
|
+
await renewSingleFlight(()=>coordinateAuthAction(async ()=>{
|
|
40
|
+
const result = await renewAdminSession({
|
|
41
|
+
data: {
|
|
42
|
+
expectedSessionId: snapshot.sessionId ?? void 0
|
|
43
|
+
}
|
|
44
|
+
}).catch((error)=>{
|
|
45
|
+
if (error?.code === 'ERR_SESSION_CHANGED') flagSessionChanged();
|
|
46
|
+
throw error;
|
|
47
47
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
if (snapshot.sessionId && result.sessionId !== snapshot.sessionId) {
|
|
49
|
+
flagSessionChanged();
|
|
50
|
+
throw ERR_SESSION_CHANGED({
|
|
51
|
+
message: 'session changed during renewal'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
observeSession(result.sessionId);
|
|
55
|
+
}));
|
|
56
|
+
requireUnchanged(snapshot);
|
|
57
|
+
if (!reusable) throw ERR_ACCESS_EXPIRED({
|
|
58
|
+
message: 'Session renewed. Please retry the operation.'
|
|
59
|
+
});
|
|
60
|
+
response = await send();
|
|
61
|
+
if ('changed' === response.headers.get(outcomeHeader)) flagSessionChanged();
|
|
62
|
+
}
|
|
51
63
|
requireUnchanged(snapshot);
|
|
52
|
-
|
|
53
|
-
message: 'Session renewed. Please retry the operation.'
|
|
54
|
-
});
|
|
55
|
-
response = await fetch(input, init);
|
|
56
|
-
if ('changed' === response.headers.get(outcomeHeader)) flagSessionChanged();
|
|
64
|
+
return response;
|
|
57
65
|
}
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
});
|
|
67
|
+
}).server(async ({ next, context, method })=>{
|
|
68
|
+
const kind = sessionRequestKind(getRequest());
|
|
69
|
+
if (!kind) throw new Error('sessionRequestMiddleware is not configured');
|
|
70
|
+
let auth;
|
|
71
|
+
try {
|
|
72
|
+
auth = await getAdminRequestContext();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof AuthError && 'ERR_ACCESS_EXPIRED' === error.code) setResponseHeader(outcomeHeader, 'renew');
|
|
75
|
+
throw error;
|
|
60
76
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
const expected = context.bylineExpectedSessionId;
|
|
73
|
-
if (('serverFn' === kind || 'GET' !== method) && (!expected || expected !== auth.sessionId)) {
|
|
74
|
-
setResponseHeader(outcomeHeader, 'changed');
|
|
75
|
-
throw ERR_SESSION_CHANGED({
|
|
76
|
-
message: 'session changed before operation'
|
|
77
|
+
const expected = context.bylineExpectedSessionId;
|
|
78
|
+
if (('serverFn' === kind || 'GET' !== method) && (!expected || expected !== auth.sessionId)) {
|
|
79
|
+
setResponseHeader(outcomeHeader, 'changed');
|
|
80
|
+
throw ERR_SESSION_CHANGED({
|
|
81
|
+
message: 'session changed before operation'
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (!auth.sessionId) throw ERR_UNAUTHENTICATED({
|
|
85
|
+
message: 'provider omitted login identity'
|
|
77
86
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
try {
|
|
88
|
+
const result = await next();
|
|
89
|
+
const response = result.result;
|
|
90
|
+
if (response instanceof Response) response.headers.delete(outcomeHeader);
|
|
91
|
+
return result;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof Response) error.headers.delete(outcomeHeader);
|
|
94
|
+
throw error;
|
|
95
|
+
} finally{
|
|
96
|
+
setResponseHeader(outcomeHeader, 'handled');
|
|
97
|
+
}
|
|
81
98
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return result;
|
|
87
|
-
} catch (error) {
|
|
88
|
-
if (error instanceof Response) error.headers.delete(outcomeHeader);
|
|
89
|
-
throw error;
|
|
90
|
-
} finally{
|
|
91
|
-
setResponseHeader(outcomeHeader, 'handled');
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
export { adminSessionMiddleware };
|
|
99
|
+
}
|
|
100
|
+
const adminSessionMiddleware = createAdminSessionMiddleware(false);
|
|
101
|
+
const adminSessionMutationMiddleware = createAdminSessionMiddleware(true);
|
|
102
|
+
export { adminSessionMiddleware, adminSessionMutationMiddleware };
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** Limit the serialized RPC envelope, not just its eventual credential fields. */
|
|
9
9
|
export declare const SIGN_IN_MAX_BODY_BYTES: number;
|
|
10
|
+
export declare const PASSKEY_MAX_BODY_BYTES: number;
|
|
10
11
|
export declare function wasSignInBodyChecked(request: Request): boolean;
|
|
11
12
|
/** Read a bounded clone before Start parses the original. Never await tee cancellation. */
|
|
12
13
|
export declare function checkSignInBody(request: Request): Promise<Response | undefined>;
|
|
14
|
+
export declare function wasPasskeyBodyChecked(request: Request, passwordProof?: boolean): boolean;
|
|
15
|
+
export declare function checkPasskeyBody(request: Request, passwordProof?: boolean): Promise<Response | undefined>;
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
const SIGN_IN_MAX_BODY_BYTES = 16384;
|
|
2
|
+
const PASSKEY_MAX_BODY_BYTES = 65536;
|
|
2
3
|
const checked = new WeakSet();
|
|
4
|
+
const passkeyChecked = new WeakMap();
|
|
3
5
|
function wasSignInBodyChecked(request) {
|
|
4
6
|
return checked.has(request);
|
|
5
7
|
}
|
|
6
8
|
async function checkSignInBody(request) {
|
|
9
|
+
const rejected = await checkBody(request, SIGN_IN_MAX_BODY_BYTES);
|
|
10
|
+
if (!rejected) checked.add(request);
|
|
11
|
+
return rejected;
|
|
12
|
+
}
|
|
13
|
+
function wasPasskeyBodyChecked(request, passwordProof = false) {
|
|
14
|
+
return passkeyChecked.get(request) === (passwordProof ? SIGN_IN_MAX_BODY_BYTES : PASSKEY_MAX_BODY_BYTES);
|
|
15
|
+
}
|
|
16
|
+
async function checkPasskeyBody(request, passwordProof = false) {
|
|
17
|
+
const limit = passwordProof ? SIGN_IN_MAX_BODY_BYTES : PASSKEY_MAX_BODY_BYTES;
|
|
18
|
+
const rejected = await checkBody(request, limit);
|
|
19
|
+
if (!rejected) passkeyChecked.set(request, limit);
|
|
20
|
+
return rejected;
|
|
21
|
+
}
|
|
22
|
+
async function checkBody(request, limit) {
|
|
7
23
|
if ('POST' !== request.method) return new Response('Method not allowed', {
|
|
8
24
|
status: 405,
|
|
9
25
|
headers: {
|
|
@@ -14,7 +30,7 @@ async function checkSignInBody(request) {
|
|
|
14
30
|
status: 415
|
|
15
31
|
});
|
|
16
32
|
const declared = request.headers.get('content-length');
|
|
17
|
-
if (null !== declared && (!/^\d+$/.test(declared) || Number(declared) >
|
|
33
|
+
if (null !== declared && (!/^\d+$/.test(declared) || Number(declared) > limit)) return new Response('Payload too large', {
|
|
18
34
|
status: 413
|
|
19
35
|
});
|
|
20
36
|
const reader = request.clone().body?.getReader();
|
|
@@ -34,11 +50,10 @@ async function checkSignInBody(request) {
|
|
|
34
50
|
]);
|
|
35
51
|
if (done) break;
|
|
36
52
|
size += value.byteLength;
|
|
37
|
-
if (size >
|
|
53
|
+
if (size > limit) return new Response('Payload too large', {
|
|
38
54
|
status: 413
|
|
39
55
|
});
|
|
40
56
|
}
|
|
41
|
-
checked.add(request);
|
|
42
57
|
return;
|
|
43
58
|
} catch {
|
|
44
59
|
return new Response('Incomplete request body', {
|
|
@@ -49,4 +64,4 @@ async function checkSignInBody(request) {
|
|
|
49
64
|
reader.cancel().catch(()=>{});
|
|
50
65
|
}
|
|
51
66
|
}
|
|
52
|
-
export { SIGN_IN_MAX_BODY_BYTES, checkSignInBody, wasSignInBodyChecked };
|
|
67
|
+
export { PASSKEY_MAX_BODY_BYTES, SIGN_IN_MAX_BODY_BYTES, checkPasskeyBody, checkSignInBody, wasPasskeyBodyChecked, wasSignInBodyChecked };
|
|
@@ -1,10 +1,35 @@
|
|
|
1
1
|
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
+
import { setResponseHeader } from "@tanstack/react-start/server";
|
|
3
|
+
import { passkeyEnrollment, passkeyEnrollmentOptions, passkeyList, passkeyManagementContext, passkeyPasswordProof, passkeyProof, passkeyProofOptions, passkeyRemove, passkeyRename, passkeySignIn, passkeySignInOptions } from "../server-fns/auth/passkeys.js";
|
|
2
4
|
import { adminSignIn } from "../server-fns/auth/sign-in.js";
|
|
3
|
-
import { checkSignInBody } from "./sign-in-body.js";
|
|
5
|
+
import { checkPasskeyBody, checkSignInBody } from "./sign-in-body.js";
|
|
4
6
|
const passwordSignInMiddleware = createMiddleware().server(async (ctx)=>{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
if ('serverFn' !== ctx.handlerType) return ctx.next();
|
|
8
|
+
const matches = (url)=>{
|
|
9
|
+
const endpoint = new URL(url, ctx.request.url).pathname.replace(/\/$/, '');
|
|
10
|
+
return ctx.pathname === endpoint || ctx.pathname.startsWith(`${endpoint}/`);
|
|
11
|
+
};
|
|
12
|
+
if (matches(adminSignIn.url)) return await checkSignInBody(ctx.request) ?? ctx.next();
|
|
13
|
+
const passkeys = [
|
|
14
|
+
passkeyManagementContext,
|
|
15
|
+
passkeySignInOptions,
|
|
16
|
+
passkeySignIn,
|
|
17
|
+
passkeyPasswordProof,
|
|
18
|
+
passkeyProofOptions,
|
|
19
|
+
passkeyProof,
|
|
20
|
+
passkeyEnrollmentOptions,
|
|
21
|
+
passkeyEnrollment,
|
|
22
|
+
passkeyList,
|
|
23
|
+
passkeyRename,
|
|
24
|
+
passkeyRemove
|
|
25
|
+
];
|
|
26
|
+
if (!passkeys.some((fn)=>matches(fn.url))) return ctx.next();
|
|
27
|
+
setResponseHeader('Cache-Control', 'no-store');
|
|
28
|
+
const rejected = await checkPasskeyBody(ctx.request, matches(passkeyPasswordProof.url));
|
|
29
|
+
if (rejected) {
|
|
30
|
+
rejected.headers.set('Cache-Control', 'no-store');
|
|
31
|
+
return rejected;
|
|
32
|
+
}
|
|
33
|
+
return ctx.next();
|
|
9
34
|
});
|
|
10
35
|
export { passwordSignInMiddleware };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { PasskeyOperation, PasskeyProvider, PasskeyRequestProtection, PasswordSignInProtection } from '@byline/auth';
|
|
9
|
+
/** Host boundary shared by the passkey POST handlers. Body and CSRF guards run first. */
|
|
10
|
+
export declare function withPasskeyAdmission<T>(input: {
|
|
11
|
+
provider: PasskeyProvider;
|
|
12
|
+
purpose: PasskeyOperation;
|
|
13
|
+
request: Request;
|
|
14
|
+
bodyChecked: boolean;
|
|
15
|
+
protection: PasskeyRequestProtection | undefined;
|
|
16
|
+
/** Required only for password proof; lookup must authenticate the expected login. */
|
|
17
|
+
password?: {
|
|
18
|
+
protection: PasswordSignInProtection | undefined;
|
|
19
|
+
resolveActorEmail(): Promise<string>;
|
|
20
|
+
};
|
|
21
|
+
work(metadata: {
|
|
22
|
+
ip: string;
|
|
23
|
+
}): Promise<T>;
|
|
24
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AuthError, AuthErrorCodes } from "@byline/auth";
|
|
2
|
+
import { normalizeClientIp } from "../../integrations/client-ip.js";
|
|
3
|
+
async function withPasskeyAdmission(input) {
|
|
4
|
+
const recordDenial = (reason)=>{
|
|
5
|
+
try {
|
|
6
|
+
Promise.resolve(input.provider.recordAdmissionDenied?.(input.purpose, reason)).catch(()=>{});
|
|
7
|
+
} catch {}
|
|
8
|
+
};
|
|
9
|
+
const denied = (reason, status, retry)=>{
|
|
10
|
+
recordDenial(reason);
|
|
11
|
+
throw new Response(429 === status ? 'Too many authentication requests' : 'Authentication temporarily unavailable', {
|
|
12
|
+
status,
|
|
13
|
+
headers: {
|
|
14
|
+
'Cache-Control': 'no-store',
|
|
15
|
+
...retry ? {
|
|
16
|
+
'Retry-After': String(retry)
|
|
17
|
+
} : {}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
const available = async (work)=>{
|
|
22
|
+
try {
|
|
23
|
+
return await work();
|
|
24
|
+
} catch {
|
|
25
|
+
return denied('unavailable', 503);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const protection = input.protection;
|
|
29
|
+
if (!input.bodyChecked || !protection) return denied('unavailable', 503);
|
|
30
|
+
const ip = await available(async ()=>normalizeClientIp(await protection.resolveClientIp(input.request)));
|
|
31
|
+
if (!ip) return denied('unavailable', 503);
|
|
32
|
+
const release = await available(()=>protection.limiter.acquire());
|
|
33
|
+
if (!release) return denied('capacity', 429, 1);
|
|
34
|
+
try {
|
|
35
|
+
const admission = await available(()=>protection.limiter.consume({
|
|
36
|
+
ip
|
|
37
|
+
}));
|
|
38
|
+
if (!admission.allowed) return denied('network', 429, admission.retryAfterSeconds);
|
|
39
|
+
if ('password-proof' === input.purpose && !input.password) return denied('unavailable', 503);
|
|
40
|
+
if (!input.password) return await input.work({
|
|
41
|
+
ip
|
|
42
|
+
});
|
|
43
|
+
const password = input.password;
|
|
44
|
+
const passwordProtection = password.protection;
|
|
45
|
+
if (!passwordProtection) return denied('unavailable', 503);
|
|
46
|
+
let email;
|
|
47
|
+
try {
|
|
48
|
+
email = await password.resolveActorEmail();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
recordDenial('unavailable');
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
const passwordIp = await available(async ()=>normalizeClientIp(await passwordProtection.resolveClientIp(input.request)));
|
|
54
|
+
if (!passwordIp) return denied('unavailable', 503);
|
|
55
|
+
const releasePassword = await available(()=>passwordProtection.limiter.acquire());
|
|
56
|
+
if (!releasePassword) return denied('capacity', 429, 1);
|
|
57
|
+
try {
|
|
58
|
+
const passwordAdmission = await available(()=>passwordProtection.limiter.consume({
|
|
59
|
+
email,
|
|
60
|
+
ip: passwordIp
|
|
61
|
+
}));
|
|
62
|
+
if (!passwordAdmission.allowed) return denied('network', 429, passwordAdmission.retryAfterSeconds);
|
|
63
|
+
const record = (outcome)=>{
|
|
64
|
+
try {
|
|
65
|
+
Promise.resolve(passwordProtection.limiter.recordResult?.({
|
|
66
|
+
email,
|
|
67
|
+
ip: passwordIp
|
|
68
|
+
}, outcome)).catch(()=>{});
|
|
69
|
+
} catch {}
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
const result = await input.work({
|
|
73
|
+
ip
|
|
74
|
+
});
|
|
75
|
+
record('success');
|
|
76
|
+
return result;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof AuthError && error.code === AuthErrorCodes.INVALID_CREDENTIALS) record('failure');
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
} finally{
|
|
82
|
+
releasePassword();
|
|
83
|
+
}
|
|
84
|
+
} finally{
|
|
85
|
+
release();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export { withPasskeyAdmission };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
/** Cosmetic capability discovery creates no ceremony and reads no account data. */
|
|
9
|
+
export declare const getPasskeyCapability: import("@tanstack/react-start").OptionalFetcher<undefined, undefined, Promise<{
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
}>>;
|
|
12
|
+
export declare const passkeySignInOptions: import("@tanstack/react-start").OptionalFetcher<undefined, (input: unknown) => Record<string, never>, Promise<import("@byline/auth").PasskeyCeremony<import("@byline/auth").PasskeyAuthenticationOptions>>>;
|
|
13
|
+
export declare const passkeySignIn: import("@tanstack/react-start").OptionalFetcher<undefined, (input: unknown) => {
|
|
14
|
+
ceremonyId: string;
|
|
15
|
+
response: unknown;
|
|
16
|
+
}, Promise<{
|
|
17
|
+
userId: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
}>>;
|
|
20
|
+
export declare const passkeyPasswordProof: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
21
|
+
bylineExpectedSessionId: string | null;
|
|
22
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
23
|
+
expectedSessionId: string;
|
|
24
|
+
password: string;
|
|
25
|
+
action: {
|
|
26
|
+
purpose: "enroll";
|
|
27
|
+
} | {
|
|
28
|
+
purpose: "remove";
|
|
29
|
+
targetUserId: string;
|
|
30
|
+
credentialRecordId: string;
|
|
31
|
+
};
|
|
32
|
+
}, Promise<import("@byline/auth").PasskeyManagementGrant>>;
|
|
33
|
+
export declare const passkeyProofOptions: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
34
|
+
bylineExpectedSessionId: string | null;
|
|
35
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
36
|
+
expectedSessionId: string;
|
|
37
|
+
action: {
|
|
38
|
+
purpose: "enroll";
|
|
39
|
+
} | {
|
|
40
|
+
purpose: "remove";
|
|
41
|
+
targetUserId: string;
|
|
42
|
+
credentialRecordId: string;
|
|
43
|
+
};
|
|
44
|
+
}, Promise<import("@byline/auth").PasskeyCeremony<import("@byline/auth").PasskeyAuthenticationOptions>>>;
|
|
45
|
+
export declare const passkeyProof: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
46
|
+
bylineExpectedSessionId: string | null;
|
|
47
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
48
|
+
expectedSessionId: string;
|
|
49
|
+
ceremonyId: string;
|
|
50
|
+
response: unknown;
|
|
51
|
+
}, Promise<import("@byline/auth").PasskeyManagementGrant>>;
|
|
52
|
+
export declare const passkeyEnrollmentOptions: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
53
|
+
bylineExpectedSessionId: string | null;
|
|
54
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
55
|
+
expectedSessionId: string;
|
|
56
|
+
grantId: string;
|
|
57
|
+
}, Promise<import("@byline/auth").PasskeyCeremony<import("@byline/auth").PasskeyRegistrationOptions>>>;
|
|
58
|
+
export declare const passkeyEnrollment: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
59
|
+
bylineExpectedSessionId: string | null;
|
|
60
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
61
|
+
expectedSessionId: string;
|
|
62
|
+
ceremonyId: string;
|
|
63
|
+
response: unknown;
|
|
64
|
+
name: string;
|
|
65
|
+
}, Promise<import("@byline/auth").PasskeyCredentialSummary>>;
|
|
66
|
+
export declare const passkeyList: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
67
|
+
bylineExpectedSessionId: string | null;
|
|
68
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
69
|
+
expectedSessionId: string;
|
|
70
|
+
targetUserId?: string | undefined;
|
|
71
|
+
}, Promise<import("@byline/auth").PasskeyCredentialSummary[]>>;
|
|
72
|
+
export declare const passkeyRename: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
73
|
+
bylineExpectedSessionId: string | null;
|
|
74
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
75
|
+
expectedSessionId: string;
|
|
76
|
+
credentialRecordId: string;
|
|
77
|
+
name: string;
|
|
78
|
+
}, Promise<import("@byline/auth").PasskeyCredentialSummary>>;
|
|
79
|
+
export declare const passkeyRemove: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
80
|
+
bylineExpectedSessionId: string | null;
|
|
81
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
82
|
+
expectedSessionId: string;
|
|
83
|
+
grantId: string;
|
|
84
|
+
}, Promise<{
|
|
85
|
+
signedOut: boolean;
|
|
86
|
+
}>>;
|
|
87
|
+
/** Cosmetic permissions for the manager; every mutation still rechecks authority. */
|
|
88
|
+
export declare const passkeyManagementContext: import("@tanstack/react-start").OptionalFetcher<readonly [import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
89
|
+
bylineExpectedSessionId: string | null;
|
|
90
|
+
}, undefined, undefined>], (input: unknown) => {
|
|
91
|
+
expectedSessionId: string;
|
|
92
|
+
}, Promise<{
|
|
93
|
+
id: string;
|
|
94
|
+
isSuperAdmin: boolean;
|
|
95
|
+
canManagePasskeys: boolean;
|
|
96
|
+
}>>;
|