import http, { IncomingMessage, ServerResponse } from 'http'; import { to_b64 } from '@sapphirecode/encoding-helper'; import auth from '../../lib/Authority'; import { get, modify_signature, Response } from '../Helper'; import create_auth_handler from '../../lib/AuthHandler'; const expires_seconds = 600; const refresh_expires_seconds = 3600; const part_expires_seconds = 60; interface CheckHeaderResult { at: string; rt?: string; data: Record; } function check_headers (resp: Response): CheckHeaderResult { const data = JSON.parse (resp.body as string); const at = data.access_token; const rt = data.refresh_token; expect (resp.headers['cache-control']) .toEqual ('no-store'); expect (resp.headers.pragma) .toEqual ('no-cache'); return { data, at, rt }; } function check_token (token: string, type: string):void { const v = auth.verify (token); expect (v.valid) .toEqual (true); expect (v.authorized) .toEqual (type === 'access_token'); expect (v.type) .toEqual (type); expect (token) .toMatch (/^[0-9a-z-._~+/]+$/ui); } // eslint-disable-next-line max-lines-per-function describe ('auth handler', () => { let server: http.Server|null = null; beforeAll (() => { const ah = create_auth_handler ((req) => { if (!req.is_basic) { req.invalid ('unknown autorization type'); } else if (req.user !== 'foo' || req.password !== 'bar') { req.deny (); } else { req.allow_access ({ access_token_expires_in: expires_seconds, include_refresh_token: true, refresh_token_expires_in: refresh_expires_seconds }); } }, { cookie_name: 'cookie_jar', refresh: { access_token_expires_in: expires_seconds, refresh_token_expires_in: refresh_expires_seconds, include_refresh_token: true } }); server = http.createServer ((req: IncomingMessage, res: ServerResponse) => { ah (req, res); }); server.listen (3000); jasmine.clock () .install (); jasmine.clock () .mockDate (new Date); }); it ('auth test sequence', async () => { // get initial access and refresh tokens const resp1 = await get ({ authorization: 'Basic foo:bar' }); expect (resp1.statusCode) .toEqual (200); const res1 = check_headers (resp1); expect (res1.data.token_type) .toEqual ('bearer'); expect (resp1.headers['set-cookie']) .toContain (`cookie_jar=${res1.at}`); check_token (res1.at as string, 'access_token'); expect (res1.data.expires_in) .toEqual (expires_seconds); check_token (res1.rt as string, 'refresh_token'); expect (res1.data.refresh_expires_in) .toEqual (refresh_expires_seconds); // get refresh token const resp2 = await get ({ authorization: `Bearer ${res1.rt}` }); expect (resp2.statusCode) .toEqual (200); const res2 = check_headers (resp2); expect (res2.data.token_type) .toEqual ('bearer'); expect (resp2.headers['set-cookie']) .toContain (`cookie_jar=${res2.at}`); check_token (res2.at as string, 'access_token'); expect (res2.data.expires_in) .toEqual (expires_seconds); expect (res2.at).not.toEqual (res1.at); check_token (res2.rt as string, 'refresh_token'); expect (res2.data.refresh_expires_in) .toEqual (refresh_expires_seconds); expect (res2.rt).not.toEqual (res1.rt); }); it ('should return the correct denial message', async () => { const resp = await get ({ authorization: 'Basic bar:baz' }); expect (resp.statusCode) .toEqual (401); const res = check_headers (resp); expect (res.data) .toEqual ({ error: 'invalid_client' }); }); it ('should allow base64 login', async () => { // get initial access and refresh tokens const resp1 = await get ({ authorization: `Basic ${to_b64 ('foo:bar')}` }); expect (resp1.statusCode) .toEqual (200); const res1 = check_headers (resp1); expect (res1.data.token_type) .toEqual ('bearer'); expect (resp1.headers['set-cookie']) .toContain (`cookie_jar=${res1.at}`); check_token (res1.at as string, 'access_token'); expect (res1.data.expires_in) .toEqual (expires_seconds); check_token (res1.rt as string, 'refresh_token'); expect (res1.data.refresh_expires_in) .toEqual (refresh_expires_seconds); }); it ('should reject invalid requests', async () => { const resp1 = await get (); expect (resp1.statusCode) .toEqual (401); const res1 = check_headers (resp1); expect (res1.data) .toEqual ({ error: 'invalid_client' }); const resp2a = await get ({ authorization: 'Basic foo:bar' }); const res2a = check_headers (resp2a); const resp2b = await get ( { authorization: `Bearer ${res2a.at}` } ); expect (resp2b.statusCode) .toEqual (400); const res2 = check_headers (resp2b); expect (res2.data) .toEqual ({ error: 'invalid_request', error_description: 'invalid bearer type' }); }); it ('should reject an invalid token', async () => { const resp1 = await get ({ authorization: 'Basic foo:bar' }); const res1 = check_headers (resp1); const resp2 = await get ( { authorization: `Bearer ${modify_signature (res1.at)}` } ); expect (resp2.statusCode) .toEqual (401); const res2 = check_headers (resp2); expect (res2.data) .toEqual ({ error: 'invalid_client' }); }); xit ('should process part token', async () => { const resp1 = await get ({ authorization: 'Basic part:bar' }); expect (resp1.statusCode) .toEqual (200); const res1 = check_headers (resp1); expect (res1.data.token_type) .toEqual ('bearer'); expect (res1.data.expires_in) .toEqual (part_expires_seconds); check_token (res1.data.part_token as string, 'part_token'); const resp2 = await get ( { authorization: `Bearer ${res1.data.part_token}` } ); expect (resp2.statusCode) .toEqual (200); const res2 = check_headers (resp2); expect (res2.data.token_type) .toEqual ('bearer'); expect (resp2.headers['set-cookie']) .toContain (`cookie_jar=${res2.at}`); check_token (res2.at as string, 'access_token'); expect (res2.data.expires_in) .toEqual (expires_seconds); expect (res2.at).not.toEqual (res1.at); check_token (res2.rt as string, 'refresh_token'); expect (res2.data.refresh_expires_in) .toEqual (refresh_expires_seconds); expect (res2.rt).not.toEqual (res1.rt); }); afterAll (() => { if (server === null) throw new Error ('server is null'); server.close (); jasmine.clock () .tick (24 * 60 * 60 * 1000); jasmine.clock () .uninstall (); }); });