+server.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import { client } from '$lib/redis.js';
  2. import { getSqlite3 } from '$lib/sqlite3/database.js';
  3. /**
  4. * Authenticate a client using client_secret_basic or client_secret_post.
  5. */
  6. async function authenticateClient(request, body) {
  7. const auth = request.headers.get('authorization');
  8. let clientId, clientSecret;
  9. if (auth && auth.startsWith('Basic ')) {
  10. const decoded = Buffer.from(auth.slice(6), 'base64').toString();
  11. const idx = decoded.indexOf(':');
  12. clientId = decoded.slice(0, idx);
  13. clientSecret = decoded.slice(idx + 1);
  14. } else {
  15. clientId = body.client_id;
  16. clientSecret = body.client_secret;
  17. }
  18. if (!clientId) return null;
  19. const db = await getSqlite3();
  20. const row = db.prepare('SELECT * FROM clients WHERE client_id = ?').get(clientId);
  21. if (!row) return null;
  22. // Public clients (token_endpoint_auth_method = "none") don't require a secret
  23. if (row.token_endpoint_auth_method !== 'none' && row.client_secret !== clientSecret) return null;
  24. return row;
  25. }
  26. export async function POST({ request }) {
  27. let body;
  28. try {
  29. body = Object.fromEntries(await request.formData());
  30. } catch {
  31. return new Response(
  32. JSON.stringify({ error: 'invalid_request', error_description: 'Invalid form body' }),
  33. { status: 400, headers: { 'Content-Type': 'application/json' } }
  34. );
  35. }
  36. const clientRow = await authenticateClient(request, body);
  37. if (!clientRow) {
  38. return new Response(
  39. JSON.stringify({ error: 'invalid_client', error_description: 'Client authentication failed' }),
  40. { status: 401, headers: { 'Content-Type': 'application/json', 'WWW-Authenticate': 'Basic' } }
  41. );
  42. }
  43. const token = body.token;
  44. if (!token) {
  45. return new Response(
  46. JSON.stringify({ error: 'invalid_request', error_description: 'token is required' }),
  47. { status: 400, headers: { 'Content-Type': 'application/json' } }
  48. );
  49. }
  50. let value = await client.get(`access_token:${token}`);
  51. let tokenType = 'access_token';
  52. if (!value) {
  53. value = await client.get(`refresh_token:${token}`);
  54. tokenType = 'refresh_token';
  55. }
  56. if (!value) {
  57. return new Response(
  58. JSON.stringify({ active: false }),
  59. { status: 200, headers: { 'Content-Type': 'application/json' } }
  60. );
  61. }
  62. let data;
  63. try {
  64. data = JSON.parse(value);
  65. } catch {
  66. return new Response(
  67. JSON.stringify({ active: false }),
  68. { status: 200, headers: { 'Content-Type': 'application/json' } }
  69. );
  70. }
  71. const now = Math.floor(Date.now() / 1000);
  72. let exp;
  73. if (data.expires_at) {
  74. exp = Math.floor(new Date(data.expires_at).getTime() / 1000);
  75. if (now > exp) {
  76. return new Response(
  77. JSON.stringify({ active: false }),
  78. { status: 200, headers: { 'Content-Type': 'application/json' } }
  79. );
  80. }
  81. } else {
  82. const ttl = await client.ttl(`${tokenType}:${token}`);
  83. if (ttl === -2) {
  84. return new Response(
  85. JSON.stringify({ active: false }),
  86. { status: 200, headers: { 'Content-Type': 'application/json' } }
  87. );
  88. }
  89. if (ttl > 0) {
  90. exp = now + ttl;
  91. }
  92. }
  93. const response = {
  94. active: true,
  95. client_id: data.client_id,
  96. scope: data.scope,
  97. token_type: tokenType
  98. };
  99. if (typeof exp === 'number') {
  100. response.exp = exp;
  101. }
  102. return new Response(
  103. JSON.stringify(response),
  104. { status: 200, headers: { 'Content-Type': 'application/json' } }
  105. );
  106. }