diff --git a/src/http-web/authentication/login-page.ts b/src/http-web/authentication/login-page.ts index 50ebcef..84e54fb 100644 --- a/src/http-web/authentication/login-page.ts +++ b/src/http-web/authentication/login-page.ts @@ -4,6 +4,7 @@ import { HttpWebDependencies } from '../server'; import { ErrorCode, ErrorInfo } from '../../http/send-error'; import { redirect_303_see_other } from '../../http/redirects'; import { FastifyInstance, FastifyReply, RouteShorthandOptions } from 'fastify'; +import { csp_headers } from '../../http/content-security-policy'; export function register_login_page_endpoint(http_server: FastifyInstance, conf: HttpConfig, { pkce_cookie, session }: HttpWebDependencies) { const opts: RouteShorthandOptions = { @@ -24,6 +25,7 @@ export function register_login_page_endpoint(http_server: FastifyInstance, conf: function send_login_page(res: FastifyReply) { res.status(200); res.header('content-type', 'text/html; charset=utf-8'); + csp_headers(res, conf.exposed_url); session.reset(res); pkce_cookie.reset(res); return render_login_page(); diff --git a/src/http-web/root-page.ts b/src/http-web/root-page.ts new file mode 100644 index 0000000..d08ede6 --- /dev/null +++ b/src/http-web/root-page.ts @@ -0,0 +1,46 @@ + +import { Req } from '../http/request'; +import { UserData } from '../storage'; +import { HttpConfig } from '../http/server'; +import { HttpWebDependencies } from './server'; +import { ErrorCode, ErrorInfo } from '../http/send-error'; +import { csp_headers } from '../http/content-security-policy'; +import { FastifyInstance, RouteShorthandOptions } from 'fastify'; + +export function register_root_page_endpoint(http_server: FastifyInstance, conf: HttpConfig, { session, logger }: HttpWebDependencies) { + const opts: RouteShorthandOptions = { + schema: { }, + }; + + http_server.get('/', opts, async (req: Req, res) => { + try { + await session.check_login(req); + } + + catch (error) { + session.reset(res); + } + + res.status(200); + res.header('content-type', 'text/html; charset=utf-8'); + csp_headers(res, conf.exposed_url); + return render_root_page(req.session?.user); + }); +} + +export const render_root_page = (user?: UserData, error_code?: ErrorCode, error?: ErrorInfo) => ` + + +Node.js + TypeScript Service + + +

Node.js + TypeScript Service

+${user +? `

Logged in as ${user.name} (${user.username})

+
+ +
` +: 'Login Page'} + + +`; diff --git a/src/http-web/server.ts b/src/http-web/server.ts index d9696d9..f526ef3 100644 --- a/src/http-web/server.ts +++ b/src/http-web/server.ts @@ -9,9 +9,11 @@ import { BaseHttpDependencies, HttpConfig, create_http_server } from '../http/se import { SnowflakeProvider } from '../utilities/snowflake-uid'; import { register_csp_report_endpoint } from '../http/content-security-policy'; +import { register_root_page_endpoint } from './root-page'; import { register_login_page_endpoint } from './authentication/login-page'; import { register_submit_login_endpoint } from './authentication/submit-login'; import { register_login_callback_endpoint } from './authentication/login-callback'; +import { register_logout_endpoint } from './authentication/logout'; export interface HttpWebDependencies extends BaseHttpDependencies { oidc: OIDCProvider; @@ -27,10 +29,14 @@ export function create_http_web_server(conf: HttpConfig, deps: HttpWebDependenci endpoints: [ register_csp_report_endpoint, + // Root page + register_root_page_endpoint, + // Login/logout register_login_page_endpoint, register_submit_login_endpoint, register_login_callback_endpoint, + register_logout_endpoint, ], content_parsers: { // 'application/ld+json': json_content_parser, diff --git a/src/http/server.ts b/src/http/server.ts index 982918f..dff3707 100644 --- a/src/http/server.ts +++ b/src/http/server.ts @@ -52,6 +52,11 @@ export function create_http_server(conf: Http endpoint(server, conf, deps); } + // Register content parsers + for (const [ media_type, parser ] of Object.entries(params.content_parsers)) { + parser(server, [ media_type ]); + } + let resolve: () => void; let status: ServerStatus = 'unstarted'; diff --git a/src/security/argon-hash.ts b/src/security/argon-hash.ts index 9a7d751..9a3b833 100644 --- a/src/security/argon-hash.ts +++ b/src/security/argon-hash.ts @@ -28,7 +28,7 @@ export function create_argon_hash_provider(conf: Argon2HashConfig) { parallelism: conf.parallelism, }); }, - verify(password: string, hash: string) { + verify(hash: string, password: string) { return verify(hash, password, { // }); diff --git a/src/security/session.ts b/src/security/session.ts index e419c04..a96acec 100644 --- a/src/security/session.ts +++ b/src/security/session.ts @@ -51,8 +51,8 @@ export function create_session_provider(conf: SessionCookieConfig, logger: pino. return { prefix, raw_key, full_key }; }, - verify_key(key: SessionKey, session: SessionData) : Promise { - return argon2.verify(conf.pepper + key.raw_key, session.key_hash); + verify_key(key: SessionKey, key_hash: string) : Promise { + return argon2.verify(key_hash, conf.pepper + key.raw_key); }, write_to_cookie(res: FastifyReply, key: SessionKey) { const session_expire = new Date(Date.now() + (conf.ttl * 1000)); @@ -65,7 +65,7 @@ export function create_session_provider(conf: SessionCookieConfig, logger: pino. return { user_id: user_id, prefix: session_key.prefix, - key_hash: await argon2.hash(session_key.raw_key), + key_hash: await self.hash_key(session_key), started: new Date(), expires: new Date(Date.now() + (conf.ttl * 1000)), }; @@ -131,7 +131,17 @@ export function create_session_provider(conf: SessionCookieConfig, logger: pino. log.debug({ session }, 'found session in store'); - const key_verified = await self.verify_key(key, session); + if (session.expires.getTime() <= Date.now()) { + log.debug('session found, but it has expired'); + + storage.delete_session(key.prefix).then(() => { }, (error) => { + log.error({ error }, 'failed to delete an expired session'); + }); + + throw new AuthTokenInvalidError(); + } + + const key_verified = await self.verify_key(key, session.key_hash); if (! key_verified) { log.debug('session found, but the key failed verification against key hash'); diff --git a/src/storage/sqlite3/v1/sessions.ts b/src/storage/sqlite3/v1/sessions.ts index 72c58b9..3e21aac 100644 --- a/src/storage/sqlite3/v1/sessions.ts +++ b/src/storage/sqlite3/v1/sessions.ts @@ -18,7 +18,7 @@ const sql_get_session = ` select session.prefix as prefix, session.key_hash as key_hash, - session.user_id as user_id, + cast(session.user_id as text) as user_id, session.started as started, session.expires as expires from sessions session @@ -57,8 +57,8 @@ export async function create_session(db: DB, data: SessionData) { data.prefix, data.key_hash, data.user_id, - data.started, - data.expires, + data.started.toISOString(), + data.expires.toISOString(), ]); } diff --git a/src/storage/sqlite3/v1/users.ts b/src/storage/sqlite3/v1/users.ts index 0c3b57d..8636e81 100644 --- a/src/storage/sqlite3/v1/users.ts +++ b/src/storage/sqlite3/v1/users.ts @@ -20,7 +20,7 @@ export interface UserRow { const sql_get_user = ` select - user.id as id, + cast(user.id as text) as id, user.username as username, user.oidc_subject as oidc_subject, user.name as name,