From 0fbbfec7373e960d0333662e6f377dbe521c0f29 Mon Sep 17 00:00:00 2001 From: CnF-Gris Date: Tue, 1 Jul 2025 17:59:23 +0000 Subject: [PATCH] Rework to add endpoint creation and better handling --- helper-scripts/{ => scripts}/entry.sh | 0 helper-scripts/services/custom-nginx.sh | 73 +++++ nginx/{ => active/automatic}/grpc/.gitkeep | 0 nginx/{ => active/automatic}/http/.gitkeep | 0 .../{quic => active/automatic/http2}/.gitkeep | 0 nginx/{ => active/automatic}/stream/.gitkeep | 0 .../active/manual/custom/.gitkeep | 0 .../active/manual/grpc/.gitkeep | 0 nginx/active/manual/http/.gitkeep | 0 nginx/active/manual/http2/.gitkeep | 0 nginx/active/manual/stream/.gitkeep | 0 nginx/inactive/automatic/grpc/.gitkeep | 0 nginx/inactive/automatic/http/.gitkeep | 0 nginx/inactive/automatic/http2/.gitkeep | 0 nginx/inactive/automatic/stream/.gitkeep | 0 nginx/inactive/manual/custom/.gitkeep | 0 nginx/inactive/manual/grpc/.gitkeep | 0 nginx/{ => inactive/manual}/grpc/conf.example | 0 nginx/inactive/manual/http/.gitkeep | 0 nginx/{ => inactive/manual}/http/conf.example | 3 - nginx/inactive/manual/http2/.gitkeep | 0 nginx/inactive/manual/http2/conf.example | 50 ++++ nginx/inactive/manual/stream/.gitkeep | 0 nginx/nginx.conf | 20 +- src/app.d.ts | 2 +- src/handles/handle.ts | 55 +++- src/hooks.server.ts | 14 +- .../FileSystem/SSLTerminations.ts | 0 .../SQLite/Database.ts} | 16 +- .../SQLite}/SQL/Sessions.sql | 0 .../SQLite}/SQL/Users.sql | 0 .../SQLite}/Sessions.ts | 12 +- .../SQLite}/Users.ts | 12 +- .../classes/{app-sessions.ts => appdata.ts} | 20 +- src/lib/classes/endpoint.ts | 119 -------- src/lib/classes/endpoints.ts | 10 + src/lib/classes/sessions.ts | 5 + src/lib/classes/ssl-termination-endpoint.ts | 253 ++++++++++++++++++ src/lib/classes/users.ts | 6 +- src/lib/enums/endpoints.ts | 6 + src/lib/utils/constants.ts | 18 +- src/lib/utils/jtw-utils.ts | 15 +- src/routes/api/login/+server.ts | 18 +- src/routes/api/logout/+server.ts | 51 ++++ src/routes/api/register/+server.ts | 2 +- src/routes/app/login/+page.svelte | 11 +- src/routes/app/program/home/+page.svelte | 3 + src/routes/app/register/+page.svelte | 9 +- 48 files changed, 625 insertions(+), 178 deletions(-) rename helper-scripts/{ => scripts}/entry.sh (100%) create mode 100644 helper-scripts/services/custom-nginx.sh rename nginx/{ => active/automatic}/grpc/.gitkeep (100%) rename nginx/{ => active/automatic}/http/.gitkeep (100%) rename nginx/{quic => active/automatic/http2}/.gitkeep (100%) rename nginx/{ => active/automatic}/stream/.gitkeep (100%) rename src/lib/db-utils/Configurations.ts => nginx/active/manual/custom/.gitkeep (100%) rename src/lib/db-utils/SSLEndpoints.ts => nginx/active/manual/grpc/.gitkeep (100%) create mode 100644 nginx/active/manual/http/.gitkeep create mode 100644 nginx/active/manual/http2/.gitkeep create mode 100644 nginx/active/manual/stream/.gitkeep create mode 100644 nginx/inactive/automatic/grpc/.gitkeep create mode 100644 nginx/inactive/automatic/http/.gitkeep create mode 100644 nginx/inactive/automatic/http2/.gitkeep create mode 100644 nginx/inactive/automatic/stream/.gitkeep create mode 100644 nginx/inactive/manual/custom/.gitkeep create mode 100644 nginx/inactive/manual/grpc/.gitkeep rename nginx/{ => inactive/manual}/grpc/conf.example (100%) create mode 100644 nginx/inactive/manual/http/.gitkeep rename nginx/{ => inactive/manual}/http/conf.example (95%) create mode 100644 nginx/inactive/manual/http2/.gitkeep create mode 100644 nginx/inactive/manual/http2/conf.example create mode 100644 nginx/inactive/manual/stream/.gitkeep create mode 100644 src/lib/broker-utils/FileSystem/SSLTerminations.ts rename src/lib/{db-utils/sqlite.ts => broker-utils/SQLite/Database.ts} (75%) rename src/lib/{db-utils => broker-utils/SQLite}/SQL/Sessions.sql (100%) rename src/lib/{db-utils => broker-utils/SQLite}/SQL/Users.sql (100%) rename src/lib/{db-utils => broker-utils/SQLite}/Sessions.ts (93%) rename src/lib/{db-utils => broker-utils/SQLite}/Users.ts (94%) rename src/lib/classes/{app-sessions.ts => appdata.ts} (72%) delete mode 100644 src/lib/classes/endpoint.ts create mode 100644 src/lib/classes/endpoints.ts create mode 100644 src/lib/classes/ssl-termination-endpoint.ts create mode 100644 src/lib/enums/endpoints.ts create mode 100644 src/routes/api/logout/+server.ts create mode 100644 src/routes/app/program/home/+page.svelte diff --git a/helper-scripts/entry.sh b/helper-scripts/scripts/entry.sh similarity index 100% rename from helper-scripts/entry.sh rename to helper-scripts/scripts/entry.sh diff --git a/helper-scripts/services/custom-nginx.sh b/helper-scripts/services/custom-nginx.sh new file mode 100644 index 0000000..1eecb5a --- /dev/null +++ b/helper-scripts/services/custom-nginx.sh @@ -0,0 +1,73 @@ +#!/sbin/openrc-run + +description="Nginx http and reverse proxy server" +extra_commands="checkconfig" +extra_started_commands="reload reopen upgrade" + +cfgfile=${cfgfile:-/etc/nginx/nginx.conf} +pidfile=/run/nginx/nginx.pid +command=${command:-/usr/sbin/nginx} +command_args="-c $cfgfile" +required_files="$cfgfile" + +depend() { + need net + use dns logger netmount +} + +start_pre() { + checkpath --directory --owner nginx:nginx ${pidfile%/*} + $command $command_args -t -q +} + +checkconfig() { + ebegin "Checking $RC_SVCNAME configuration" + start_pre + eend $? +} + +reload() { + ebegin "Reloading $RC_SVCNAME configuration" + # start_pre && start-stop-daemon --signal HUP --pidfile $pidfile + nginx -s reload + # call our service + eend $? +} + +reopen() { + ebegin "Reopening $RC_SVCNAME log files" + start-stop-daemon --signal USR1 --pidfile $pidfile + eend $? +} + +upgrade() { + start_pre || return 1 + + ebegin "Upgrading $RC_SVCNAME binary" + + einfo "Sending USR2 to old binary" + start-stop-daemon --signal USR2 --pidfile $pidfile + + einfo "Sleeping 3 seconds before pid-files checking" + sleep 3 + + if [ ! -f $pidfile.oldbin ]; then + eerror "File with old pid ($pidfile.oldbin) not found" + return 1 + fi + + if [ ! -f $pidfile ]; then + eerror "New binary failed to start" + return 1 + fi + + einfo "Sleeping 3 seconds before WINCH" + sleep 3 ; start-stop-daemon --signal 28 --pidfile $pidfile.oldbin + + einfo "Sending QUIT to old binary" + start-stop-daemon --signal QUIT --pidfile $pidfile.oldbin + + einfo "Upgrade completed" + + eend $? "Upgrade failed" +} diff --git a/nginx/grpc/.gitkeep b/nginx/active/automatic/grpc/.gitkeep similarity index 100% rename from nginx/grpc/.gitkeep rename to nginx/active/automatic/grpc/.gitkeep diff --git a/nginx/http/.gitkeep b/nginx/active/automatic/http/.gitkeep similarity index 100% rename from nginx/http/.gitkeep rename to nginx/active/automatic/http/.gitkeep diff --git a/nginx/quic/.gitkeep b/nginx/active/automatic/http2/.gitkeep similarity index 100% rename from nginx/quic/.gitkeep rename to nginx/active/automatic/http2/.gitkeep diff --git a/nginx/stream/.gitkeep b/nginx/active/automatic/stream/.gitkeep similarity index 100% rename from nginx/stream/.gitkeep rename to nginx/active/automatic/stream/.gitkeep diff --git a/src/lib/db-utils/Configurations.ts b/nginx/active/manual/custom/.gitkeep similarity index 100% rename from src/lib/db-utils/Configurations.ts rename to nginx/active/manual/custom/.gitkeep diff --git a/src/lib/db-utils/SSLEndpoints.ts b/nginx/active/manual/grpc/.gitkeep similarity index 100% rename from src/lib/db-utils/SSLEndpoints.ts rename to nginx/active/manual/grpc/.gitkeep diff --git a/nginx/active/manual/http/.gitkeep b/nginx/active/manual/http/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/active/manual/http2/.gitkeep b/nginx/active/manual/http2/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/active/manual/stream/.gitkeep b/nginx/active/manual/stream/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/inactive/automatic/grpc/.gitkeep b/nginx/inactive/automatic/grpc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/inactive/automatic/http/.gitkeep b/nginx/inactive/automatic/http/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/inactive/automatic/http2/.gitkeep b/nginx/inactive/automatic/http2/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/inactive/automatic/stream/.gitkeep b/nginx/inactive/automatic/stream/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/inactive/manual/custom/.gitkeep b/nginx/inactive/manual/custom/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/inactive/manual/grpc/.gitkeep b/nginx/inactive/manual/grpc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/grpc/conf.example b/nginx/inactive/manual/grpc/conf.example similarity index 100% rename from nginx/grpc/conf.example rename to nginx/inactive/manual/grpc/conf.example diff --git a/nginx/inactive/manual/http/.gitkeep b/nginx/inactive/manual/http/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/http/conf.example b/nginx/inactive/manual/http/conf.example similarity index 95% rename from nginx/http/conf.example rename to nginx/inactive/manual/http/conf.example index e94ae2f..9de10e1 100644 --- a/nginx/http/conf.example +++ b/nginx/inactive/manual/http/conf.example @@ -12,9 +12,6 @@ server { # endpoint port listen PORT ssl; - # Uncomment if http2 - # http2 on; - # Here put the unencrypted # endpoint port location / { diff --git a/nginx/inactive/manual/http2/.gitkeep b/nginx/inactive/manual/http2/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/inactive/manual/http2/conf.example b/nginx/inactive/manual/http2/conf.example new file mode 100644 index 0000000..41e4f1e --- /dev/null +++ b/nginx/inactive/manual/http2/conf.example @@ -0,0 +1,50 @@ +# Example conf + + +# Example TLS endpoint +server { + + # Use this to avoid port scanners to know + # what you are using + more_clear_headers Server; + + # Here put the TLS termination + # endpoint port + listen PORT ssl; + http2 on; + + # Here put the unencrypted + # endpoint port + location / { + proxy_pass http://localhost:8080; + } + + # Put relevant keys here + ssl_certificate /services-keys/Example/cert.pem; + ssl_certificate_key /services-keys/Example/key.pem; + ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; + +} + +# Example Termination endpoint +server { + + # Use this to avoid port scanners to know + # what you are using + more_clear_headers Server; + + # Here put the unencrypted + # endpoint port + listen 127.0.0.1:8080; + + # Uncomment if http2 + # http2 on; + + # Here put the original + # service endpoint port + location / { + proxy_pass https://127.0.0.1:PORT; + } + + +} \ No newline at end of file diff --git a/nginx/inactive/manual/stream/.gitkeep b/nginx/inactive/manual/stream/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/nginx/nginx.conf b/nginx/nginx.conf index b169a9e..bf5d242 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -34,13 +34,25 @@ http { '"$http_user_agent" "$http_x_forwarded_for"'; - # Includes virtual hosts configs. - include /etc/nginx/http/*.conf; - include /etc/nginx/grpc/*.conf; + # Includes automatic virtual hosts configs. + include /etc/nginx/active/automatic/grpc/*.conf; + include /etc/nginx/active/automatic/http/*.conf; + include /etc/nginx/active/automatic/http2/*.conf; + + # Includes manual configs + include /etc/nginx/active/manual/grpc/*.conf; + include /etc/nginx/active/manual/http/*.conf; + include /etc/nginx/active/manual/http2/*.conf; + include /etc/nginx/active/manual/custom/*.conf; + } stream { - include /etc/nginx/stream*.conf; + # Include automatic stream config + include /etc/nginx/active/automatic/stream/*.conf; + + # Include manual configs + include /etc/nginx/active/manual/stream/*.conf; } \ No newline at end of file diff --git a/src/app.d.ts b/src/app.d.ts index af756b5..5887219 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,4 +1,4 @@ -import type { AppData } from "$lib/classes/app-sessions"; +import type { AppData } from "$lib/classes/appdata"; // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces diff --git a/src/handles/handle.ts b/src/handles/handle.ts index 47fd112..a9796f6 100644 --- a/src/handles/handle.ts +++ b/src/handles/handle.ts @@ -1,13 +1,27 @@ -import { AppData } from "$lib/classes/app-sessions"; +import { AppData } from "$lib/classes/appdata"; +import { SESSION_COOKIE_NAME } from "$lib/utils/constants"; import { logger } from "$lib/utils/logger"; import { error, redirect, type Handle } from "@sveltejs/kit"; import { sequence } from "@sveltejs/kit/hooks"; -const sessionConstructorHandle = (async ({event, resolve}) => { +const sessionConstructorHandle = (async ({ event, resolve }) => { const data = await AppData.extractAppDataFromCookies(event.cookies) + // Prevents stray cookies from remaining + // in session (e.g. User has been deleted, + // but cookie is still in session) + if (!data) { + event.cookies.delete( + SESSION_COOKIE_NAME, + { + path: "/" + } + ) + } + + logger.debug(`User: ${data?.user.username}\nToken ${data?.session.sessionToken}`, "Session Handle") event.locals.session = data @@ -17,12 +31,12 @@ const sessionConstructorHandle = (async ({event, resolve}) => { }) satisfies Handle -const apiHandle = (async ({event, resolve}) => { +const apiHandle = (async ({ event, resolve }) => { logger.debug(event.url.pathname, "API Handle") logger.debug(`Session Data: ${event.locals.session}`, "API Handle") - if(!event.url.pathname.startsWith("/api/program")) { + if (!event.url.pathname.startsWith("/api/program")) { // next handle return await resolve(event) } @@ -38,12 +52,12 @@ const apiHandle = (async ({event, resolve}) => { }) satisfies Handle -const appHandle = (async ({event, resolve}) => { +const appHandle = (async ({ event, resolve }) => { - logger.debug(event.url.pathname, "API Handle") - logger.debug(`Session Data: ${event.locals.session}`, "API Handle") + logger.debug(event.url.pathname, "APP Handle") + logger.debug(`Session Data: ${event.locals.session}`, "APP Handle") - if(!event.url.pathname.startsWith("/app/program")) { + if (!event.url.pathname.startsWith("/app/program")) { // next handle return await resolve(event) } @@ -57,8 +71,31 @@ const appHandle = (async ({event, resolve}) => { }) satisfies Handle +const appNonAuthHandle = (async ({ event, resolve }) => { + + + logger.debug(`Session Data: ${event.locals.session}`, "APP Non Auth") + + if ( + !event.url.pathname.startsWith("/app/login") && + !event.url.pathname.startsWith("/app/register") + ) { + // next handle + return await resolve(event) + } + + // It's for frontend, should redirect + if (event.locals.session) { + return redirect(302, "/app/program") + } + + return await resolve(event) + +}) satisfies Handle + export const handles = sequence( sessionConstructorHandle, apiHandle, - appHandle + appHandle, + appNonAuthHandle ) \ No newline at end of file diff --git a/src/hooks.server.ts b/src/hooks.server.ts index f0fdbb6..225411a 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,19 +1,23 @@ import type { ServerInit } from '@sveltejs/kit'; import { handles } from './handles/handle'; -import { SSLSnifferApp } from '$lib/db-utils/sqlite'; +import { DatabaseBrokerManager } from '$lib/broker-utils/SQLite/Database'; import { UserApp } from '$lib/classes/users'; -import { UserDBBroker } from '$lib/db-utils/Users'; +import { UserDBBroker } from '$lib/broker-utils/SQLite/Users'; import { SessionApp } from '$lib/classes/sessions'; -import { SessionDBBroker } from '$lib/db-utils/Sessions'; -import { AppData } from '$lib/classes/app-sessions'; +import { SessionDBBroker } from '$lib/broker-utils/SQLite/Sessions'; +import { AppData } from '$lib/classes/appdata'; import { logger } from '$lib/utils/logger'; import { JoseApp } from '$lib/utils/jtw-utils'; export const init: ServerInit = async () => { logger.debug("Starting app", "App Init") + + if(!DatabaseBrokerManager.ready) { + DatabaseBrokerManager.init() + } - SSLSnifferApp.init() + UserApp.init( new UserDBBroker() ) diff --git a/src/lib/broker-utils/FileSystem/SSLTerminations.ts b/src/lib/broker-utils/FileSystem/SSLTerminations.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/db-utils/sqlite.ts b/src/lib/broker-utils/SQLite/Database.ts similarity index 75% rename from src/lib/db-utils/sqlite.ts rename to src/lib/broker-utils/SQLite/Database.ts index e9e8a29..52ee417 100644 --- a/src/lib/db-utils/sqlite.ts +++ b/src/lib/broker-utils/SQLite/Database.ts @@ -2,7 +2,7 @@ import { DB_PATH } from "$lib/utils/constants"; import { logger } from "$lib/utils/logger"; import { Database, Statement } from "bun:sqlite"; -export class SSLSnifferApp { +export class DatabaseBrokerManager { private static initialized = false @@ -13,13 +13,17 @@ export class SSLSnifferApp { logger.debug("Initializing Database", "SSLSnifferApp") - if (SSLSnifferApp.initialized) { + if (DatabaseBrokerManager.initialized) { logger.debug("database initialized Twice?", "SSLSnifferApp") throw new Error("SSLSniffer has already been initialized") } - SSLSnifferApp.db = SSLSnifferApp.initDatabase() - SSLSnifferApp.initialized = true + DatabaseBrokerManager.db = DatabaseBrokerManager.initDatabase() + DatabaseBrokerManager.initialized = true + } + + public static get ready() { + return DatabaseBrokerManager.initialized } @@ -27,7 +31,7 @@ export class SSLSnifferApp { logger.debug(`Statement: ${query}`, "SQLite Query Preparation") - return SSLSnifferApp.db.prepare(query) + return DatabaseBrokerManager.db.prepare(query) } private static initDatabase() { @@ -56,7 +60,7 @@ export class SSLSnifferApp { // Change of variable to make it more readable const forceful = !graceful - SSLSnifferApp.db.close(forceful) + DatabaseBrokerManager.db.close(forceful) } } diff --git a/src/lib/db-utils/SQL/Sessions.sql b/src/lib/broker-utils/SQLite/SQL/Sessions.sql similarity index 100% rename from src/lib/db-utils/SQL/Sessions.sql rename to src/lib/broker-utils/SQLite/SQL/Sessions.sql diff --git a/src/lib/db-utils/SQL/Users.sql b/src/lib/broker-utils/SQLite/SQL/Users.sql similarity index 100% rename from src/lib/db-utils/SQL/Users.sql rename to src/lib/broker-utils/SQLite/SQL/Users.sql diff --git a/src/lib/db-utils/Sessions.ts b/src/lib/broker-utils/SQLite/Sessions.ts similarity index 93% rename from src/lib/db-utils/Sessions.ts rename to src/lib/broker-utils/SQLite/Sessions.ts index 17a7b27..b74d90b 100644 --- a/src/lib/db-utils/Sessions.ts +++ b/src/lib/broker-utils/SQLite/Sessions.ts @@ -1,6 +1,6 @@ import { Session, type ISessionBroker } from "$lib/classes/sessions" import { logger } from "$lib/utils/logger" -import { SSLSnifferApp } from "./sqlite" +import { DatabaseBrokerManager } from "./Database" class SessionDB { @@ -40,7 +40,7 @@ export class SessionDBBroker implements ISessionBroker { createTable(): void { - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` CREATE TABLE IF NOT EXISTS sessions ( session_id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -70,7 +70,7 @@ export class SessionDBBroker implements ISessionBroker { const token : string = crypto.randomUUID(); // Insert into DB - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` INSERT INTO sessions (user_id, session_token) VALUES (@userID, @token); @@ -129,7 +129,7 @@ export class SessionDBBroker implements ISessionBroker { private getSessionDBFromToken(token: string): SessionDB | null { logger.debug(`token: ${token}`, "DB Session from Token") - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` SELECT session_id, user_id, session_token FROM sessions @@ -147,7 +147,7 @@ export class SessionDBBroker implements ISessionBroker { } private getSessionDBFromUserID(userID: number): SessionDB | null { - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` SELECT session_id, user_id, session_token FROM sessions @@ -165,7 +165,7 @@ export class SessionDBBroker implements ISessionBroker { private getSessionDBFromSessionID(sessionID: number): SessionDB | null { - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` SELECT session_id, user_id, session_token FROM sessions diff --git a/src/lib/db-utils/Users.ts b/src/lib/broker-utils/SQLite/Users.ts similarity index 94% rename from src/lib/db-utils/Users.ts rename to src/lib/broker-utils/SQLite/Users.ts index cd750bc..3c3eebf 100644 --- a/src/lib/db-utils/Users.ts +++ b/src/lib/broker-utils/SQLite/Users.ts @@ -1,7 +1,7 @@ import type { Session, SessionApp } from "$lib/classes/sessions"; import { User, type IUserBroker } from "$lib/classes/users"; import { logger } from "$lib/utils/logger"; -import { SSLSnifferApp } from "./sqlite"; +import { DatabaseBrokerManager } from "./Database"; import * as argon2 from "argon2"; @@ -37,7 +37,7 @@ export class UserDBBroker implements IUserBroker { public createTable(): void { - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` CREATE TABLE IF NOT EXISTS users ( user_id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -54,7 +54,7 @@ export class UserDBBroker implements IUserBroker { this.validateUniqueness(username) - const insertUser = SSLSnifferApp.prepare( + const insertUser = DatabaseBrokerManager.prepare( ` INSERT INTO users (username, password_hash) VALUES (@username, @password); @@ -127,7 +127,7 @@ export class UserDBBroker implements IUserBroker { } const passwordHash = await argon2.hash(newPassword) - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` UPDATE users SET password_hash = @newPassword @@ -173,7 +173,7 @@ export class UserDBBroker implements IUserBroker { } private getUserFromUsername(username: string): UserDB | null { - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` SELECT user_id, username, password_hash FROM users @@ -201,7 +201,7 @@ export class UserDBBroker implements IUserBroker { private getUserFromUserID(userID: number): UserDB | null { - const stmt = SSLSnifferApp.prepare( + const stmt = DatabaseBrokerManager.prepare( ` SELECT user_id, username, password_hash FROM users diff --git a/src/lib/classes/app-sessions.ts b/src/lib/classes/appdata.ts similarity index 72% rename from src/lib/classes/app-sessions.ts rename to src/lib/classes/appdata.ts index 3e97c45..521b6aa 100644 --- a/src/lib/classes/app-sessions.ts +++ b/src/lib/classes/appdata.ts @@ -3,6 +3,7 @@ import { SessionApp, type Session } from "./sessions"; import { UserApp, type User } from "./users"; import { JoseApp } from "$lib/utils/jtw-utils"; import { logger } from "$lib/utils/logger"; +import { SESSION_COOKIE_NAME } from "$lib/utils/constants"; export class AppData { @@ -28,7 +29,7 @@ export class AppData { public static async extractAppDataFromCookies(cookies: Cookies) { - const encodedSessionToken = cookies.get("session") + const encodedSessionToken = cookies.get(SESSION_COOKIE_NAME) logger.debug(`Session Cookie: ${encodedSessionToken}`, "APP Session Building 1") @@ -40,7 +41,18 @@ export class AppData { logger.debug(`Session Cookie: ${decodedSessionToken}`, "APP Session Building 2") - const sessionToken = (await JoseApp.verifyObject(decodedSessionToken)).token + + + const candidateToken = (await JoseApp.verifyObject(decodedSessionToken)) + + if (!candidateToken) { + cookies.delete(SESSION_COOKIE_NAME, { + path: "/" + }) + return null + } + + const sessionToken : string = candidateToken.token logger.debug(`Session Token: ${sessionToken}`, "APP Session Building 3") @@ -60,4 +72,8 @@ export class AppData { ) } + public toString() { + return `User:\t${this.user}\nSession:\t${this.session}` + } + } \ No newline at end of file diff --git a/src/lib/classes/endpoint.ts b/src/lib/classes/endpoint.ts deleted file mode 100644 index d0bbcf7..0000000 --- a/src/lib/classes/endpoint.ts +++ /dev/null @@ -1,119 +0,0 @@ -import type { NginxProtocol } from "$lib/enums/protocols" -import { isPortAvailable, validatePort } from "$lib/utils/ports-utils" - -// TODO: inherit from a super class - -/** - * This class represents an SSL Termination Endpoint. - * - * While it's possible to create it directly, it is - * discouraged in favor of the Factory methods as it does - * more checks than this class - */ -export class SSLTerminationEndpoint { - - public sslPort: number - public clearPort: number - public servicePort: number - public protocol: NginxProtocol - public certificateURI: string - public privateKeyURI: string - - constructor( - sslPort: number, - clearPort: number, - servicePort: number, - protocol: NginxProtocol, - certificateURI: string, - privateKeyURI: string - ) { - validatePort(sslPort) - validatePort(clearPort) - validatePort(servicePort) - - // TODO: check for file existance - - - this.sslPort = sslPort - this.clearPort = clearPort - this.servicePort = servicePort - - this.protocol = protocol - this.certificateURI = certificateURI - this.privateKeyURI = privateKeyURI - - } - -} - -export class SSLTerminationEndpointFactory { - - /** - * Creates an SSLEndpoint and chooses ports - * automatically - * - * @param servicePort - * @param protocol - * @param certificateURI - * @param privateKeyURI - */ - public static async createSSLEndpoint( - servicePort: number, - protocol: NginxProtocol, - certificateURI: string, - privateKeyURI: string - ): Promise - public static async createSSLEndpoint( - servicePort: number, - protocol: NginxProtocol, - certificateURI: string, - privateKeyURI: string, - sslPort?: number, - clearPort?: number, - ): Promise { - - if (!sslPort) { - sslPort = await SSLTerminationEndpointFactory.generatePort() - } - - if (!clearPort) { - clearPort = await SSLTerminationEndpointFactory.generatePort() - } - - return new SSLTerminationEndpoint( - sslPort, - clearPort, - servicePort, - protocol, - certificateURI, - privateKeyURI - ) - } - - private static async generatePort() { - - const MIN_PORT = 1025 - const MAX_PORT = 65535 - const MAX_RETRIES = 100 - - let counter = 0 - - while (counter < MAX_RETRIES) { - - let candidatePort = Math.trunc( - Math.random() * (MAX_PORT - MIN_PORT) + MIN_PORT - ) - - if (await isPortAvailable(candidatePort)) { - return candidatePort - } - - counter++ - } - - // UGLY: change this to a more specific error - throw Error(`Couldn't find an available port in less than ${MAX_RETRIES}`) - - } - -} \ No newline at end of file diff --git a/src/lib/classes/endpoints.ts b/src/lib/classes/endpoints.ts new file mode 100644 index 0000000..a489d87 --- /dev/null +++ b/src/lib/classes/endpoints.ts @@ -0,0 +1,10 @@ +import type { EndpointType } from "$lib/enums/endpoints"; + +export interface IEndpoint { + + type: EndpointType + name: string + path: string + hash: string + +} \ No newline at end of file diff --git a/src/lib/classes/sessions.ts b/src/lib/classes/sessions.ts index 8b90cc9..d18c2fe 100644 --- a/src/lib/classes/sessions.ts +++ b/src/lib/classes/sessions.ts @@ -1,5 +1,6 @@ export interface ISessionBroker { + // TODO: change in init() createTable(): void createSessionFromUserID(userID: number): Session getSessionFromUserID(userID: number): Session | null @@ -23,6 +24,10 @@ export class Session { this.sessionToken = sessionToken } + public toString() { + return this.sessionToken + } + } export class SessionApp { diff --git a/src/lib/classes/ssl-termination-endpoint.ts b/src/lib/classes/ssl-termination-endpoint.ts new file mode 100644 index 0000000..7c45e07 --- /dev/null +++ b/src/lib/classes/ssl-termination-endpoint.ts @@ -0,0 +1,253 @@ +import { EndpointType } from "$lib/enums/endpoints" +import type { NginxProtocol } from "$lib/enums/protocols" +import { doesFileExists } from "$lib/utils/filesystem-utils" +import { isPortAvailable, validatePort } from "$lib/utils/ports-utils" +import type { init } from "../../hooks.server" +import type { IEndpoint } from "./endpoints" + +// TODO: inherit from a super class +export interface ISSLTerminationBroker { + + /** + * Initialize the Broker and everything related to it + */ + init(): Promise + + // Creation should throw if something goes wrong + // with reasons why + createSSLTerminationSimple( + name: string, + servicePort: number, + serviceEndpoint: string, + certificateURI: string, + privateKeyURI: string + ): Promise + + createSSLTerminationComplete( + name: string, + sslPort: number, + clearPort: number, + servicePort: number, + serviceEndpoint: string, + certificateURI: string, + privateKeyURI: string + ): Promise + + + // Getting endpoints may be null, react over them + getSSLTerminationByName( + name: string + ): Promise + + + // Throw if something goes wrong + modifySSLTerminationByName( + name: string, + changes: SSLTerminationChanges + ): Promise + + + deleteSSLTerminationByName( + name: string + ): Promise + + + getAllSSLTerminations(): Promise + +} + +/** + * This class represents an SSL Termination Endpoint. + * + * While it's possible to create it directly, it is + * discouraged in favor of the Factory methods as it does + * more checks than this class + */ +export class SSLTermination implements IEndpoint { + + private static _type: EndpointType = EndpointType.SSL_TERMINATION + + public path: string + public hash: string + public name: string + public sslPort: number + public clearPort: number + public servicePort: number + public serviceEndpoint: string + public protocol: NginxProtocol + public certificateURI: string + public privateKeyURI: string + + public get type() { + return SSLTermination._type + } + + constructor( + name: string, + path: string, + hash: string, + sslPort: number, + clearPort: number, + servicePort: number, + serviceEndpoint: string, + protocol: NginxProtocol, + certificateURI: string, + privateKeyURI: string + ) { + + validatePort(sslPort) + validatePort(clearPort) + validatePort(servicePort) + + this.name = name + this.path = path + this.hash = hash + this.sslPort = sslPort + this.clearPort = clearPort + this.servicePort = servicePort + this.serviceEndpoint = serviceEndpoint + this.protocol = protocol + this.certificateURI = certificateURI + this.privateKeyURI = privateKeyURI + + } + + +} + +type SSLTerminationChanges = { + name?: string, + sslPort?: number, + clearPort?: number, + servicePort?: number, + serviceEndpoint?: string, + protocol?: NginxProtocol, + certificateURI?: string, + privateKeyURI?: string +} + +export class SSLTerminationEndpointApp { + + private static initialized: boolean = false + private static broker: ISSLTerminationBroker + + public static get ready() { + return SSLTerminationEndpointApp.initialized + } + + public static init(broker: ISSLTerminationBroker) { + SSLTerminationEndpointApp.assureNotInitialized() + + SSLTerminationEndpointApp.broker = broker + broker.init() + SSLTerminationEndpointApp.initialized = true + } + + + // Creation should throw if something goes wrong + // with reasons why + public static async createSSLTerminationSimple( + name: string, + servicePort: number, + serviceEndpoint: string, + certificateURI: string, + privateKeyURI: string + ): Promise { + + SSLTerminationEndpointApp.assureInitialized() + + return await this.broker.createSSLTerminationSimple( + name, + servicePort, + serviceEndpoint, + certificateURI, + privateKeyURI + ) + + } + + public static async createSSLTerminationComplete( + name: string, + sslPort: number, + clearPort: number, + servicePort: number, + serviceEndpoint: string, + certificateURI: string, + privateKeyURI: string + ): Promise { + + SSLTerminationEndpointApp.assureInitialized() + + return await this.broker.createSSLTerminationComplete( + name, + sslPort, + clearPort, + servicePort, + serviceEndpoint, + certificateURI, + privateKeyURI + ) + } + + + // Getting endpoints may be null, react over them + public static async getSSLTerminationByName( + name: string + ): Promise { + + SSLTerminationEndpointApp.assureInitialized() + + return await this.broker.getSSLTerminationByName( + name + ) + + } + + + // Throw if something goes wrong + public static async modifySSLTerminationByName( + name: string, + changes: SSLTerminationChanges + ): Promise { + SSLTerminationEndpointApp.assureInitialized() + + return await this.broker.modifySSLTerminationByName( + name, + changes + ) + } + + + public static async deleteSSLTerminationByName( + name: string + ): Promise { + SSLTerminationEndpointApp.assureInitialized() + + return await this.broker.deleteSSLTerminationByName( + name + ) + } + + + public static async getAllSSLTerminations(): Promise { + SSLTerminationEndpointApp.assureInitialized() + + return await this.broker.getAllSSLTerminations() + } + + + private static assureNotInitialized() { + if (SSLTerminationEndpointApp.initialized) { + // UGLY: more specific + throw new Error("SSLTerminationEndpointApp has been already initialized") + } + } + + private static assureInitialized() { + if (SSLTerminationEndpointApp.initialized) { + // UGLY: more specific + throw new Error("SSLTerminationEndpointApp has not been initialized yet") + } + } + +} \ No newline at end of file diff --git a/src/lib/classes/users.ts b/src/lib/classes/users.ts index 4e5db99..df77d25 100644 --- a/src/lib/classes/users.ts +++ b/src/lib/classes/users.ts @@ -2,7 +2,7 @@ import type { Session } from "./sessions" export interface IUserBroker { - + // TODO: change in init() createTable(): void createUser(username: string, password: string): Promise getUser(username: string, password: string): Promise @@ -24,6 +24,10 @@ export class User { this.username = username } + public toString() { + return `userID:\t${this.userID}\nusername:\t${this.username}` + } + } diff --git a/src/lib/enums/endpoints.ts b/src/lib/enums/endpoints.ts new file mode 100644 index 0000000..8ad991b --- /dev/null +++ b/src/lib/enums/endpoints.ts @@ -0,0 +1,6 @@ +export enum EndpointType { + + SSL_TERMINATION = "SSL-Termination", + MANUAL = "Manual" + +} \ No newline at end of file diff --git a/src/lib/utils/constants.ts b/src/lib/utils/constants.ts index c34e3eb..cc65c7e 100644 --- a/src/lib/utils/constants.ts +++ b/src/lib/utils/constants.ts @@ -3,4 +3,20 @@ export const DB_PATH = "src/db/db.sqlite" export const SERVER_PRIVATE_DIR = "src/private" export const SERVER_PRIVATE_KEY_PATH = `${SERVER_PRIVATE_DIR}/key.pem` export const SERVER_PUBLIC_KEY_PATH = `${SERVER_PRIVATE_DIR}/pub.pem` -export const DEBUG = import.meta.env.DEV \ No newline at end of file +export const DEBUG = import.meta.env.DEV + +// NGINX +export const NGINX_BASE = "/etc/nginx" + +// API ROUTES +export const API_BASE = "/api" +export const PROTECTED_API_BASE = `${API_BASE}/program` + +// APP ROUTES +export const APP_BASE = "/app" +export const PROTECTED_APP_BASE = `${APP_BASE}/program` + +export const APP_HOME = `${PROTECTED_APP_BASE}/home` + +// Cookies +export const SESSION_COOKIE_NAME = "session" \ No newline at end of file diff --git a/src/lib/utils/jtw-utils.ts b/src/lib/utils/jtw-utils.ts index efc4e47..c350558 100644 --- a/src/lib/utils/jtw-utils.ts +++ b/src/lib/utils/jtw-utils.ts @@ -1,5 +1,5 @@ import * as jose from "jose"; -import { loadFile } from "./filesystem-utils"; +import { doesFileExists, loadFile } from "./filesystem-utils"; import { SERVER_PRIVATE_KEY_PATH, SERVER_PUBLIC_KEY_PATH } from "./constants"; import { openSSLInit } from "./openssl-utils"; import { logger } from "./logger"; @@ -16,7 +16,14 @@ export class JoseApp { JoseApp.assureNotInitialized() - await openSSLInit() + if ( + !await doesFileExists(SERVER_PRIVATE_KEY_PATH) || + !await doesFileExists(SERVER_PUBLIC_KEY_PATH) + ) { + await openSSLInit() + } + + JoseApp.privateKey = await JoseApp.loadPrivateKey() JoseApp.publicKey = await JoseApp.loadPublicKey() @@ -68,7 +75,7 @@ export class JoseApp { public static async verifyObject(jwt: string) { JoseApp.assureInitialized() - + let _payload: Uint8Array try { @@ -77,7 +84,7 @@ export class JoseApp { JoseApp.publicKey ) _payload = payload - } catch(err) { + } catch (err) { logger.debug(`Error: ${err}`, "JOSE Verify") return null } diff --git a/src/routes/api/login/+server.ts b/src/routes/api/login/+server.ts index 79efa49..aafb05c 100644 --- a/src/routes/api/login/+server.ts +++ b/src/routes/api/login/+server.ts @@ -1,8 +1,8 @@ import { error, json, text, type Cookies } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { UserApp } from '$lib/classes/users'; +import { User, UserApp } from '$lib/classes/users'; import { SessionApp } from '$lib/classes/sessions'; -import { AppData } from '$lib/classes/app-sessions'; +import { AppData } from '$lib/classes/appdata'; import { logger } from '$lib/utils/logger'; /*********************************************************** @@ -45,12 +45,18 @@ export const POST: RequestHandler = async ({ request, locals, cookies }) => { } userJson = tmpJSON + let user: User | null // If this fails, should be a 500 - const user = await UserApp.getUser( - userJson.username, - userJson.password - ) + try { + user = await UserApp.getUser( + userJson.username, + userJson.password + ) + } catch { + return error(400, "The provided credentials are non correct") + } + if (!user) { return error(400, "The provided credentials are not correct") diff --git a/src/routes/api/logout/+server.ts b/src/routes/api/logout/+server.ts new file mode 100644 index 0000000..6a0fbb1 --- /dev/null +++ b/src/routes/api/logout/+server.ts @@ -0,0 +1,51 @@ +import { error, json, text, type Cookies } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { User, UserApp } from '$lib/classes/users'; +import { SessionApp } from '$lib/classes/sessions'; +import { AppData } from '$lib/classes/appdata'; +import { logger } from '$lib/utils/logger'; +import { SESSION_COOKIE_NAME } from '$lib/utils/constants'; + +/*********************************************************** + * + * Author: Christian Risi 26/06/2025 + * + * + * + * +***********************************************************/ + +export const GET: RequestHandler = async ({ request, locals, cookies }) => { + + const req: Request = request + const local: App.Locals = locals + const cookie: Cookies = cookies + + logger.debug(`locals: ${local.session}`, "API Logout") + + cookie.delete( + SESSION_COOKIE_NAME, + { + path: "/" + } + ) + + return text("OK") +} + + +export const fallback: RequestHandler = async ({ }) => { + + // TODO: return method not allowed + const res = new Response( + null, + { + status: 405, + statusText: "Method Not Allowed", + headers: { + Allow: "GET" + } + } + ) + return res +}; \ No newline at end of file diff --git a/src/routes/api/register/+server.ts b/src/routes/api/register/+server.ts index aee2f58..66a8040 100644 --- a/src/routes/api/register/+server.ts +++ b/src/routes/api/register/+server.ts @@ -2,7 +2,7 @@ import { error, json, text, type Cookies } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { UserApp, User } from '$lib/classes/users'; import { SessionApp, Session } from '$lib/classes/sessions'; -import { AppData } from '$lib/classes/app-sessions'; +import { AppData } from '$lib/classes/appdata'; import { logger } from '$lib/utils/logger'; /*********************************************************** diff --git a/src/routes/app/login/+page.svelte b/src/routes/app/login/+page.svelte index 42ca939..9f925b5 100644 --- a/src/routes/app/login/+page.svelte +++ b/src/routes/app/login/+page.svelte @@ -1,4 +1,6 @@ @@ -49,4 +54,6 @@ {error} {/if} +Login +