import { client } from '$lib/redis.js'; import { getSqlite3 } from '$lib/sqlite3/database.js'; /** * Authenticate a client using client_secret_basic or client_secret_post. */ async function authenticateClient(request, body) { const auth = request.headers.get('authorization'); let clientId, clientSecret; if (auth && auth.startsWith('Basic ')) { const decoded = Buffer.from(auth.slice(6), 'base64').toString(); const idx = decoded.indexOf(':'); clientId = decoded.slice(0, idx); clientSecret = decoded.slice(idx + 1); } else { clientId = body.client_id; clientSecret = body.client_secret; } if (!clientId) return null; const db = await getSqlite3(); const row = db.prepare('SELECT * FROM clients WHERE client_id = ?').get(clientId); if (!row) return null; // Public clients (token_endpoint_auth_method = "none") don't require a secret if (row.token_endpoint_auth_method !== 'none' && row.client_secret !== clientSecret) return null; return row; } export async function POST({ request }) { let body; try { body = Object.fromEntries(await request.formData()); } catch { return new Response( JSON.stringify({ error: 'invalid_request', error_description: 'Invalid form body' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); } const clientRow = await authenticateClient(request, body); if (!clientRow) { return new Response( JSON.stringify({ error: 'invalid_client', error_description: 'Client authentication failed' }), { status: 401, headers: { 'Content-Type': 'application/json', 'WWW-Authenticate': 'Basic' } } ); } const token = body.token; if (!token) { return new Response( JSON.stringify({ error: 'invalid_request', error_description: 'token is required' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); } let value = await client.get(`access_token:${token}`); let tokenType = 'access_token'; if (!value) { value = await client.get(`refresh_token:${token}`); tokenType = 'refresh_token'; } if (!value) { return new Response( JSON.stringify({ active: false }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } let data; try { data = JSON.parse(value); } catch { return new Response( JSON.stringify({ active: false }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } const now = Math.floor(Date.now() / 1000); let exp; if (data.expires_at) { exp = Math.floor(new Date(data.expires_at).getTime() / 1000); if (now > exp) { return new Response( JSON.stringify({ active: false }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } } else { const ttl = await client.ttl(`${tokenType}:${token}`); if (ttl === -2) { return new Response( JSON.stringify({ active: false }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } if (ttl > 0) { exp = now + ttl; } } const response = { active: true, client_id: data.client_id, scope: data.scope, token_type: tokenType }; if (typeof exp === 'number') { response.exp = exp; } return new Response( JSON.stringify(response), { status: 200, headers: { 'Content-Type': 'application/json' } } ); }