Rework to add endpoint creation and better handling
This commit is contained in:
parent
3de4354458
commit
0fbbfec737
73
helper-scripts/services/custom-nginx.sh
Normal file
73
helper-scripts/services/custom-nginx.sh
Normal file
@ -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"
|
||||
}
|
||||
0
nginx/active/manual/http/.gitkeep
Normal file
0
nginx/active/manual/http/.gitkeep
Normal file
0
nginx/active/manual/http2/.gitkeep
Normal file
0
nginx/active/manual/http2/.gitkeep
Normal file
0
nginx/active/manual/stream/.gitkeep
Normal file
0
nginx/active/manual/stream/.gitkeep
Normal file
0
nginx/inactive/automatic/grpc/.gitkeep
Normal file
0
nginx/inactive/automatic/grpc/.gitkeep
Normal file
0
nginx/inactive/automatic/http/.gitkeep
Normal file
0
nginx/inactive/automatic/http/.gitkeep
Normal file
0
nginx/inactive/automatic/http2/.gitkeep
Normal file
0
nginx/inactive/automatic/http2/.gitkeep
Normal file
0
nginx/inactive/automatic/stream/.gitkeep
Normal file
0
nginx/inactive/automatic/stream/.gitkeep
Normal file
0
nginx/inactive/manual/custom/.gitkeep
Normal file
0
nginx/inactive/manual/custom/.gitkeep
Normal file
0
nginx/inactive/manual/grpc/.gitkeep
Normal file
0
nginx/inactive/manual/grpc/.gitkeep
Normal file
0
nginx/inactive/manual/http/.gitkeep
Normal file
0
nginx/inactive/manual/http/.gitkeep
Normal file
@ -12,9 +12,6 @@ server {
|
||||
# endpoint port
|
||||
listen PORT ssl;
|
||||
|
||||
# Uncomment if http2
|
||||
# http2 on;
|
||||
|
||||
# Here put the unencrypted
|
||||
# endpoint port
|
||||
location / {
|
||||
0
nginx/inactive/manual/http2/.gitkeep
Normal file
0
nginx/inactive/manual/http2/.gitkeep
Normal file
50
nginx/inactive/manual/http2/conf.example
Normal file
50
nginx/inactive/manual/http2/conf.example
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
0
nginx/inactive/manual/stream/.gitkeep
Normal file
0
nginx/inactive/manual/stream/.gitkeep
Normal file
@ -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;
|
||||
|
||||
}
|
||||
2
src/app.d.ts
vendored
2
src/app.d.ts
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
)
|
||||
@ -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()
|
||||
)
|
||||
|
||||
0
src/lib/broker-utils/FileSystem/SSLTerminations.ts
Normal file
0
src/lib/broker-utils/FileSystem/SSLTerminations.ts
Normal file
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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}`
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<SSLTerminationEndpoint>
|
||||
public static async createSSLEndpoint(
|
||||
servicePort: number,
|
||||
protocol: NginxProtocol,
|
||||
certificateURI: string,
|
||||
privateKeyURI: string,
|
||||
sslPort?: number,
|
||||
clearPort?: number,
|
||||
): Promise<SSLTerminationEndpoint> {
|
||||
|
||||
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}`)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
10
src/lib/classes/endpoints.ts
Normal file
10
src/lib/classes/endpoints.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { EndpointType } from "$lib/enums/endpoints";
|
||||
|
||||
export interface IEndpoint {
|
||||
|
||||
type: EndpointType
|
||||
name: string
|
||||
path: string
|
||||
hash: string
|
||||
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
253
src/lib/classes/ssl-termination-endpoint.ts
Normal file
253
src/lib/classes/ssl-termination-endpoint.ts
Normal file
@ -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<void>
|
||||
|
||||
// Creation should throw if something goes wrong
|
||||
// with reasons why
|
||||
createSSLTerminationSimple(
|
||||
name: string,
|
||||
servicePort: number,
|
||||
serviceEndpoint: string,
|
||||
certificateURI: string,
|
||||
privateKeyURI: string
|
||||
): Promise<SSLTermination>
|
||||
|
||||
createSSLTerminationComplete(
|
||||
name: string,
|
||||
sslPort: number,
|
||||
clearPort: number,
|
||||
servicePort: number,
|
||||
serviceEndpoint: string,
|
||||
certificateURI: string,
|
||||
privateKeyURI: string
|
||||
): Promise<SSLTermination>
|
||||
|
||||
|
||||
// Getting endpoints may be null, react over them
|
||||
getSSLTerminationByName(
|
||||
name: string
|
||||
): Promise<SSLTermination|null>
|
||||
|
||||
|
||||
// Throw if something goes wrong
|
||||
modifySSLTerminationByName(
|
||||
name: string,
|
||||
changes: SSLTerminationChanges
|
||||
): Promise<SSLTermination>
|
||||
|
||||
|
||||
deleteSSLTerminationByName(
|
||||
name: string
|
||||
): Promise<SSLTermination|null>
|
||||
|
||||
|
||||
getAllSSLTerminations(): Promise<SSLTermination[]>
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<SSLTermination> {
|
||||
|
||||
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<SSLTermination> {
|
||||
|
||||
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<SSLTermination|null> {
|
||||
|
||||
SSLTerminationEndpointApp.assureInitialized()
|
||||
|
||||
return await this.broker.getSSLTerminationByName(
|
||||
name
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Throw if something goes wrong
|
||||
public static async modifySSLTerminationByName(
|
||||
name: string,
|
||||
changes: SSLTerminationChanges
|
||||
): Promise<SSLTermination> {
|
||||
SSLTerminationEndpointApp.assureInitialized()
|
||||
|
||||
return await this.broker.modifySSLTerminationByName(
|
||||
name,
|
||||
changes
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
public static async deleteSSLTerminationByName(
|
||||
name: string
|
||||
): Promise<SSLTermination|null> {
|
||||
SSLTerminationEndpointApp.assureInitialized()
|
||||
|
||||
return await this.broker.deleteSSLTerminationByName(
|
||||
name
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
public static async getAllSSLTerminations(): Promise<SSLTermination[]> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,7 +2,7 @@ import type { Session } from "./sessions"
|
||||
|
||||
|
||||
export interface IUserBroker {
|
||||
|
||||
// TODO: change in init()
|
||||
createTable(): void
|
||||
createUser(username: string, password: string): Promise<User>
|
||||
getUser(username: string, password: string): Promise<User|null>
|
||||
@ -24,6 +24,10 @@ export class User {
|
||||
this.username = username
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `userID:\t${this.userID}\nusername:\t${this.username}`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
6
src/lib/enums/endpoints.ts
Normal file
6
src/lib/enums/endpoints.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export enum EndpointType {
|
||||
|
||||
SSL_TERMINATION = "SSL-Termination",
|
||||
MANUAL = "Manual"
|
||||
|
||||
}
|
||||
@ -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
|
||||
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"
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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")
|
||||
|
||||
51
src/routes/api/logout/+server.ts
Normal file
51
src/routes/api/logout/+server.ts
Normal file
@ -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
|
||||
};
|
||||
@ -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';
|
||||
|
||||
/***********************************************************
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { APP_HOME } from "$lib/utils/constants";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
|
||||
@ -27,11 +29,12 @@
|
||||
}
|
||||
)
|
||||
|
||||
if (res.status != 200) {
|
||||
error = res.statusText
|
||||
if (res.status !== 200) {
|
||||
error = `${res.status}: ${(await res.json()).message}`
|
||||
return
|
||||
}
|
||||
|
||||
redirect(302, "/app/program")
|
||||
goto(APP_HOME)
|
||||
|
||||
}
|
||||
|
||||
@ -50,4 +53,6 @@
|
||||
<span>{error}</span>
|
||||
{/if}
|
||||
|
||||
<a href="/app/register">Register</a>
|
||||
|
||||
|
||||
|
||||
3
src/routes/app/program/home/+page.svelte
Normal file
3
src/routes/app/program/home/+page.svelte
Normal file
@ -0,0 +1,3 @@
|
||||
<h1>Home</h1>
|
||||
|
||||
Welcome to SSL-Sniffer
|
||||
@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { APP_HOME } from "$lib/utils/constants";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
|
||||
@ -28,9 +30,12 @@
|
||||
)
|
||||
|
||||
if (res.status != 201) {
|
||||
error = res.statusText
|
||||
error = `${res.status}: ${(await res.json()).message}`
|
||||
return
|
||||
}
|
||||
|
||||
goto(APP_HOME)
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
@ -49,4 +54,6 @@
|
||||
<span>{error}</span>
|
||||
{/if}
|
||||
|
||||
<a href="/app/login">Login</a>
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user