Files
auth-portal/apps/api/src/lib/complete-login.ts
T
DenozordecandCursor 46dc2f714c
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
feat(auth): добавить вход по passkey
Альтернатива паролю на портале; SSO приложений без изменений.

Co-authored-by: Cursor <[email protected]>
2026-08-19 10:24:46 +07:00

65 lines
1.8 KiB
TypeScript

import type { FastifyInstance, FastifyReply } from 'fastify'
import { randomBytes } from 'node:crypto'
import {
createRefreshSession,
touchLastLogin,
type UserRow,
} from '@authportal/db'
import { issueAccessToken } from './issue-access-token.js'
import { safeAudit } from './audit.js'
import { targetAppFromReturnTo } from './target-app.js'
export const REFRESH_COOKIE = 'refresh_token'
export function completeLogin(
app: FastifyInstance,
reply: FastifyReply,
user: UserRow,
opts: {
method: 'password' | 'passkey'
returnTo?: string
ip: string | null
userAgent: string | null
},
) {
const body = issueAccessToken(app, user)
const refreshRaw = randomBytes(32).toString('hex')
const refreshExpires = new Date(
Date.now() + app.config.refreshTtlDays * 24 * 60 * 60 * 1000,
)
createRefreshSession(app.db, user.id, refreshRaw, refreshExpires, {
ip: opts.ip,
userAgent: opts.userAgent,
})
touchLastLogin(app.db, user.id, opts.ip)
reply.header(
'Set-Cookie',
`${REFRESH_COOKIE}=${refreshRaw}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${app.config.refreshTtlDays * 86400}${app.config.isProd ? '; Secure' : ''}`,
)
const targetApp = targetAppFromReturnTo(opts.returnTo)
const isPasskey = opts.method === 'passkey'
safeAudit(app, {
action: isPasskey ? 'auth.passkey_login' : 'auth.login',
severity: 'info',
actorUserId: user.id,
actorEmail: user.email,
actorName: user.name,
targetType: 'session',
targetId: user.id,
summary: isPasskey
? `Вход (passkey): ${user.email}`
: `Вход: ${user.email}`,
details: {
method: opts.method,
user_agent: opts.userAgent,
return_to: opts.returnTo ?? null,
target_app: targetApp,
},
ip: opts.ip,
})
return body
}