This commit is contained in:
@ -9,6 +9,7 @@ import { IncomingMessage, ServerResponse } from 'http';
|
||||
import { to_utf8 } from '@sapphirecode/encoding-helper';
|
||||
import auth from './Authority';
|
||||
import { debug } from './debug';
|
||||
import { build_cookie, CookieSettings } from './cookie';
|
||||
|
||||
const logger = debug ('auth');
|
||||
|
||||
@ -37,10 +38,6 @@ interface AccessResponse {
|
||||
type AuthHandler =
|
||||
(req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
|
||||
|
||||
function build_cookie (name: string, value: string): string {
|
||||
return `${name}=${value}; Secure; HttpOnly; SameSite=Strict`;
|
||||
}
|
||||
|
||||
class AuthRequest {
|
||||
public request: IncomingMessage;
|
||||
public response: ServerResponse;
|
||||
@ -56,8 +53,8 @@ class AuthRequest {
|
||||
|
||||
public body: string;
|
||||
|
||||
private _cookie_name?: string;
|
||||
private _refresh_cookie_name?: string;
|
||||
private _cookie?: CookieSettings;
|
||||
private _refresh_cookie?: CookieSettings;
|
||||
private _is_successful: boolean;
|
||||
|
||||
public get is_successful (): boolean {
|
||||
@ -68,8 +65,8 @@ class AuthRequest {
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
body: string,
|
||||
cookie?: string,
|
||||
refresh_cookie?: string
|
||||
cookie?: CookieSettings,
|
||||
refresh_cookie?: CookieSettings
|
||||
) {
|
||||
this.request = req;
|
||||
this.response = res;
|
||||
@ -78,8 +75,8 @@ class AuthRequest {
|
||||
this.is_bearer = false;
|
||||
this.user = '';
|
||||
this.password = '';
|
||||
this._cookie_name = cookie;
|
||||
this._refresh_cookie_name = refresh_cookie;
|
||||
this._cookie = cookie;
|
||||
this._refresh_cookie = refresh_cookie;
|
||||
this._is_successful = false;
|
||||
logger ('started processing new auth request');
|
||||
}
|
||||
@ -106,7 +103,6 @@ class AuthRequest {
|
||||
const at = await auth.sign (
|
||||
'access_token',
|
||||
access_token_expires_in,
|
||||
|
||||
{ data }
|
||||
);
|
||||
const result: AccessResult = { access_token_id: at.id };
|
||||
@ -119,8 +115,8 @@ class AuthRequest {
|
||||
|
||||
const cookies = [];
|
||||
|
||||
if (typeof this._cookie_name === 'string')
|
||||
cookies.push (build_cookie (this._cookie_name, at.signature));
|
||||
if (typeof this._cookie !== 'undefined')
|
||||
cookies.push (build_cookie (this._cookie, at.signature));
|
||||
|
||||
if (include_refresh_token) {
|
||||
logger ('including refresh token');
|
||||
@ -135,9 +131,8 @@ class AuthRequest {
|
||||
res.refresh_expires_in = refresh_token_expires_in;
|
||||
result.refresh_token_id = rt.id;
|
||||
|
||||
if (typeof this._refresh_cookie_name === 'string')
|
||||
// eslint-disable-next-line max-len
|
||||
cookies.push (build_cookie (this._refresh_cookie_name, rt.signature));
|
||||
if (typeof this._refresh_cookie !== 'undefined')
|
||||
cookies.push (build_cookie (this._refresh_cookie, rt.signature));
|
||||
}
|
||||
|
||||
if (cookies.length > 0) {
|
||||
@ -228,8 +223,8 @@ type AuthRequestHandler = (req: AuthRequest) => Promise<void> | void;
|
||||
interface CreateHandlerOptions {
|
||||
refresh?: AccessSettings;
|
||||
modules?: Record<string, AuthRequestHandler>;
|
||||
cookie_name?: string;
|
||||
refresh_cookie_name?: string;
|
||||
cookie?: CookieSettings;
|
||||
refresh_cookie?: CookieSettings;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
@ -308,8 +303,9 @@ export default function create_auth_handler (
|
||||
): AuthHandler {
|
||||
logger ('creating new auth handler');
|
||||
if (
|
||||
typeof options?.cookie_name !== 'undefined'
|
||||
&& options.cookie_name === options.refresh_cookie_name
|
||||
typeof options?.cookie !== 'undefined'
|
||||
&& typeof options?.refresh_cookie !== 'undefined'
|
||||
&& options.cookie.name === options.refresh_cookie.name
|
||||
)
|
||||
throw new Error ('access and refresh cookies cannot have the same name');
|
||||
|
||||
@ -331,8 +327,8 @@ export default function create_auth_handler (
|
||||
req,
|
||||
res,
|
||||
body,
|
||||
options?.cookie_name,
|
||||
options?.refresh_cookie_name
|
||||
options?.cookie,
|
||||
options?.refresh_cookie
|
||||
);
|
||||
const token = (/(?<type>\S+) (?<token>.+)/ui)
|
||||
.exec (req.headers.authorization as string);
|
||||
|
@ -9,7 +9,7 @@ import { IncomingMessage, ServerResponse } from 'http';
|
||||
import authority from './Authority';
|
||||
import { AuthRequest, AccessSettings } from './AuthHandler';
|
||||
import { debug } from './debug';
|
||||
import { extract_cookie } from './cookie';
|
||||
import { extract_cookie, CookieSettings } from './cookie';
|
||||
|
||||
const logger = debug ('gateway');
|
||||
|
||||
@ -27,8 +27,8 @@ interface RefreshSettings extends AccessSettings {
|
||||
|
||||
interface GatewayOptions {
|
||||
redirect_url?: string;
|
||||
cookie_name?: string;
|
||||
refresh_cookie_name?: string;
|
||||
cookie?: CookieSettings;
|
||||
refresh_cookie?: CookieSettings;
|
||||
refresh_settings?: RefreshSettings;
|
||||
}
|
||||
|
||||
@ -38,8 +38,9 @@ class GatewayClass {
|
||||
public constructor (options: GatewayOptions = {}) {
|
||||
logger ('creating new gateway');
|
||||
if (
|
||||
typeof options.cookie_name === 'string'
|
||||
&& options.cookie_name === options.refresh_cookie_name
|
||||
typeof options?.cookie !== 'undefined'
|
||||
&& typeof options?.refresh_cookie !== 'undefined'
|
||||
&& options.cookie.name === options.refresh_cookie.name
|
||||
)
|
||||
throw new Error ('access and refresh cookies cannot have the same name');
|
||||
|
||||
@ -80,7 +81,7 @@ class GatewayClass {
|
||||
logger ('authenticating incoming request');
|
||||
let auth = this.get_header_auth (req);
|
||||
if (auth === null)
|
||||
auth = extract_cookie (this._options.cookie_name, req.headers.cookie);
|
||||
auth = extract_cookie (this._options.cookie?.name, req.headers.cookie);
|
||||
if (auth === null) {
|
||||
logger ('found no auth token');
|
||||
return false;
|
||||
@ -101,7 +102,7 @@ class GatewayClass {
|
||||
res: ServerResponse
|
||||
): Promise<boolean> {
|
||||
if (
|
||||
typeof this._options.refresh_cookie_name === 'undefined'
|
||||
typeof this._options.refresh_cookie === 'undefined'
|
||||
|| typeof this._options.refresh_settings === 'undefined'
|
||||
)
|
||||
return false;
|
||||
@ -109,7 +110,7 @@ class GatewayClass {
|
||||
logger ('trying to apply refresh token');
|
||||
|
||||
const refresh = extract_cookie (
|
||||
this._options.refresh_cookie_name,
|
||||
this._options.refresh_cookie.name,
|
||||
req.headers.cookie
|
||||
);
|
||||
if (refresh === null) {
|
||||
@ -124,8 +125,8 @@ class GatewayClass {
|
||||
req,
|
||||
res,
|
||||
''
|
||||
, this._options.cookie_name,
|
||||
this._options.refresh_cookie_name
|
||||
, this._options.cookie,
|
||||
this._options.refresh_cookie
|
||||
);
|
||||
const refresh_result = await auth_request.allow_access ({
|
||||
...this._options.refresh_settings,
|
||||
|
@ -3,8 +3,51 @@ import { debug } from './debug';
|
||||
|
||||
const logger = debug ('cookies');
|
||||
|
||||
function build_cookie (name: string, value: string): string {
|
||||
return `${name}=${value}; Secure; HttpOnly; SameSite=Strict`;
|
||||
type SameSiteValue = 'Lax' | 'None' | 'Strict';
|
||||
|
||||
interface CookieSettings {
|
||||
name: string;
|
||||
secure?: boolean;
|
||||
http_only?: boolean;
|
||||
same_site?: SameSiteValue|null;
|
||||
expires?: string;
|
||||
max_age?: number;
|
||||
domain?: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
const default_settings: Omit<CookieSettings, 'name'> = {
|
||||
secure: true,
|
||||
http_only: true,
|
||||
same_site: 'Strict'
|
||||
};
|
||||
|
||||
function build_cookie (
|
||||
settings: CookieSettings,
|
||||
value: string
|
||||
): string {
|
||||
const local_settings = { ...default_settings, ...settings };
|
||||
const sections = [ `${local_settings.name}=${value}` ];
|
||||
|
||||
if (local_settings.secure)
|
||||
sections.push ('Secure');
|
||||
if (local_settings.http_only)
|
||||
sections.push ('HttpOnly');
|
||||
if (
|
||||
typeof local_settings.same_site !== 'undefined'
|
||||
&& local_settings.same_site !== null
|
||||
)
|
||||
sections.push (`SameSite=${local_settings.same_site}`);
|
||||
if (typeof local_settings.expires !== 'undefined')
|
||||
sections.push (`Expires=${local_settings.expires}`);
|
||||
if (typeof local_settings.max_age !== 'undefined')
|
||||
sections.push (`Max-Age=${local_settings.max_age}`);
|
||||
if (typeof local_settings.domain !== 'undefined')
|
||||
sections.push (`Domain=${local_settings.domain}`);
|
||||
if (typeof local_settings.path !== 'undefined')
|
||||
sections.push (`Path=${local_settings.path}`);
|
||||
|
||||
return sections.join ('; ');
|
||||
}
|
||||
|
||||
function extract_cookie (
|
||||
@ -32,4 +75,4 @@ function extract_cookie (
|
||||
return result;
|
||||
}
|
||||
|
||||
export { build_cookie, extract_cookie };
|
||||
export { build_cookie, extract_cookie, SameSiteValue, CookieSettings };
|
||||
|
@ -34,6 +34,10 @@ import keystore, {
|
||||
KeyStore, KeyStoreExport,
|
||||
LabelledKey, Key
|
||||
} from './KeyStore';
|
||||
import {
|
||||
CookieSettings,
|
||||
SameSiteValue
|
||||
} from './cookie';
|
||||
|
||||
export {
|
||||
create_gateway,
|
||||
@ -63,5 +67,7 @@ export {
|
||||
KeyStore,
|
||||
KeyStoreExport,
|
||||
LabelledKey,
|
||||
Key
|
||||
Key,
|
||||
CookieSettings,
|
||||
SameSiteValue
|
||||
};
|
||||
|
Reference in New Issue
Block a user