mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 22:46:32 +00:00
fix(server): tidy passkey quirks after the DI fold
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
passkeyRegisterVerifyRequestSchema,
|
||||
passkeyLoginVerifyRequestSchema,
|
||||
passkeyRenameRequestSchema,
|
||||
passkeyDeleteRequestSchema,
|
||||
} from '@trek/shared';
|
||||
|
||||
/**
|
||||
@@ -46,3 +47,4 @@ export class PasskeyRegisterOptionsDto extends createZodDto(passkeyRegisterOptio
|
||||
export class PasskeyRegisterVerifyDto extends createZodDto(passkeyRegisterVerifyRequestSchema) {}
|
||||
export class PasskeyLoginVerifyDto extends createZodDto(passkeyLoginVerifyRequestSchema) {}
|
||||
export class PasskeyRenameDto extends createZodDto(passkeyRenameRequestSchema) {}
|
||||
export class PasskeyDeleteDto extends createZodDto(passkeyDeleteRequestSchema) {}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { setAuthCookie } from '../../services/cookie';
|
||||
import { getClientIp } from '../audit/client-ip';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { PasskeyService } from './passkey.service';
|
||||
import { PasskeyRegisterOptionsDto, PasskeyRegisterVerifyDto, PasskeyLoginVerifyDto, PasskeyRenameDto } from './auth.dto';
|
||||
import { PasskeyRegisterOptionsDto, PasskeyRegisterVerifyDto, PasskeyLoginVerifyDto, PasskeyRenameDto, PasskeyDeleteDto } from './auth.dto';
|
||||
import type { User } from '../../types';
|
||||
|
||||
const WINDOW = 15 * 60 * 1000;
|
||||
@@ -110,7 +110,7 @@ export class PasskeyController {
|
||||
|
||||
@Delete('credentials/:id')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
remove(@CurrentUser() user: User, @Param('id') id: string, @Body() body: { password?: string }, @Req() req: Request) {
|
||||
remove(@CurrentUser() user: User, @Param('id') id: string, @Body() body: PasskeyDeleteDto, @Req() req: Request) {
|
||||
this.limit('login', req, 5);
|
||||
const result = this.passkeys.deletePasskey(user.id, id, body?.password);
|
||||
if (result.error) throw new HttpException({ error: result.error }, result.status!);
|
||||
|
||||
@@ -32,6 +32,11 @@ const NOT_CONFIGURED = { error: 'Passkey login is not configured for this server
|
||||
// used to tell "no such credential" apart from "bad signature" (CWE-203).
|
||||
const AUTH_FAILED = { error: 'Authentication failed', status: 401 } as const;
|
||||
|
||||
// Reference-compared sentinel (oidc invite_exhausted precedent): thrown inside
|
||||
// the register transaction to keep the duplicate 409 distinct from the generic
|
||||
// insert-failure 400 without string-matching SQLite errors.
|
||||
const DUPLICATE_CREDENTIAL = new Error('duplicate credential');
|
||||
|
||||
interface CredentialRow {
|
||||
id: number;
|
||||
user_id: number;
|
||||
@@ -205,27 +210,34 @@ export class PasskeyService {
|
||||
// from the raw client payload.
|
||||
const { credential, credentialDeviceType, credentialBackedUp, aaguid } = verification.registrationInfo;
|
||||
|
||||
if (this.db.get('SELECT id FROM webauthn_credentials WHERE credential_id = ?', credential.id)) {
|
||||
return { error: 'This passkey is already registered.', status: 409 };
|
||||
}
|
||||
|
||||
const name = sanitizeName(body?.name) || defaultCredentialName(credentialDeviceType);
|
||||
// Duplicate check + INSERT in one transaction so the UNIQUE race can't slip
|
||||
// between them; the sentinel keeps the legacy 409-vs-400 split intact.
|
||||
try {
|
||||
this.db.run(
|
||||
`INSERT INTO webauthn_credentials
|
||||
(user_id, credential_id, public_key, counter, transports, device_type, backed_up, name, aaguid, last_used_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`,
|
||||
userId,
|
||||
credential.id,
|
||||
Buffer.from(credential.publicKey),
|
||||
credential.counter ?? 0,
|
||||
credential.transports ? JSON.stringify(credential.transports) : null,
|
||||
credentialDeviceType ?? null,
|
||||
credentialBackedUp ? 1 : 0,
|
||||
name,
|
||||
aaguid ?? null,
|
||||
);
|
||||
} catch {
|
||||
this.db.transaction((conn) => {
|
||||
if (conn.prepare('SELECT id FROM webauthn_credentials WHERE credential_id = ?').get(credential.id)) {
|
||||
throw DUPLICATE_CREDENTIAL;
|
||||
}
|
||||
conn.prepare(
|
||||
`INSERT INTO webauthn_credentials
|
||||
(user_id, credential_id, public_key, counter, transports, device_type, backed_up, name, aaguid, last_used_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`,
|
||||
).run(
|
||||
userId,
|
||||
credential.id,
|
||||
Buffer.from(credential.publicKey),
|
||||
credential.counter ?? 0,
|
||||
credential.transports ? JSON.stringify(credential.transports) : null,
|
||||
credentialDeviceType ?? null,
|
||||
credentialBackedUp ? 1 : 0,
|
||||
name,
|
||||
aaguid ?? null,
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
if (err === DUPLICATE_CREDENTIAL) {
|
||||
return { error: 'This passkey is already registered.', status: 409 };
|
||||
}
|
||||
return { error: 'Could not register this passkey.', status: 400 };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user