From 84be087743c4c3cb6f0eefb17badf4f1ccc92542 Mon Sep 17 00:00:00 2001 From: Timo Hocker Date: Mon, 2 May 2022 13:30:09 +0200 Subject: [PATCH] logout function --- CHANGELOG.md | 4 ++++ README.md | 18 +++++++++++++++++- lib/Gateway.ts | 22 ++++++++++++++++++++++ package.json | 2 +- test/Helper.ts | 5 +++-- test/spec/Gateway.ts | 29 ++++++++++++++++++++++++++--- 6 files changed, 73 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c385171..b5f5e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 3.2.0 + +- Logout function + ## 3.1.0 - Option to enable body parsing diff --git a/README.md b/README.md index 77682fa..2fb2274 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # auth-server-helper -version: 3.1.x +version: 3.2.x customizable and simple authentication @@ -168,6 +168,22 @@ const {blacklist} = require('@sapphirecode/auth-server-helper'); blacklist.add_signature(token_id); // the token id is returned from any function that creates tokens ``` +#### Logout function + +```js +const {logout} = require('@sapphirecode/auth-server-helper'); + +// create a new express route +app.get('logout',(req,res)=>{ + // call the gateway's logout function + gateway.logout(req); + + // respond ok + res.status(200); + res.end(); +}) +``` + ### Exporting and importing public keys to validate tokens across server instances ```js diff --git a/lib/Gateway.ts b/lib/Gateway.ts index 032fd6d..1deeded 100644 --- a/lib/Gateway.ts +++ b/lib/Gateway.ts @@ -10,6 +10,7 @@ import authority from './Authority'; import { AuthRequest, AccessSettings } from './AuthHandler'; import { debug } from './debug'; import { extract_cookie, CookieSettings } from './cookie'; +import blacklist from './Blacklist'; const logger = debug ('gateway'); @@ -181,6 +182,27 @@ class GatewayClass { logger ('failed to authenticate, redirecting client'); return this.redirect (res); } + + public logout (req: IncomingMessage): void { + const l = logger.extend ('logout'); + l ('invalidating all submitted tokens'); + const auth_strings = [ + this.get_header_auth (req), + extract_cookie (this._options.cookie?.name, req.headers.cookie), + extract_cookie (this._options.refresh_cookie?.name, req.headers.cookie) + ]; + const tokens = auth_strings + .filter ((v) => v !== null) + .map ((v) => authority.verify (v as string)) + .filter ((v) => v.valid); + + l ('found %d tokens: %O', tokens.length, tokens); + + for (const token of tokens) + blacklist.add_signature (token.id); + + l ('complete'); + } } export default function create_gateway (options: GatewayOptions): Gateway { diff --git a/package.json b/package.json index 040a754..04877b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sapphirecode/auth-server-helper", - "version": "3.1.0", + "version": "3.2.0", "main": "dist/index.js", "author": { "name": "Timo Hocker", diff --git a/test/Helper.ts b/test/Helper.ts index 0bb6481..c4f54ce 100644 --- a/test/Helper.ts +++ b/test/Helper.ts @@ -16,10 +16,11 @@ export class Response extends http.IncomingMessage { export function get ( // eslint-disable-next-line default-param-last headers: http.OutgoingHttpHeaders = {}, - body?: string + body?: string|null, + path = '' ): Promise { return new Promise ((resolve) => { - const req = http.request ('http://localhost:3000', { + const req = http.request (`http://localhost:3000${path}`, { headers, method: typeof body === 'string' ? 'POST' : 'GET' }, (res: Response) => { diff --git a/test/spec/Gateway.ts b/test/spec/Gateway.ts index 8b84679..55f14b8 100644 --- a/test/spec/Gateway.ts +++ b/test/spec/Gateway.ts @@ -6,7 +6,7 @@ */ import http from 'http'; -import { create_gateway } from '../../lib/index'; +import { GatewayClass, create_gateway } from '../../lib/index'; import authority from '../../lib/Authority'; import blacklist from '../../lib/Blacklist'; import { clock_finalize, clock_setup, get } from '../Helper'; @@ -18,7 +18,7 @@ describe ('gateway', () => { beforeAll (() => { clock_setup (); - const g = create_gateway ({ + const g = new GatewayClass ({ redirect_url: 'http://localhost/auth', cookie: { name: 'cookie_jar' }, refresh_cookie: { name: 'mint_cookies' }, @@ -31,11 +31,15 @@ describe ('gateway', () => { server = http.createServer ((req, res) => { const passed_handler = () => { + if (typeof req.url !== 'undefined') { + if (req.url.endsWith ('logout')) + g.logout (req); + } res.writeHead (200); const con = req.connection as unknown as Record; res.end (JSON.stringify (con.auth)); }; - g (req, res, passed_handler); + g.process_request (req, res, passed_handler); }); server.listen (3000); }); @@ -169,4 +173,23 @@ describe ('gateway', () => { }) .toThrowError ('access and refresh cookies cannot have the same name'); }); + + it ('should logout all tokens', async () => { + const token = await authority.sign ('access_token', 60); + const refresh = await authority.sign ('refresh_token', 3600); + const resp = await get ( + // eslint-disable-next-line max-len + { cookie: `foo=bar;cookie_jar=${token.signature};asd=efg;mint_cookies=${refresh.signature}` }, + null, + '/logout' + ); + expect (resp.statusCode) + .toEqual (200); + const blacklisted = blacklist.export_blacklist () + .map ((v) => v.hash); + expect (blacklisted) + .toContain (token.id); + expect (blacklisted) + .toContain (refresh.id); + }); });