update
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
// packages/backend/src/repositories/passkey.repository.ts
|
||||
import { Database } from 'sqlite3';
|
||||
import { getDb } from '../database';
|
||||
// Import new async helpers and the instance getter
|
||||
import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/connection';
|
||||
|
||||
// 定义 Passkey 数据库记录的接口
|
||||
export interface PasskeyRecord {
|
||||
@@ -13,20 +15,15 @@ export interface PasskeyRecord {
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export class PasskeyRepository {
|
||||
private db: Database;
|
||||
// Define the expected row structure from the database if it matches PasskeyRecord
|
||||
type DbPasskeyRow = PasskeyRecord;
|
||||
|
||||
constructor() {
|
||||
this.db = getDb();
|
||||
}
|
||||
export class PasskeyRepository {
|
||||
// Remove constructor or leave it empty, db instance will be fetched in each method
|
||||
// constructor() { }
|
||||
|
||||
/**
|
||||
* 保存新的 Passkey 凭证
|
||||
* @param credentialId Base64URL 编码的凭证 ID
|
||||
* @param publicKey Base64URL 编码的公钥
|
||||
* @param counter 签名计数器
|
||||
* @param transports 传输方式 (JSON 字符串)
|
||||
* @param name 用户提供的名称 (可选)
|
||||
* @returns Promise<number> 新插入记录的 ID
|
||||
*/
|
||||
async savePasskey(
|
||||
@@ -40,136 +37,147 @@ export class PasskeyRepository {
|
||||
INSERT INTO passkeys (credential_id, public_key, counter, transports, name, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now'))
|
||||
`;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, [credentialId, publicKey, counter, transports, name ?? null], function (err) {
|
||||
if (err) {
|
||||
console.error('保存 Passkey 时出错:', err.message);
|
||||
return reject(new Error(`保存 Passkey 时出错: ${err.message}`));
|
||||
}
|
||||
resolve(this.lastID);
|
||||
});
|
||||
});
|
||||
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 记录
|
||||
* @param credentialId Base64URL 编码的凭证 ID
|
||||
* @returns Promise<PasskeyRecord | null> 找到的记录或 null
|
||||
*/
|
||||
async getPasskeyByCredentialId(credentialId: string): Promise<PasskeyRecord | null> {
|
||||
const sql = `SELECT * FROM passkeys WHERE credential_id = ?`;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.get(sql, [credentialId], (err, row: PasskeyRecord) => {
|
||||
if (err) {
|
||||
console.error('按 Credential ID 获取 Passkey 时出错:', err.message);
|
||||
return reject(new Error(`按 Credential ID 获取 Passkey 时出错: ${err.message}`));
|
||||
}
|
||||
resolve(row || null);
|
||||
});
|
||||
});
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const row = await getDbRow<DbPasskeyRow>(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<PasskeyRecord[]> 所有记录的数组
|
||||
* 获取所有已注册的 Passkey 记录 (仅选择必要字段)
|
||||
* @returns Promise<Partial<PasskeyRecord>[]> 所有记录的部分信息的数组
|
||||
*/
|
||||
async getAllPasskeys(): Promise<PasskeyRecord[]> {
|
||||
const sql = `SELECT id, credential_id, name, transports, created_at FROM passkeys ORDER BY created_at DESC`; // 仅选择必要字段
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.all(sql, [], (err, rows: PasskeyRecord[]) => {
|
||||
if (err) {
|
||||
console.error('获取所有 Passkey 时出错:', err.message);
|
||||
return reject(new Error(`获取所有 Passkey 时出错: ${err.message}`));
|
||||
}
|
||||
resolve(rows);
|
||||
});
|
||||
});
|
||||
// Adjust return type based on selected columns
|
||||
async getAllPasskeys(): Promise<Array<Pick<PasskeyRecord, 'id' | 'credential_id' | 'name' | 'transports' | 'created_at'>>> {
|
||||
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<Pick<PasskeyRecord, 'id' | 'credential_id' | 'name' | 'transports' | 'created_at'>>(db, sql);
|
||||
return rows;
|
||||
} catch (err: any) {
|
||||
console.error('获取所有 Passkey 时出错:', err.message);
|
||||
throw new Error(`获取所有 Passkey 时出错: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Passkey 的签名计数器
|
||||
* @param credentialId Base64URL 编码的凭证 ID
|
||||
* @param newCounter 新的计数器值
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async updatePasskeyCounter(credentialId: string, newCounter: number): Promise<void> {
|
||||
const sql = `UPDATE passkeys SET counter = ?, updated_at = strftime('%s', 'now') WHERE credential_id = ?`;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, [newCounter, credentialId], function (err) {
|
||||
if (err) {
|
||||
console.error('更新 Passkey 计数器时出错:', err.message);
|
||||
return reject(new Error(`更新 Passkey 计数器时出错: ${err.message}`));
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
return reject(new Error(`未找到 Credential ID 为 ${credentialId} 的 Passkey 进行更新`));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
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
|
||||
* @param id Passkey 记录的 ID
|
||||
* @returns Promise<void>
|
||||
* @returns Promise<boolean> 是否成功删除
|
||||
*/
|
||||
async deletePasskeyById(id: number): Promise<void> {
|
||||
async deletePasskeyById(id: number): Promise<boolean> {
|
||||
const sql = `DELETE FROM passkeys WHERE id = ?`;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, [id], function (err) {
|
||||
if (err) {
|
||||
console.error('按 ID 删除 Passkey 时出错:', err.message);
|
||||
return reject(new Error(`按 ID 删除 Passkey 时出错: ${err.message}`));
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
return reject(new Error(`未找到 ID 为 ${id} 的 Passkey 进行删除`));
|
||||
}
|
||||
console.log(`ID 为 ${id} 的 Passkey 已删除。`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
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
|
||||
* @param credentialId Base64URL 编码的凭证 ID
|
||||
* @returns Promise<void>
|
||||
* @returns Promise<boolean> 是否成功删除
|
||||
*/
|
||||
async deletePasskeyByCredentialId(credentialId: string): Promise<void> {
|
||||
async deletePasskeyByCredentialId(credentialId: string): Promise<boolean> {
|
||||
const sql = `DELETE FROM passkeys WHERE credential_id = ?`;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, [credentialId], function (err) {
|
||||
if (err) {
|
||||
console.error('按 Credential ID 删除 Passkey 时出错:', err.message);
|
||||
return reject(new Error(`按 Credential ID 删除 Passkey 时出错: ${err.message}`));
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
// It's possible the user tries to delete a non-existent key, maybe not an error?
|
||||
console.warn(`尝试删除不存在的 Credential ID: ${credentialId}`);
|
||||
} else {
|
||||
console.log(`Credential ID 为 ${credentialId} 的 Passkey 已删除。`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
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 记录(自动补全)
|
||||
* @param prefix 前缀字符串
|
||||
* @returns Promise<PasskeyRecord[]> 匹配的记录数组
|
||||
*/
|
||||
async searchPasskeyByPrefix(prefix: string): Promise<PasskeyRecord[]> {
|
||||
// Adjust return type based on selected columns if not selecting all (*)
|
||||
async searchPasskeyByPrefix(prefix: string): Promise<DbPasskeyRow[]> {
|
||||
const sql = `SELECT * FROM passkeys WHERE credential_id LIKE ? OR name LIKE ? ORDER BY created_at DESC`;
|
||||
const likePrefix = `${prefix}%`;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.all(sql, [likePrefix, likePrefix], (err, rows: PasskeyRecord[]) => {
|
||||
if (err) {
|
||||
console.error('模糊查找 Passkey 时出错:', err.message);
|
||||
return reject(new Error(`模糊查找 Passkey 时出错: ${err.message}`));
|
||||
}
|
||||
resolve(rows);
|
||||
});
|
||||
});
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const rows = await allDb<DbPasskeyRow>(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'
|
||||
|
||||
Reference in New Issue
Block a user