@better-forge/auth 0.1.0 → 0.1.1

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/src/context.ts CHANGED
@@ -1,85 +1,85 @@
1
- import type { DatabaseSync } from 'node:sqlite'
2
- // Loaded for the request and reply properties they add (`cookies`, `jwt`, `user`, route `rateLimit` config).
3
- import type {} from '@fastify/cookie'
4
- import type {} from '@fastify/jwt'
5
- import type {} from '@fastify/rate-limit'
6
- import type { Audit } from '@better-forge/audit'
7
- import type { SqliteDatabase } from '@better-forge/db'
8
- import type { RoleRegistry } from './roles.js'
9
- import type { TokenCatalogue } from './tokenCatalogue.js'
10
-
11
- /** Which sessions were ended, so the application can close what they had open (for example live connections). */
12
- export type SessionsRevoked =
13
- | { sessionId: string }
14
- | { userId: number; exceptSessionId?: string | undefined }
15
-
16
- export interface UnknownSsoUser {
17
- email: string
18
- subject: string
19
- issuer: string
20
- }
21
-
22
- export type SignInOutcome = { role: string } | { error: string } | 'two-factor'
23
-
24
- export interface AuthHooks {
25
- /** Called after sessions were deleted, whatever the reason (sign-out, password reset, role change, ...). */
26
- onSessionsRevoked?: (match: SessionsRevoked) => void
27
- /**
28
- * An SSO sign-in succeeded at the provider but no account matches. Return the role of an account to create for
29
- * this person, or null to refuse. Only called when the provider's identity is verified; without this hook nobody
30
- * is created.
31
- */
32
- resolveUnknownSsoUser?: (user: UnknownSsoUser) => Promise<{ role: string } | null>
33
- /**
34
- * Where a sign-in goes next, for one that started with a `returnTo` target the application listed in
35
- * `returnTargets`, or for a role that belongs somewhere else than `urls.home`. Return undefined for the default.
36
- */
37
- signInRedirect?: (flow: { returnTo?: string | undefined }, outcome: SignInOutcome) => string | undefined
38
- }
39
-
40
- /** A `@fastify/rate-limit` route configuration. Passed through untouched, so a group id can be shared with other routes. */
41
- export interface RateLimitConfig {
42
- groupId?: string
43
- max: number
44
- timeWindow: string | number
45
- }
46
-
47
- /**
48
- * Everything the auth module needs from the application. All of it is passed in: the module reads no globals, so
49
- * two applications (or two tests) can each have their own.
50
- */
51
- export interface AuthOptions {
52
- /** Database handle and the raw connection (for transactions). Both are used lazily, so they may be opened after this is created. */
53
- db: SqliteDatabase
54
- sqlite: DatabaseSync
55
- audit: Pick<Audit, 'write' | 'actor' | 'diffObjects' | 'snapshot'>
56
- roles: RoleRegistry
57
- tokens: TokenCatalogue
58
- /** Encrypts and decrypts secrets the module stores (the SSO client secret, authenticator secrets). */
59
- secretBox: { encrypt(plain: string): string; decrypt(stored: string): string }
60
- cookies: { session: string; csrf: string; oidcFlow: string; twoFactorChallenge: string }
61
- /** Prefix that tells an API token from a session token in a Bearer header. */
62
- apiTokenPrefix: string
63
- /** Name an authenticator app shows next to the account. */
64
- totpIssuer: string
65
- /** Where the sign-in routes are mounted. The cookie paths and the SSO callback derive from it. */
66
- basePath: string
67
- urls: {
68
- /** Where a failed sign-in sends the browser, e.g. `/admin/login`. */
69
- signIn: string
70
- /** Where a finished sign-in goes by default. */
71
- home: string
72
- /** The page that closes the SSO confirmation popup. */
73
- ssoConfirm: string
74
- /** The page that shows the result of an SSO test. */
75
- ssoTest: string
76
- }
77
- /** The address the application is reached at, from the environment; '' when unknown. */
78
- publicUrl: (env: NodeJS.ProcessEnv) => string
79
- /** Whether cookies are marked Secure. Evaluated on every response. */
80
- secureCookies: () => boolean
81
- rateLimits: { signIn: RateLimitConfig; sensitiveAction: RateLimitConfig }
82
- /** `returnTo` values a sign-in may start with; the application's `signInRedirect` hook turns them into a destination. */
83
- returnTargets?: readonly string[]
84
- hooks?: AuthHooks
85
- }
1
+ import type { DatabaseSync } from 'node:sqlite'
2
+ // Loaded for the request and reply properties they add (`cookies`, `jwt`, `user`, route `rateLimit` config).
3
+ import type {} from '@fastify/cookie'
4
+ import type {} from '@fastify/jwt'
5
+ import type {} from '@fastify/rate-limit'
6
+ import type { Audit } from '@better-forge/audit'
7
+ import type { SqliteDatabase } from '@better-forge/db'
8
+ import type { RoleRegistry } from './roles.js'
9
+ import type { TokenCatalogue } from './tokenCatalogue.js'
10
+
11
+ /** Which sessions were ended, so the application can close what they had open (for example live connections). */
12
+ export type SessionsRevoked =
13
+ | { sessionId: string }
14
+ | { userId: number; exceptSessionId?: string | undefined }
15
+
16
+ export interface UnknownSsoUser {
17
+ email: string
18
+ subject: string
19
+ issuer: string
20
+ }
21
+
22
+ export type SignInOutcome = { role: string } | { error: string } | 'two-factor'
23
+
24
+ export interface AuthHooks {
25
+ /** Called after sessions were deleted, whatever the reason (sign-out, password reset, role change, ...). */
26
+ onSessionsRevoked?: (match: SessionsRevoked) => void
27
+ /**
28
+ * An SSO sign-in succeeded at the provider but no account matches. Return the role of an account to create for
29
+ * this person, or null to refuse. Only called when the provider's identity is verified; without this hook nobody
30
+ * is created.
31
+ */
32
+ resolveUnknownSsoUser?: (user: UnknownSsoUser) => Promise<{ role: string } | null>
33
+ /**
34
+ * Where a sign-in goes next, for one that started with a `returnTo` target the application listed in
35
+ * `returnTargets`, or for a role that belongs somewhere else than `urls.home`. Return undefined for the default.
36
+ */
37
+ signInRedirect?: (flow: { returnTo?: string | undefined }, outcome: SignInOutcome) => string | undefined
38
+ }
39
+
40
+ /** A `@fastify/rate-limit` route configuration. Passed through untouched, so a group id can be shared with other routes. */
41
+ export interface RateLimitConfig {
42
+ groupId?: string
43
+ max: number
44
+ timeWindow: string | number
45
+ }
46
+
47
+ /**
48
+ * Everything the auth module needs from the application. All of it is passed in: the module reads no globals, so
49
+ * two applications (or two tests) can each have their own.
50
+ */
51
+ export interface AuthOptions {
52
+ /** Database handle and the raw connection (for transactions). Both are used lazily, so they may be opened after this is created. */
53
+ db: SqliteDatabase
54
+ sqlite: DatabaseSync
55
+ audit: Pick<Audit, 'write' | 'actor' | 'diffObjects' | 'snapshot'>
56
+ roles: RoleRegistry
57
+ tokens: TokenCatalogue
58
+ /** Encrypts and decrypts secrets the module stores (the SSO client secret, authenticator secrets). */
59
+ secretBox: { encrypt(plain: string): string; decrypt(stored: string): string }
60
+ cookies: { session: string; csrf: string; oidcFlow: string; twoFactorChallenge: string }
61
+ /** Prefix that tells an API token from a session token in a Bearer header. */
62
+ apiTokenPrefix: string
63
+ /** Name an authenticator app shows next to the account. */
64
+ totpIssuer: string
65
+ /** Where the sign-in routes are mounted. The cookie paths and the SSO callback derive from it. */
66
+ basePath: string
67
+ urls: {
68
+ /** Where a failed sign-in sends the browser, e.g. `/admin/login`. */
69
+ signIn: string
70
+ /** Where a finished sign-in goes by default. */
71
+ home: string
72
+ /** The page that closes the SSO confirmation popup. */
73
+ ssoConfirm: string
74
+ /** The page that shows the result of an SSO test. */
75
+ ssoTest: string
76
+ }
77
+ /** The address the application is reached at, from the environment; '' when unknown. */
78
+ publicUrl: (env: NodeJS.ProcessEnv) => string
79
+ /** Whether cookies are marked Secure. Evaluated on every response. */
80
+ secureCookies: () => boolean
81
+ rateLimits: { signIn: RateLimitConfig; sensitiveAction: RateLimitConfig }
82
+ /** `returnTo` values a sign-in may start with; the application's `signInRedirect` hook turns them into a destination. */
83
+ returnTargets?: readonly string[]
84
+ hooks?: AuthHooks
85
+ }
package/src/createAuth.ts CHANGED
@@ -1,58 +1,58 @@
1
- import type { AuthOptions } from './context.js'
2
- import { createApiTokenRoutes } from './apiTokenRoutes.js'
3
- import { createApiTokens } from './apiTokens.js'
4
- import { createIdentityConfirmation } from './identityConfirmation.js'
5
- import { createMiddleware } from './middleware.js'
6
- import { createOidc } from './oidc.js'
7
- import { createOidcConfigResolver } from './oidcConfig.js'
8
- import { createOidcSettings } from './oidcSettings.js'
9
- import { createOidcSettingsRoutes } from './oidcSettingsRoutes.js'
10
- import { createSessions } from './sessions.js'
11
- import { createSignInAudit } from './signInAudit.js'
12
- import { createSignInRoutes } from './authRoutes.js'
13
- import { createTwoFactor } from './twoFactor.js'
14
- import { createUserRoutes } from './userRoutes.js'
15
-
16
- /**
17
- * Accounts, sessions, two-factor sign-in, single sign-on, API tokens and the routes that manage them. Everything is
18
- * built from `options` and kept in the returned closures; nothing is shared between two calls.
19
- */
20
- export function createAuth(options: AuthOptions) {
21
- options.roles.freeze()
22
-
23
- const apiTokens = createApiTokens(options)
24
- const sessions = createSessions(options, apiTokens)
25
- const middleware = createMiddleware(sessions, apiTokens)
26
- const identityConfirmation = createIdentityConfirmation(options)
27
- const twoFactor = createTwoFactor(options)
28
- const signInAudit = createSignInAudit(options)
29
- const oidcConfig = createOidcConfigResolver({ publicUrl: options.publicUrl, callbackPath: `${options.basePath}/oidc/callback` })
30
- const oidcSettings = createOidcSettings(options, oidcConfig)
31
- const oidc = createOidc(options, signInAudit)
32
-
33
- const signIn = createSignInRoutes(options, { sessions, middleware, twoFactor, identityConfirmation, signInAudit, oidcSettings, oidc })
34
- const users = createUserRoutes(options, { sessions, twoFactor, identityConfirmation })
35
- const oidcSettingsRoutes = createOidcSettingsRoutes(options, { identityConfirmation, oidc, oidcSettings, resolver: oidcConfig, startOidcFlow: signIn.startOidcFlow })
36
- const tokenRoutes = createApiTokenRoutes(options, apiTokens, identityConfirmation)
37
-
38
- return {
39
- sessions,
40
- middleware,
41
- identityConfirmation,
42
- twoFactor,
43
- signInAudit,
44
- oidcConfig,
45
- oidcSettings,
46
- oidc,
47
- apiTokens,
48
- /** Plain plugins (not wrapped for encapsulation), so the hooks of the scope they are registered in apply to them. */
49
- routes: {
50
- signIn: signIn.authRoutes,
51
- users: users.userRoutes,
52
- oidcSettings: oidcSettingsRoutes.oidcSettingsRoutes,
53
- apiTokens: tokenRoutes.apiTokenRoutes,
54
- },
55
- }
56
- }
57
-
58
- export type Auth = ReturnType<typeof createAuth>
1
+ import type { AuthOptions } from './context.js'
2
+ import { createApiTokenRoutes } from './apiTokenRoutes.js'
3
+ import { createApiTokens } from './apiTokens.js'
4
+ import { createIdentityConfirmation } from './identityConfirmation.js'
5
+ import { createMiddleware } from './middleware.js'
6
+ import { createOidc } from './oidc.js'
7
+ import { createOidcConfigResolver } from './oidcConfig.js'
8
+ import { createOidcSettings } from './oidcSettings.js'
9
+ import { createOidcSettingsRoutes } from './oidcSettingsRoutes.js'
10
+ import { createSessions } from './sessions.js'
11
+ import { createSignInAudit } from './signInAudit.js'
12
+ import { createSignInRoutes } from './authRoutes.js'
13
+ import { createTwoFactor } from './twoFactor.js'
14
+ import { createUserRoutes } from './userRoutes.js'
15
+
16
+ /**
17
+ * Accounts, sessions, two-factor sign-in, single sign-on, API tokens and the routes that manage them. Everything is
18
+ * built from `options` and kept in the returned closures; nothing is shared between two calls.
19
+ */
20
+ export function createAuth(options: AuthOptions) {
21
+ options.roles.freeze()
22
+
23
+ const apiTokens = createApiTokens(options)
24
+ const sessions = createSessions(options, apiTokens)
25
+ const middleware = createMiddleware(sessions, apiTokens)
26
+ const identityConfirmation = createIdentityConfirmation(options)
27
+ const twoFactor = createTwoFactor(options)
28
+ const signInAudit = createSignInAudit(options)
29
+ const oidcConfig = createOidcConfigResolver({ publicUrl: options.publicUrl, callbackPath: `${options.basePath}/oidc/callback` })
30
+ const oidcSettings = createOidcSettings(options, oidcConfig)
31
+ const oidc = createOidc(options, signInAudit)
32
+
33
+ const signIn = createSignInRoutes(options, { sessions, middleware, twoFactor, identityConfirmation, signInAudit, oidcSettings, oidc })
34
+ const users = createUserRoutes(options, { sessions, twoFactor, identityConfirmation })
35
+ const oidcSettingsRoutes = createOidcSettingsRoutes(options, { identityConfirmation, oidc, oidcSettings, resolver: oidcConfig, startOidcFlow: signIn.startOidcFlow })
36
+ const tokenRoutes = createApiTokenRoutes(options, apiTokens, identityConfirmation)
37
+
38
+ return {
39
+ sessions,
40
+ middleware,
41
+ identityConfirmation,
42
+ twoFactor,
43
+ signInAudit,
44
+ oidcConfig,
45
+ oidcSettings,
46
+ oidc,
47
+ apiTokens,
48
+ /** Plain plugins (not wrapped for encapsulation), so the hooks of the scope they are registered in apply to them. */
49
+ routes: {
50
+ signIn: signIn.authRoutes,
51
+ users: users.userRoutes,
52
+ oidcSettings: oidcSettingsRoutes.oidcSettingsRoutes,
53
+ apiTokens: tokenRoutes.apiTokenRoutes,
54
+ },
55
+ }
56
+ }
57
+
58
+ export type Auth = ReturnType<typeof createAuth>
@@ -1,50 +1,50 @@
1
- import bcrypt from 'bcryptjs'
2
- import { and, eq, gt } from 'drizzle-orm'
3
- import type { FastifyReply } from 'fastify'
4
- import { authSessions, users } from './tables.js'
5
- import type { AuthOptions } from './context.js'
6
- import type { AuthIdentity } from './sessions.js'
7
-
8
- /** Error code telling the admin UI to confirm the user through the identity provider, then retry. */
9
- export const SSO_CONFIRMATION_REQUIRED = 'SSO_CONFIRMATION_REQUIRED'
10
-
11
- /** How long an SSO sign-in or SSO confirmation covers sensitive actions in that session. */
12
- export const SSO_CONFIRMATION_WINDOW_MS = 10 * 60 * 1000
13
-
14
- export function createIdentityConfirmation(ctx: AuthOptions) {
15
- const { db } = ctx
16
-
17
- /**
18
- * Sensitive actions are confirmed the way the session signed in: a password session with the current
19
- * password, an SSO session with a recent sign-in at the identity provider (the sign-in itself, or a
20
- * confirmation popup started from the UI). Returns true, or sends the refusal and returns false.
21
- */
22
- async function confirmIdentity(identity: AuthIdentity, currentPassword: unknown, reply: FastifyReply): Promise<boolean> {
23
- if (identity.authMethod === 'oidc') {
24
- const session = (await db.select().from(authSessions).where(eq(authSessions.id, identity.sessionId)))[0]
25
- const verifiedAt = session?.verifiedAt ?? session?.createdAt ?? 0
26
- if (Date.now() - verifiedAt <= SSO_CONFIRMATION_WINDOW_MS) return true
27
- await reply.code(403).send({ error: 'Confirm your identity with single sign-on to continue', code: SSO_CONFIRMATION_REQUIRED })
28
- return false
29
- }
30
- const user = (await db.select().from(users).where(eq(users.id, identity.userId)))[0]
31
- if (user && typeof currentPassword === 'string' && currentPassword && await bcrypt.compare(currentPassword, user.passwordHash)) return true
32
- await reply.code(400).send({ error: 'Current password is incorrect' })
33
- return false
34
- }
35
-
36
- /** Records a successful SSO confirmation on a live SSO session of that user. False when there is no such session. */
37
- async function markSessionVerified(sessionId: string, userId: number): Promise<boolean> {
38
- const updated = await db.update(authSessions).set({ verifiedAt: Date.now() }).where(and(
39
- eq(authSessions.id, sessionId),
40
- eq(authSessions.userId, userId),
41
- eq(authSessions.authMethod, 'oidc'),
42
- gt(authSessions.expiresAt, Date.now()),
43
- )).returning({ id: authSessions.id })
44
- return updated.length > 0
45
- }
46
-
47
- return { confirmIdentity, markSessionVerified }
48
- }
49
-
50
- export type IdentityConfirmation = ReturnType<typeof createIdentityConfirmation>
1
+ import bcrypt from 'bcryptjs'
2
+ import { and, eq, gt } from 'drizzle-orm'
3
+ import type { FastifyReply } from 'fastify'
4
+ import { authSessions, users } from './tables.js'
5
+ import type { AuthOptions } from './context.js'
6
+ import type { AuthIdentity } from './sessions.js'
7
+
8
+ /** Error code telling the admin UI to confirm the user through the identity provider, then retry. */
9
+ export const SSO_CONFIRMATION_REQUIRED = 'SSO_CONFIRMATION_REQUIRED'
10
+
11
+ /** How long an SSO sign-in or SSO confirmation covers sensitive actions in that session. */
12
+ export const SSO_CONFIRMATION_WINDOW_MS = 10 * 60 * 1000
13
+
14
+ export function createIdentityConfirmation(ctx: AuthOptions) {
15
+ const { db } = ctx
16
+
17
+ /**
18
+ * Sensitive actions are confirmed the way the session signed in: a password session with the current
19
+ * password, an SSO session with a recent sign-in at the identity provider (the sign-in itself, or a
20
+ * confirmation popup started from the UI). Returns true, or sends the refusal and returns false.
21
+ */
22
+ async function confirmIdentity(identity: AuthIdentity, currentPassword: unknown, reply: FastifyReply): Promise<boolean> {
23
+ if (identity.authMethod === 'oidc') {
24
+ const session = (await db.select().from(authSessions).where(eq(authSessions.id, identity.sessionId)))[0]
25
+ const verifiedAt = session?.verifiedAt ?? session?.createdAt ?? 0
26
+ if (Date.now() - verifiedAt <= SSO_CONFIRMATION_WINDOW_MS) return true
27
+ await reply.code(403).send({ error: 'Confirm your identity with single sign-on to continue', code: SSO_CONFIRMATION_REQUIRED })
28
+ return false
29
+ }
30
+ const user = (await db.select().from(users).where(eq(users.id, identity.userId)))[0]
31
+ if (user && typeof currentPassword === 'string' && currentPassword && await bcrypt.compare(currentPassword, user.passwordHash)) return true
32
+ await reply.code(400).send({ error: 'Current password is incorrect' })
33
+ return false
34
+ }
35
+
36
+ /** Records a successful SSO confirmation on a live SSO session of that user. False when there is no such session. */
37
+ async function markSessionVerified(sessionId: string, userId: number): Promise<boolean> {
38
+ const updated = await db.update(authSessions).set({ verifiedAt: Date.now() }).where(and(
39
+ eq(authSessions.id, sessionId),
40
+ eq(authSessions.userId, userId),
41
+ eq(authSessions.authMethod, 'oidc'),
42
+ gt(authSessions.expiresAt, Date.now()),
43
+ )).returning({ id: authSessions.id })
44
+ return updated.length > 0
45
+ }
46
+
47
+ return { confirmIdentity, markSessionVerified }
48
+ }
49
+
50
+ export type IdentityConfirmation = ReturnType<typeof createIdentityConfirmation>
package/src/index.ts CHANGED
@@ -1,17 +1,17 @@
1
- export { createAuth, type Auth } from './createAuth.js'
2
- export { authDbModule } from './module.js'
3
- export { apiTokens, authSessions, oidcSettings, users } from './tables.js'
4
- export { acceptApiTokens, belongsTo, registerAdminApi, type AdminRouteGroup } from './routeScope.js'
5
- export { createSetupRoutes, type SetupRoutesOptions } from './setupRoutes.js'
6
- export { ADMIN_ROLE, createRoleRegistry, type RoleRegistry } from './roles.js'
7
- export { createTokenCatalogue, type TokenCatalogue } from './tokenCatalogue.js'
8
- export type { AuthHooks, AuthOptions, RateLimitConfig, SessionsRevoked, SignInOutcome, UnknownSsoUser } from './context.js'
9
- export { ALLOW_PENDING_PASSWORD_CHANGE, PASSWORD_CHANGE_REQUIRED, requestIdentity } from './middleware.js'
10
- export { API_TOKEN_NOT_ACCEPTED, type AuthenticateOptions, type AuthIdentity, type AuthMethod } from './sessions.js'
11
- export { SSO_CONFIRMATION_REQUIRED, SSO_CONFIRMATION_WINDOW_MS } from './identityConfirmation.js'
12
- export { checkEmailVerified, OIDC_CONFIRMATION_MAX_AGE_SECONDS } from './oidc.js'
13
- export type { OidcDecision, OidcDenial, OidcDenialCode, OidcFlowState, OidcTestResult, SsoProvisioning } from './oidc.js'
14
- export type { OidcSettingsRow } from './oidcSettings.js'
15
- export { createOidcConfigResolver, DEFAULT_OIDC_BUTTON_LABEL, DEFAULT_OIDC_SCOPES, isHttpUrl, passwordLoginEnabled, type OidcConfig, type OidcSettingsInput } from './oidcConfig.js'
16
- export { consumeRecoveryCode, generateRecoveryCodes, generateTotpCode, generateTotpSecret, hashRecoveryCode, totpUri, verifyTotp } from './totp.js'
17
- export type { CredentialDenialCode, SignInDenial, SignInMethod } from './signInAudit.js'
1
+ export { createAuth, type Auth } from './createAuth.js'
2
+ export { authDbModule } from './module.js'
3
+ export { apiTokens, authSessions, oidcSettings, users } from './tables.js'
4
+ export { acceptApiTokens, belongsTo, registerAdminApi, type AdminRouteGroup } from './routeScope.js'
5
+ export { createSetupRoutes, type SetupRoutesOptions } from './setupRoutes.js'
6
+ export { ADMIN_ROLE, createRoleRegistry, type RoleRegistry } from './roles.js'
7
+ export { createTokenCatalogue, type TokenCatalogue } from './tokenCatalogue.js'
8
+ export type { AuthHooks, AuthOptions, RateLimitConfig, SessionsRevoked, SignInOutcome, UnknownSsoUser } from './context.js'
9
+ export { ALLOW_PENDING_PASSWORD_CHANGE, PASSWORD_CHANGE_REQUIRED, requestIdentity } from './middleware.js'
10
+ export { API_TOKEN_NOT_ACCEPTED, type AuthenticateOptions, type AuthIdentity, type AuthMethod } from './sessions.js'
11
+ export { SSO_CONFIRMATION_REQUIRED, SSO_CONFIRMATION_WINDOW_MS } from './identityConfirmation.js'
12
+ export { checkEmailVerified, OIDC_CONFIRMATION_MAX_AGE_SECONDS } from './oidc.js'
13
+ export type { OidcDecision, OidcDenial, OidcDenialCode, OidcFlowState, OidcTestResult, SsoProvisioning } from './oidc.js'
14
+ export type { OidcSettingsRow } from './oidcSettings.js'
15
+ export { createOidcConfigResolver, DEFAULT_OIDC_BUTTON_LABEL, DEFAULT_OIDC_SCOPES, isHttpUrl, passwordLoginEnabled, type OidcConfig, type OidcSettingsInput } from './oidcConfig.js'
16
+ export { consumeRecoveryCode, generateRecoveryCodes, generateTotpCode, generateTotpSecret, hashRecoveryCode, totpUri, verifyTotp } from './totp.js'
17
+ export type { CredentialDenialCode, SignInDenial, SignInMethod } from './signInAudit.js'