First running version

This commit is contained in:
qingjie.du
2026-03-30 17:39:13 +09:00
parent 5ffea3d849
commit bce2a5672c
67 changed files with 16503 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
/**
* Passkey Storage Interface and In-Memory Implementation
* Spec Section 3.7, 7.3.1
*/
export interface PasskeyCredential {
id: string; // base64-encoded credential ID
publicKey: string; // base64-encoded public key
counter: number; // signature counter (prevents cloned authenticators)
transports?: string[]; // e.g., ["platform", "usb"]
createdAt: string; // ISO 8601 timestamp
lastUsed?: string; // ISO 8601 timestamp
}
export interface PasskeyUser {
userId: string;
displayName: string;
credentials: PasskeyCredential[];
createdAt: string; // ISO 8601 timestamp
}
export interface IPasskeyStorage {
// User operations
getUser(userId: string): Promise<PasskeyUser | null>;
createUser(userId: string, displayName: string): Promise<PasskeyUser>;
userExists(userId: string): Promise<boolean>;
// Credential operations
addCredential(userId: string, credential: PasskeyCredential): Promise<void>;
getCredential(credentialId: string): Promise<PasskeyCredential | null>;
getCredentialsByUserId(userId: string): Promise<PasskeyCredential[]>;
updateCredentialCounter(credentialId: string, counter: number): Promise<void>;
deleteCredential(userId: string, credentialId: string): Promise<void>;
}
/**
* In-Memory Passkey Storage Implementation
* Suitable for demo/localhost mode. For production, implement persistent storage
* (e.g., MongoDB, PostgreSQL, etc.)
*/
export class InMemoryPasskeyStorage implements IPasskeyStorage {
private users = new Map<string, PasskeyUser>();
async getUser(userId: string): Promise<PasskeyUser | null> {
return this.users.get(userId) ?? null;
}
async createUser(userId: string, displayName: string): Promise<PasskeyUser> {
if (this.users.has(userId)) {
throw new Error(`User ${userId} already exists`);
}
const user: PasskeyUser = {
userId,
displayName,
credentials: [],
createdAt: new Date().toISOString(),
};
this.users.set(userId, user);
return user;
}
async userExists(userId: string): Promise<boolean> {
return this.users.has(userId);
}
async addCredential(userId: string, credential: PasskeyCredential): Promise<void> {
const user = this.users.get(userId);
if (!user) {
throw new Error(`User ${userId} not found`);
}
// Check for duplicate credential ID
if (user.credentials.some(c => c.id === credential.id)) {
throw new Error(`Credential already exists for user ${userId}`);
}
user.credentials.push(credential);
}
async getCredential(credentialId: string): Promise<PasskeyCredential | null> {
for (const user of this.users.values()) {
const cred = user.credentials.find(c => c.id === credentialId);
if (cred) return cred;
}
return null;
}
async getCredentialsByUserId(userId: string): Promise<PasskeyCredential[]> {
const user = this.users.get(userId);
return user?.credentials ?? [];
}
async updateCredentialCounter(credentialId: string, counter: number): Promise<void> {
for (const user of this.users.values()) {
const cred = user.credentials.find(c => c.id === credentialId);
if (cred) {
cred.counter = counter;
cred.lastUsed = new Date().toISOString();
return;
}
}
throw new Error(`Credential ${credentialId} not found`);
}
async deleteCredential(userId: string, credentialId: string): Promise<void> {
const user = this.users.get(userId);
if (!user) {
throw new Error(`User ${userId} not found`);
}
const idx = user.credentials.findIndex(c => c.id === credentialId);
if (idx >= 0) {
user.credentials.splice(idx, 1);
}
}
}