auth.test.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. import assert from 'node:assert/strict';
  2. import type { Request } from 'express';
  3. import test from 'node:test';
  4. import { ApiError } from '../src/http/api-error.js';
  5. import { ParseSessionAuthenticator } from '../src/modules/saas-platform/auth.js';
  6. const parseConfig = {
  7. appId: 'saas-voc-test-app',
  8. masterKey: 'master-key-that-must-not-be-forwarded',
  9. maintenanceKey: 'maintenance-key-that-must-not-be-forwarded',
  10. serverUrl: 'http://127.0.0.1:4400/parse',
  11. };
  12. function requestWithHeaders(values: Record<string, string>): Request {
  13. const normalized = Object.fromEntries(Object.entries(values).map(([key, value]) => [key.toLowerCase(), value]));
  14. return {
  15. header(name: string) {
  16. return normalized[name.toLowerCase()];
  17. },
  18. } as Request;
  19. }
  20. test('Parse session authentication returns a principal without forwarding privileged keys', async () => {
  21. const sessionToken = 'session-token-only-for-user-auth';
  22. let requestedUrl = '';
  23. let requestedHeaders = new Headers();
  24. const fetchImplementation: typeof fetch = async (input, init) => {
  25. requestedUrl = String(input);
  26. requestedHeaders = new Headers(init?.headers);
  27. return new Response(JSON.stringify({
  28. objectId: 'parse-user-1',
  29. email: 'analyst@example.test',
  30. name: 'VOC Analyst',
  31. }), { status: 200, headers: { 'Content-Type': 'application/json' } });
  32. };
  33. const authenticator = new ParseSessionAuthenticator(parseConfig, fetchImplementation);
  34. const principal = await authenticator.authenticate(requestWithHeaders({ Authorization: `Bearer ${sessionToken}` }));
  35. assert.deepEqual(principal, {
  36. userId: 'parse-user-1',
  37. email: 'analyst@example.test',
  38. displayName: 'VOC Analyst',
  39. authMode: 'parse',
  40. });
  41. assert.equal(requestedUrl, 'http://127.0.0.1:4400/parse/users/me');
  42. assert.ok(!requestedUrl.includes(sessionToken));
  43. assert.equal(requestedHeaders.get('X-Parse-Application-Id'), parseConfig.appId);
  44. assert.equal(requestedHeaders.get('X-Parse-Session-Token'), sessionToken);
  45. assert.equal(requestedHeaders.get('X-Parse-Master-Key'), null);
  46. assert.ok(![...requestedHeaders.values()].includes(parseConfig.masterKey));
  47. assert.ok(![...requestedHeaders.values()].includes(parseConfig.maintenanceKey));
  48. });
  49. test('Parse session authentication rejects missing or invalid sessions and surfaces outages', async () => {
  50. const unauthorized = new ParseSessionAuthenticator(parseConfig, async () => new Response(null, { status: 401 }));
  51. assert.equal(await unauthorized.authenticate(requestWithHeaders({})), null);
  52. assert.equal(await unauthorized.authenticate(requestWithHeaders({ 'X-Parse-Session-Token': 'invalid' })), null);
  53. const unavailable = new ParseSessionAuthenticator(parseConfig, async () => {
  54. throw new Error('connection refused');
  55. });
  56. await assert.rejects(
  57. unavailable.authenticate(requestWithHeaders({ 'X-Parse-Session-Token': 'session' })),
  58. (error: unknown) => error instanceof ApiError
  59. && error.status === 503
  60. && error.code === 'authentication_unavailable',
  61. );
  62. });