import { IOAuthClient } from '../types/index.js'; import { BaseDao } from './base/BaseDao.js'; import { JsonFileBaseDao } from './base/JsonFileBaseDao.js'; /** * OAuth Client DAO interface with OAuth client-specific operations */ export interface OAuthClientDao extends BaseDao { /** * Find OAuth client by client ID */ findByClientId(clientId: string): Promise; /** * Find OAuth clients by owner */ findByOwner(owner: string): Promise; /** * Validate client credentials */ validateCredentials(clientId: string, clientSecret?: string): Promise; } /** * JSON file-based OAuth Client DAO implementation */ export class OAuthClientDaoImpl extends JsonFileBaseDao implements OAuthClientDao { protected async getAll(): Promise { const settings = await this.loadSettings(); return settings.oauthClients || []; } protected async saveAll(clients: IOAuthClient[]): Promise { const settings = await this.loadSettings(); settings.oauthClients = clients; await this.saveSettings(settings); } protected getEntityId(client: IOAuthClient): string { return client.clientId; } protected createEntity(_data: Omit): IOAuthClient { throw new Error('clientId must be provided'); } protected updateEntity(existing: IOAuthClient, updates: Partial): IOAuthClient { return { ...existing, ...updates, clientId: existing.clientId, // clientId should not be updated }; } async findAll(): Promise { return this.getAll(); } async findById(clientId: string): Promise { return this.findByClientId(clientId); } async findByClientId(clientId: string): Promise { const clients = await this.getAll(); return clients.find((client) => client.clientId === clientId) || null; } async findByOwner(owner: string): Promise { const clients = await this.getAll(); return clients.filter((client) => client.owner === owner); } async create(data: IOAuthClient): Promise { const clients = await this.getAll(); // Check if client already exists if (clients.find((client) => client.clientId === data.clientId)) { throw new Error(`OAuth client ${data.clientId} already exists`); } const newClient: IOAuthClient = { ...data, owner: data.owner || 'admin', }; clients.push(newClient); await this.saveAll(clients); return newClient; } async update(clientId: string, updates: Partial): Promise { const clients = await this.getAll(); const index = clients.findIndex((client) => client.clientId === clientId); if (index === -1) { return null; } // Don't allow clientId changes const { clientId: _, ...allowedUpdates } = updates; const updatedClient = this.updateEntity(clients[index], allowedUpdates); clients[index] = updatedClient; await this.saveAll(clients); return updatedClient; } async delete(clientId: string): Promise { const clients = await this.getAll(); const index = clients.findIndex((client) => client.clientId === clientId); if (index === -1) { return false; } clients.splice(index, 1); await this.saveAll(clients); return true; } async exists(clientId: string): Promise { const client = await this.findByClientId(clientId); return client !== null; } async count(): Promise { const clients = await this.getAll(); return clients.length; } async validateCredentials(clientId: string, clientSecret?: string): Promise { const client = await this.findByClientId(clientId); if (!client) { return false; } // If client has no secret (public client), accept if no secret provided if (!client.clientSecret) { return !clientSecret; } // If client has a secret, it must match return client.clientSecret === clientSecret; } }