cookie settings
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2022-01-10 10:06:54 +01:00
parent 3aaaf10fd9
commit cc8762e4ec
10 changed files with 248 additions and 90 deletions

View File

@ -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);

View File

@ -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,

View File

@ -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 };

View File

@ -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
};