Compare commits
2 Commits
4c27d0eace
...
80d04f7441
Author | SHA1 | Date | |
---|---|---|---|
80d04f7441 | |||
f39759bad9 |
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 2.0.0
|
||||
|
||||
Complete redesign
|
||||
|
||||
## 1.1.0
|
||||
|
||||
add user_id to res.connection, so request handlers can access the current user
|
||||
|
||||
## 1.0.0
|
||||
|
||||
initial release
|
@ -1,8 +1,6 @@
|
||||
# auth-server-helper
|
||||
|
||||
version: 0.0.0
|
||||
|
||||
undefined
|
||||
version: 2.0.0
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of Auth-Server-Helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, January 2021
|
||||
*/
|
||||
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
import { to_utf8 } from '@sapphirecode/encoding-helper';
|
||||
import auth from './Authority';
|
||||
@ -6,6 +13,7 @@ interface AccessSettings {
|
||||
access_token_expires_in: number
|
||||
include_refresh_token?: boolean
|
||||
refresh_token_expires_in?: number
|
||||
data?: Record<string, unknown>
|
||||
}
|
||||
|
||||
interface AccessResult {
|
||||
@ -29,16 +37,19 @@ class AuthRequest {
|
||||
public is_basic: boolean;
|
||||
public user: string;
|
||||
public password: string;
|
||||
public body: string;
|
||||
|
||||
private _cookie_name?: string;
|
||||
|
||||
public constructor (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
body: string,
|
||||
cookie?: string
|
||||
) {
|
||||
this.request = req;
|
||||
this.response = res;
|
||||
this.body = body;
|
||||
this.is_basic = false;
|
||||
this.user = '';
|
||||
this.password = '';
|
||||
@ -54,11 +65,12 @@ class AuthRequest {
|
||||
public allow_access ({
|
||||
access_token_expires_in,
|
||||
include_refresh_token,
|
||||
refresh_token_expires_in
|
||||
refresh_token_expires_in,
|
||||
data
|
||||
}: AccessSettings): AccessResult {
|
||||
this.default_header ();
|
||||
|
||||
const at = auth.sign ('access_token', access_token_expires_in);
|
||||
const at = auth.sign ('access_token', access_token_expires_in, { data });
|
||||
const result: AccessResult = { access_token_id: at.id };
|
||||
|
||||
const res: AccessResponse = {
|
||||
@ -77,7 +89,11 @@ class AuthRequest {
|
||||
if (include_refresh_token) {
|
||||
if (typeof refresh_token_expires_in !== 'number')
|
||||
throw new Error ('no expiry time defined for refresh tokens');
|
||||
const rt = auth.sign ('refresh_token', refresh_token_expires_in);
|
||||
const rt = auth.sign (
|
||||
'refresh_token',
|
||||
refresh_token_expires_in,
|
||||
{ data }
|
||||
);
|
||||
res.refresh_token = rt.signature;
|
||||
res.refresh_expires_in = refresh_token_expires_in;
|
||||
result.refresh_token_id = rt.id;
|
||||
@ -88,6 +104,31 @@ class AuthRequest {
|
||||
return result;
|
||||
}
|
||||
|
||||
public allow_part (
|
||||
part_token_expires_in: number,
|
||||
next_module: string,
|
||||
data?: Record<string, unknown>
|
||||
): string {
|
||||
this.default_header ();
|
||||
|
||||
const pt = auth.sign (
|
||||
'part_token',
|
||||
part_token_expires_in,
|
||||
{ next_module, data }
|
||||
);
|
||||
|
||||
const res = {
|
||||
token_type: 'bearer',
|
||||
part_token: pt.signature,
|
||||
expires_in: part_token_expires_in
|
||||
};
|
||||
|
||||
this.response.writeHead (200);
|
||||
this.response.end (JSON.stringify (res));
|
||||
|
||||
return pt.id;
|
||||
}
|
||||
|
||||
public invalid (error_description?: string) {
|
||||
this.default_header ();
|
||||
this.response.writeHead (400);
|
||||
@ -117,11 +158,22 @@ export default function create_auth_handler (
|
||||
default_handler: AuthRequestHandler,
|
||||
options?: CreateHandlerOptions
|
||||
) {
|
||||
return function process_request (
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
return async function process_request (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse
|
||||
): Promise<void>|void {
|
||||
const request = new AuthRequest (req, res, options?.cookie_name);
|
||||
): Promise<void> {
|
||||
const body: string = await new Promise ((resolve) => {
|
||||
let data = '';
|
||||
req.on ('data', (c) => {
|
||||
data += c;
|
||||
});
|
||||
req.on ('end', () => {
|
||||
resolve (data);
|
||||
});
|
||||
});
|
||||
|
||||
const request = new AuthRequest (req, res, body, options?.cookie_name);
|
||||
const token = (/(?<type>\S+) (?<token>.+)/ui)
|
||||
.exec (req.headers.authorization as string);
|
||||
|
||||
|
@ -21,6 +21,7 @@ interface VerificationResult {
|
||||
valid: boolean;
|
||||
type: TokenType;
|
||||
next_module?: string;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface SignatureResult {
|
||||
@ -28,6 +29,12 @@ interface SignatureResult {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface SignatureOptions
|
||||
{
|
||||
data?: Record<string, unknown>
|
||||
next_module?: string
|
||||
}
|
||||
|
||||
class Authority {
|
||||
public verify (key: string): VerificationResult {
|
||||
const result: VerificationResult = {
|
||||
@ -58,7 +65,8 @@ class Authority {
|
||||
|
||||
result.valid = true;
|
||||
result.authorized = result.type === 'access_token';
|
||||
result.next_module = data.obj;
|
||||
result.next_module = data.next_module;
|
||||
result.data = data.obj;
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -66,17 +74,18 @@ class Authority {
|
||||
public sign (
|
||||
type: TokenType,
|
||||
valid_for: number,
|
||||
next_module?: string
|
||||
options?: SignatureOptions
|
||||
): SignatureResult {
|
||||
const time = Date.now ();
|
||||
const key = keystore.get_key (time / 1000, valid_for);
|
||||
const attributes = {
|
||||
id: create_salt (),
|
||||
iat: time,
|
||||
id: create_salt (),
|
||||
iat: time,
|
||||
type,
|
||||
valid_for
|
||||
valid_for,
|
||||
next_module: options?.next_module
|
||||
};
|
||||
const signature = sign_object (next_module, key, attributes);
|
||||
const signature = sign_object (options?.data, key, attributes);
|
||||
return { id: attributes.id, signature };
|
||||
}
|
||||
}
|
||||
|
@ -46,4 +46,4 @@
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of Auth-Server-Helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, January 2021
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
|
||||
export class Response extends http.IncomingMessage {
|
||||
@ -5,19 +12,27 @@ export class Response extends http.IncomingMessage {
|
||||
}
|
||||
|
||||
export function get (
|
||||
headers: http.OutgoingHttpHeaders = {}
|
||||
// eslint-disable-next-line default-param-last
|
||||
headers: http.OutgoingHttpHeaders = {},
|
||||
body?: string
|
||||
): Promise<Response> {
|
||||
return new Promise ((resolve) => {
|
||||
http.get ('http://localhost:3000', { headers }, (res: Response) => {
|
||||
let body = '';
|
||||
const req = http.request ('http://localhost:3000', {
|
||||
headers,
|
||||
method: typeof body === 'string' ? 'POST' : 'GET'
|
||||
}, (res: Response) => {
|
||||
let data = '';
|
||||
res.on ('data', (d) => {
|
||||
body += d;
|
||||
data += d;
|
||||
});
|
||||
res.on ('end', () => {
|
||||
res.body = body;
|
||||
res.body = data;
|
||||
resolve (res);
|
||||
});
|
||||
});
|
||||
if (typeof body === 'string')
|
||||
req.write (body);
|
||||
req.end ();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of Auth-Server-Helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, January 2021
|
||||
*/
|
||||
|
||||
import http, { IncomingMessage, ServerResponse } from 'http';
|
||||
import { to_b64 } from '@sapphirecode/encoding-helper';
|
||||
import auth from '../../lib/Authority';
|
||||
@ -46,22 +53,37 @@ describe ('auth handler', () => {
|
||||
if (!req.is_basic) {
|
||||
req.invalid ('unknown autorization type');
|
||||
}
|
||||
else if (req.user !== 'foo' || req.password !== 'bar') {
|
||||
req.deny ();
|
||||
}
|
||||
else {
|
||||
else if (req.user === 'foo' && req.password === 'bar') {
|
||||
req.allow_access ({
|
||||
access_token_expires_in: expires_seconds,
|
||||
include_refresh_token: true,
|
||||
refresh_token_expires_in: refresh_expires_seconds
|
||||
});
|
||||
}
|
||||
else if (req.user === 'part' && req.password === 'bar') {
|
||||
req.allow_part (part_expires_seconds, 'two_factor');
|
||||
}
|
||||
else {
|
||||
req.deny ();
|
||||
}
|
||||
}, {
|
||||
cookie_name: 'cookie_jar',
|
||||
refresh: {
|
||||
access_token_expires_in: expires_seconds,
|
||||
refresh_token_expires_in: refresh_expires_seconds,
|
||||
include_refresh_token: true
|
||||
},
|
||||
modules: {
|
||||
two_factor (request) {
|
||||
if (request.body === 'letmein') {
|
||||
request.allow_access ({
|
||||
access_token_expires_in: expires_seconds,
|
||||
include_refresh_token: true,
|
||||
refresh_token_expires_in: refresh_expires_seconds
|
||||
});
|
||||
}
|
||||
else { request.deny (); }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -183,7 +205,7 @@ describe ('auth handler', () => {
|
||||
});
|
||||
|
||||
|
||||
xit ('should process part token', async () => {
|
||||
it ('should process part token', async () => {
|
||||
const resp1 = await get ({ authorization: 'Basic part:bar' });
|
||||
expect (resp1.statusCode)
|
||||
.toEqual (200);
|
||||
@ -195,7 +217,8 @@ describe ('auth handler', () => {
|
||||
check_token (res1.data.part_token as string, 'part_token');
|
||||
|
||||
const resp2 = await get (
|
||||
{ authorization: `Bearer ${res1.data.part_token}` }
|
||||
{ authorization: `Bearer ${res1.data.part_token}` },
|
||||
'letmein'
|
||||
);
|
||||
expect (resp2.statusCode)
|
||||
.toEqual (200);
|
||||
|
@ -56,7 +56,7 @@ describe ('authority', () => {
|
||||
});
|
||||
|
||||
it ('should create a part token', () => {
|
||||
const token = auth.sign ('part_token', 60, '2fa');
|
||||
const token = auth.sign ('part_token', 60, { next_module: '2fa' });
|
||||
jasmine.clock ()
|
||||
.tick (30000);
|
||||
const res = auth.verify (token.signature);
|
||||
|
@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of Auth-Server-Helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, January 2021
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
import gateway from '../../lib/Gateway';
|
||||
import authority from '../../lib/Authority';
|
||||
|
Loading…
x
Reference in New Issue
Block a user