feat(auth): добавить вход по passkey
quality / commitlint (push) Skipped
quality / changes (push) Successful in 8s
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m11s
quality / api (push) Successful in 47s
CD / quality (push) Successful in 2m10s
CD / publish (push) Successful in 1m50s

Альтернатива паролю на портале; SSO приложений без изменений.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
DenozordecandCursor committed 2026-08-19 10:24:46 +07:00
1 parent f76b89c1df
commit 46dc2f714c
26 files changed
+1820 -72

No files matched your search

+38
View File
@@ -24,6 +24,9 @@ export const configSchema = z.object({
logLevel: z.string().default('info'),
isProd: z.boolean(),
auditIngestSecret: z.string().min(8).optional(),
webauthnRpID: z.string().min(1),
webauthnRpName: z.string().min(1).default('Auth Portal'),
webauthnOrigins: z.array(z.string().url()).min(1),
})
export type AppConfig = z.infer<typeof configSchema>
@@ -35,6 +38,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
env.AUDIT_INGEST_SECRET ??
(isProd ? undefined : 'dev-audit-ingest-secret')
const issuer = env.ISSUER ?? 'https://auth.shnt.top'
const { rpID, origins } = webauthnFromIssuer(issuer, env, isProd)
return configSchema.parse({
databaseUrl: env.DATABASE_URL ?? 'sqlite:data/app.db',
@@ -56,9 +60,43 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
boolFromEnv(env.NODE_ENV === 'production' ? 'true' : undefined, false) ||
isProd,
auditIngestSecret,
webauthnRpID: rpID,
webauthnRpName: env.WEBAUTHN_RP_NAME || 'Auth Portal',
webauthnOrigins: origins,
})
}
function webauthnFromIssuer(
issuer: string,
env: NodeJS.ProcessEnv,
isProd: boolean,
): { rpID: string; origins: string[] } {
let issuerUrl: URL
try {
issuerUrl = new URL(issuer)
} catch {
issuerUrl = new URL('https://auth.shnt.top')
}
const rpID = (env.WEBAUTHN_RP_ID || issuerUrl.hostname).trim()
const origins = new Set<string>()
origins.add(issuerUrl.origin)
const extra = env.WEBAUTHN_ORIGINS ?? ''
for (const raw of extra.split(',')) {
const value = raw.trim().replace(/\/$/, '')
if (!value) continue
try {
origins.add(new URL(value).origin)
} catch {
/* skip invalid */
}
}
if (!isProd) {
origins.add('http://localhost:5173')
origins.add('http://localhost:8080')
}
return { rpID, origins: [...origins] }
}
export function oidcIssuerFromConfig(config: AppConfig): string {
return (config.oidcIssuer ?? config.issuer).replace(/\/$/, '')
}