feat: 添加 passkey 登录功能
This commit is contained in:
@@ -1 +1,132 @@
|
||||
// This file is intentionally left empty as Passkey functionality has been removed.
|
||||
import { getDbInstance, runDb, getDb, allDb } from '../database/connection';
|
||||
|
||||
export interface Passkey {
|
||||
id: number;
|
||||
user_id: number;
|
||||
credential_id: string;
|
||||
public_key: string;
|
||||
counter: number;
|
||||
transports: string | null; // JSON string
|
||||
name: string | null;
|
||||
backed_up: boolean; // SQLite stores booleans as 0 or 1
|
||||
last_used_at: number | null;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export interface NewPasskey {
|
||||
user_id: number;
|
||||
credential_id: string;
|
||||
public_key: string;
|
||||
counter: number;
|
||||
transports?: string | null; // JSON string
|
||||
name?: string | null;
|
||||
backed_up?: boolean;
|
||||
}
|
||||
|
||||
// Helper to convert DB result (0/1) to boolean for backed_up field
|
||||
function mapPasskeyResult(dbResult: any): Passkey | null {
|
||||
if (!dbResult) return null;
|
||||
return {
|
||||
...dbResult,
|
||||
backed_up: !!dbResult.backed_up, // Ensure boolean
|
||||
transports: dbResult.transports, // Already string or null
|
||||
};
|
||||
}
|
||||
|
||||
function mapPasskeyResults(dbResults: any[]): Passkey[] {
|
||||
return dbResults.map(row => ({
|
||||
...row,
|
||||
backed_up: !!row.backed_up,
|
||||
transports: row.transports,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
export class PasskeyRepository {
|
||||
async createPasskey(passkeyData: NewPasskey): Promise<Passkey> {
|
||||
const db = await getDbInstance();
|
||||
const sql = `
|
||||
INSERT INTO passkeys (user_id, credential_id, public_key, counter, transports, name, backed_up, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now'))
|
||||
RETURNING *
|
||||
`;
|
||||
// Note: RETURNING * might not work as expected with the 'sqlite3' package's run method.
|
||||
// We'll do a SELECT after INSERT if needed, or rely on lastID and then select.
|
||||
// For simplicity with 'sqlite3', we'll insert then select.
|
||||
|
||||
const insertSql = `
|
||||
INSERT INTO passkeys (user_id, credential_id, public_key, counter, transports, name, backed_up, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now'))
|
||||
`;
|
||||
const params = [
|
||||
passkeyData.user_id,
|
||||
passkeyData.credential_id,
|
||||
passkeyData.public_key,
|
||||
passkeyData.counter,
|
||||
passkeyData.transports ?? null,
|
||||
passkeyData.name ?? null,
|
||||
passkeyData.backed_up ? 1 : 0, // Store boolean as 0 or 1
|
||||
];
|
||||
|
||||
const { lastID } = await runDb(db, insertSql, params);
|
||||
|
||||
// Fetch the inserted row
|
||||
const newPasskey = await this.getPasskeyById(lastID);
|
||||
if (!newPasskey) {
|
||||
throw new Error('Failed to create or retrieve passkey after insert.');
|
||||
}
|
||||
return newPasskey;
|
||||
}
|
||||
|
||||
async getPasskeyById(id: number): Promise<Passkey | null> {
|
||||
const db = await getDbInstance();
|
||||
const sql = 'SELECT * FROM passkeys WHERE id = ?';
|
||||
const result = await getDb<any>(db, sql, [id]);
|
||||
return mapPasskeyResult(result);
|
||||
}
|
||||
|
||||
async getPasskeyByCredentialId(credentialId: string): Promise<Passkey | null> {
|
||||
const db = await getDbInstance();
|
||||
const sql = 'SELECT * FROM passkeys WHERE credential_id = ?';
|
||||
const result = await getDb<any>(db, sql, [credentialId]);
|
||||
return mapPasskeyResult(result);
|
||||
}
|
||||
|
||||
async getPasskeysByUserId(userId: number): Promise<Passkey[]> {
|
||||
const db = await getDbInstance();
|
||||
const sql = 'SELECT * FROM passkeys WHERE user_id = ? ORDER BY created_at DESC';
|
||||
const results = await allDb<any>(db, sql, [userId]);
|
||||
return mapPasskeyResults(results);
|
||||
}
|
||||
|
||||
async updatePasskeyCounter(credentialId: string, newCounter: number): Promise<boolean> {
|
||||
const db = await getDbInstance();
|
||||
const sql = "UPDATE passkeys SET counter = ?, updated_at = strftime('%s', 'now') WHERE credential_id = ?";
|
||||
const { changes } = await runDb(db, sql, [newCounter, credentialId]);
|
||||
return changes > 0;
|
||||
}
|
||||
|
||||
async updatePasskeyLastUsedAt(credentialId: string): Promise<boolean> {
|
||||
const db = await getDbInstance();
|
||||
const sql = "UPDATE passkeys SET last_used_at = strftime('%s', 'now'), updated_at = strftime('%s', 'now') WHERE credential_id = ?";
|
||||
const { changes } = await runDb(db, sql, [credentialId]);
|
||||
return changes > 0;
|
||||
}
|
||||
|
||||
async deletePasskey(credentialId: string): Promise<boolean> {
|
||||
const db = await getDbInstance();
|
||||
const sql = 'DELETE FROM passkeys WHERE credential_id = ?';
|
||||
const { changes } = await runDb(db, sql, [credentialId]);
|
||||
return changes > 0;
|
||||
}
|
||||
|
||||
async deletePasskeysByUserId(userId: number): Promise<boolean> {
|
||||
const db = await getDbInstance();
|
||||
const sql = 'DELETE FROM passkeys WHERE user_id = ?';
|
||||
const { changes } = await runDb(db, sql, [userId]);
|
||||
return changes > 0;
|
||||
}
|
||||
}
|
||||
|
||||
export const passkeyRepository = new PasskeyRepository();
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { getDbInstance, getDb, allDb } from '../database/connection';
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
hashed_password?: string; // Optional, as not always needed by consumers
|
||||
two_factor_secret?: string | null;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export class UserRepository {
|
||||
async findUserById(id: number): Promise<User | null> {
|
||||
const db = await getDbInstance();
|
||||
const sql = 'SELECT id, username, hashed_password, two_factor_secret, created_at, updated_at FROM users WHERE id = ?';
|
||||
const user = await getDb<User>(db, sql, [id]);
|
||||
return user ?? null;
|
||||
}
|
||||
|
||||
async findUserByUsername(username: string): Promise<User | null> {
|
||||
const db = await getDbInstance();
|
||||
const sql = 'SELECT id, username, hashed_password, two_factor_secret, created_at, updated_at FROM users WHERE username = ?';
|
||||
const user = await getDb<User>(db, sql, [username]);
|
||||
return user ?? null;
|
||||
}
|
||||
|
||||
// Add other user-related methods if needed, e.g., createUser, updateUserPassword, etc.
|
||||
}
|
||||
|
||||
export const userRepository = new UserRepository();
|
||||
Reference in New Issue
Block a user