Compare commits
No commits in common. "main-dev" and "main" have entirely different histories.
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
// Displayed name
|
// Displayed name
|
||||||
"name": "SSL-Sniffer",
|
"name": "Vulnbox",
|
||||||
|
|
||||||
// Service name from compose file
|
// Service name from compose file
|
||||||
"service": "ssl-sniffer",
|
"service": "vulnbox",
|
||||||
|
|
||||||
// Compose-File
|
// Compose-File
|
||||||
"dockerComposeFile": ["../compose.yaml"],
|
"dockerComposeFile": ["../compose.yaml"],
|
||||||
@ -12,8 +12,6 @@
|
|||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"svelte.svelte-vscode",
|
|
||||||
"vitest.explorer",
|
|
||||||
"william-voyek.vscode-nginx",
|
"william-voyek.vscode-nginx",
|
||||||
"fabiospampinato.vscode-highlight",
|
"fabiospampinato.vscode-highlight",
|
||||||
"fabiospampinato.vscode-todo-plus"
|
"fabiospampinato.vscode-todo-plus"
|
||||||
@ -22,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
// The WorkspaceFolder inside container
|
// The WorkspaceFolder inside container
|
||||||
"workspaceFolder": "/workspace",
|
"workspaceFolder": "/etc/nginx",
|
||||||
|
|
||||||
// Env in container
|
// Env in container
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
|
|||||||
31
.gitignore
vendored
31
.gitignore
vendored
@ -1,36 +1,7 @@
|
|||||||
test-results
|
|
||||||
node_modules
|
|
||||||
|
|
||||||
# Output
|
|
||||||
.output
|
|
||||||
.vercel
|
|
||||||
.netlify
|
|
||||||
.wrangler
|
|
||||||
/.svelte-kit
|
|
||||||
/build
|
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Env
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
!.env.test
|
|
||||||
|
|
||||||
# Vite
|
|
||||||
vite.config.js.timestamp-*
|
|
||||||
vite.config.ts.timestamp-*
|
|
||||||
|
|
||||||
# Custom
|
|
||||||
config/*/*
|
config/*/*
|
||||||
private/**
|
private/**
|
||||||
services/**
|
services/**
|
||||||
src/private/**
|
|
||||||
src/db/**
|
|
||||||
**/http/*
|
**/http/*
|
||||||
!**/*.gitkeep
|
!**/*.gitkeep
|
||||||
!**/*.example
|
!**/*.example
|
||||||
!**/http/example.conf
|
!**/http/example.conf
|
||||||
|
|
||||||
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"command": "npm run dev -- --open --host",
|
|
||||||
"name": "Node Launch server",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
10
DOCKERFILE
10
DOCKERFILE
@ -1,4 +1,4 @@
|
|||||||
FROM node:alpine
|
FROM alpine
|
||||||
|
|
||||||
ENV PATH="$PATH:/docker-bin"
|
ENV PATH="$PATH:/docker-bin"
|
||||||
|
|
||||||
@ -6,7 +6,7 @@ RUN apk update && apk upgrade
|
|||||||
RUN apk add nginx openrc \
|
RUN apk add nginx openrc \
|
||||||
openssl nginx-mod-stream \
|
openssl nginx-mod-stream \
|
||||||
nginx-mod-http-headers-more \
|
nginx-mod-http-headers-more \
|
||||||
tcpdump curl git
|
tcpdump
|
||||||
|
|
||||||
# NGINX
|
# NGINX
|
||||||
RUN adduser -D -g 'www' www
|
RUN adduser -D -g 'www' www
|
||||||
@ -19,13 +19,9 @@ RUN cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig
|
|||||||
RUN mkdir /run/openrc
|
RUN mkdir /run/openrc
|
||||||
RUN touch /run/openrc/softlevel
|
RUN touch /run/openrc/softlevel
|
||||||
|
|
||||||
# Make workdir
|
|
||||||
RUN mkdir /workspace
|
|
||||||
|
|
||||||
# Make entrypoint
|
|
||||||
WORKDIR /docker-bin
|
WORKDIR /docker-bin
|
||||||
|
|
||||||
COPY ./helper-scripts/scripts /docker-bin
|
COPY ./helper-scripts /docker-bin
|
||||||
|
|
||||||
RUN chmod +x /docker-bin/*
|
RUN chmod +x /docker-bin/*
|
||||||
|
|
||||||
|
|||||||
105
README.md
105
README.md
@ -1,38 +1,81 @@
|
|||||||
# sv
|
# SSL Sniffer
|
||||||
|
|
||||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
> [!CAUTION]
|
||||||
|
> While the name may suggest this software has `packet-sniffing`
|
||||||
|
> capabilities,
|
||||||
|
> this software only ***ease*** the `sniffing-process` by terminating `TLS`
|
||||||
|
> in a transparent way.
|
||||||
|
|
||||||
## Creating a project
|
## How to use the software
|
||||||
|
|
||||||
If you're seeing this, you've probably already done this step. Congrats!
|
- Have [Firegex](https://github.com/Pwnzer0tt1/firegex) installed on the `vulnbox` (OPTIONAL)
|
||||||
|
- Copy all keys on `ssl-sniffer/private/<service-name>/[key|cert].pem`
|
||||||
|
- Copy a template from one of the available templates:
|
||||||
|
- `grpc`: `cp ssl-sniffer/nginx/grpc/conf.example ssl-sniffer/nginx/grpc/<service-name>.conf`
|
||||||
|
- `http`: `cp ssl-sniffer/nginx/http/conf.example ssl-sniffer/nginx/http/<service-name>.conf`
|
||||||
|
- Modify the copied template
|
||||||
|
- Add a rule to hijack the port to the one specified in your conf (OPTIONAL)
|
||||||
|
- Run `docker compose up -d --build`
|
||||||
|
- Check that your service is still reachable
|
||||||
|
|
||||||
```bash
|
> [!TIP]
|
||||||
# create a new project in the current directory
|
> Remember to capture traffic from the `lo` interface, otherwise you won't
|
||||||
npx sv create
|
> see any benefit in setting such infrastructure
|
||||||
|
|
||||||
# create a new project in my-app
|
## Full example
|
||||||
npx sv create my-app
|
|
||||||
|
### Cheesy Cheats-API Template
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# CheesyAPI conf
|
||||||
|
|
||||||
|
|
||||||
|
# CheesyAPI 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 15555 ssl;
|
||||||
|
http2 on;
|
||||||
|
|
||||||
|
# Here put the unencrypted
|
||||||
|
# endpoint port
|
||||||
|
location / {
|
||||||
|
grpc_pass grpc://127.0.0.1:15554;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Put relevant keys here
|
||||||
|
ssl_certificate /services-keys/CheesyAPI/cert.pem;
|
||||||
|
ssl_certificate_key /services-keys/CheesyAPI/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:15554;
|
||||||
|
http2 on;
|
||||||
|
|
||||||
|
# Here put the original
|
||||||
|
# service endpoint port
|
||||||
|
location / {
|
||||||
|
grpc_pass grpcs://127.0.0.1:5555;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Developing
|

|
||||||
|
|
||||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# or start the server and open the app in a new browser tab
|
|
||||||
npm run dev -- --open
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
To create a production version of your app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
You can preview the production build with `npm run preview`.
|
|
||||||
|
|
||||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
|
||||||
|
|||||||
@ -6,6 +6,5 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./nginx:/etc/nginx/
|
- ./nginx:/etc/nginx/
|
||||||
- ./private/:/services-keys
|
- ./private/:/services-keys
|
||||||
- .:/workspace
|
|
||||||
network_mode: host
|
network_mode: host
|
||||||
entrypoint: entry.sh
|
entrypoint: entry.sh
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
|
||||||
|
|
||||||
test('home page has expected h1', async ({ page }) => {
|
|
||||||
await page.goto('/');
|
|
||||||
await expect(page.locator('h1')).toBeVisible();
|
|
||||||
});
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import { includeIgnoreFile } from '@eslint/compat';
|
|
||||||
import js from '@eslint/js';
|
|
||||||
import svelte from 'eslint-plugin-svelte';
|
|
||||||
import globals from 'globals';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import ts from 'typescript-eslint';
|
|
||||||
import svelteConfig from './svelte.config.js';
|
|
||||||
|
|
||||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
|
||||||
|
|
||||||
export default ts.config(
|
|
||||||
includeIgnoreFile(gitignorePath),
|
|
||||||
js.configs.recommended,
|
|
||||||
...ts.configs.recommended,
|
|
||||||
...svelte.configs.recommended,
|
|
||||||
{
|
|
||||||
languageOptions: {
|
|
||||||
globals: { ...globals.browser, ...globals.node }
|
|
||||||
},
|
|
||||||
rules: { // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
|
||||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
|
||||||
"no-undef": 'off' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
'**/*.svelte',
|
|
||||||
'**/*.svelte.ts',
|
|
||||||
'**/*.svelte.js'
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
extraFileExtensions: ['.svelte'],
|
|
||||||
parser: ts.parser,
|
|
||||||
svelteConfig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
}
|
|
||||||
@ -12,6 +12,9 @@ server {
|
|||||||
# endpoint port
|
# endpoint port
|
||||||
listen PORT ssl;
|
listen PORT ssl;
|
||||||
|
|
||||||
|
# Uncomment if http2
|
||||||
|
# http2 on;
|
||||||
|
|
||||||
# Here put the unencrypted
|
# Here put the unencrypted
|
||||||
# endpoint port
|
# endpoint port
|
||||||
location / {
|
location / {
|
||||||
@ -1,50 +0,0 @@
|
|||||||
# 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -35,14 +35,12 @@ http {
|
|||||||
|
|
||||||
|
|
||||||
# Includes virtual hosts configs.
|
# Includes virtual hosts configs.
|
||||||
include /etc/nginx/active/http/*.conf;
|
include /etc/nginx/http/*.conf;
|
||||||
|
include /etc/nginx/grpc/*.conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream {
|
stream {
|
||||||
|
|
||||||
# Include stream config
|
include /etc/nginx/stream*.conf;
|
||||||
include /etc/nginx/active/stream/*.conf;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
3269
package-lock.json
generated
3269
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "workspace",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"devDependencies": {
|
|
||||||
"@eslint/compat": "^1.2.5",
|
|
||||||
"@eslint/js": "^9.18.0",
|
|
||||||
"@playwright/test": "^1.49.1",
|
|
||||||
"@sveltejs/adapter-auto": "^6.0.0",
|
|
||||||
"@sveltejs/adapter-node": "^5.2.12",
|
|
||||||
"@sveltejs/kit": "^2.16.0",
|
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
||||||
"@vitest/browser": "^3.2.3",
|
|
||||||
"eslint": "^9.18.0",
|
|
||||||
"eslint-plugin-svelte": "^3.0.0",
|
|
||||||
"globals": "^16.0.0",
|
|
||||||
"playwright": "^1.53.0",
|
|
||||||
"svelte": "^5.0.0",
|
|
||||||
"svelte-check": "^4.0.0",
|
|
||||||
"typescript": "^5.0.0",
|
|
||||||
"typescript-eslint": "^8.20.0",
|
|
||||||
"vite": "^6.2.6",
|
|
||||||
"vitest": "^3.2.4",
|
|
||||||
"vitest-browser-svelte": "^0.1.0"
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite dev",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"prepare": "svelte-kit sync || echo ''",
|
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"test:unit": "vitest",
|
|
||||||
"test": "npm run test:unit -- --run && npm run test:e2e",
|
|
||||||
"test:e2e": "playwright test"
|
|
||||||
},
|
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
|
||||||
"argon2": "^0.43.0",
|
|
||||||
"jose": "^6.0.11"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { defineConfig } from '@playwright/test';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
webServer: {
|
|
||||||
command: 'npm run build && npm run preview',
|
|
||||||
port: 4173
|
|
||||||
},
|
|
||||||
testDir: 'e2e'
|
|
||||||
});
|
|
||||||
20
src/app.d.ts
vendored
20
src/app.d.ts
vendored
@ -1,20 +0,0 @@
|
|||||||
import type { AppData } from "$lib/server/classes/appdata";
|
|
||||||
|
|
||||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
|
||||||
// for information about these interfaces
|
|
||||||
declare global {
|
|
||||||
namespace App {
|
|
||||||
// interface Error {}
|
|
||||||
// interface PageData {}
|
|
||||||
// interface PageState {}
|
|
||||||
// interface Platform {}
|
|
||||||
|
|
||||||
interface Locals {
|
|
||||||
session: AppData | null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { };
|
|
||||||
12
src/app.html
12
src/app.html
@ -1,12 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
%sveltekit.head%
|
|
||||||
</head>
|
|
||||||
<body data-sveltekit-preload-data="hover">
|
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import type { ServerInit } from '@sveltejs/kit';
|
|
||||||
import { handles } from './lib/server/handles/handle';
|
|
||||||
import { DatabaseBrokerManager } from '$lib/server/broker-utils/SQLite/Database';
|
|
||||||
import { UserApp } from '$lib/server/classes/users';
|
|
||||||
import { UserDBBroker } from '$lib/server/broker-utils/SQLite/Users';
|
|
||||||
import { SessionApp } from '$lib/server/classes/sessions';
|
|
||||||
import { SessionDBBroker } from '$lib/server/broker-utils/SQLite/Sessions';
|
|
||||||
import { AppData } from '$lib/server/classes/appdata';
|
|
||||||
import { logger } from '$lib/server/utils/logger';
|
|
||||||
import { JoseApp } from '$lib/server/utils/jtw-utils';
|
|
||||||
import { EndpointManagerApp } from '$lib/server/classes/endpoints/endpoint-manager';
|
|
||||||
import { SSLTerminationEndpointApp } from '$lib/server/classes/endpoints/ssl-termination-endpoint';
|
|
||||||
import { SSLTerminationFSBroker } from '$lib/server/broker-utils/FileSystem/SSLTerminations';
|
|
||||||
import { ManualEndpointApp } from '$lib/server/classes/endpoints/manual-endpoint';
|
|
||||||
import { ManualFSBroker } from '$lib/server/broker-utils/FileSystem/Manual';
|
|
||||||
|
|
||||||
export const init: ServerInit = async () => {
|
|
||||||
|
|
||||||
logger.debug("Starting app", "App Init")
|
|
||||||
|
|
||||||
if (!DatabaseBrokerManager.ready) {
|
|
||||||
DatabaseBrokerManager.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!UserApp.ready) {
|
|
||||||
UserApp.init(
|
|
||||||
new UserDBBroker()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SessionApp.ready) {
|
|
||||||
SessionApp.init(
|
|
||||||
new SessionDBBroker()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!JoseApp.ready) {
|
|
||||||
// This is async
|
|
||||||
await JoseApp.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!EndpointManagerApp.ready) {
|
|
||||||
// This is async
|
|
||||||
await EndpointManagerApp.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SSLTerminationEndpointApp.ready) {
|
|
||||||
SSLTerminationEndpointApp.init(
|
|
||||||
new SSLTerminationFSBroker()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ManualEndpointApp.ready) {
|
|
||||||
ManualEndpointApp.init(
|
|
||||||
new ManualFSBroker()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
logger.debug("Init run successfully", "App Init")
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handle = handles
|
|
||||||
@ -1,465 +0,0 @@
|
|||||||
import type { FSHeader, IEndpointFS } from "$lib/server/broker-utils/FileSystem/endpoints/endpoints"
|
|
||||||
import { NGINX_ACTIVE, NGINX_BASE, NGINX_INACTIVE, NGINX_TRACKED } from "$lib/server/utils/constants"
|
|
||||||
import { doesFileExist, isDir, listFiles, loadFile } from "$lib/server/utils/filesystem-utils"
|
|
||||||
import { watch, type FSWatcher } from "node:fs"
|
|
||||||
import { parseConf } from "./utils"
|
|
||||||
import { logger } from "$lib/server/utils/logger"
|
|
||||||
import { EndpointStatus, EndpointType } from "$lib/server/enums/endpoints"
|
|
||||||
import { validatePath } from "$lib/shared/utils/path-utils"
|
|
||||||
|
|
||||||
export class EndpointBrokerManagerFS {
|
|
||||||
|
|
||||||
private static initialized = false
|
|
||||||
private static watcher: FSWatcher
|
|
||||||
/** Here we store all endpoints
|
|
||||||
* - Key: path
|
|
||||||
* - Value: any IEndpointFS
|
|
||||||
*/
|
|
||||||
private static endpoints: Map<string, IEndpointFS>
|
|
||||||
private static usedPorts: number[]
|
|
||||||
private static lastNginxReload: Date = new Date()
|
|
||||||
|
|
||||||
public static get ready() {
|
|
||||||
return EndpointBrokerManagerFS.initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async init() {
|
|
||||||
|
|
||||||
if (EndpointBrokerManagerFS.ready) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("Broker already initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
EndpointBrokerManagerFS.endpoints = new Map()
|
|
||||||
|
|
||||||
const candidateConfigurations = (await listFiles(NGINX_TRACKED, true))
|
|
||||||
const configurations: string[] = []
|
|
||||||
|
|
||||||
for (const candidatePath of candidateConfigurations) {
|
|
||||||
if (await isDir(candidatePath)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!candidatePath.endsWith(".conf")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations.push(candidatePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (const confPath of configurations) {
|
|
||||||
const file = await loadFile(confPath)
|
|
||||||
await file.lock()
|
|
||||||
|
|
||||||
const conf = await file.text()
|
|
||||||
const fileStats = await file.getStats()
|
|
||||||
|
|
||||||
const fsHeader: FSHeader = {
|
|
||||||
name: confPath.split("/").pop()!,
|
|
||||||
stats: fileStats,
|
|
||||||
path: confPath,
|
|
||||||
hash: await file.hash()
|
|
||||||
}
|
|
||||||
|
|
||||||
await file.release()
|
|
||||||
const endpoint = await parseConf(fsHeader, conf)
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Parsed Conf:\n${endpoint}`,
|
|
||||||
"Manager Init"
|
|
||||||
)
|
|
||||||
|
|
||||||
const relativePath = confPath.replace(NGINX_TRACKED, "")
|
|
||||||
EndpointBrokerManagerFS.endpoints.set(
|
|
||||||
relativePath,
|
|
||||||
endpoint
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Initialize a file watcher
|
|
||||||
EndpointBrokerManagerFS.watcher = EndpointBrokerManagerFS.watchNginxDirectory()
|
|
||||||
EndpointBrokerManagerFS.initialized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async createEndpoint(endpoint: IEndpointFS) {
|
|
||||||
if (endpoint.type === EndpointType.MANUAL) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("You can't create a manual conf automatically")
|
|
||||||
}
|
|
||||||
|
|
||||||
const REAL_PATH = `${NGINX_TRACKED}/${endpoint.path}`
|
|
||||||
|
|
||||||
if (await doesFileExist(REAL_PATH)) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("File already existant")
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = await loadFile(REAL_PATH, true)
|
|
||||||
await file.lock()
|
|
||||||
await file.write(endpoint.toConf())
|
|
||||||
await file.release()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async deleteEndpoint(path: string) {
|
|
||||||
|
|
||||||
validatePath(path)
|
|
||||||
|
|
||||||
const REAL_PATH = `${NGINX_TRACKED}/${path}`
|
|
||||||
|
|
||||||
if (! await doesFileExist(REAL_PATH)) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("This path does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = await loadFile(REAL_PATH)
|
|
||||||
await file.delete()
|
|
||||||
const endpoint = EndpointBrokerManagerFS.endpoints.get(
|
|
||||||
path
|
|
||||||
)
|
|
||||||
EndpointBrokerManagerFS.endpoints.delete(
|
|
||||||
path
|
|
||||||
)
|
|
||||||
|
|
||||||
return endpoint
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async changeEndpoint(path: string, newEndpoint: IEndpointFS) {
|
|
||||||
|
|
||||||
if (newEndpoint.type === EndpointType.MANUAL) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("Change of Manual endpoint is not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
validatePath(path)
|
|
||||||
|
|
||||||
const REAL_PATH = `${NGINX_TRACKED}/${path}`
|
|
||||||
|
|
||||||
if (! await doesFileExist(REAL_PATH)) {
|
|
||||||
// UGLY: more specific
|
|
||||||
throw new Error("The requested file does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
const epFile = await loadFile(REAL_PATH)
|
|
||||||
await epFile.lock()
|
|
||||||
|
|
||||||
await epFile.write(
|
|
||||||
newEndpoint.toConf()
|
|
||||||
)
|
|
||||||
|
|
||||||
await epFile.release()
|
|
||||||
|
|
||||||
EndpointBrokerManagerFS.endpoints.set(path, newEndpoint)
|
|
||||||
|
|
||||||
const status = await EndpointBrokerManagerFS.getStatus(path)
|
|
||||||
|
|
||||||
if (status === EndpointStatus.ACTIVE) {
|
|
||||||
await this.activateEndpoint(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async activateEndpoint(path: string): Promise<boolean> {
|
|
||||||
|
|
||||||
validatePath(path)
|
|
||||||
|
|
||||||
|
|
||||||
const endpoint = EndpointBrokerManagerFS.getEndpointByPath(path)
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("This specified endpoint doesn't exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
const activePath = `${NGINX_ACTIVE}/${path}`
|
|
||||||
|
|
||||||
if (! await doesFileExist(activePath)) {
|
|
||||||
const file = await loadFile(activePath, true)
|
|
||||||
await file.write(
|
|
||||||
endpoint.toConf()
|
|
||||||
)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = await loadFile(activePath)
|
|
||||||
await file.lock()
|
|
||||||
|
|
||||||
const fileHash = await file.hash()
|
|
||||||
|
|
||||||
if (endpoint.hash === fileHash) {
|
|
||||||
await file.release()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
await file.write(
|
|
||||||
endpoint.toConf()
|
|
||||||
)
|
|
||||||
|
|
||||||
await file.release()
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async deactivateEndpoint(path: string): Promise<boolean> {
|
|
||||||
|
|
||||||
validatePath(path)
|
|
||||||
|
|
||||||
const endpoint = EndpointBrokerManagerFS.getEndpointByPath(path)
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("This specified endpoint doesn't exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
const activePath = `${NGINX_ACTIVE}/${path}`
|
|
||||||
|
|
||||||
if (! await doesFileExist(activePath)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = await loadFile(activePath)
|
|
||||||
await file.delete()
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static getEndpointByPath(path: string): IEndpointFS | null {
|
|
||||||
|
|
||||||
validatePath(path)
|
|
||||||
|
|
||||||
const endpoint = EndpointBrokerManagerFS.endpoints.get(
|
|
||||||
path
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoint
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async getAll(): Promise<IEndpointFS[]> {
|
|
||||||
return Array.from(EndpointBrokerManagerFS.endpoints.values())
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getStatus(path: string) {
|
|
||||||
|
|
||||||
validatePath(path)
|
|
||||||
|
|
||||||
const REAL_ACTIVE_PATH = `${NGINX_ACTIVE}/${path}`
|
|
||||||
const REAL_TRACKED_PATH = `${NGINX_TRACKED}/${path}`
|
|
||||||
|
|
||||||
if (await doesFileExist(REAL_ACTIVE_PATH)) {
|
|
||||||
return EndpointStatus.ACTIVE
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await doesFileExist(REAL_TRACKED_PATH)) {
|
|
||||||
return EndpointStatus.INACTIVE
|
|
||||||
}
|
|
||||||
|
|
||||||
// UGLY: more specific
|
|
||||||
throw new Error("This path is non existant")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: private methods
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is the only one that adds or removes files from
|
|
||||||
* our manager. No other method should mess up with the mapping
|
|
||||||
* of the manager
|
|
||||||
*
|
|
||||||
* @returns FSWatcher, to avoid having it garbage collected
|
|
||||||
*/
|
|
||||||
private static watchNginxDirectory(): FSWatcher {
|
|
||||||
|
|
||||||
const OPTIONS = {
|
|
||||||
recursive: true
|
|
||||||
}
|
|
||||||
|
|
||||||
const NGINX_TRACK = NGINX_TRACKED
|
|
||||||
|
|
||||||
const WATCHER = watch(NGINX_TRACK, OPTIONS, async (eventType, filename) => {
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`EVENT: ${eventType}\nFile: ${filename}`,
|
|
||||||
"Watcher "
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!filename) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const RELATIVE_PATH = filename
|
|
||||||
const FULL_PATH = `${NGINX_TRACK}/${RELATIVE_PATH}`
|
|
||||||
|
|
||||||
if (!RELATIVE_PATH.endsWith(".conf")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: check if it's a directory, if so, skip
|
|
||||||
|
|
||||||
if (await isDir(FULL_PATH)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UGLY: there may be race conditions, rarely, but
|
|
||||||
// UGLY: there may be
|
|
||||||
// UGLY: probably solved
|
|
||||||
|
|
||||||
// TODO: Find a way to lock files
|
|
||||||
// TODO: probably solved
|
|
||||||
switch (eventType) {
|
|
||||||
|
|
||||||
case "change": {
|
|
||||||
|
|
||||||
const oldEndpoint = EndpointBrokerManagerFS.endpoints.get(
|
|
||||||
RELATIVE_PATH
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if (!oldEndpoint) {
|
|
||||||
logger.debug(`File changed but was never tracked\nPATH: ${FULL_PATH}`, "EP Manager")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing to do, it's not managed by us
|
|
||||||
if (oldEndpoint.type === EndpointType.MANUAL) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const file = await loadFile(FULL_PATH)
|
|
||||||
await file.lock()
|
|
||||||
|
|
||||||
// NOTE: HERE USE FILE
|
|
||||||
const newHash = await file.hash()
|
|
||||||
const oldHash = oldEndpoint.hash
|
|
||||||
|
|
||||||
|
|
||||||
if (newHash === oldHash) {
|
|
||||||
// Files are equal
|
|
||||||
// or we are very unlucky
|
|
||||||
await file.release()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: HERE USE FILE
|
|
||||||
const stats = await file.getStats()
|
|
||||||
const hash = await file.hash()
|
|
||||||
const conf = await file.text()
|
|
||||||
|
|
||||||
const fsHeader: FSHeader = {
|
|
||||||
name: filename!.split("/").pop()!,
|
|
||||||
stats: stats,
|
|
||||||
path: FULL_PATH,
|
|
||||||
hash: hash
|
|
||||||
}
|
|
||||||
|
|
||||||
const newEndpoint = parseConf(fsHeader, conf)
|
|
||||||
|
|
||||||
// Check if files are trying to represent
|
|
||||||
// the same endpoint
|
|
||||||
if (oldEndpoint.headerHash() !== newEndpoint.headerHash()) {
|
|
||||||
// Files are not equal
|
|
||||||
// or we are very unlucky
|
|
||||||
await file.release()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endpoints are different, but changes were never
|
|
||||||
// coming from here
|
|
||||||
logger.debug(
|
|
||||||
"Corrupted file detected", "EP Manager Corruption Detected"
|
|
||||||
)
|
|
||||||
await file.write(oldEndpoint.toConf())
|
|
||||||
|
|
||||||
|
|
||||||
await file.release()
|
|
||||||
// NOTE: HERE DO NOT USE FILE
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basically it's rename
|
|
||||||
default: {
|
|
||||||
const isNew = await doesFileExist(FULL_PATH)
|
|
||||||
|
|
||||||
if (!isNew) {
|
|
||||||
// This means that the file doesn't exist
|
|
||||||
// let's just acknowledge this
|
|
||||||
|
|
||||||
// UGLY: not checking for false values
|
|
||||||
// UGLY: hints that something went wrong
|
|
||||||
EndpointBrokerManagerFS.endpoints.delete(
|
|
||||||
RELATIVE_PATH
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Technically the file is new
|
|
||||||
const file = await loadFile(FULL_PATH)
|
|
||||||
await file.lock()
|
|
||||||
|
|
||||||
// NOTE: HERE USE FILE
|
|
||||||
const stats = await file.getStats()
|
|
||||||
const hash = await file.hash()
|
|
||||||
const conf = await file.text()
|
|
||||||
|
|
||||||
await file.release()
|
|
||||||
|
|
||||||
// NOTE: HERE DO NOT USE FILE
|
|
||||||
|
|
||||||
// UGLY: forcing typecasting
|
|
||||||
const fsHeader: FSHeader = {
|
|
||||||
name: filename!.split("/").pop()!,
|
|
||||||
stats: stats,
|
|
||||||
path: FULL_PATH,
|
|
||||||
hash: hash
|
|
||||||
}
|
|
||||||
const endpoint = parseConf(fsHeader, conf)
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Endpoint:\n${endpoint}`,
|
|
||||||
"Watcher Parsing"
|
|
||||||
)
|
|
||||||
|
|
||||||
EndpointBrokerManagerFS.endpoints.set(
|
|
||||||
RELATIVE_PATH,
|
|
||||||
endpoint
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return WATCHER
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import type { IManualBroker, Manual } from "$lib/server/classes/endpoints/manual-endpoint";
|
|
||||||
import { EndpointType } from "$lib/server/enums/endpoints";
|
|
||||||
import { EndpointBrokerManagerFS } from "./Endpoint-Manager";
|
|
||||||
import type { ManualFS } from "./endpoints/manual-fs";
|
|
||||||
|
|
||||||
export class ManualFSBroker implements IManualBroker {
|
|
||||||
public async init(): Promise<void> {
|
|
||||||
// Do nothing?
|
|
||||||
}
|
|
||||||
public async getManualByPath(path: string): Promise<Manual| null> {
|
|
||||||
const endpoint = EndpointBrokerManagerFS.getEndpointByPath(path)
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endpoint.type !== EndpointType.MANUAL) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("This is not a Manual Endpoint")
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoint.toIEndpoint() as Manual
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllManuals(): Promise<Manual[]> {
|
|
||||||
const endpoints = (await EndpointBrokerManagerFS.getAll()).filter(async (endpoint) => {
|
|
||||||
if (endpoint.type !== EndpointType.MANUAL) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}) as ManualFS[]
|
|
||||||
|
|
||||||
return endpoints.map( (endpoint) => {
|
|
||||||
return endpoint.toIEndpoint() as Manual
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async activateEndpointByPath(path: string): Promise<boolean> {
|
|
||||||
return await EndpointBrokerManagerFS.activateEndpoint(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deactivateEndpointByPath(path: string): Promise<boolean> {
|
|
||||||
return await EndpointBrokerManagerFS.deactivateEndpoint(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
import type { ISSLTerminationBroker, SSLTermination, SSLTerminationChanges } from "$lib/server/classes/endpoints/ssl-termination-endpoint"
|
|
||||||
import { EndpointType } from "$lib/server/enums/endpoints"
|
|
||||||
import type { NginxProtocol } from "$lib/server/enums/protocols"
|
|
||||||
import { isPortAvailable, validatePort } from "$lib/server/utils/ports-utils"
|
|
||||||
import { validatePath } from "$lib/shared/utils/path-utils"
|
|
||||||
import { EndpointBrokerManagerFS } from "./Endpoint-Manager"
|
|
||||||
import { SSLTerminationFS } from "./endpoints/ssltermination-fs"
|
|
||||||
|
|
||||||
export class SSLTerminationFSBroker implements ISSLTerminationBroker {
|
|
||||||
|
|
||||||
|
|
||||||
public async init(): Promise<void> {
|
|
||||||
// Do nothing?
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async createSSLTermination(
|
|
||||||
name: string,
|
|
||||||
sslPort: number,
|
|
||||||
clearPort: number,
|
|
||||||
servicePort: number,
|
|
||||||
serviceEndpoint: string,
|
|
||||||
protocol: NginxProtocol,
|
|
||||||
certificateURI: string,
|
|
||||||
privateKeyURI: string
|
|
||||||
): Promise<SSLTermination> {
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isPortAvailable(sslPort) ||
|
|
||||||
!isPortAvailable(clearPort)
|
|
||||||
) {
|
|
||||||
// UGLY be specific
|
|
||||||
throw new Error("some ports were already in use")
|
|
||||||
}
|
|
||||||
|
|
||||||
validatePort(servicePort)
|
|
||||||
|
|
||||||
const newEndpoint = new SSLTerminationFS(
|
|
||||||
name,
|
|
||||||
null,
|
|
||||||
sslPort,
|
|
||||||
clearPort,
|
|
||||||
servicePort,
|
|
||||||
serviceEndpoint,
|
|
||||||
protocol,
|
|
||||||
certificateURI,
|
|
||||||
privateKeyURI
|
|
||||||
)
|
|
||||||
|
|
||||||
await EndpointBrokerManagerFS.createEndpoint(newEndpoint)
|
|
||||||
return (await this.getSSLTerminationFSByPath(newEndpoint.path))?.toIEndpoint() as SSLTermination
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async activateEndpointByPath(path: string): Promise<boolean> {
|
|
||||||
return await EndpointBrokerManagerFS.activateEndpoint(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async deactivateEndpointByPath(path: string): Promise<boolean> {
|
|
||||||
return await EndpointBrokerManagerFS.deactivateEndpoint(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async getSSLTerminationByPath(path: string): Promise<SSLTermination | null> {
|
|
||||||
const endpoint = await this.getSSLTerminationFSByPath(path)
|
|
||||||
return endpoint?.toIEndpoint() as SSLTermination
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async modifySSLTerminationByPath(path: string, changes: SSLTerminationChanges): Promise<SSLTermination> {
|
|
||||||
const oldEndpoint = await this.getSSLTerminationFSByPath(path)
|
|
||||||
|
|
||||||
if (!oldEndpoint) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("There was no old endpoint at this path")
|
|
||||||
}
|
|
||||||
|
|
||||||
const newEndpoint = new SSLTerminationFS(
|
|
||||||
changes.name ?? oldEndpoint.name,
|
|
||||||
oldEndpoint.stats,
|
|
||||||
changes.sslPort ?? oldEndpoint.sslPort,
|
|
||||||
changes.clearPort ?? oldEndpoint.clearPort,
|
|
||||||
changes.servicePort ?? oldEndpoint.servicePort,
|
|
||||||
changes.serviceEndpoint ?? oldEndpoint.serviceEndpoint,
|
|
||||||
changes.protocol ?? oldEndpoint.protocol,
|
|
||||||
changes.certificateURI ?? oldEndpoint.certificateURI,
|
|
||||||
changes.privateKeyURI ?? oldEndpoint.privateKeyURI
|
|
||||||
)
|
|
||||||
|
|
||||||
EndpointBrokerManagerFS.changeEndpoint(path, newEndpoint)
|
|
||||||
|
|
||||||
return newEndpoint.toIEndpoint() as SSLTermination
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async deleteSSLTerminationByPath(path: string): Promise<SSLTermination | null> {
|
|
||||||
const candidate = await EndpointBrokerManagerFS.deleteEndpoint(path)
|
|
||||||
|
|
||||||
if (!candidate) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidate as SSLTerminationFS
|
|
||||||
}
|
|
||||||
|
|
||||||
// Technically expensive to implement
|
|
||||||
public async getAllSSLTerminations(): Promise<SSLTermination[]> {
|
|
||||||
|
|
||||||
const endpoints = (await EndpointBrokerManagerFS.getAll()).filter((endpoint) => {
|
|
||||||
if (endpoint.type !== EndpointType.SSL_TERMINATION) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}) as SSLTerminationFS[]
|
|
||||||
|
|
||||||
return endpoints.map((endpoint) => {
|
|
||||||
return endpoint.toIEndpoint() as SSLTermination
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getSSLTerminationFSByPath(path: string): Promise<SSLTerminationFS | null> {
|
|
||||||
const endpoint = EndpointBrokerManagerFS.getEndpointByPath(path)
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endpoint.type !== EndpointType.SSL_TERMINATION) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("This is not an SSLEndpoint")
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoint as SSLTerminationFS
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import type { IEndpoint } from "$lib/server/classes/endpoints/endpoints-interfaces";
|
|
||||||
import { EndpointType } from "$lib/server/enums/endpoints";
|
|
||||||
import type { FileStats } from "$lib/server/utils/filesystem-utils";
|
|
||||||
|
|
||||||
export interface IEndpointFS {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies which Enpoint this is
|
|
||||||
*/
|
|
||||||
type: EndpointType
|
|
||||||
/** File name */
|
|
||||||
name: string
|
|
||||||
/**
|
|
||||||
* File stats,
|
|
||||||
* useful to understand
|
|
||||||
* whether the file has been actually
|
|
||||||
* parsed by nginx during last reload
|
|
||||||
*/
|
|
||||||
stats: FileStats | null
|
|
||||||
/** File path */
|
|
||||||
path: string
|
|
||||||
/** Hash of the file */
|
|
||||||
hash: string
|
|
||||||
|
|
||||||
/** converts the IEndpoint to */
|
|
||||||
toConf(): string
|
|
||||||
|
|
||||||
ports(): number[]
|
|
||||||
|
|
||||||
headerHash(): string
|
|
||||||
|
|
||||||
toIEndpoint(): IEndpoint
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FSHeader = {
|
|
||||||
name: string,
|
|
||||||
stats: FileStats
|
|
||||||
path: string,
|
|
||||||
hash: string
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import { EndpointType } from "$lib/server/enums/endpoints";
|
|
||||||
import type { Stats } from "fs";
|
|
||||||
import type { FSHeader, IEndpointFS } from "./endpoints";
|
|
||||||
import type { IEndpoint } from "$lib/server/classes/endpoints/endpoints-interfaces";
|
|
||||||
import { Manual } from "$lib/server/classes/endpoints/manual-endpoint";
|
|
||||||
|
|
||||||
// TODO: add broker implementation
|
|
||||||
|
|
||||||
export class ManualFS implements IEndpointFS {
|
|
||||||
|
|
||||||
private static __type = EndpointType.MANUAL
|
|
||||||
private static __hash = "emanuel"
|
|
||||||
|
|
||||||
public get type() {
|
|
||||||
return ManualFS.__type
|
|
||||||
}
|
|
||||||
|
|
||||||
public get hash() {
|
|
||||||
return ManualFS.__hash
|
|
||||||
}
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
stats: Stats;
|
|
||||||
path: string;
|
|
||||||
body: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string,
|
|
||||||
stats: Stats,
|
|
||||||
path: string,
|
|
||||||
body: string
|
|
||||||
) {
|
|
||||||
this.name = name
|
|
||||||
this.stats = stats
|
|
||||||
this.path = path
|
|
||||||
this.body = body
|
|
||||||
}
|
|
||||||
toIEndpoint(): IEndpoint {
|
|
||||||
|
|
||||||
return new Manual(
|
|
||||||
this.name,
|
|
||||||
this.path,
|
|
||||||
this.body
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
toConf(): string {
|
|
||||||
return this.body
|
|
||||||
}
|
|
||||||
|
|
||||||
ports(): number[] {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
headerHash(): string {
|
|
||||||
return this.hash
|
|
||||||
}
|
|
||||||
|
|
||||||
public static parseConf(fsHeader: FSHeader, conf: string) {
|
|
||||||
return new ManualFS(
|
|
||||||
fsHeader.name,
|
|
||||||
fsHeader.stats,
|
|
||||||
fsHeader.path,
|
|
||||||
conf
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,294 +0,0 @@
|
|||||||
import { EndpointType } from "$lib/server/enums/endpoints"
|
|
||||||
import { httpVersion, protocolToString, proxyProtocol, secureProtocol, stringToProtocol, type NginxProtocol } from "$lib/server/enums/protocols"
|
|
||||||
import { validatePort } from "$lib/server/utils/ports-utils"
|
|
||||||
import type { Stats } from "fs"
|
|
||||||
import type { FSHeader, IEndpointFS } from "./endpoints"
|
|
||||||
import { createHeader, parseDefaultHeader, parseGenericHeader } from "../utils"
|
|
||||||
import { hashUtil } from "$lib/server/utils/filesystem-utils"
|
|
||||||
import type { IEndpoint } from "$lib/server/classes/endpoints/endpoints-interfaces"
|
|
||||||
import { SSLTermination, type ISSLTerminationBroker, type SSLTerminationChanges } from "$lib/server/classes/endpoints/ssl-termination-endpoint"
|
|
||||||
import { EndpointBrokerManagerFS } from "../Endpoint-Manager"
|
|
||||||
|
|
||||||
// TODO: add broker implementation
|
|
||||||
|
|
||||||
|
|
||||||
export class SSLTerminationFS implements IEndpointFS {
|
|
||||||
|
|
||||||
private static __type = EndpointType.SSL_TERMINATION
|
|
||||||
|
|
||||||
public get type() {
|
|
||||||
return SSLTerminationFS.__type
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public name: string
|
|
||||||
public stats: Stats|null
|
|
||||||
public get path() {
|
|
||||||
return `${this.protocol}/name.conf`
|
|
||||||
}
|
|
||||||
public get hash() {
|
|
||||||
return hashUtil(
|
|
||||||
this.toConf()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
public sslPort: number
|
|
||||||
public clearPort: number
|
|
||||||
public servicePort: number
|
|
||||||
public serviceEndpoint: string
|
|
||||||
public protocol: NginxProtocol
|
|
||||||
public certificateURI: string
|
|
||||||
public privateKeyURI: string
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string,
|
|
||||||
stats: Stats|null,
|
|
||||||
sslPort: number,
|
|
||||||
clearPort: number,
|
|
||||||
servicePort: number,
|
|
||||||
serviceEndpoint: string,
|
|
||||||
protocol: NginxProtocol,
|
|
||||||
certificateURI: string,
|
|
||||||
privateKeyURI: string
|
|
||||||
) {
|
|
||||||
|
|
||||||
this.name = name
|
|
||||||
this.stats = stats
|
|
||||||
this.sslPort = sslPort
|
|
||||||
this.clearPort = clearPort
|
|
||||||
this.servicePort = servicePort
|
|
||||||
this.serviceEndpoint = serviceEndpoint
|
|
||||||
this.protocol = protocol
|
|
||||||
this.certificateURI = certificateURI
|
|
||||||
this.privateKeyURI = privateKeyURI
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public toIEndpoint(): IEndpoint {
|
|
||||||
return new SSLTermination(
|
|
||||||
this.name,
|
|
||||||
this.path,
|
|
||||||
this.sslPort,
|
|
||||||
this.clearPort,
|
|
||||||
this.servicePort,
|
|
||||||
this.serviceEndpoint,
|
|
||||||
this.protocol,
|
|
||||||
this.certificateURI,
|
|
||||||
this.privateKeyURI
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
headerHash(): string {
|
|
||||||
return hashUtil(
|
|
||||||
this.createHeader()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
toConf(): string {
|
|
||||||
|
|
||||||
const HEADER = this.createHeader()
|
|
||||||
const SSL_SERVER = this.createSSLServer()
|
|
||||||
const CLEAR_SERVER = this.createClearServer()
|
|
||||||
|
|
||||||
const CONF = HEADER + SSL_SERVER + CLEAR_SERVER
|
|
||||||
|
|
||||||
return CONF
|
|
||||||
}
|
|
||||||
|
|
||||||
ports(): number[] {
|
|
||||||
|
|
||||||
return [
|
|
||||||
this.sslPort,
|
|
||||||
this.clearPort
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static parseConf(fsHeader: FSHeader, conf: string): SSLTerminationFS {
|
|
||||||
// TODO: parse header
|
|
||||||
const defHeader = parseDefaultHeader(conf)
|
|
||||||
|
|
||||||
const keyValue = parseGenericHeader(conf)
|
|
||||||
|
|
||||||
const name = keyValue.get("NAME")
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
throw new Error("Could not parse")
|
|
||||||
}
|
|
||||||
|
|
||||||
const protocol = stringToProtocol(
|
|
||||||
keyValue.get("PROTOCOL") ?? ""
|
|
||||||
)
|
|
||||||
|
|
||||||
const sslPort = Number.parseInt(
|
|
||||||
keyValue.get(
|
|
||||||
"SSL_PORT"
|
|
||||||
) ?? ""
|
|
||||||
)
|
|
||||||
|
|
||||||
validatePort(sslPort)
|
|
||||||
|
|
||||||
const clearPort = Number.parseInt(
|
|
||||||
keyValue.get(
|
|
||||||
"CLEAR_PORT"
|
|
||||||
) ?? ""
|
|
||||||
)
|
|
||||||
|
|
||||||
validatePort(clearPort)
|
|
||||||
|
|
||||||
const servicePort = Number.parseInt(
|
|
||||||
keyValue.get(
|
|
||||||
"SERVICE_PORT"
|
|
||||||
) ?? ""
|
|
||||||
)
|
|
||||||
|
|
||||||
validatePort(servicePort)
|
|
||||||
|
|
||||||
const serviceEndpoint = keyValue.get("SERVICE_ENDPOINT")
|
|
||||||
|
|
||||||
if (!serviceEndpoint) {
|
|
||||||
throw new Error("Could not parse")
|
|
||||||
}
|
|
||||||
|
|
||||||
const certificateURI = keyValue.get("CERTIFICATE_PATH")
|
|
||||||
|
|
||||||
if (!certificateURI) {
|
|
||||||
throw new Error("Could not parse")
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateKeyURI = keyValue.get("KEY_PATH")
|
|
||||||
|
|
||||||
if (!privateKeyURI) {
|
|
||||||
throw new Error("Could not parse")
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SSLTerminationFS(
|
|
||||||
name,
|
|
||||||
fsHeader.stats,
|
|
||||||
sslPort,
|
|
||||||
clearPort,
|
|
||||||
servicePort,
|
|
||||||
serviceEndpoint,
|
|
||||||
protocol,
|
|
||||||
certificateURI,
|
|
||||||
privateKeyURI
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private createHeader() {
|
|
||||||
return createHeader(
|
|
||||||
this,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
key: "NAME",
|
|
||||||
value: this.name
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "PROTOCOL",
|
|
||||||
value: protocolToString(this.protocol)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "SSL_PORT",
|
|
||||||
value: this.sslPort
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "CLEAR_PORT",
|
|
||||||
value: this.clearPort
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "SERVICE_PORT",
|
|
||||||
value: this.servicePort
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "SERVICE_ENDPOINT",
|
|
||||||
value: this.serviceEndpoint
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "CERTIFICATE_PATH",
|
|
||||||
value: this.certificateURI
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "KEY_PATH",
|
|
||||||
value: this.privateKeyURI
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UGLY: refactor into a flexible method
|
|
||||||
private createSSLServer() {
|
|
||||||
|
|
||||||
const CLEAR_PROTOCOL = `${this.protocol}`
|
|
||||||
const HTTP_VERSION = httpVersion(this.protocol)
|
|
||||||
const PROXY_OPTION = proxyProtocol(this.protocol)
|
|
||||||
|
|
||||||
// UGLY: put to constants
|
|
||||||
let conf = [
|
|
||||||
"server {\n",
|
|
||||||
"\tmore_clear_headers Server;\n",
|
|
||||||
`\tlisten ${this.sslPort};`
|
|
||||||
]
|
|
||||||
|
|
||||||
if (HTTP_VERSION !== 1) {
|
|
||||||
conf.push(
|
|
||||||
`\thttp${HTTP_VERSION} on;`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check if we should support less protocols
|
|
||||||
conf.push(
|
|
||||||
"\n",
|
|
||||||
"\tlocation / {",
|
|
||||||
`\t\t${PROXY_OPTION} ${CLEAR_PROTOCOL}://127.0.0.1:${this.clearPort};`,
|
|
||||||
"\t}",
|
|
||||||
"\n",
|
|
||||||
`ssl_certificate ${this.certificateURI};`,
|
|
||||||
`ssl_certificate_key ${this.privateKeyURI};`,
|
|
||||||
"\tssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;",
|
|
||||||
"\n",
|
|
||||||
"}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return conf.join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
private createClearServer() {
|
|
||||||
|
|
||||||
const SSL_PROTOCOL = secureProtocol(this.protocol)
|
|
||||||
const HTTP_VERSION = httpVersion(this.protocol)
|
|
||||||
const PROXY_OPTION = proxyProtocol(this.protocol)
|
|
||||||
|
|
||||||
// UGLY: put to constants
|
|
||||||
let conf = [
|
|
||||||
"server {\n",
|
|
||||||
"\tmore_clear_headers Server;\n",
|
|
||||||
`\tlisten ${this.clearPort};`
|
|
||||||
]
|
|
||||||
|
|
||||||
if (HTTP_VERSION !== 1) {
|
|
||||||
conf.push(
|
|
||||||
`\thttp${HTTP_VERSION} on;`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check if we should support less protocols
|
|
||||||
conf.push(
|
|
||||||
"\n",
|
|
||||||
"\tlocation / {",
|
|
||||||
`\t\t${PROXY_OPTION} ${SSL_PROTOCOL}://${this.serviceEndpoint}:${this.servicePort};`,
|
|
||||||
"\t}",
|
|
||||||
"\n",
|
|
||||||
"}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return conf.join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
import { EndpointType, matchEndpoint } from "$lib/server/enums/endpoints"
|
|
||||||
import type { FSHeader, IEndpointFS } from "./endpoints/endpoints"
|
|
||||||
import { ManualFS } from "./endpoints/manual-fs"
|
|
||||||
import { SSLTerminationFS } from "./endpoints/ssltermination-fs"
|
|
||||||
|
|
||||||
export const HEADER_BOUNDARY = "**********************************************************"
|
|
||||||
export const HEADER_UPPER = `/*${HEADER_BOUNDARY}`
|
|
||||||
export const HEADER_LOWER = `${HEADER_BOUNDARY}*/`
|
|
||||||
export const SPLITTING = ": "
|
|
||||||
export const DEFAULT_HEADER_LEN = 5
|
|
||||||
export const CUSTOM_HEADER_START = DEFAULT_HEADER_LEN + 1
|
|
||||||
|
|
||||||
export type HeaderKeyValueFS = {
|
|
||||||
key: string,
|
|
||||||
value: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DefaultHeader = {
|
|
||||||
type: EndpointType,
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseConf(
|
|
||||||
fsHeader: FSHeader,
|
|
||||||
conf: string
|
|
||||||
): IEndpointFS {
|
|
||||||
|
|
||||||
const TYPE = getType(conf)
|
|
||||||
|
|
||||||
switch (TYPE) {
|
|
||||||
|
|
||||||
case EndpointType.SSL_TERMINATION:
|
|
||||||
return SSLTerminationFS.parseConf(fsHeader, conf)
|
|
||||||
default:
|
|
||||||
return ManualFS.parseConf(fsHeader, conf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createHeader(endpoint: IEndpointFS, variables?: HeaderKeyValueFS[]): string {
|
|
||||||
|
|
||||||
if (!variables) {
|
|
||||||
variables = []
|
|
||||||
}
|
|
||||||
|
|
||||||
let header = [
|
|
||||||
HEADER_UPPER,
|
|
||||||
"SSL-SNIFFER AUTOMATICALLY GENERATED",
|
|
||||||
`TYPE: ${endpoint.type}`,
|
|
||||||
`NAME: ${endpoint.name}`,
|
|
||||||
HEADER_BOUNDARY
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const variable of variables) {
|
|
||||||
header.push(
|
|
||||||
`${variable.key}${SPLITTING}${variable.value}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
header.push(HEADER_LOWER)
|
|
||||||
return header.join("\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function getType(conf: string): EndpointType {
|
|
||||||
/** Row (remember array order) where we find the type */
|
|
||||||
const TYPE_DATA_ROW = 2
|
|
||||||
/** What separator was used */
|
|
||||||
const TYPE_DATA_SEPARATOR = " "
|
|
||||||
/** Whether the value is on the left(0) of right(1) */
|
|
||||||
const TYPE_DATA_ARRAY_POSITION = 1
|
|
||||||
const DEFAULT_LABEL = EndpointType.MANUAL
|
|
||||||
|
|
||||||
const confLines = conf.split("\n")
|
|
||||||
|
|
||||||
if (confLines.length < 4 ) {
|
|
||||||
return matchEndpoint(DEFAULT_LABEL)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
const label = confLines[TYPE_DATA_ROW]
|
|
||||||
.split(TYPE_DATA_SEPARATOR)[TYPE_DATA_ARRAY_POSITION]
|
|
||||||
|
|
||||||
return matchEndpoint(label)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseDefaultHeader(conf: string) : DefaultHeader {
|
|
||||||
|
|
||||||
const confLines = conf.split("\n")
|
|
||||||
|
|
||||||
const HEADER = confLines.slice(0, DEFAULT_HEADER_LEN + 1)
|
|
||||||
|
|
||||||
const type = matchEndpoint(HEADER[2].split(" ")[1])
|
|
||||||
const name = HEADER[3].split(" ")[1]
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: type,
|
|
||||||
name: name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseGenericHeader(conf: string) : Map<string, string> {
|
|
||||||
const confLines = conf.split("\n")
|
|
||||||
|
|
||||||
const headerProbableLines = confLines.slice(CUSTOM_HEADER_START)
|
|
||||||
const keyValueMap = new Map<string, string>()
|
|
||||||
|
|
||||||
let index = 0
|
|
||||||
let parsed = false
|
|
||||||
|
|
||||||
while (!parsed) {
|
|
||||||
const line = headerProbableLines[index]
|
|
||||||
|
|
||||||
if (line === HEADER_LOWER) {
|
|
||||||
parsed = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyValue = line.split(SPLITTING)
|
|
||||||
|
|
||||||
keyValueMap.set(
|
|
||||||
keyValue[0],
|
|
||||||
keyValue[1]
|
|
||||||
)
|
|
||||||
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyValueMap
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import { DB_PATH } from "$lib/server/utils/constants";
|
|
||||||
import { logger } from "$lib/server/utils/logger";
|
|
||||||
// TODO: remove bun dependencies
|
|
||||||
import { DatabaseSync } from 'node:sqlite';
|
|
||||||
|
|
||||||
export class DatabaseBrokerManager {
|
|
||||||
|
|
||||||
private static initialized = false
|
|
||||||
|
|
||||||
private static db: DatabaseSync
|
|
||||||
|
|
||||||
// UGLY: should be more flexible
|
|
||||||
public static init() {
|
|
||||||
|
|
||||||
logger.debug("Initializing Database", "SSLSnifferApp")
|
|
||||||
|
|
||||||
if (DatabaseBrokerManager.initialized) {
|
|
||||||
logger.debug("database initialized Twice?", "SSLSnifferApp")
|
|
||||||
throw new Error("SSLSniffer has already been initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
DatabaseBrokerManager.db = DatabaseBrokerManager.initDatabase()
|
|
||||||
DatabaseBrokerManager.initialized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get ready() {
|
|
||||||
return DatabaseBrokerManager.initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static prepare(query: string) {
|
|
||||||
|
|
||||||
logger.debug(`Statement: ${query}`, "SQLite Query Preparation")
|
|
||||||
|
|
||||||
return DatabaseBrokerManager.db.prepare(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static initDatabase() {
|
|
||||||
|
|
||||||
const db = new DatabaseSync(DB_PATH)
|
|
||||||
|
|
||||||
// Improve performance
|
|
||||||
db.exec("PRAGMA journal_mode = WAL;");
|
|
||||||
// Activate cascade operations
|
|
||||||
db.exec("PRAGMA foreign_keys = ON")
|
|
||||||
return db
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static closeDatabase() {
|
|
||||||
DatabaseBrokerManager.db.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS sessions (
|
|
||||||
session_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER UNIQUE NOT NULL,
|
|
||||||
session_token TEXT UNIQUE NOT NULL,
|
|
||||||
FOREIGN KEY (user_id) references users(user_id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO sessions (user_id, session_token)
|
|
||||||
VALUES (@userID, @token);
|
|
||||||
|
|
||||||
SELECT session_id, user_id, session_token
|
|
||||||
FROM sessions
|
|
||||||
WHERE session_token = @token;
|
|
||||||
|
|
||||||
SELECT session_id, user_id, session_token
|
|
||||||
FROM sessions
|
|
||||||
WHERE user_id = @userID;
|
|
||||||
|
|
||||||
SELECT session_id, user_id, session_token
|
|
||||||
FROM sessions
|
|
||||||
WHERE session_id = @sessionID;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
username TEXT UNIQUE NOT NULL ,
|
|
||||||
password_hash TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO users (username, password_hash)
|
|
||||||
VALUES (@username, @password);
|
|
||||||
|
|
||||||
SELECT user_id, username, password_hash
|
|
||||||
FROM users
|
|
||||||
WHERE username = @username;
|
|
||||||
|
|
||||||
SELECT user_id, username, password_hash
|
|
||||||
FROM users
|
|
||||||
WHERE user_id = @user_id;
|
|
||||||
|
|
||||||
UPDATE users
|
|
||||||
SET password_hash = @newPassword
|
|
||||||
WHERE username = @username;
|
|
||||||
|
|
||||||
@ -1,207 +0,0 @@
|
|||||||
import { Session, type ISessionBroker } from "$lib/server/classes/sessions"
|
|
||||||
import { logger } from "$lib/server/utils/logger"
|
|
||||||
import { DatabaseBrokerManager } from "./Database"
|
|
||||||
|
|
||||||
class SessionDB {
|
|
||||||
|
|
||||||
public session_id: number
|
|
||||||
public user_id: number
|
|
||||||
public session_token: string
|
|
||||||
|
|
||||||
// TODO: support fingerprinting to
|
|
||||||
// TODO: increase security
|
|
||||||
// TODO: while logging in
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
session_id: number,
|
|
||||||
user_id: number,
|
|
||||||
session_token: string
|
|
||||||
) {
|
|
||||||
this.session_id = session_id
|
|
||||||
this.user_id = user_id
|
|
||||||
this.session_token = session_token
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SessionDBBroker implements ISessionBroker {
|
|
||||||
|
|
||||||
private static initialized = false
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
if (SessionDBBroker.initialized) {
|
|
||||||
// UGLY: make more specific
|
|
||||||
throw Error("SessionBroker has already been initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Correctly initialized", "SessionDBBroker")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
createTable(): void {
|
|
||||||
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
|
||||||
session_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER UNIQUE NOT NULL,
|
|
||||||
session_token TEXT UNIQUE NOT NULL,
|
|
||||||
FOREIGN KEY (user_id) references users(user_id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
stmt.run()
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
createSessionFromUserID(userID: number): Session {
|
|
||||||
|
|
||||||
// Check for existing Sessions
|
|
||||||
const sessionCheck = this.getSessionFromUserID(userID)
|
|
||||||
|
|
||||||
if (sessionCheck) {
|
|
||||||
// UGLY: more specific
|
|
||||||
throw new Error("There's already a session associated with the user")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new Session
|
|
||||||
const token : string = crypto.randomUUID();
|
|
||||||
|
|
||||||
// Insert into DB
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
INSERT INTO sessions (user_id, session_token)
|
|
||||||
VALUES (@userID, @token);
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
stmt.run({
|
|
||||||
userID: userID,
|
|
||||||
token: token
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// Check if Session has been successfully created
|
|
||||||
const session = this.getSessionFromUserID(userID)
|
|
||||||
|
|
||||||
logger.debug(`session: ${session}`, "DB Session Create")
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
// UGLY: more specific
|
|
||||||
throw new Error("Something wrong happened during the creationg of the session")
|
|
||||||
}
|
|
||||||
|
|
||||||
return session
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
getSessionFromUserID(userID: number): Session | null {
|
|
||||||
const candidateSession = this.getSessionDBFromUserID(userID)
|
|
||||||
|
|
||||||
if (!candidateSession) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Session(
|
|
||||||
candidateSession.session_id,
|
|
||||||
candidateSession.user_id,
|
|
||||||
candidateSession.session_token
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
getSessionFromToken(token: string): Session | null {
|
|
||||||
const candidateSession = this.getSessionDBFromToken(token)
|
|
||||||
|
|
||||||
if (!candidateSession) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Session(
|
|
||||||
candidateSession.session_id,
|
|
||||||
candidateSession.user_id,
|
|
||||||
candidateSession.session_token
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSessionDBFromToken(token: string): SessionDB | null {
|
|
||||||
|
|
||||||
logger.debug(`token: ${token}`, "DB Session from Token")
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
SELECT session_id, user_id, session_token
|
|
||||||
FROM sessions
|
|
||||||
WHERE session_token = @token;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
const sessions = stmt.all({
|
|
||||||
token: token
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
return this.parseSessionDBUnique(sessions)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSessionDBFromUserID(userID: number): SessionDB | null {
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
SELECT session_id, user_id, session_token
|
|
||||||
FROM sessions
|
|
||||||
WHERE user_id = @userID;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
const sessions = stmt.all({
|
|
||||||
userID: userID
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
return this.parseSessionDBUnique(sessions)
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSessionDBFromSessionID(sessionID: number): SessionDB | null {
|
|
||||||
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
SELECT session_id, user_id, session_token
|
|
||||||
FROM sessions
|
|
||||||
WHERE session_id = @sessionID;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
const sessions = stmt.all({
|
|
||||||
sessionID: sessionID
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
return this.parseSessionDBUnique(sessions)
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseSessionDBUnique(sessions: any[]) {
|
|
||||||
|
|
||||||
if (sessions.length > 1) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("Duplicate session?")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessions.length < 1) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const session: any = sessions[0]
|
|
||||||
|
|
||||||
return new SessionDB(
|
|
||||||
session.session_id,
|
|
||||||
session.user_id,
|
|
||||||
session.session_token
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,230 +0,0 @@
|
|||||||
import type { Session, SessionApp } from "$lib/server/classes/sessions";
|
|
||||||
import { User, type IUserBroker } from "$lib/server/classes/users";
|
|
||||||
import { logger } from "$lib/server/utils/logger";
|
|
||||||
import { DatabaseBrokerManager } from "./Database";
|
|
||||||
import * as argon2 from "argon2";
|
|
||||||
|
|
||||||
|
|
||||||
class UserDB {
|
|
||||||
|
|
||||||
public user_id: number
|
|
||||||
public username: string
|
|
||||||
public password_hash: string
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
user_id: number,
|
|
||||||
username: string,
|
|
||||||
password_hash: string
|
|
||||||
) {
|
|
||||||
this.user_id = user_id
|
|
||||||
this.username = username
|
|
||||||
this.password_hash = password_hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UserDBBroker implements IUserBroker {
|
|
||||||
|
|
||||||
private static initialized = false
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
if (UserDBBroker.initialized) {
|
|
||||||
// UGLY: make more specific
|
|
||||||
throw Error("UserDB has been already initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Correctly initialized", "UserDBBroker")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public createTable(): void {
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
username TEXT UNIQUE NOT NULL ,
|
|
||||||
password_hash TEXT NOT NULL
|
|
||||||
);
|
|
||||||
`
|
|
||||||
)
|
|
||||||
stmt.run()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createUser(username: string, password: string): Promise<User> {
|
|
||||||
|
|
||||||
this.validateUniqueness(username)
|
|
||||||
|
|
||||||
const insertUser = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
INSERT INTO users (username, password_hash)
|
|
||||||
VALUES (@username, @password);
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
const passwordHash = await argon2.hash(password)
|
|
||||||
|
|
||||||
try {
|
|
||||||
insertUser.run({
|
|
||||||
username: username,
|
|
||||||
password: passwordHash
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
// UGLY: make this more specific
|
|
||||||
console.error("Duplicate after check??")
|
|
||||||
// UGLY: create a logger
|
|
||||||
console.error(`Insert User ${Date.now()}:\n\t${error}\n\n`)
|
|
||||||
throw new Error("You can't have duplicates")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const user = await this.getUser(username, password)
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
// UGLY: make this more specific
|
|
||||||
throw new Error("Something went wrong during the creation of the user")
|
|
||||||
}
|
|
||||||
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getUser(username: string, password: string): Promise<User | null> {
|
|
||||||
|
|
||||||
const userToVerify = this.getUserFromUsername(username)
|
|
||||||
|
|
||||||
if (!userToVerify) {
|
|
||||||
// UGLY: make this more specific
|
|
||||||
throw new Error("The specified user does not exist on the database")
|
|
||||||
}
|
|
||||||
|
|
||||||
let match = false
|
|
||||||
|
|
||||||
try {
|
|
||||||
match = await argon2.verify(userToVerify.password_hash, password)
|
|
||||||
} catch (error) {
|
|
||||||
// UGLY: make this more specific
|
|
||||||
throw new Error("Argon2 had an error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new User(
|
|
||||||
userToVerify.user_id,
|
|
||||||
userToVerify.username
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updatePassword(username: string, password: string, newPassword: string): Promise<void> {
|
|
||||||
|
|
||||||
const userToUpdate = await this.getUser(username, password)
|
|
||||||
|
|
||||||
if (!userToUpdate) {
|
|
||||||
// UGLY: make this more specific
|
|
||||||
throw new Error("Something went wrong while fetching the user")
|
|
||||||
}
|
|
||||||
const passwordHash = await argon2.hash(newPassword)
|
|
||||||
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
UPDATE users
|
|
||||||
SET password_hash = @newPassword
|
|
||||||
WHERE username = @username;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
stmt.run({
|
|
||||||
username: userToUpdate.username,
|
|
||||||
newPassword: passwordHash
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public getUserFromSession(session: Session): User {
|
|
||||||
|
|
||||||
const userDB = this.getUserFromUserID(session.userID)
|
|
||||||
|
|
||||||
if (!userDB) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("Could not find user inside database")
|
|
||||||
}
|
|
||||||
|
|
||||||
return new User(
|
|
||||||
userDB.user_id,
|
|
||||||
userDB.username
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private validateUniqueness(username: string) {
|
|
||||||
|
|
||||||
const user = this.getUserFromUsername(username)
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("User is already on the system")
|
|
||||||
}
|
|
||||||
|
|
||||||
private getUserFromUsername(username: string): UserDB | null {
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
SELECT user_id, username, password_hash
|
|
||||||
FROM users
|
|
||||||
WHERE username = @username;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
const user: any | null = stmt.get({
|
|
||||||
username: username,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UserDB(
|
|
||||||
user.user_id,
|
|
||||||
user.username,
|
|
||||||
user.password_hash
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private getUserFromUserID(userID: number): UserDB | null {
|
|
||||||
|
|
||||||
const stmt = DatabaseBrokerManager.prepare(
|
|
||||||
`
|
|
||||||
SELECT user_id, username, password_hash
|
|
||||||
FROM users
|
|
||||||
WHERE user_id = @user_id;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
const user: any | null = stmt.get({
|
|
||||||
user_id: userID,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UserDB(
|
|
||||||
user.user_id,
|
|
||||||
user.username,
|
|
||||||
user.password_hash
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
import type { Cookies } from "@sveltejs/kit";
|
|
||||||
import { SessionApp, type Session } from "./sessions";
|
|
||||||
import { UserApp, type User } from "./users";
|
|
||||||
import { JoseApp } from "$lib/server/utils/jtw-utils";
|
|
||||||
import { logger } from "$lib/server/utils/logger";
|
|
||||||
import { SESSION_COOKIE_NAME } from "$lib/shared/constants";
|
|
||||||
|
|
||||||
export class AppData {
|
|
||||||
|
|
||||||
public session: Session
|
|
||||||
public user: User
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
session: Session,
|
|
||||||
user: User
|
|
||||||
) {
|
|
||||||
this.session = session
|
|
||||||
this.user = user
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async toCookie() {
|
|
||||||
|
|
||||||
const signedSession = await JoseApp.signObject({token: this.session.sessionToken})
|
|
||||||
const encodedSession = btoa(signedSession)
|
|
||||||
|
|
||||||
return encodedSession
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async extractAppDataFromCookies(cookies: Cookies) {
|
|
||||||
|
|
||||||
const encodedSessionToken = cookies.get(SESSION_COOKIE_NAME)
|
|
||||||
|
|
||||||
logger.debug(`Session Cookie: ${encodedSessionToken}`, "APP Session Building 1")
|
|
||||||
|
|
||||||
if (!encodedSessionToken) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedSessionToken = atob(encodedSessionToken)
|
|
||||||
|
|
||||||
logger.debug(`Session Cookie: ${decodedSessionToken}`, "APP Session Building 2")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
const session = SessionApp.getSessionFromToken(sessionToken)
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = UserApp.getUserFromSession(
|
|
||||||
session
|
|
||||||
)
|
|
||||||
|
|
||||||
return new AppData(
|
|
||||||
session,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString() {
|
|
||||||
return `User:\t${this.user}\nSession:\t${this.session}`
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import { EndpointBrokerManagerFS } from "$lib/server/broker-utils/FileSystem/Endpoint-Manager"
|
|
||||||
import type { IEndpoint } from "./endpoints-interfaces"
|
|
||||||
|
|
||||||
|
|
||||||
// UGLY: refactor this
|
|
||||||
export class EndpointManagerApp {
|
|
||||||
|
|
||||||
private static initialized = false
|
|
||||||
/** Here we store all endpoints
|
|
||||||
* - Key: path
|
|
||||||
* - Value: any IEndpointFS
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static get ready() {
|
|
||||||
return EndpointManagerApp.initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async init() {
|
|
||||||
await EndpointBrokerManagerFS.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async activateEndpoint(path: string): Promise<boolean> {
|
|
||||||
return await EndpointBrokerManagerFS.activateEndpoint(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async deactivateEndpoint(path: string): Promise<boolean> {
|
|
||||||
return await EndpointBrokerManagerFS.deactivateEndpoint(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getStatus(path: string) {
|
|
||||||
return await EndpointBrokerManagerFS.getStatus(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static getEndpointByPath(path: string): IEndpoint | null {
|
|
||||||
|
|
||||||
const endpoint = EndpointBrokerManagerFS.getEndpointByPath(path)
|
|
||||||
|
|
||||||
if(!endpoint) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoint.toIEndpoint()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async getAll(): Promise<IEndpoint[]> {
|
|
||||||
|
|
||||||
const endpoints = await EndpointBrokerManagerFS.getAll()
|
|
||||||
|
|
||||||
return endpoints.map( (endpoint) => {
|
|
||||||
return endpoint.toIEndpoint()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import type { EndpointType } from "$lib/server/enums/endpoints";
|
|
||||||
|
|
||||||
export interface IEndpoint {
|
|
||||||
|
|
||||||
type: EndpointType
|
|
||||||
name: string
|
|
||||||
path: string
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
import { EndpointType } from "$lib/server/enums/endpoints";
|
|
||||||
import type { init } from "../../../../hooks.server";
|
|
||||||
import type { IEndpoint } from "./endpoints-interfaces";
|
|
||||||
|
|
||||||
export interface IManualBroker {
|
|
||||||
|
|
||||||
init(): Promise<void>
|
|
||||||
|
|
||||||
getManualByPath(path: string): Promise<Manual| null>
|
|
||||||
|
|
||||||
getAllManuals(): Promise<Manual[]>
|
|
||||||
|
|
||||||
activateEndpointByPath(
|
|
||||||
path: string
|
|
||||||
): Promise<boolean>
|
|
||||||
|
|
||||||
deactivateEndpointByPath(
|
|
||||||
path: string
|
|
||||||
): Promise<boolean>
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class Manual implements IEndpoint {
|
|
||||||
|
|
||||||
|
|
||||||
private static __type = EndpointType.SSL_TERMINATION
|
|
||||||
|
|
||||||
public get type() {
|
|
||||||
return Manual.__type
|
|
||||||
}
|
|
||||||
|
|
||||||
public name: string;
|
|
||||||
public path: string;
|
|
||||||
public body: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string,
|
|
||||||
path: string,
|
|
||||||
body: string
|
|
||||||
) {
|
|
||||||
this.name = name
|
|
||||||
this.path = path
|
|
||||||
this.body = body
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class ManualEndpointApp {
|
|
||||||
|
|
||||||
private static initialized: boolean = false
|
|
||||||
private static broker: IManualBroker
|
|
||||||
|
|
||||||
public static get ready() {
|
|
||||||
return ManualEndpointApp.initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
public static init(broker: IManualBroker) {
|
|
||||||
ManualEndpointApp.assureNotInitialized()
|
|
||||||
|
|
||||||
ManualEndpointApp.broker = broker
|
|
||||||
broker.init()
|
|
||||||
ManualEndpointApp.initialized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async getManualByPath(path: string) {
|
|
||||||
ManualEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return ManualEndpointApp.broker.getManualByPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getAllManual() {
|
|
||||||
ManualEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await ManualEndpointApp.broker.getAllManuals()
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async activateEndpointByPath(
|
|
||||||
path: string
|
|
||||||
): Promise<boolean> {
|
|
||||||
ManualEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await ManualEndpointApp.broker.activateEndpointByPath(path)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async deactivateEndpointByPath(
|
|
||||||
path: string
|
|
||||||
): Promise<boolean> {
|
|
||||||
ManualEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await ManualEndpointApp.broker.deactivateEndpointByPath(path)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static assureNotInitialized() {
|
|
||||||
if (ManualEndpointApp.initialized) {
|
|
||||||
// UGLY: more specific
|
|
||||||
throw new Error("SSLTerminationEndpointApp has been already initialized")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static assureInitialized() {
|
|
||||||
if (ManualEndpointApp.initialized) {
|
|
||||||
// UGLY: more specific
|
|
||||||
throw new Error("SSLTerminationEndpointApp has not been initialized yet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,250 +0,0 @@
|
|||||||
import { EndpointType } from "$lib/server/enums/endpoints"
|
|
||||||
import type { NginxProtocol } from "$lib/server/enums/protocols"
|
|
||||||
import { validatePort } from "$lib/server/utils/ports-utils"
|
|
||||||
import type { IEndpoint } from "./endpoints-interfaces"
|
|
||||||
|
|
||||||
// TODO: inherit from a super class
|
|
||||||
export interface ISSLTerminationBroker {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the Broker and everything related to it
|
|
||||||
*/
|
|
||||||
init(): Promise<void>
|
|
||||||
|
|
||||||
// TODO: in the next version support
|
|
||||||
// TODO: creation of endpoints
|
|
||||||
// TODO: according to path
|
|
||||||
|
|
||||||
// NOTES: it's useless to generate ports backend
|
|
||||||
// NOTES: generate them frontend and validate backend
|
|
||||||
|
|
||||||
createSSLTermination(
|
|
||||||
name: string,
|
|
||||||
sslPort: number,
|
|
||||||
clearPort: number,
|
|
||||||
servicePort: number,
|
|
||||||
serviceEndpoint: string,
|
|
||||||
protocol: NginxProtocol,
|
|
||||||
certificateURI: string,
|
|
||||||
privateKeyURI: string
|
|
||||||
): Promise<SSLTermination>
|
|
||||||
|
|
||||||
activateEndpointByPath(
|
|
||||||
path: string
|
|
||||||
): Promise<boolean>
|
|
||||||
|
|
||||||
deactivateEndpointByPath(
|
|
||||||
path: string
|
|
||||||
): Promise<boolean>
|
|
||||||
|
|
||||||
|
|
||||||
// Getting endpoints may be null, react over them
|
|
||||||
getSSLTerminationByPath(
|
|
||||||
path: string
|
|
||||||
): Promise<SSLTermination|null>
|
|
||||||
|
|
||||||
|
|
||||||
// Throw if something goes wrong
|
|
||||||
modifySSLTerminationByPath(
|
|
||||||
path: string,
|
|
||||||
changes: SSLTerminationChanges
|
|
||||||
): Promise<SSLTermination>
|
|
||||||
|
|
||||||
|
|
||||||
deleteSSLTerminationByPath(
|
|
||||||
path: 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.SSL_TERMINATION
|
|
||||||
|
|
||||||
public get type() {
|
|
||||||
return SSLTermination.__type
|
|
||||||
}
|
|
||||||
|
|
||||||
public name: string
|
|
||||||
public path: string
|
|
||||||
public sslPort: number
|
|
||||||
public clearPort: number
|
|
||||||
public servicePort: number
|
|
||||||
public serviceEndpoint: string
|
|
||||||
public protocol: NginxProtocol
|
|
||||||
public certificateURI: string
|
|
||||||
public privateKeyURI: string
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string,
|
|
||||||
path: string,
|
|
||||||
sslPort: number,
|
|
||||||
clearPort: number,
|
|
||||||
servicePort: number,
|
|
||||||
serviceEndpoint: string,
|
|
||||||
protocol: NginxProtocol,
|
|
||||||
certificateURI: string,
|
|
||||||
privateKeyURI: string
|
|
||||||
) {
|
|
||||||
|
|
||||||
this.name = name
|
|
||||||
this.path = path
|
|
||||||
this.sslPort = sslPort
|
|
||||||
this.clearPort = clearPort
|
|
||||||
this.servicePort = servicePort
|
|
||||||
this.serviceEndpoint = serviceEndpoint
|
|
||||||
this.protocol = protocol
|
|
||||||
this.certificateURI = certificateURI
|
|
||||||
this.privateKeyURI = privateKeyURI
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SSLTerminationChanges = {
|
|
||||||
name?: string,
|
|
||||||
path?: 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
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async createSSLTermination(
|
|
||||||
name: string,
|
|
||||||
sslPort: number,
|
|
||||||
clearPort: number,
|
|
||||||
servicePort: number,
|
|
||||||
serviceEndpoint: string,
|
|
||||||
protocol: NginxProtocol,
|
|
||||||
certificateURI: string,
|
|
||||||
privateKeyURI: string
|
|
||||||
): Promise<SSLTermination> {
|
|
||||||
|
|
||||||
SSLTerminationEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await this.broker.createSSLTermination(
|
|
||||||
name,
|
|
||||||
sslPort,
|
|
||||||
clearPort,
|
|
||||||
servicePort,
|
|
||||||
serviceEndpoint,
|
|
||||||
protocol,
|
|
||||||
certificateURI,
|
|
||||||
privateKeyURI
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Getting endpoints may be null, react over them
|
|
||||||
public static async getSSLTerminationByPath(
|
|
||||||
name: string
|
|
||||||
): Promise<SSLTermination|null> {
|
|
||||||
|
|
||||||
SSLTerminationEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await this.broker.getSSLTerminationByPath(
|
|
||||||
name
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Throw if something goes wrong
|
|
||||||
public static async modifySSLTerminationByPath(
|
|
||||||
name: string,
|
|
||||||
changes: SSLTerminationChanges
|
|
||||||
): Promise<SSLTermination> {
|
|
||||||
SSLTerminationEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await this.broker.modifySSLTerminationByPath(
|
|
||||||
name,
|
|
||||||
changes
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async deleteSSLTerminationByPath(
|
|
||||||
name: string
|
|
||||||
): Promise<SSLTermination|null> {
|
|
||||||
SSLTerminationEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await this.broker.deleteSSLTerminationByPath(
|
|
||||||
name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async getAllSSLTerminations(): Promise<SSLTermination[]> {
|
|
||||||
SSLTerminationEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await SSLTerminationEndpointApp.broker.getAllSSLTerminations()
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async activateEndpointByPath(path: string) {
|
|
||||||
SSLTerminationEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await SSLTerminationEndpointApp.broker.activateEndpointByPath(
|
|
||||||
path
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async deactivateEndpointByPath(path: string) {
|
|
||||||
SSLTerminationEndpointApp.assureInitialized()
|
|
||||||
|
|
||||||
return await SSLTerminationEndpointApp.broker.deactivateEndpointByPath(
|
|
||||||
path
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
export interface ISessionBroker {
|
|
||||||
|
|
||||||
// TODO: change in init()
|
|
||||||
createTable(): void
|
|
||||||
createSessionFromUserID(userID: number): Session
|
|
||||||
getSessionFromUserID(userID: number): Session | null
|
|
||||||
getSessionFromToken(token: string): Session | null
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Session {
|
|
||||||
|
|
||||||
public sessionID: number
|
|
||||||
public userID: number
|
|
||||||
public sessionToken: string
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
sessionID: number,
|
|
||||||
userID: number,
|
|
||||||
sessionToken: string
|
|
||||||
) {
|
|
||||||
this.sessionID = sessionID
|
|
||||||
this.userID = userID
|
|
||||||
this.sessionToken = sessionToken
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString() {
|
|
||||||
return this.sessionToken
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SessionApp {
|
|
||||||
|
|
||||||
private static broker: ISessionBroker
|
|
||||||
private static initialized: boolean = false
|
|
||||||
|
|
||||||
public static get ready() {
|
|
||||||
return SessionApp.initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
public static init(broker: ISessionBroker) {
|
|
||||||
if (SessionApp.initialized) {
|
|
||||||
// UGLY: make this Error more specific
|
|
||||||
throw Error("SessionApp has already been initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
SessionApp.initialized = true
|
|
||||||
SessionApp.broker = broker
|
|
||||||
SessionApp.broker.createTable()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createSessionFromUserID(userID: number): Session {
|
|
||||||
SessionApp.assertInitialized()
|
|
||||||
return SessionApp.broker.createSessionFromUserID(userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getSessionFromUserID(userID: number): Session | null {
|
|
||||||
SessionApp.assertInitialized()
|
|
||||||
return SessionApp.broker.getSessionFromUserID(userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getSessionFromToken(token: string): Session | null {
|
|
||||||
SessionApp.assertInitialized()
|
|
||||||
return SessionApp.broker.getSessionFromToken(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static assertInitialized() {
|
|
||||||
|
|
||||||
if (!SessionApp.initialized) {
|
|
||||||
// UGLY: make more specific
|
|
||||||
throw Error("SessionApp has't been Initialized yet!")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
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>
|
|
||||||
updatePassword(username: string, password: string, newPassword: string): Promise<void>
|
|
||||||
getUserFromSession(session: Session): User
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class User {
|
|
||||||
|
|
||||||
public userID: number
|
|
||||||
public username: string
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
userID: number,
|
|
||||||
username: string
|
|
||||||
) {
|
|
||||||
this.userID = userID
|
|
||||||
this.username = username
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString() {
|
|
||||||
return `userID:\t${this.userID}\nusername:\t${this.username}`
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class UserApp {
|
|
||||||
|
|
||||||
private static broker : IUserBroker
|
|
||||||
private static initialized: boolean = false
|
|
||||||
|
|
||||||
public static get ready() {
|
|
||||||
return UserApp.initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static init(broker: IUserBroker) {
|
|
||||||
|
|
||||||
if (UserApp.initialized) {
|
|
||||||
// UGLY: make this Error more specific
|
|
||||||
throw Error("UserApp has already been initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
UserApp.initialized = true
|
|
||||||
UserApp.broker = broker
|
|
||||||
UserApp.broker.createTable()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static getUserFromSession(session: Session): User {
|
|
||||||
UserApp.assertInitialized()
|
|
||||||
return UserApp.broker.getUserFromSession(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async createUser(username: string, password: string): Promise<User> {
|
|
||||||
UserApp.assertInitialized()
|
|
||||||
return await UserApp.broker.createUser(username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getUser(username: string, password: string): Promise<User|null> {
|
|
||||||
UserApp.assertInitialized()
|
|
||||||
return await UserApp.broker.getUser(username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async updatePassword(username: string, password: string, newPassword: string) {
|
|
||||||
UserApp.assertInitialized()
|
|
||||||
return await UserApp.broker.updatePassword(username, password, newPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static assertInitialized() {
|
|
||||||
|
|
||||||
if (!UserApp.initialized) {
|
|
||||||
// UGLY: make more specific
|
|
||||||
throw Error("User app has't been Initialized yet!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
export enum EndpointType {
|
|
||||||
|
|
||||||
SSL_TERMINATION = "SSLTermination",
|
|
||||||
MANUAL = "Manual"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export enum EndpointStatus {
|
|
||||||
ACTIVE,
|
|
||||||
INACTIVE
|
|
||||||
}
|
|
||||||
|
|
||||||
export function matchEndpoint(label: string) {
|
|
||||||
|
|
||||||
switch(label) {
|
|
||||||
|
|
||||||
case "SSLTermination":
|
|
||||||
return EndpointType.SSL_TERMINATION
|
|
||||||
default:
|
|
||||||
return EndpointType.MANUAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
export enum NginxProtocol {
|
|
||||||
HTTP = "http",
|
|
||||||
HTTP2 = "http",
|
|
||||||
QUIC = "http",
|
|
||||||
GRPC = "grpc"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UGLY: move these fnction into utils
|
|
||||||
export function httpVersion(protocol: NginxProtocol): number {
|
|
||||||
|
|
||||||
switch (protocol) {
|
|
||||||
case NginxProtocol.HTTP2:
|
|
||||||
case NginxProtocol.GRPC:
|
|
||||||
return 2
|
|
||||||
case NginxProtocol.QUIC:
|
|
||||||
return 3
|
|
||||||
default:
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function protocolToString(procotol: NginxProtocol) {
|
|
||||||
switch (procotol) {
|
|
||||||
case NginxProtocol.HTTP:
|
|
||||||
return "HTTP"
|
|
||||||
case NginxProtocol.HTTP2:
|
|
||||||
return "HTTP2"
|
|
||||||
case NginxProtocol.QUIC:
|
|
||||||
return "QUIC"
|
|
||||||
case NginxProtocol.GRPC:
|
|
||||||
return "GRPC"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringToProtocol(label: string) {
|
|
||||||
switch (label) {
|
|
||||||
case "HTTP":
|
|
||||||
return NginxProtocol.HTTP
|
|
||||||
|
|
||||||
case "HTTP2":
|
|
||||||
return NginxProtocol.HTTP2
|
|
||||||
|
|
||||||
case "QUIC":
|
|
||||||
return NginxProtocol.QUIC
|
|
||||||
|
|
||||||
case "GRPC":
|
|
||||||
return NginxProtocol.GRPC
|
|
||||||
default:
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("Protocol not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function secureProtocol(protocol: NginxProtocol) {
|
|
||||||
return `${protocol}s`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function proxyProtocol(protocol: NginxProtocol) {
|
|
||||||
switch (protocol) {
|
|
||||||
case NginxProtocol.GRPC:
|
|
||||||
return "grpc_pass"
|
|
||||||
default:
|
|
||||||
return "proxy_pass"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
import { AppData } from "$lib/server/classes/appdata";
|
|
||||||
import { logger } from "$lib/server/utils/logger";
|
|
||||||
import { APP_HOME, SESSION_COOKIE_NAME } from "$lib/shared/constants";
|
|
||||||
import { error, redirect, type Handle } from "@sveltejs/kit";
|
|
||||||
import { sequence } from "@sveltejs/kit/hooks";
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
return await resolve(event)
|
|
||||||
|
|
||||||
}) satisfies Handle
|
|
||||||
|
|
||||||
|
|
||||||
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")) {
|
|
||||||
// next handle
|
|
||||||
return await resolve(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's a backend, should not redirect
|
|
||||||
if (!event.locals.session) {
|
|
||||||
// Satisfies HTTP Codes:
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/401
|
|
||||||
return error(401, "Not Authorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
return await resolve(event)
|
|
||||||
|
|
||||||
}) satisfies Handle
|
|
||||||
|
|
||||||
const appHandle = (async ({ event, resolve }) => {
|
|
||||||
|
|
||||||
logger.debug(event.url.pathname, "APP Handle")
|
|
||||||
logger.debug(`Session Data: ${event.locals.session}`, "APP Handle")
|
|
||||||
|
|
||||||
if (!event.url.pathname.startsWith("/app/program")) {
|
|
||||||
// next handle
|
|
||||||
return await resolve(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's for frontend, should redirect
|
|
||||||
if (!event.locals.session) {
|
|
||||||
return redirect(302, "/app/login")
|
|
||||||
}
|
|
||||||
|
|
||||||
return await resolve(event)
|
|
||||||
|
|
||||||
}) 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_HOME)
|
|
||||||
}
|
|
||||||
|
|
||||||
return await resolve(event)
|
|
||||||
|
|
||||||
}) satisfies Handle
|
|
||||||
|
|
||||||
export const handles = sequence(
|
|
||||||
sessionConstructorHandle,
|
|
||||||
apiHandle,
|
|
||||||
appHandle,
|
|
||||||
appNonAuthHandle
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
// place files you want to import through the `$lib` alias in this folder.
|
|
||||||
export {PKG} from "./utils/constants";
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
export const PKG = __PKG__
|
|
||||||
export const DB_PATH = "src/db/db.sqlite"
|
|
||||||
export const SERVER_PRIVATE_DIR = "src/private"
|
|
||||||
export const SERVER_TMP_FILE = `${SERVER_PRIVATE_DIR}/tmp.pem`
|
|
||||||
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
|
|
||||||
|
|
||||||
// NGINX
|
|
||||||
export const NGINX_BASE = "/etc/nginx"
|
|
||||||
export const NGINX_INACTIVE = `${NGINX_BASE}/inactive`
|
|
||||||
export const NGINX_ACTIVE = `${NGINX_BASE}/active`
|
|
||||||
export const NGINX_TRACKED = NGINX_INACTIVE
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
import { Stats, type OpenMode } from 'node:fs';
|
|
||||||
import * as Node from 'node:fs/promises';
|
|
||||||
import { createHash, type BinaryLike } from 'node:crypto';
|
|
||||||
|
|
||||||
export type FileStats = Stats
|
|
||||||
|
|
||||||
export class FileHandle {
|
|
||||||
|
|
||||||
private path: string
|
|
||||||
private fd: Node.FileHandle | null
|
|
||||||
|
|
||||||
private get file() {
|
|
||||||
if (!this.fd) {
|
|
||||||
return this.path
|
|
||||||
}
|
|
||||||
return this.fd
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
path: string
|
|
||||||
) {
|
|
||||||
this.path = path
|
|
||||||
this.fd = null
|
|
||||||
}
|
|
||||||
|
|
||||||
public async text(encoding?: BufferEncoding) {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!encoding) {
|
|
||||||
encoding = "utf-8"
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileContent = await Node.readFile(this.path, { encoding: encoding })
|
|
||||||
return fileContent
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async write(text: string, append?: boolean) {
|
|
||||||
|
|
||||||
if (!append) {
|
|
||||||
append = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const flag: OpenMode = append ? "a+" : "w"
|
|
||||||
|
|
||||||
await Node.writeFile(this.file, text, { flag: flag })
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public async lastChange() {
|
|
||||||
const stats = await Node.stat(this.path)
|
|
||||||
return stats.mtime
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getStats() {
|
|
||||||
const stats = await Node.stat(this.path)
|
|
||||||
return stats
|
|
||||||
}
|
|
||||||
|
|
||||||
public async hash() {
|
|
||||||
return hashUtil(
|
|
||||||
await Node.readFile(this.file)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public async lock() {
|
|
||||||
this.fd = await Node.open(this.path, "r+")
|
|
||||||
}
|
|
||||||
|
|
||||||
public async release() {
|
|
||||||
if (!this.fd) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.fd.close()
|
|
||||||
this.fd = null
|
|
||||||
}
|
|
||||||
|
|
||||||
public async delete() {
|
|
||||||
await Node.unlink(this.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the file exists
|
|
||||||
*
|
|
||||||
* @param path `string` path for the file
|
|
||||||
* @returns if the file is readable
|
|
||||||
*/
|
|
||||||
export async function doesFileExist(path: string): Promise<boolean> {
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Node.access(path)
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isDir(path: string): Promise<boolean> {
|
|
||||||
const stats = await Node.stat(path)
|
|
||||||
|
|
||||||
return stats.isDirectory()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function loadFile(path: string, create?: boolean): Promise<FileHandle> {
|
|
||||||
|
|
||||||
const DEFAULT_MODE = 0o600
|
|
||||||
|
|
||||||
// Safe to use
|
|
||||||
// worst case: create = false -> create = false
|
|
||||||
if (!create) {
|
|
||||||
create = false
|
|
||||||
}
|
|
||||||
|
|
||||||
let fd: Node.FileHandle | null = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
fd = await Node.open(path, "r+", DEFAULT_MODE)
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have a FD, return it
|
|
||||||
if (fd) {
|
|
||||||
await fd.close()
|
|
||||||
return new FileHandle(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!create) {
|
|
||||||
// UGLY: make more specific
|
|
||||||
throw new Error("The required file does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do want this to throw without catching here
|
|
||||||
fd = await Node.open(path, "w", DEFAULT_MODE)
|
|
||||||
await fd.close()
|
|
||||||
|
|
||||||
return new FileHandle(path)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// UGLY: move this to a new file
|
|
||||||
export function hashUtil(data: BinaryLike) {
|
|
||||||
const hash = createHash("sha256")
|
|
||||||
.update(
|
|
||||||
data
|
|
||||||
).digest('base64')
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listFiles(path: string, recursive?: boolean, relative?: boolean,): Promise<string[]> {
|
|
||||||
|
|
||||||
if (!recursive) {
|
|
||||||
recursive = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!relative) {
|
|
||||||
relative = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await isDir(path)) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("This is not a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativePaths = await Node.readdir(path, {recursive: recursive})
|
|
||||||
|
|
||||||
if (relative) {
|
|
||||||
return relativePaths
|
|
||||||
}
|
|
||||||
|
|
||||||
return relativePaths.map( (_path) => {
|
|
||||||
return `${path}/${_path}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
import * as jose from "jose";
|
|
||||||
import { doesFileExist, loadFile } from "./filesystem-utils";
|
|
||||||
import { SERVER_PRIVATE_KEY_PATH, SERVER_PUBLIC_KEY_PATH } from "./constants";
|
|
||||||
import { openSSLInit } from "./openssl-utils";
|
|
||||||
import { logger } from "./logger";
|
|
||||||
|
|
||||||
|
|
||||||
export class JoseApp {
|
|
||||||
|
|
||||||
private static initialized = false
|
|
||||||
|
|
||||||
private static privateKey: CryptoKey
|
|
||||||
private static publicKey: CryptoKey
|
|
||||||
|
|
||||||
public static get ready() {
|
|
||||||
return JoseApp.initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async init() {
|
|
||||||
|
|
||||||
JoseApp.assureNotInitialized()
|
|
||||||
|
|
||||||
if (
|
|
||||||
!await doesFileExist(SERVER_PRIVATE_KEY_PATH) ||
|
|
||||||
!await doesFileExist(SERVER_PUBLIC_KEY_PATH)
|
|
||||||
) {
|
|
||||||
await openSSLInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JoseApp.privateKey = await JoseApp.loadPrivateKey()
|
|
||||||
JoseApp.publicKey = await JoseApp.loadPublicKey()
|
|
||||||
|
|
||||||
JoseApp.initialized = true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async loadPrivateKey() {
|
|
||||||
|
|
||||||
JoseApp.assureNotInitialized()
|
|
||||||
|
|
||||||
const privateKeyFile = await loadFile(SERVER_PRIVATE_KEY_PATH)
|
|
||||||
return await jose.importPKCS8(
|
|
||||||
await privateKeyFile.text(),
|
|
||||||
"ES512"
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async loadPublicKey() {
|
|
||||||
|
|
||||||
JoseApp.assureNotInitialized()
|
|
||||||
|
|
||||||
const publicKeyFile = await loadFile(SERVER_PUBLIC_KEY_PATH)
|
|
||||||
return await jose.importSPKI(
|
|
||||||
await publicKeyFile.text(),
|
|
||||||
"ES512"
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async signObject(object: any) {
|
|
||||||
|
|
||||||
JoseApp.assureInitialized()
|
|
||||||
|
|
||||||
const payload = new TextEncoder().encode(
|
|
||||||
JSON.stringify(object)
|
|
||||||
)
|
|
||||||
|
|
||||||
return await new jose.CompactSign(
|
|
||||||
payload
|
|
||||||
).setProtectedHeader({
|
|
||||||
alg: "ES512"
|
|
||||||
}).sign(JoseApp.privateKey)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async verifyObject(jwt: string) {
|
|
||||||
|
|
||||||
JoseApp.assureInitialized()
|
|
||||||
|
|
||||||
let _payload: Uint8Array
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { payload, protectedHeader } = await jose.compactVerify(
|
|
||||||
jwt,
|
|
||||||
JoseApp.publicKey
|
|
||||||
)
|
|
||||||
_payload = payload
|
|
||||||
} catch (err) {
|
|
||||||
logger.debug(`Error: ${err}`, "JOSE Verify")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`Payload: ${new TextDecoder().decode(_payload)}`, "JOSE Verify")
|
|
||||||
|
|
||||||
return JSON.parse(
|
|
||||||
new TextDecoder().decode(_payload)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static assureInitialized() {
|
|
||||||
|
|
||||||
if (!JoseApp.initialized) {
|
|
||||||
// UGLY: Be specific
|
|
||||||
throw new Error("JoseSingleton hasn't been initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static assureNotInitialized() {
|
|
||||||
|
|
||||||
if (JoseApp.initialized) {
|
|
||||||
// UGLY: Be specific
|
|
||||||
throw new Error("JoseSingleton has already been initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import { DEBUG } from "./constants"
|
|
||||||
import { LOGGER_DELIMITER } from "../../shared/constants"
|
|
||||||
|
|
||||||
class Logger {
|
|
||||||
private static initialized = false
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
if (Logger.initialized) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("Logger has already been initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public debug(message: any, title?: string) {
|
|
||||||
if (!DEBUG) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
title = "INFO"
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTime: string = new Date().toLocaleString()
|
|
||||||
|
|
||||||
const debugMessage =
|
|
||||||
`
|
|
||||||
${LOGGER_DELIMITER}
|
|
||||||
|
|
||||||
${title} DEBUG ${currentTime}
|
|
||||||
|
|
||||||
${message}
|
|
||||||
|
|
||||||
${LOGGER_DELIMITER}
|
|
||||||
|
|
||||||
`
|
|
||||||
console.info(
|
|
||||||
debugMessage
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const logger = new Logger()
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
// TODO: remove bun dependencies
|
|
||||||
|
|
||||||
import { logger } from "./logger"
|
|
||||||
import { shell } from "./shell-commands"
|
|
||||||
|
|
||||||
|
|
||||||
export async function reloadNginx() {
|
|
||||||
|
|
||||||
if (!await validateSchema()) {
|
|
||||||
// UGLY: make this a specific error
|
|
||||||
throw new Error("Something went wrong while validating")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start nginx, should be side-effect free
|
|
||||||
startNginx()
|
|
||||||
const output = await shell(`rc-service nginx reload`)
|
|
||||||
|
|
||||||
logger.debug(`rc-service reload output:\n${output.stdout}`, "NGINX - RELOAD")
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startNginx() {
|
|
||||||
|
|
||||||
if (!await validateSchema()) {
|
|
||||||
// UGLY: make this a specific error
|
|
||||||
throw new Error("Something went wrong while validating")
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = await shell(`rc-service nginx start`)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function validateSchema() {
|
|
||||||
|
|
||||||
const output = await shell(`nginx -t 2>&1`)
|
|
||||||
|
|
||||||
logger.debug(`nginx -t output:\n${output.stdout}`, "NGINX - VALIDATE")
|
|
||||||
|
|
||||||
const successRegex = new RegExp("test is successful", "gm")
|
|
||||||
|
|
||||||
const result = successRegex.test(output.stdout)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import { doesFileExist, loadFile, type FileHandle } from "./filesystem-utils";
|
|
||||||
import { SERVER_PRIVATE_KEY_PATH, SERVER_PUBLIC_KEY_PATH, SERVER_TMP_FILE } from "./constants";
|
|
||||||
import { shell, type shellOutput } from "./shell-commands";
|
|
||||||
|
|
||||||
export async function openSSLInit() {
|
|
||||||
|
|
||||||
await openSSLCreatePrivateKey()
|
|
||||||
await openSSLCreatePublicKey()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function openSSLCreatePrivateKey() {
|
|
||||||
|
|
||||||
// UGLY: may be refactored to output only the private key
|
|
||||||
const pemPromise = await shell(`openssl ecparam -genkey -name secp521r1 -noout -out ${SERVER_TMP_FILE}`)
|
|
||||||
const outputPromise = shell(`openssl pkcs8 -topk8 -nocrypt < ${SERVER_TMP_FILE}`)
|
|
||||||
|
|
||||||
// const outputPromise = $`openssl ecparam -genkey -name secp521r1 -noout | openssl pkcs8 -topk8 -nocrypt`.text()
|
|
||||||
const filePromise = loadFile(SERVER_PRIVATE_KEY_PATH, true)
|
|
||||||
|
|
||||||
const [output, file] : [shellOutput, FileHandle]= await Promise.all([
|
|
||||||
outputPromise,
|
|
||||||
filePromise
|
|
||||||
])
|
|
||||||
|
|
||||||
await file.write(output.stdout)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function openSSLCreatePublicKey() {
|
|
||||||
|
|
||||||
// UGLY: may be refactored to output only the private key
|
|
||||||
if (! await doesFileExist(SERVER_PRIVATE_KEY_PATH)) {
|
|
||||||
// UGLY: make more specific
|
|
||||||
throw new Error("You must generate the private key before attempting to generate the public one")
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputPromise = shell(`openssl ec -in ${SERVER_PRIVATE_KEY_PATH} -pubout `)
|
|
||||||
const filePromise = loadFile(SERVER_PUBLIC_KEY_PATH, true)
|
|
||||||
|
|
||||||
const [output, file] = await Promise.all([
|
|
||||||
outputPromise,
|
|
||||||
filePromise
|
|
||||||
])
|
|
||||||
|
|
||||||
await file.write(output.stdout)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
|
|
||||||
import { shell } from "./shell-commands"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This methods runs `netstat -ltun`
|
|
||||||
* to take all ports occupied by
|
|
||||||
* other services on the host machine
|
|
||||||
*
|
|
||||||
* Since this is run in docker with
|
|
||||||
* network mode `host`, it takes all
|
|
||||||
* ports
|
|
||||||
*
|
|
||||||
* @returns occupied ports
|
|
||||||
*/
|
|
||||||
export async function portScan(): Promise<Set<number>> {
|
|
||||||
|
|
||||||
const portRegex = new RegExp("(?:\:)(?<port_number>[0-9]+)", "gm")
|
|
||||||
const netstatOutput = await shell(`netstat -ltun`)
|
|
||||||
const ports = netstatOutput.stdout.matchAll(portRegex)
|
|
||||||
|
|
||||||
const portArray : Set<number>= new Set()
|
|
||||||
|
|
||||||
for (const match of ports) {
|
|
||||||
portArray.add(
|
|
||||||
Number(match[1])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return portArray
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method checks if the port is actually a port,
|
|
||||||
* throws otherwise
|
|
||||||
* @param port_number port to validate
|
|
||||||
*/
|
|
||||||
export function validatePort(port_number: number): void {
|
|
||||||
// Validate against Float
|
|
||||||
if (port_number % 1 !== 0) {
|
|
||||||
throw new Error("The specified port is not an Integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate for range
|
|
||||||
if (port_number < 1 || port_number > 65535 ) {
|
|
||||||
throw new Error("The specified port is not in the range 1...65535")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method checks for the availability of a port
|
|
||||||
*
|
|
||||||
* Throws if the port is not a valid port
|
|
||||||
*
|
|
||||||
* @param port_number port to check
|
|
||||||
* @returns `true` if port is available, `false` otherwise
|
|
||||||
*/
|
|
||||||
export async function isPortAvailable(port_number: number): Promise<boolean> {
|
|
||||||
|
|
||||||
validatePort(port_number)
|
|
||||||
|
|
||||||
const occupied_ports = await portScan()
|
|
||||||
|
|
||||||
// Validate for availability
|
|
||||||
if (occupied_ports.has(port_number)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { exec , spawn } from "node:child_process"
|
|
||||||
import { promisify } from "node:util"
|
|
||||||
|
|
||||||
export const shell = promisify(exec)
|
|
||||||
|
|
||||||
export type shellOutput = {
|
|
||||||
stdout: string,
|
|
||||||
stderr: string
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
// 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"
|
|
||||||
|
|
||||||
// Logger
|
|
||||||
export const LOGGER_DELIMITER = "####################################################################"
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
export function validatePath(path: string) {
|
|
||||||
|
|
||||||
const regex = new RegExp(".*\.\..*")
|
|
||||||
if (regex.test(path)) {
|
|
||||||
// UGLY: be specific
|
|
||||||
throw new Error("Thi spath is invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>SSL-Sniffer</h1>
|
|
||||||
<p>A Sniffer for all your needs</p>
|
|
||||||
|
|
||||||
<nav data-sveltekit-reload>
|
|
||||||
<a href="/app/login">Login</a>
|
|
||||||
or
|
|
||||||
<a href="/app/register">Register</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import { error, json, text, type Cookies } from '@sveltejs/kit';
|
|
||||||
import type { RequestHandler } from './$types';
|
|
||||||
import { User, UserApp } from '$lib/server/classes/users';
|
|
||||||
import { SessionApp } from '$lib/server/classes/sessions';
|
|
||||||
import { AppData } from '$lib/server/classes/appdata';
|
|
||||||
import { logger } from '$lib/server/utils/logger';
|
|
||||||
|
|
||||||
/***********************************************************
|
|
||||||
*
|
|
||||||
* Author: Christian Risi 26/06/2025
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
***********************************************************/
|
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ request, locals, cookies }) => {
|
|
||||||
|
|
||||||
const req: Request = request
|
|
||||||
const local: App.Locals = locals
|
|
||||||
const cookie: Cookies = cookies
|
|
||||||
|
|
||||||
logger.debug(`locals: ${local.session}`, "API Login")
|
|
||||||
|
|
||||||
const session = local.session
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
// The user is providing valid credentials
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/403
|
|
||||||
return error(403, "Forbidden")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let userJson: { username: string, password: string }
|
|
||||||
let tmpJSON: any
|
|
||||||
|
|
||||||
try {
|
|
||||||
tmpJSON = await req.json()
|
|
||||||
} catch {
|
|
||||||
return error(400, "Bad Request")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tmpJSON.username || !tmpJSON.password) {
|
|
||||||
return error(400, "Bad Request")
|
|
||||||
}
|
|
||||||
|
|
||||||
userJson = tmpJSON
|
|
||||||
let user: User | null
|
|
||||||
|
|
||||||
// If this fails, should be a 500
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldSession = SessionApp.getSessionFromUserID(
|
|
||||||
user.userID
|
|
||||||
)
|
|
||||||
|
|
||||||
// If we have no session, then probably a 500?
|
|
||||||
if (!oldSession) {
|
|
||||||
return error(500, "Internal Server Error")
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionCookie = await new AppData(
|
|
||||||
oldSession,
|
|
||||||
user
|
|
||||||
).toCookie()
|
|
||||||
|
|
||||||
cookie.set(
|
|
||||||
"session",
|
|
||||||
sessionCookie,
|
|
||||||
{
|
|
||||||
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: "POST"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return res
|
|
||||||
};
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import { error, json, text, type Cookies } from '@sveltejs/kit';
|
|
||||||
import type { RequestHandler } from './$types';
|
|
||||||
import { User, UserApp } from '$lib/server/classes/users';
|
|
||||||
import { SessionApp } from '$lib/server/classes/sessions';
|
|
||||||
import { AppData } from '$lib/server/classes/appdata';
|
|
||||||
import { logger } from '$lib/server/utils/logger';
|
|
||||||
import { SESSION_COOKIE_NAME } from '$lib/server/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
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { EndpointManagerApp } from "$lib/server/classes/endpoints/endpoint-manager";
|
|
||||||
import { validatePath } from "$lib/shared/utils/path-utils";
|
|
||||||
import { type RequestHandler, error } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
|
|
||||||
export const PATCH: RequestHandler = async ({ request }) => {
|
|
||||||
let path = await request.json();
|
|
||||||
|
|
||||||
try {
|
|
||||||
validatePath(path);
|
|
||||||
|
|
||||||
let res = await EndpointManagerApp.activateEndpoint(path);
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: res ? 200 : 304
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
return error(400, "Bad Request");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { EndpointManagerApp } from "$lib/server/classes/endpoints/endpoint-manager";
|
|
||||||
import { type RequestHandler, json } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ }) => {
|
|
||||||
let endpoints = await EndpointManagerApp.getAll();
|
|
||||||
|
|
||||||
return json(endpoints);
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { EndpointManagerApp } from "$lib/server/classes/endpoints/endpoint-manager";
|
|
||||||
import { validatePath } from "$lib/shared/utils/path-utils";
|
|
||||||
import { type RequestHandler, error } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
|
|
||||||
export const PATCH: RequestHandler = async ({ request }) => {
|
|
||||||
let path = await request.json();
|
|
||||||
|
|
||||||
try {
|
|
||||||
validatePath(path);
|
|
||||||
|
|
||||||
let res = await EndpointManagerApp.deactivateEndpoint(path);
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: res ? 200 : 304
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
return error(400, "Bad Request");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import { error, json, text } from '@sveltejs/kit';
|
|
||||||
import type { RequestHandler } from './$types';
|
|
||||||
|
|
||||||
/***********************************************************
|
|
||||||
*
|
|
||||||
* Author: Christian Risi 26/06/2025
|
|
||||||
*
|
|
||||||
* There is no POST handling here as it's not
|
|
||||||
* idempotent, so semantically it's better a
|
|
||||||
* PUT instead of a POST
|
|
||||||
*
|
|
||||||
***********************************************************/
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ request }) => {
|
|
||||||
|
|
||||||
return text("If you read this you are authenticated")
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PUT: RequestHandler = async ({ request }) => {
|
|
||||||
return json(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PATCH: RequestHandler = async ({ request }) => {
|
|
||||||
|
|
||||||
return json(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DELETE: RequestHandler = async ({ request, }) => {
|
|
||||||
|
|
||||||
// TODO: make it delete the resource
|
|
||||||
return json(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fallback: RequestHandler = async ({ request }) => {
|
|
||||||
|
|
||||||
// TODO: return method not allowed
|
|
||||||
const res = new Response(
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
status: 405,
|
|
||||||
statusText: "Method Not Allowed",
|
|
||||||
headers: {
|
|
||||||
Allow: "GET, PUT, PATCH, DELETE"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return res
|
|
||||||
};
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { EndpointManagerApp } from "$lib/server/classes/endpoints/endpoint-manager";
|
|
||||||
import { type RequestHandler, error, json } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({}) => {
|
|
||||||
let status = await EndpointManagerApp.getStatus();
|
|
||||||
|
|
||||||
return json({ status });
|
|
||||||
};
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import { error, json, redirect, text, type Cookies } from '@sveltejs/kit';
|
|
||||||
import type { RequestHandler } from './$types';
|
|
||||||
import { UserApp, User } from '$lib/server/classes/users';
|
|
||||||
import { SessionApp, Session } from '$lib/server/classes/sessions';
|
|
||||||
import { AppData } from '$lib/server/classes/appdata';
|
|
||||||
import { logger } from '$lib/server/utils/logger';
|
|
||||||
import { DEBUG } from '$lib/server/utils/constants';
|
|
||||||
|
|
||||||
/***********************************************************
|
|
||||||
*
|
|
||||||
* Author: Christian Risi 26/06/2025
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
***********************************************************/
|
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ request, locals, cookies }) => {
|
|
||||||
|
|
||||||
const req: Request = request
|
|
||||||
const local: App.Locals = locals
|
|
||||||
const cookie: Cookies = cookies
|
|
||||||
|
|
||||||
const session = local.session
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
return redirect(307, "api/register")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
// The user is not providing credentials
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/401
|
|
||||||
return error(401, "Unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let userJson: { username: string, password: string }
|
|
||||||
let tmpJSON: any
|
|
||||||
|
|
||||||
try {
|
|
||||||
tmpJSON = await req.json()
|
|
||||||
} catch {
|
|
||||||
return error(400, "Bad Request")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tmpJSON.username || !tmpJSON.password) {
|
|
||||||
return error(400, "Bad Request")
|
|
||||||
}
|
|
||||||
|
|
||||||
userJson = tmpJSON
|
|
||||||
|
|
||||||
// If this fails, should be a 400?
|
|
||||||
let user: User
|
|
||||||
try {
|
|
||||||
user = await UserApp.createUser(
|
|
||||||
userJson.username,
|
|
||||||
userJson.password
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
return error(400, "The user already exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let newSession
|
|
||||||
try {
|
|
||||||
newSession = SessionApp.createSessionFromUserID(
|
|
||||||
user.userID
|
|
||||||
)
|
|
||||||
} catch(err){
|
|
||||||
logger.debug(`error: ${err}`, "API Register")
|
|
||||||
return error(500, "Internal Server Error")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const sessionCookie = await new AppData(
|
|
||||||
newSession,
|
|
||||||
user
|
|
||||||
).toCookie()
|
|
||||||
|
|
||||||
cookie.set(
|
|
||||||
"session",
|
|
||||||
sessionCookie,
|
|
||||||
{
|
|
||||||
path: "/"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = new Response(
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
status: 201,
|
|
||||||
statusText: "Created",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fallback: RequestHandler = async ({ request }) => {
|
|
||||||
|
|
||||||
// TODO: return method not allowed
|
|
||||||
const res = new Response(
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
status: 405,
|
|
||||||
statusText: "Method Not Allowed",
|
|
||||||
headers: {
|
|
||||||
Allow: "POST"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return res
|
|
||||||
};
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import { error, type json, type RequestHandler } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
// UGLY: this should be more flexible
|
|
||||||
class ReloadNginxReq {
|
|
||||||
|
|
||||||
public auth: string
|
|
||||||
public nginx: string
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
json: any
|
|
||||||
) {
|
|
||||||
if (!json) {
|
|
||||||
throw new Error("This is an invalid JSON")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!json.nginx || !json.auth) {
|
|
||||||
throw new Error("Can't parse this JSON")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.auth = json.auth
|
|
||||||
this.nginx = json.nginx
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ request }) => {
|
|
||||||
|
|
||||||
let parsedReq : ReloadNginxReq
|
|
||||||
|
|
||||||
try {
|
|
||||||
parsedReq = new ReloadNginxReq(
|
|
||||||
await request.json()
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 400,
|
|
||||||
statusText: "Bad Request"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Reload Data about Nginx
|
|
||||||
|
|
||||||
// TODO: Notify frontends
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: 200,
|
|
||||||
statusText: "OK"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const fallback: RequestHandler = async () => {
|
|
||||||
|
|
||||||
// TODO: return method not allowed
|
|
||||||
const res = new Response(
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
status: 405,
|
|
||||||
statusText: "Method Not Allowed",
|
|
||||||
headers: {
|
|
||||||
Allow: "POST"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return res
|
|
||||||
};
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
import { error, json, redirect, text, type Cookies } from '@sveltejs/kit';
|
|
||||||
import type { RequestHandler } from './$types';
|
|
||||||
import { UserApp, User } from '$lib/server/classes/users';
|
|
||||||
import { SessionApp, Session } from '$lib/server/classes/sessions';
|
|
||||||
import { AppData } from '$lib/server/classes/appdata';
|
|
||||||
import { logger } from '$lib/server/utils/logger';
|
|
||||||
import { DEBUG } from '$lib/server/utils/constants';
|
|
||||||
|
|
||||||
/***********************************************************
|
|
||||||
*
|
|
||||||
* Author: Christian Risi 26/06/2025
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
***********************************************************/
|
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ request, locals, cookies }) => {
|
|
||||||
|
|
||||||
const req: Request = request
|
|
||||||
const local: App.Locals = locals
|
|
||||||
const cookie: Cookies = cookies
|
|
||||||
|
|
||||||
const session = local.session
|
|
||||||
|
|
||||||
if (!DEBUG) {
|
|
||||||
return redirect(307, "api/program/register")
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(session)
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
// The user is providing valid credentials
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/403
|
|
||||||
return error(403, "Forbidden")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let userJson: { username: string, password: string }
|
|
||||||
let tmpJSON: any
|
|
||||||
|
|
||||||
try {
|
|
||||||
tmpJSON = await req.json()
|
|
||||||
} catch {
|
|
||||||
return error(400, "Bad Request")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tmpJSON.username || !tmpJSON.password) {
|
|
||||||
return error(400, "Bad Request")
|
|
||||||
}
|
|
||||||
|
|
||||||
userJson = tmpJSON
|
|
||||||
|
|
||||||
// If this fails, should be a 400?
|
|
||||||
let user: User
|
|
||||||
try {
|
|
||||||
user = await UserApp.createUser(
|
|
||||||
userJson.username,
|
|
||||||
userJson.password
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
return error(400, "The user already exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let newSession
|
|
||||||
try {
|
|
||||||
newSession = SessionApp.createSessionFromUserID(
|
|
||||||
user.userID
|
|
||||||
)
|
|
||||||
} catch(err){
|
|
||||||
logger.debug(`error: ${err}`, "API Register")
|
|
||||||
return error(500, "Internal Server Error")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const sessionCookie = await new AppData(
|
|
||||||
newSession,
|
|
||||||
user
|
|
||||||
).toCookie()
|
|
||||||
|
|
||||||
cookie.set(
|
|
||||||
"session",
|
|
||||||
sessionCookie,
|
|
||||||
{
|
|
||||||
path: "/"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = new Response(
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
status: 201,
|
|
||||||
statusText: "Created",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fallback: RequestHandler = async ({ request }) => {
|
|
||||||
|
|
||||||
// TODO: return method not allowed
|
|
||||||
const res = new Response(
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
status: 405,
|
|
||||||
statusText: "Method Not Allowed",
|
|
||||||
headers: {
|
|
||||||
Allow: "POST"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return res
|
|
||||||
};
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { PKG } from '$lib/server';
|
|
||||||
import { json } from '@sveltejs/kit';
|
|
||||||
|
|
||||||
export function GET() {
|
|
||||||
|
|
||||||
const version = `SSL-Sniffer version ${PKG.version}`
|
|
||||||
return json(version)
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
import { APP_HOME } from "$lib/shared/constants";
|
|
||||||
import { redirect } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
|
|
||||||
let error = $state("")
|
|
||||||
|
|
||||||
async function loginButton(event: SubmitEvent) {
|
|
||||||
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
const form = event.target as HTMLFormElement
|
|
||||||
|
|
||||||
const username : string | null = form.username.value
|
|
||||||
const password : string | null = form.password.value
|
|
||||||
|
|
||||||
// UGLY: standardize
|
|
||||||
const jsonPayload = {
|
|
||||||
username: username,
|
|
||||||
password: password
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(
|
|
||||||
"/api/login",
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(jsonPayload)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
|
||||||
error = `${res.status}: ${(await res.json()).message}`
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await goto(APP_HOME)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Login</h1>
|
|
||||||
|
|
||||||
|
|
||||||
<form action="" onsubmit={loginButton} >
|
|
||||||
<input name="username" type="text" required minlength="4" autocomplete="username" />
|
|
||||||
<input name="password" type="password" required minlength="8" autocomplete="current-password" />
|
|
||||||
<input type="submit">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{#if (error.length !== 0)}
|
|
||||||
<span>{error}</span>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<a href="/app/register">Register</a>
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<h1>Home</h1>
|
|
||||||
|
|
||||||
Welcome to SSL-Sniffer
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
import { APP_HOME } from "$lib/shared/constants";
|
|
||||||
import { redirect } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
|
|
||||||
let error = $state("")
|
|
||||||
|
|
||||||
async function registerButton(event: SubmitEvent) {
|
|
||||||
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
const form = event.target as HTMLFormElement
|
|
||||||
|
|
||||||
const username : string | null = form.username.value
|
|
||||||
const password : string | null = form.password.value
|
|
||||||
|
|
||||||
// UGLY: standardize
|
|
||||||
const jsonPayload = {
|
|
||||||
username: username,
|
|
||||||
password: password
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(
|
|
||||||
"/api/register",
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(jsonPayload)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (res.status != 201) {
|
|
||||||
error = `${res.status}: ${(await res.json()).message}`
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
goto(APP_HOME)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Register</h1>
|
|
||||||
|
|
||||||
|
|
||||||
<form action="" onsubmit={registerButton} >
|
|
||||||
<input name="username" type="text" required minlength="4" autocomplete="username" />
|
|
||||||
<input name="password" type="password" required minlength="8" autocomplete="new-password" />
|
|
||||||
<input name="confirm-password" type="password" required minlength="8" autocomplete="new-password" />
|
|
||||||
<input type="submit">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{#if (error.length !== 0)}
|
|
||||||
<span>{error}</span>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<a href="/app/login">Login</a>
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,19 +0,0 @@
|
|||||||
import adapter from "@sveltejs/adapter-auto"
|
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
|
||||||
const config = {
|
|
||||||
// Consult https://svelte.dev/docs/kit/integrations
|
|
||||||
// for more information about preprocessors
|
|
||||||
preprocess: vitePreprocess(),
|
|
||||||
|
|
||||||
kit: {
|
|
||||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
|
||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
|
||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
|
||||||
adapter: adapter(),
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
|
|
||||||
// TODO: make tests for Database
|
|
||||||
describe('create user database', () => {
|
|
||||||
it('creates ', () => {
|
|
||||||
expect(1 + 2).toBe(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { validateSchema } from '$lib/server/utils/nginx-utils';
|
|
||||||
import { portScan } from '$lib/server/utils/ports-utils';
|
|
||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
|
|
||||||
describe('sum test', () => {
|
|
||||||
it('adds 1 + 2 to equal 3', () => {
|
|
||||||
expect(1 + 2).toBe(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('port gathering test', () => {
|
|
||||||
it('gathers ports from terminal', async () => {
|
|
||||||
const ports = await portScan()
|
|
||||||
console.log(ports)
|
|
||||||
expect(ports.size).not.toBe(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
describe('nginx validation', () => {
|
|
||||||
it('validates nginx configurations', async () => {
|
|
||||||
const validation = await validateSchema()
|
|
||||||
expect(validation).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { watch } from "node:fs"
|
|
||||||
import * as readline from "node:readline"
|
|
||||||
|
|
||||||
// TODO: make tests for Database
|
|
||||||
describe('watch nginx', () => {
|
|
||||||
|
|
||||||
const OPTIONS = {
|
|
||||||
recursive: true
|
|
||||||
}
|
|
||||||
|
|
||||||
const NGINX_BASE = "/etc/nginx"
|
|
||||||
|
|
||||||
const input = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout,
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(NGINX_BASE, OPTIONS, (eventType, filename) => {
|
|
||||||
console.log(`event: ${eventType}`)
|
|
||||||
console.log(`file: ${filename}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log("Done")
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./.svelte-kit/tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"strict": true,
|
|
||||||
"moduleResolution": "bundler"
|
|
||||||
}
|
|
||||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
|
||||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
|
||||||
//
|
|
||||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
|
||||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
|
||||||
}
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
|
||||||
import { defineConfig } from 'vite';
|
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
const file = fileURLToPath(new URL('package.json', import.meta.url));
|
|
||||||
const json = readFileSync(file, 'utf8');
|
|
||||||
const pkg = JSON.parse(json);
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [sveltekit()],
|
|
||||||
define: {
|
|
||||||
__PKG__: pkg
|
|
||||||
},
|
|
||||||
test: {
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
extends: './vite.config.ts',
|
|
||||||
test: {
|
|
||||||
name: 'client',
|
|
||||||
environment: 'browser',
|
|
||||||
browser: {
|
|
||||||
enabled: true,
|
|
||||||
provider: 'playwright',
|
|
||||||
instances: [{ browser: 'chromium' }]
|
|
||||||
},
|
|
||||||
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
|
||||||
exclude: ['src/lib/server/**'],
|
|
||||||
setupFiles: ['./vitest-setup-client.ts']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
extends: './vite.config.ts',
|
|
||||||
test: {
|
|
||||||
name: 'server',
|
|
||||||
environment: 'node',
|
|
||||||
include: [
|
|
||||||
'src/**/*.{test,spec}.{js,ts}',
|
|
||||||
'tests/**/*.{test,spec}.{js,ts}'
|
|
||||||
],
|
|
||||||
exclude: [
|
|
||||||
'src/**/*.svelte.{test,spec}.{js,ts}'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
/// <reference types="@vitest/browser/matchers" />
|
|
||||||
/// <reference types="@vitest/browser/providers/playwright" />
|
|
||||||
Loading…
x
Reference in New Issue
Block a user