Compare commits

..

No commits in common. "80d04f7441a0fd0786c51b07c04d090d55c7e3cf" and "4c27d0eace9ff5764d332246a1c731bc85b5b759" have entirely different histories.

9 changed files with 28 additions and 145 deletions

View File

@ -1,13 +0,0 @@
# 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

View File

@ -1,6 +1,8 @@
# auth-server-helper # auth-server-helper
version: 2.0.0 version: 0.0.0
undefined
## Installation ## Installation

View File

@ -1,10 +1,3 @@
/*
* 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 { IncomingMessage, ServerResponse } from 'http';
import { to_utf8 } from '@sapphirecode/encoding-helper'; import { to_utf8 } from '@sapphirecode/encoding-helper';
import auth from './Authority'; import auth from './Authority';
@ -13,7 +6,6 @@ interface AccessSettings {
access_token_expires_in: number access_token_expires_in: number
include_refresh_token?: boolean include_refresh_token?: boolean
refresh_token_expires_in?: number refresh_token_expires_in?: number
data?: Record<string, unknown>
} }
interface AccessResult { interface AccessResult {
@ -37,19 +29,16 @@ class AuthRequest {
public is_basic: boolean; public is_basic: boolean;
public user: string; public user: string;
public password: string; public password: string;
public body: string;
private _cookie_name?: string; private _cookie_name?: string;
public constructor ( public constructor (
req: IncomingMessage, req: IncomingMessage,
res: ServerResponse, res: ServerResponse,
body: string,
cookie?: string cookie?: string
) { ) {
this.request = req; this.request = req;
this.response = res; this.response = res;
this.body = body;
this.is_basic = false; this.is_basic = false;
this.user = ''; this.user = '';
this.password = ''; this.password = '';
@ -65,12 +54,11 @@ class AuthRequest {
public allow_access ({ public allow_access ({
access_token_expires_in, access_token_expires_in,
include_refresh_token, include_refresh_token,
refresh_token_expires_in, refresh_token_expires_in
data
}: AccessSettings): AccessResult { }: AccessSettings): AccessResult {
this.default_header (); this.default_header ();
const at = auth.sign ('access_token', access_token_expires_in, { data }); const at = auth.sign ('access_token', access_token_expires_in);
const result: AccessResult = { access_token_id: at.id }; const result: AccessResult = { access_token_id: at.id };
const res: AccessResponse = { const res: AccessResponse = {
@ -89,11 +77,7 @@ class AuthRequest {
if (include_refresh_token) { if (include_refresh_token) {
if (typeof refresh_token_expires_in !== 'number') if (typeof refresh_token_expires_in !== 'number')
throw new Error ('no expiry time defined for refresh tokens'); throw new Error ('no expiry time defined for refresh tokens');
const rt = auth.sign ( const rt = auth.sign ('refresh_token', refresh_token_expires_in);
'refresh_token',
refresh_token_expires_in,
{ data }
);
res.refresh_token = rt.signature; res.refresh_token = rt.signature;
res.refresh_expires_in = refresh_token_expires_in; res.refresh_expires_in = refresh_token_expires_in;
result.refresh_token_id = rt.id; result.refresh_token_id = rt.id;
@ -104,31 +88,6 @@ class AuthRequest {
return result; 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) { public invalid (error_description?: string) {
this.default_header (); this.default_header ();
this.response.writeHead (400); this.response.writeHead (400);
@ -158,22 +117,11 @@ export default function create_auth_handler (
default_handler: AuthRequestHandler, default_handler: AuthRequestHandler,
options?: CreateHandlerOptions options?: CreateHandlerOptions
) { ) {
// eslint-disable-next-line max-lines-per-function return function process_request (
return async function process_request (
req: IncomingMessage, req: IncomingMessage,
res: ServerResponse res: ServerResponse
): Promise<void> { ): Promise<void>|void {
const body: string = await new Promise ((resolve) => { const request = new AuthRequest (req, res, options?.cookie_name);
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) const token = (/(?<type>\S+) (?<token>.+)/ui)
.exec (req.headers.authorization as string); .exec (req.headers.authorization as string);

View File

@ -21,7 +21,6 @@ interface VerificationResult {
valid: boolean; valid: boolean;
type: TokenType; type: TokenType;
next_module?: string; next_module?: string;
data?: Record<string, unknown>;
} }
interface SignatureResult { interface SignatureResult {
@ -29,12 +28,6 @@ interface SignatureResult {
id: string; id: string;
} }
interface SignatureOptions
{
data?: Record<string, unknown>
next_module?: string
}
class Authority { class Authority {
public verify (key: string): VerificationResult { public verify (key: string): VerificationResult {
const result: VerificationResult = { const result: VerificationResult = {
@ -65,8 +58,7 @@ class Authority {
result.valid = true; result.valid = true;
result.authorized = result.type === 'access_token'; result.authorized = result.type === 'access_token';
result.next_module = data.next_module; result.next_module = data.obj;
result.data = data.obj;
return result; return result;
} }
@ -74,18 +66,17 @@ class Authority {
public sign ( public sign (
type: TokenType, type: TokenType,
valid_for: number, valid_for: number,
options?: SignatureOptions next_module?: string
): SignatureResult { ): SignatureResult {
const time = Date.now (); const time = Date.now ();
const key = keystore.get_key (time / 1000, valid_for); const key = keystore.get_key (time / 1000, valid_for);
const attributes = { const attributes = {
id: create_salt (), id: create_salt (),
iat: time, iat: time,
type, type,
valid_for, valid_for
next_module: options?.next_module
}; };
const signature = sign_object (options?.data, key, attributes); const signature = sign_object (next_module, key, attributes);
return { id: attributes.id, signature }; return { id: attributes.id, signature };
} }
} }

View File

@ -46,4 +46,4 @@
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
} }
} }

View File

@ -1,10 +1,3 @@
/*
* 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 http from 'http';
export class Response extends http.IncomingMessage { export class Response extends http.IncomingMessage {
@ -12,27 +5,19 @@ export class Response extends http.IncomingMessage {
} }
export function get ( export function get (
// eslint-disable-next-line default-param-last headers: http.OutgoingHttpHeaders = {}
headers: http.OutgoingHttpHeaders = {},
body?: string
): Promise<Response> { ): Promise<Response> {
return new Promise ((resolve) => { return new Promise ((resolve) => {
const req = http.request ('http://localhost:3000', { http.get ('http://localhost:3000', { headers }, (res: Response) => {
headers, let body = '';
method: typeof body === 'string' ? 'POST' : 'GET'
}, (res: Response) => {
let data = '';
res.on ('data', (d) => { res.on ('data', (d) => {
data += d; body += d;
}); });
res.on ('end', () => { res.on ('end', () => {
res.body = data; res.body = body;
resolve (res); resolve (res);
}); });
}); });
if (typeof body === 'string')
req.write (body);
req.end ();
}); });
} }

View File

@ -1,10 +1,3 @@
/*
* 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 http, { IncomingMessage, ServerResponse } from 'http';
import { to_b64 } from '@sapphirecode/encoding-helper'; import { to_b64 } from '@sapphirecode/encoding-helper';
import auth from '../../lib/Authority'; import auth from '../../lib/Authority';
@ -53,37 +46,22 @@ describe ('auth handler', () => {
if (!req.is_basic) { if (!req.is_basic) {
req.invalid ('unknown autorization type'); req.invalid ('unknown autorization type');
} }
else if (req.user === 'foo' && req.password === 'bar') { else if (req.user !== 'foo' || req.password !== 'bar') {
req.deny ();
}
else {
req.allow_access ({ req.allow_access ({
access_token_expires_in: expires_seconds, access_token_expires_in: expires_seconds,
include_refresh_token: true, include_refresh_token: true,
refresh_token_expires_in: refresh_expires_seconds 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', cookie_name: 'cookie_jar',
refresh: { refresh: {
access_token_expires_in: expires_seconds, access_token_expires_in: expires_seconds,
refresh_token_expires_in: refresh_expires_seconds, refresh_token_expires_in: refresh_expires_seconds,
include_refresh_token: true 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 (); }
}
} }
}); });
@ -205,7 +183,7 @@ describe ('auth handler', () => {
}); });
it ('should process part token', async () => { xit ('should process part token', async () => {
const resp1 = await get ({ authorization: 'Basic part:bar' }); const resp1 = await get ({ authorization: 'Basic part:bar' });
expect (resp1.statusCode) expect (resp1.statusCode)
.toEqual (200); .toEqual (200);
@ -217,8 +195,7 @@ describe ('auth handler', () => {
check_token (res1.data.part_token as string, 'part_token'); check_token (res1.data.part_token as string, 'part_token');
const resp2 = await get ( const resp2 = await get (
{ authorization: `Bearer ${res1.data.part_token}` }, { authorization: `Bearer ${res1.data.part_token}` }
'letmein'
); );
expect (resp2.statusCode) expect (resp2.statusCode)
.toEqual (200); .toEqual (200);

View File

@ -56,7 +56,7 @@ describe ('authority', () => {
}); });
it ('should create a part token', () => { it ('should create a part token', () => {
const token = auth.sign ('part_token', 60, { next_module: '2fa' }); const token = auth.sign ('part_token', 60, '2fa');
jasmine.clock () jasmine.clock ()
.tick (30000); .tick (30000);
const res = auth.verify (token.signature); const res = auth.verify (token.signature);

View File

@ -1,10 +1,3 @@
/*
* 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 http from 'http';
import gateway from '../../lib/Gateway'; import gateway from '../../lib/Gateway';
import authority from '../../lib/Authority'; import authority from '../../lib/Authority';