// packages/backend/src/repositories/passkey.repository.ts import { Database } from 'sqlite3'; // Import new async helpers and the instance getter import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/connection'; // 定义 Passkey 数据库记录的接口 export interface PasskeyRecord { id: number; credential_id: string; // Base64URL encoded public_key: string; // Base64URL encoded counter: number; transports: string | null; // JSON string or null name: string | null; created_at: number; updated_at: number; } // Define the expected row structure from the database if it matches PasskeyRecord type DbPasskeyRow = PasskeyRecord; export class PasskeyRepository { // Remove constructor or leave it empty, db instance will be fetched in each method // constructor() { } /** * 保存新的 Passkey 凭证 * @returns Promise 新插入记录的 ID */ async savePasskey( credentialId: string, publicKey: string, counter: number, transports: string | null, name?: string ): Promise { const sql = ` INSERT INTO passkeys (credential_id, public_key, counter, transports, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')) `; const params = [credentialId, publicKey, counter, transports, name ?? null]; try { const db = await getDbInstance(); const result = await runDb(db, sql, params); // Ensure lastID is valid before returning if (typeof result.lastID !== 'number' || result.lastID <= 0) { throw new Error('保存 Passkey 后未能获取有效的 lastID'); } return result.lastID; } catch (err: any) { console.error('保存 Passkey 时出错:', err.message); // Handle potential UNIQUE constraint errors on credential_id if (err.message.includes('UNIQUE constraint failed')) { throw new Error(`Credential ID "${credentialId}" 已存在。`); } throw new Error(`保存 Passkey 时出错: ${err.message}`); } } /** * 根据 Credential ID 获取 Passkey 记录 * @returns Promise 找到的记录或 null */ async getPasskeyByCredentialId(credentialId: string): Promise { const sql = `SELECT * FROM passkeys WHERE credential_id = ?`; try { const db = await getDbInstance(); const row = await getDbRow(db, sql, [credentialId]); return row || null; } catch (err: any) { console.error('按 Credential ID 获取 Passkey 时出错:', err.message); throw new Error(`按 Credential ID 获取 Passkey 时出错: ${err.message}`); } } /** * 获取所有已注册的 Passkey 记录 (仅选择必要字段) * @returns Promise[]> 所有记录的部分信息的数组 */ // Adjust return type based on selected columns async getAllPasskeys(): Promise>> { const sql = `SELECT id, credential_id, name, transports, created_at FROM passkeys ORDER BY created_at DESC`; try { const db = await getDbInstance(); // Adjust the generic type for allDb to match the selected columns const rows = await allDb>(db, sql); return rows; } catch (err: any) { console.error('获取所有 Passkey 时出错:', err.message); throw new Error(`获取所有 Passkey 时出错: ${err.message}`); } } /** * 更新 Passkey 的签名计数器 * @returns Promise */ async updatePasskeyCounter(credentialId: string, newCounter: number): Promise { const sql = `UPDATE passkeys SET counter = ?, updated_at = strftime('%s', 'now') WHERE credential_id = ?`; try { const db = await getDbInstance(); const result = await runDb(db, sql, [newCounter, credentialId]); if (result.changes === 0) { // Consider if this should be an error or just a warning/no-op console.warn(`未找到 Credential ID 为 ${credentialId} 的 Passkey 进行计数器更新`); // throw new Error(`未找到 Credential ID 为 ${credentialId} 的 Passkey 进行更新`); } } catch (err: any) { console.error('更新 Passkey 计数器时出错:', err.message); throw new Error(`更新 Passkey 计数器时出错: ${err.message}`); } } /** * 根据 ID 删除 Passkey * @returns Promise 是否成功删除 */ async deletePasskeyById(id: number): Promise { const sql = `DELETE FROM passkeys WHERE id = ?`; try { const db = await getDbInstance(); const result = await runDb(db, sql, [id]); if (result.changes > 0) { console.log(`ID 为 ${id} 的 Passkey 已删除。`); return true; } else { console.warn(`尝试删除不存在的 Passkey ID: ${id}`); return false; } } catch (err: any) { console.error('按 ID 删除 Passkey 时出错:', err.message); throw new Error(`按 ID 删除 Passkey 时出错: ${err.message}`); } } /** * 根据 Credential ID 删除 Passkey * @returns Promise 是否成功删除 */ async deletePasskeyByCredentialId(credentialId: string): Promise { const sql = `DELETE FROM passkeys WHERE credential_id = ?`; try { const db = await getDbInstance(); const result = await runDb(db, sql, [credentialId]); if (result.changes > 0) { console.log(`Credential ID 为 ${credentialId} 的 Passkey 已删除。`); return true; } else { console.warn(`尝试删除不存在的 Credential ID: ${credentialId}`); return false; } } catch (err: any) { console.error('按 Credential ID 删除 Passkey 时出错:', err.message); throw new Error(`按 Credential ID 删除 Passkey 时出错: ${err.message}`); } } /** * 根据 credential_id 或 name 前缀模糊查找 Passkey 记录(自动补全) * @returns Promise 匹配的记录数组 */ // Adjust return type based on selected columns if not selecting all (*) async searchPasskeyByPrefix(prefix: string): Promise { const sql = `SELECT * FROM passkeys WHERE credential_id LIKE ? OR name LIKE ? ORDER BY created_at DESC`; const likePrefix = `${prefix}%`; try { const db = await getDbInstance(); const rows = await allDb(db, sql, [likePrefix, likePrefix]); return rows; } catch (err: any) { console.error('模糊查找 Passkey 时出错:', err.message); throw new Error(`模糊查找 Passkey 时出错: ${err.message}`); } } } // Export an instance or the class itself depending on usage pattern // If used as a singleton service, export an instance: // export const passkeyRepository = new PasskeyRepository(); // If instantiated elsewhere (e.g., dependency injection), export the class: // export { PasskeyRepository }; // For now, let's assume it's used like other repositories (exporting functions/class) // Exporting the class seems more appropriate given its structure // Removed redundant export below as the class is already exported with 'export class'