import db from '../../db' import { v4 as uuidv4 } from 'uuid' import bcrypt from 'bcryptjs' import type { OAuthClient, OAuthToken, OAuthCode } from '../modules/auth/types' const ACCESS_TOKEN_EXPIRY = 7 * 24 * 60 * 60 * 1000 const REFRESH_TOKEN_EXPIRY = 30 * 24 * 60 * 60 * 1000 const CODE_EXPIRY = 10 * 60 * 1000 export function getClients(): OAuthClient[] { const stmt = db.prepare('SELECT * FROM oauth_clients ORDER BY created_at DESC') const rows = stmt.all() as any[] return rows.map(row => ({ ...row, redirect_uris: JSON.parse(row.redirect_uris || '[]'), allowed_scopes: JSON.parse(row.allowed_scopes || '[]'), grant_types: JSON.parse(row.grant_types || '[]') })) } export function getClientById(clientId: string): OAuthClient | undefined { const stmt = db.prepare('SELECT * FROM oauth_clients WHERE client_id = ?') const row = stmt.get(clientId) as any if (!row) return undefined return { ...row, redirect_uris: JSON.parse(row.redirect_uris || '[]'), allowed_scopes: JSON.parse(row.allowed_scopes || '[]'), grant_types: JSON.parse(row.grant_types || '[]') } } export function createClient( clientName: string, redirectUris: string[], allowedScopes: string[], grantTypes: string[], platform = 'web' ): { client: OAuthClient; clientSecret: string } { const clientId = uuidv4().replace(/-/g, '') const clientSecret = uuidv4() const clientSecretHash = bcrypt.hashSync(clientSecret, 10) const stmt = db.prepare(` INSERT INTO oauth_clients (client_id, client_secret_hash, client_name, redirect_uris, allowed_scopes, grant_types, platform) VALUES (?, ?, ?, ?, ?, ?, ?) `) stmt.run( clientId, clientSecretHash, clientName, JSON.stringify(redirectUris), JSON.stringify(allowedScopes), JSON.stringify(grantTypes), platform ) return { client: { id: 0, client_id: clientId, client_name: clientName, redirect_uris: redirectUris, allowed_scopes: allowedScopes, grant_types: grantTypes, platform, is_active: 1 }, clientSecret } } export function verifyClientSecret(client: OAuthClient, clientSecret: string): boolean { return bcrypt.compareSync(clientSecret, client.client_secret_hash || '') } export function updateClient(clientId: string, data: Partial): OAuthClient | undefined { const updates: string[] = [] const params: any[] = [] if (data.client_name !== undefined) { updates.push('client_name = ?') params.push(data.client_name) } if (data.redirect_uris !== undefined) { updates.push('redirect_uris = ?') params.push(JSON.stringify(data.redirect_uris)) } if (data.allowed_scopes !== undefined) { updates.push('allowed_scopes = ?') params.push(JSON.stringify(data.allowed_scopes)) } if (data.grant_types !== undefined) { updates.push('grant_types = ?') params.push(JSON.stringify(data.grant_types)) } if (data.platform !== undefined) { updates.push('platform = ?') params.push(data.platform) } if (data.is_active !== undefined) { updates.push('is_active = ?') params.push(data.is_active) } if (updates.length === 0) return getClientById(clientId) updates.push('updated_at = CURRENT_TIMESTAMP') params.push(clientId) const stmt = db.prepare(`UPDATE oauth_clients SET ${updates.join(', ')} WHERE client_id = ?`) stmt.run(...params) return getClientById(clientId) } export function deleteClient(clientId: string): boolean { const stmt = db.prepare('DELETE FROM oauth_clients WHERE client_id = ?') const result = stmt.run(clientId) return result.changes > 0 } export function createToken( userId: number, clientId: string, scope?: string ): { accessToken: string; refreshToken: string; expiresAt: Date } { const accessToken = uuidv4().replace(/-/g, '') const refreshToken = uuidv4().replace(/-/g, '') const expiresAt = new Date(Date.now() + ACCESS_TOKEN_EXPIRY) const stmt = db.prepare(` INSERT INTO oauth_tokens (user_id, client_id, access_token, refresh_token, scope, expires_at) VALUES (?, ?, ?, ?, ?, ?) `) stmt.run(userId, clientId, accessToken, refreshToken, scope || null, expiresAt.toISOString()) return { accessToken, refreshToken, expiresAt } } export function createTokenWithoutRefresh( userId: number, clientId: string, scope?: string ): { accessToken: string; expiresAt: Date } { const accessToken = uuidv4().replace(/-/g, '') const expiresAt = new Date(Date.now() + ACCESS_TOKEN_EXPIRY) const stmt = db.prepare(` INSERT INTO oauth_tokens (user_id, client_id, access_token, scope, expires_at) VALUES (?, ?, ?, ?, ?) `) stmt.run(userId, clientId, accessToken, scope || null, expiresAt.toISOString()) return { accessToken, expiresAt } } export function createClientCredentialsToken( clientId: string, scope?: string ): { accessToken: string; expiresAt: Date } { const accessToken = uuidv4().replace(/-/g, '') const expiresAt = new Date(Date.now() + ACCESS_TOKEN_EXPIRY) const stmt = db.prepare(` INSERT INTO oauth_tokens (client_id, access_token, scope, expires_at) VALUES (?, ?, ?, ?) `) stmt.run(clientId, accessToken, scope || null, expiresAt.toISOString()) return { accessToken, expiresAt } } export function getTokenByAccessToken(accessToken: string): OAuthToken | undefined { const stmt = db.prepare('SELECT * FROM oauth_tokens WHERE access_token = ? AND revoked = 0') const row = stmt.get(accessToken) as any if (!row) return undefined return { ...row, refresh_token: row.refresh_token || undefined, scope: row.scope || undefined } } export function getTokenByRefreshToken(refreshToken: string): OAuthToken | undefined { const stmt = db.prepare('SELECT * FROM oauth_tokens WHERE refresh_token = ? AND revoked = 0') const row = stmt.get(refreshToken) as any if (!row) return undefined return { ...row, scope: row.scope || undefined } } export function refreshAccessToken(refreshToken: string): { accessToken: string; refreshToken: string; expiresAt: Date } | null { const oldToken = getTokenByRefreshToken(refreshToken) if (!oldToken) return null revokeToken(refreshToken, 'refresh') return createToken(oldToken.user_id!, oldToken.client_id, oldToken.scope || undefined) } export function revokeToken(token: string, type: 'access' | 'refresh' = 'access'): boolean { const column = type === 'access' ? 'access_token' : 'refresh_token' const stmt = db.prepare(`UPDATE oauth_tokens SET revoked = 1 WHERE ${column} = ?`) const result = stmt.run(token) return result.changes > 0 } export function revokeAllUserTokens(userId: number): number { const stmt = db.prepare('UPDATE oauth_tokens SET revoked = 1 WHERE user_id = ?') const result = stmt.run(userId) return result.changes } export function createAuthorizationCode( userId: number, clientId: string, redirectUri: string, scope?: string, codeChallenge?: string, codeChallengeMethod?: string ): string { const code = uuidv4().replace(/-/g, '') const expiresAt = new Date(Date.now() + CODE_EXPIRY) const stmt = db.prepare(` INSERT INTO oauth_codes (code, user_id, client_id, redirect_uri, scope, code_challenge, code_challenge_method, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `) stmt.run(code, userId, clientId, redirectUri, scope || null, codeChallenge || null, codeChallengeMethod || null, expiresAt.toISOString()) return code } export function getAuthorizationCode(code: string): OAuthCode | undefined { const stmt = db.prepare('SELECT * FROM oauth_codes WHERE code = ? AND used = 0 AND expires_at > datetime("now")') const row = stmt.get(code) as any if (!row) return undefined return { ...row, scope: row.scope || undefined, code_challenge: row.code_challenge || undefined, code_challenge_method: row.code_challenge_method || undefined } } export function useAuthorizationCode(code: string): boolean { const stmt = db.prepare('UPDATE oauth_codes SET used = 1 WHERE code = ?') const result = stmt.run(code) return result.changes > 0 } export function validateRedirectUri(client: OAuthClient, redirectUri: string): boolean { return client.redirect_uris.includes(redirectUri) } export function validateScope(client: OAuthClient, scope: string): boolean { if (!scope) return true const requestedScopes = scope.split(' ') return requestedScopes.every(s => client.allowed_scopes.includes(s)) }