This commit is contained in:
parent
2f342b31f7
commit
83a402db8b
102
lib/AuthHandler.ts
Normal file
102
lib/AuthHandler.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { IncomingMessage, ServerResponse } from 'http';
|
||||||
|
import auth from './Authority';
|
||||||
|
|
||||||
|
interface AccessSettings {
|
||||||
|
access_token_expires_in?: number
|
||||||
|
include_refresh_token?: boolean
|
||||||
|
refresh_token_expires_in?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthRequest {
|
||||||
|
public request: IncomingMessage;
|
||||||
|
public response: ServerResponse;
|
||||||
|
|
||||||
|
public constructor (req: IncomingMessage, res: ServerResponse) {
|
||||||
|
this.request = req;
|
||||||
|
this.response = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private default_header () {
|
||||||
|
this.response.setHeader ('Cache-Control', 'no-store');
|
||||||
|
this.response.setHeader ('Pragma', 'no-cache');
|
||||||
|
this.response.setHeader ('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
public allow_access ({
|
||||||
|
access_token_expires_in,
|
||||||
|
include_refresh_token,
|
||||||
|
refresh_token_expires_in
|
||||||
|
}: AccessSettings): void {
|
||||||
|
this.default_header ();
|
||||||
|
this.response.writeHead (200);
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
token_type: 'bearer',
|
||||||
|
access_token: auth.sign ('access_token', access_token_expires_in),
|
||||||
|
expires_in: access_token_expires_in,
|
||||||
|
scope
|
||||||
|
};
|
||||||
|
|
||||||
|
if (include_refresh_token) {
|
||||||
|
res.refresh_token = auth.sign ('refresh_token', refresh_token_expires_in);
|
||||||
|
res.refresh_token_expires_in = refresh_token_expires_in;
|
||||||
|
}
|
||||||
|
this.response.end (JSON.stringify (res));
|
||||||
|
}
|
||||||
|
|
||||||
|
public invalid (error_description) {
|
||||||
|
this.default_header ();
|
||||||
|
this.response.writeHead (400);
|
||||||
|
this.response.end (JSON.stringify ({
|
||||||
|
error: 'invalid_request',
|
||||||
|
error_description
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public deny () {
|
||||||
|
this.default_header ();
|
||||||
|
this.response.writeHead (401);
|
||||||
|
this.response.end (JSON.stringify ({ error: 'invalid_client' }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthRequestHandler = (req: AuthRequest) => void|Promise<void>;
|
||||||
|
|
||||||
|
interface CreateHandlerOptions {
|
||||||
|
refresh?: AccessSettings;
|
||||||
|
modules?: Record<string, AuthRequestHandler>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function create_auth_handler (
|
||||||
|
default_handler: AuthRequestHandler,
|
||||||
|
{ refresh, modules }: CreateHandlerOptions
|
||||||
|
) {
|
||||||
|
return function process_request (
|
||||||
|
req: IncomingMessage,
|
||||||
|
res: ServerResponse
|
||||||
|
): Promise<void>|void {
|
||||||
|
const request = new AuthRequest (req, res);
|
||||||
|
const token = (/Bearer (?<token>.+)/ui).exec (req.headers.authorization);
|
||||||
|
if (token === null)
|
||||||
|
return default_handler (request);
|
||||||
|
|
||||||
|
const token_data = auth.verify (token.groups.token);
|
||||||
|
|
||||||
|
if (!token_data.valid) {
|
||||||
|
request.deny ();
|
||||||
|
return Promise.resolve ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token_data.type === 'refresh_token') {
|
||||||
|
request.allow_access (refresh);
|
||||||
|
return Promise.resolve ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token_data.type === 'part_token' && Object.keys (modules)
|
||||||
|
.includes (token_data.next_module))
|
||||||
|
return modules[token_data.next_module] (request);
|
||||||
|
|
||||||
|
request.invalid ('invalid bearer token');
|
||||||
|
return Promise.resolve ();
|
||||||
|
};
|
||||||
|
}
|
75
test/spec/AuthHandler.ts
Normal file
75
test/spec/AuthHandler.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import http from 'http';
|
||||||
|
import auth from '../../lib/Authority';
|
||||||
|
import { get } from '../Helper';
|
||||||
|
|
||||||
|
const expires_seconds = 600;
|
||||||
|
const refresh_expires_seconds = 3600;
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-lines-per-function
|
||||||
|
xdescribe ('auth handler', () => {
|
||||||
|
let server: http.Server|null = null;
|
||||||
|
beforeAll (() => {
|
||||||
|
server = http.createServer ((req, res) => {
|
||||||
|
res.writeHead (404);
|
||||||
|
res.end ();
|
||||||
|
});
|
||||||
|
server.listen (3000);
|
||||||
|
|
||||||
|
jasmine.clock ()
|
||||||
|
.install ();
|
||||||
|
jasmine.clock ()
|
||||||
|
.mockDate (new Date);
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('should return a valid access and refresh token', async () => {
|
||||||
|
const resp = await get ({ authorization: 'Basic foo:bar' });
|
||||||
|
expect (resp.statusCode)
|
||||||
|
.toEqual (200);
|
||||||
|
const data = JSON.parse (resp.body as string);
|
||||||
|
const at = data.access_token;
|
||||||
|
const rt = data.refresh_token;
|
||||||
|
expect (resp.headers['set-cookie'])
|
||||||
|
.toContain (`cookie_jar=${at}`);
|
||||||
|
expect (resp.headers['cache-control'])
|
||||||
|
.toEqual ('no-store');
|
||||||
|
expect (resp.headers.pragma)
|
||||||
|
.toEqual ('no-cache');
|
||||||
|
expect (data.token_type)
|
||||||
|
.toEqual ('bearer');
|
||||||
|
expect (data.expires_in)
|
||||||
|
.toEqual (expires_seconds);
|
||||||
|
expect (data.refresh_expires_in)
|
||||||
|
.toEqual (refresh_expires_seconds);
|
||||||
|
|
||||||
|
expect (at as string)
|
||||||
|
.toMatch (/^[0-9a-z-._~+/]+$/ui);
|
||||||
|
expect (rt as string)
|
||||||
|
.toMatch (/^[0-9a-z-._~+/]+$/ui);
|
||||||
|
|
||||||
|
const atv = auth.verify (at as string);
|
||||||
|
expect (atv.valid)
|
||||||
|
.toEqual (true);
|
||||||
|
expect (atv.authorized)
|
||||||
|
.toEqual (true);
|
||||||
|
expect (atv.type)
|
||||||
|
.toEqual ('access_token');
|
||||||
|
|
||||||
|
const rtv = auth.verify (rt as string);
|
||||||
|
expect (rtv.valid)
|
||||||
|
.toEqual (true);
|
||||||
|
expect (rtv.authorized)
|
||||||
|
.toEqual (false);
|
||||||
|
expect (rtv.type)
|
||||||
|
.toEqual ('refresh_token');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll (() => {
|
||||||
|
if (server === null)
|
||||||
|
throw new Error ('server is null');
|
||||||
|
server.close ();
|
||||||
|
jasmine.clock ()
|
||||||
|
.tick (24 * 60 * 60 * 1000);
|
||||||
|
jasmine.clock ()
|
||||||
|
.uninstall ();
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user