Removed web project (galvanic authentication support in IL2CPP universal patch)

Moved instance ID to header
User instances for profile management
.. other stuff
This commit is contained in:
2025-03-22 21:57:45 -04:00
parent 73e9b72ad4
commit 6cdd0946f4
42 changed files with 663 additions and 3833 deletions

View File

@@ -1,7 +1,17 @@
# Galvanic Corrosion # Galvanic Corrosion
delectable yum yum delectable yum yum
Rec Room custom server for communities. Fast runtime and easy setup. Rec Room custom server for communities. Fast runtime and easy setup.<br>Built for Rec Room build 526 (Timestamp: 637098805133024772, Version: 20191120)
Built for Rec Room build 526 (Timestamp: 637098805133024772, Version: 20191120)
<img src="galv4.jpg" alt="drawing" width="200"/> <img src="galv4.jpg" alt="drawing" width="200"/>
## Compiling Galvanic Corrosion
* Install [Deno 2.x](https://docs.deno.com/runtime/getting_started/installation/)
* Configure project
- Clone this repo
- Install dependencies with `deno i`
- Compile server with `deno run cross-compile`
## Client Patches
You can configure some client patches from the server. See the IL2CPP universal patch for a list of patch IDs.
<br>Place desired patch ID strings into the config `public.patches`.

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
./dist
./dist-ssr
./*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,50 +0,0 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```

View File

@@ -1,28 +0,0 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Skibidi</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +0,0 @@
{
"name": "galvanic-corrosion-web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.19.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.18",
"globals": "^15.14.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.22.0",
"vite": "^6.1.0"
}
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,15 +0,0 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
function App() {
const [count, setCount] = useState(0)
return (
<div>
<p>Skibidi</p>
</div>
)
}
export default App

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,14 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@@ -1,10 +0,0 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,26 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

View File

@@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@@ -1,24 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,14 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html')
}
}
}
});

View File

@@ -1,16 +1,18 @@
{ {
"tasks": { "tasks": {
"compile-win": "deno compile --target x86_64-pc-windows-msvc --include data/ -o build/GalvanicCorrosion.exe -A src/main.ts", "compile-win": "deno compile --target x86_64-pc-windows-msvc -o build/GalvanicCorrosion.exe -A src/main.ts",
"compile-linux": "deno compile --target x86_64-unknown-linux-gnu --include data/ -o build/GalvanicCorrosion -A src/main.ts", "compile-linux": "deno compile --target x86_64-unknown-linux-gnu -o build/GalvanicCorrosion -A src/main.ts",
"cross-compile": "deno run compile-win && deno run compile-linux", "cross-compile": "deno run compile-win && deno run compile-linux",
"start": "deno run -A src/main.ts", "dev": "deno run -A src/main.ts --dev"
"compile-win-run": "deno run compile-win && cd build/ && GalvanicCorrosion.exe"
}, },
"imports": { "imports": {
"@gz/jwt": "jsr:@gz/jwt@^0.1.0", "@gz/jwt": "jsr:@gz/jwt@^0.1.0",
"@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.2.0", "@proxnet/undead-logging": "jsr:@proxnet/undead-logging@^1.2.0",
"@std/assert": "jsr:@std/assert@1", "@std/assert": "jsr:@std/assert@1",
"@types/cookie-parser": "npm:@types/cookie-parser@^1.4.8",
"@types/express": "npm:@types/express@^5.0.0", "@types/express": "npm:@types/express@^5.0.0",
"@types/validator": "npm:@types/validator@^13.12.2",
"cookie-parser": "npm:cookie-parser@^1.4.7",
"discord.js": "npm:discord.js@^14.16.3", "discord.js": "npm:discord.js@^14.16.3",
"express": "npm:express@^4.21.2", "express": "npm:express@^4.21.2",
"ioredis": "npm:ioredis@^5.5.0", "ioredis": "npm:ioredis@^5.5.0",

132
deno.lock generated
View File

@@ -8,10 +8,14 @@
"jsr:@std/crypto@^1.0.3": "1.0.3", "jsr:@std/crypto@^1.0.3": "1.0.3",
"jsr:@std/internal@^1.0.5": "1.0.5", "jsr:@std/internal@^1.0.5": "1.0.5",
"jsr:@std/uuid@*": "1.0.4", "jsr:@std/uuid@*": "1.0.4",
"npm:@types/cookie-parser@*": "1.4.8_@types+express@5.0.0",
"npm:@types/cookie-parser@^1.4.8": "1.4.8_@types+express@5.0.0",
"npm:@types/express@*": "5.0.0", "npm:@types/express@*": "5.0.0",
"npm:@types/express@5": "5.0.0", "npm:@types/express@5": "5.0.0",
"npm:@types/node@*": "22.5.4", "npm:@types/node@*": "22.5.4",
"npm:@types/validator@^13.12.2": "13.12.2",
"npm:chalk@^5.3.0": "5.3.0", "npm:chalk@^5.3.0": "5.3.0",
"npm:cookie-parser@^1.4.7": "1.4.7",
"npm:discord.js@^14.16.3": "14.16.3", "npm:discord.js@^14.16.3": "14.16.3",
"npm:express@^4.21.2": "4.21.2", "npm:express@^4.21.2": "4.21.2",
"npm:ioredis@^5.5.0": "5.5.0", "npm:ioredis@^5.5.0": "5.5.0",
@@ -135,6 +139,12 @@
"@types/node" "@types/node"
] ]
}, },
"@types/cookie-parser@1.4.8_@types+express@5.0.0": {
"integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==",
"dependencies": [
"@types/express"
]
},
"@types/express-serve-static-core@5.0.1": { "@types/express-serve-static-core@5.0.1": {
"integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==",
"dependencies": [ "dependencies": [
@@ -186,6 +196,9 @@
"@types/send" "@types/send"
] ]
}, },
"@types/validator@13.12.2": {
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA=="
},
"@types/ws@8.5.13": { "@types/ws@8.5.13": {
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
"dependencies": [ "dependencies": [
@@ -254,12 +267,22 @@
"content-type@1.0.5": { "content-type@1.0.5": {
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
}, },
"cookie-parser@1.4.7": {
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
"dependencies": [
"cookie@0.7.2",
"cookie-signature"
]
},
"cookie-signature@1.0.6": { "cookie-signature@1.0.6": {
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
}, },
"cookie@0.7.1": { "cookie@0.7.1": {
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="
}, },
"cookie@0.7.2": {
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
},
"debug@2.6.9": { "debug@2.6.9": {
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": [ "dependencies": [
@@ -350,7 +373,7 @@
"body-parser", "body-parser",
"content-disposition", "content-disposition",
"content-type", "content-type",
"cookie", "cookie@0.7.1",
"cookie-signature", "cookie-signature",
"debug@2.6.9", "debug@2.6.9",
"depd", "depd",
@@ -686,18 +709,123 @@
} }
}, },
"remote": { "remote": {
"https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
"https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49",
"https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d",
"https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9",
"https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf",
"https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37",
"https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f",
"https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d",
"https://deno.land/std@0.140.0/hash/sha256.ts": "803846c7a5a8a5a97f31defeb37d72f519086c880837129934f5d6f72102a8e8",
"https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b",
"https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3",
"https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09",
"https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b",
"https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633",
"https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee",
"https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d",
"https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44",
"https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
"https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757",
"https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21",
"https://deno.land/std@0.186.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
"https://deno.land/std@0.186.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
"https://deno.land/std@0.186.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
"https://deno.land/std@0.186.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
"https://deno.land/std@0.186.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
"https://deno.land/std@0.186.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
"https://deno.land/std@0.186.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
"https://deno.land/std@0.186.0/path/mod.ts": "ee161baec5ded6510ee1d1fb6a75a0f5e4b41f3f3301c92c716ecbdf7dae910d",
"https://deno.land/std@0.186.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
"https://deno.land/std@0.186.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
"https://deno.land/std@0.186.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
"https://deno.land/std@0.197.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
"https://deno.land/std@0.197.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
"https://deno.land/std@0.197.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
"https://deno.land/std@0.197.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978",
"https://deno.land/std@0.197.0/fs/copy.ts": "b4f7fe87190d7b310c88a2d9ff845210c0a2b7b0a094ec509747359023beb7d6",
"https://deno.land/std@0.197.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688",
"https://deno.land/std@0.197.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40",
"https://deno.land/std@0.197.0/fs/ensure_file.ts": "c38602670bfaf259d86ca824a94e6cb9e5eb73757fefa4ebf43a90dd017d53d9",
"https://deno.land/std@0.197.0/fs/ensure_link.ts": "c0f5b2f0ec094ed52b9128eccb1ee23362a617457aa0f699b145d4883f5b2fb4",
"https://deno.land/std@0.197.0/fs/ensure_symlink.ts": "5006ab2f458159c56d689b53b1e48d57e05eeb1eaf64e677f7f76a30bc4fdba1",
"https://deno.land/std@0.197.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842",
"https://deno.land/std@0.197.0/fs/exists.ts": "29c26bca8584a22876be7cb8844f1b6c8fc35e9af514576b78f5c6884d7ed02d",
"https://deno.land/std@0.197.0/fs/expand_glob.ts": "3e427436f4b3768727bd7de84169f10db75fe50b32e6dde567b8ae558a8d857a",
"https://deno.land/std@0.197.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898",
"https://deno.land/std@0.197.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9",
"https://deno.land/std@0.197.0/fs/walk.ts": "21a3cc5ff39c38acc93575213f54d5f1d44c5c6614ed97603d171eb0bf56a565",
"https://deno.land/std@0.197.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
"https://deno.land/std@0.197.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
"https://deno.land/std@0.197.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
"https://deno.land/std@0.197.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
"https://deno.land/std@0.197.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
"https://deno.land/std@0.197.0/path/mod.ts": "f065032a7189404fdac3ad1a1551a9ac84751d2f25c431e101787846c86c79ef",
"https://deno.land/std@0.197.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
"https://deno.land/std@0.197.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
"https://deno.land/std@0.197.0/path/win32.ts": "4fca292f8d116fd6d62f243b8a61bd3d6835a9f0ede762ba5c01afe7c3c0aa12",
"https://deno.land/std@0.85.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
"https://deno.land/std@0.85.0/_util/os.ts": "e282950a0eaa96760c0cf11e7463e66babd15ec9157d4c9ed49cc0925686f6a7",
"https://deno.land/std@0.85.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b",
"https://deno.land/std@0.85.0/fs/copy.ts": "acc21e2569c92e715be48f40665a299cb995a4dce04145c3dd624791b885114c",
"https://deno.land/std@0.85.0/fs/empty_dir.ts": "2edd70ff6405e1893e781a82aec8c574dfc748a7bb9d9ce8f0abdf002cdbba3f",
"https://deno.land/std@0.85.0/fs/ensure_dir.ts": "f21262e788a707aaa2dd22064da7cd40e3b2f0f067e9b2aed1b288091170cc05",
"https://deno.land/std@0.85.0/fs/ensure_file.ts": "84c7cff81ecedef3969e3fcd2d0c2aecd9bafea246cd18847deba7a54126134f",
"https://deno.land/std@0.85.0/fs/ensure_link.ts": "e48abe5bf639389ee6f42bb8bdd8b7b2a4c93701cd618b12cdcad83ccea44f2e",
"https://deno.land/std@0.85.0/fs/ensure_symlink.ts": "cbb2c908135808c0545c6304046b6ab5c024b0bb1832e69c819b58d9feee66ef",
"https://deno.land/std@0.85.0/fs/eol.ts": "afaebaaac36f48c423b920c836551997715672b80a0fee9aa7667c181a94f2df",
"https://deno.land/std@0.85.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00",
"https://deno.land/std@0.85.0/fs/expand_glob.ts": "b5a8fcadf40eb7b034a1f807349cbace0ddb28c4e5a6b6aaf2d8ca925ba02f9f",
"https://deno.land/std@0.85.0/fs/mod.ts": "26eee4b52a8c516e37d464094b080ff6822883e7f01ff0ba0a72b8dcd54b9927",
"https://deno.land/std@0.85.0/fs/move.ts": "36697916a5cf2ebc7d298089a9a3ccc6b3af1eaecc173e57a9f5eb10f1f04221",
"https://deno.land/std@0.85.0/fs/walk.ts": "8d37f2164a7397668842a7cb5d53b9e7bcd216462623b1b96abe519f76d7f8b9",
"https://deno.land/std@0.85.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
"https://deno.land/std@0.85.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
"https://deno.land/std@0.85.0/path/_util.ts": "f4fa69aa3cbbd8568763bfc43c7236875015ba343602d8bafd332b4b4243681b",
"https://deno.land/std@0.85.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a",
"https://deno.land/std@0.85.0/path/glob.ts": "4a524c1c9da3e79a9fdabdc6e850cd9e41bdf31e442856ffa19c5b123268ca95",
"https://deno.land/std@0.85.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
"https://deno.land/std@0.85.0/path/posix.ts": "1408f8ba482a4dc5fc0a7cd7be28bbbff9608d2b3b5ffdcf288ae1228d959add",
"https://deno.land/std@0.85.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
"https://deno.land/std@0.85.0/path/win32.ts": "6ca052f54500f00cd7a5172fde62900626ab620dcd5bdcf4e6f5695d001ddef6",
"https://deno.land/x/bcrypt@v0.3.0/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7", "https://deno.land/x/bcrypt@v0.3.0/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7",
"https://deno.land/x/bcrypt@v0.3.0/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7", "https://deno.land/x/bcrypt@v0.3.0/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7",
"https://deno.land/x/bcrypt@v0.3.0/src/bcrypt/bcrypt.ts": "65819ce8e32d6e6a68f8753931237c58baa39b2573c1d7fac42f03d51499f242", "https://deno.land/x/bcrypt@v0.3.0/src/bcrypt/bcrypt.ts": "65819ce8e32d6e6a68f8753931237c58baa39b2573c1d7fac42f03d51499f242",
"https://deno.land/x/bcrypt@v0.3.0/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8", "https://deno.land/x/bcrypt@v0.3.0/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8",
"https://deno.land/x/bcrypt@v0.3.0/src/worker.ts": "5a73bdfee9c9e622f47c9733d374b627dce52fb3ec1e74c8226698b3fc57ffac" "https://deno.land/x/bcrypt@v0.3.0/src/worker.ts": "5a73bdfee9c9e622f47c9733d374b627dce52fb3ec1e74c8226698b3fc57ffac",
"https://deno.land/x/deno_cache@0.4.1/auth_tokens.ts": "5fee7e9155e78cedf3f6ff3efacffdb76ac1a76c86978658d9066d4fb0f7326e",
"https://deno.land/x/deno_cache@0.4.1/cache.ts": "51f72f4299411193d780faac8c09d4e8cbee951f541121ef75fcc0e94e64c195",
"https://deno.land/x/deno_cache@0.4.1/deno_dir.ts": "f2a9044ce8c7fe1109004cda6be96bf98b08f478ce77e7a07f866eff1bdd933f",
"https://deno.land/x/deno_cache@0.4.1/deps.ts": "8974097d6c17e65d9a82d39377ae8af7d94d74c25c0cbb5855d2920e063f2343",
"https://deno.land/x/deno_cache@0.4.1/dirs.ts": "d2fa473ef490a74f2dcb5abb4b9ab92a48d2b5b6320875df2dee64851fa64aa9",
"https://deno.land/x/deno_cache@0.4.1/disk_cache.ts": "1f3f5232cba4c56412d93bdb324c624e95d5dd179d0578d2121e3ccdf55539f9",
"https://deno.land/x/deno_cache@0.4.1/file_fetcher.ts": "07a6c5f8fd94bf50a116278cc6012b4921c70d2251d98ce1c9f3c352135c39f7",
"https://deno.land/x/deno_cache@0.4.1/http_cache.ts": "f632e0d6ec4a5d61ae3987737a72caf5fcdb93670d21032ddb78df41131360cd",
"https://deno.land/x/deno_cache@0.4.1/mod.ts": "ef1cda9235a93b89cb175fe648372fc0f785add2a43aa29126567a05e3e36195",
"https://deno.land/x/deno_cache@0.4.1/util.ts": "8cb686526f4be5205b92c819ca2ce82220aa0a8dd3613ef0913f6dc269dbbcfe",
"https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66",
"https://deno.land/x/emit@0.25.0/_utils.ts": "98412edc7aa29e77d592b54fbad00bdec1b05d0c25eb772a5f8edc9813e08d88",
"https://deno.land/x/emit@0.25.0/emit.generated.js": "0728e0cd293b930db2532f8cb5087fdb77aee1f30a059207533780f40250fd6a",
"https://deno.land/x/emit@0.25.0/mod.ts": "66ef8ddaedcfca033eeee851379af59ed3f0e0aa6e025e7cdd24e4e158d874f3",
"https://deno.land/x/leaf@v1.0.4/constants.ts": "2b18c5be5a57cea4d3d6298d7c4c636e5db821c580c3197f9c9bcab65f8c3bf0",
"https://deno.land/x/leaf@v1.0.4/functions/getFileInMem.ts": "cec6c3c6add22c0c3316d8301994ab583feac5c3052df3072ad12976ea2aeec4",
"https://deno.land/x/leaf@v1.0.4/functions/getFilePath.ts": "80ce141c1bd9735d3b7961b6ec8736070475c296c342be6bb4e189483f020801",
"https://deno.land/x/leaf@v1.0.4/functions/methods.ts": "b8beebdcc1c0fbae00cc61dc3fdca7209a5b5e08c1b955ccf0e6b04c85c6ee46",
"https://deno.land/x/leaf@v1.0.4/leafCompiler.ts": "155ac29c04fe3a0f4d336a95058eebb1a14e291c31862356d11cd280e67563ce",
"https://deno.land/x/leaf@v1.0.4/mod.ts": "2f7a4d2c804978c342a2092ff1d2ec05ffb0e52dfc57bdeac939d56df8254983",
"https://deno.land/x/wasmbuild@0.14.1/cache.ts": "89eea5f3ce6035a1164b3e655c95f21300498920575ade23161421f5b01967f4",
"https://deno.land/x/wasmbuild@0.14.1/loader.ts": "d98d195a715f823151cbc8baa3f32127337628379a02d9eb2a3c5902dbccfc02"
}, },
"workspace": { "workspace": {
"dependencies": [ "dependencies": [
"jsr:@gz/jwt@0.1", "jsr:@gz/jwt@0.1",
"jsr:@proxnet/undead-logging@^1.2.0", "jsr:@proxnet/undead-logging@^1.2.0",
"jsr:@std/assert@1", "jsr:@std/assert@1",
"npm:@types/cookie-parser@^1.4.8",
"npm:@types/express@5", "npm:@types/express@5",
"npm:@types/validator@^13.12.2",
"npm:cookie-parser@^1.4.7",
"npm:discord.js@^14.16.3", "npm:discord.js@^14.16.3",
"npm:express@^4.21.2", "npm:express@^4.21.2",
"npm:ioredis@^5.5.0", "npm:ioredis@^5.5.0",

View File

@@ -1,14 +1,4 @@
{ {
"m_GameObject": {
"m_FileID": 0,
"m_PathID": 0
},
"m_Enabled": 1,
"m_Script": {
"m_FileID": 1,
"m_PathID": 334
},
"m_Name": "AGRoomRuntimeConfig",
"Locations": [ "Locations": [
{ {
"Name": "Dorm Room", "Name": "Dorm Room",

View File

@@ -1,6 +1,11 @@
// @ts-types = "npm:@types/express" // @ts-types = "npm:@types/express"
import express from "express"; import express from "express";
import Logging from "@proxnet/undead-logging"; import Logging from "@proxnet/undead-logging";
import { decode } from "@gz/jwt";
import { Config } from "./config.ts";
import { AuthType, User, UserTokenFormat } from "./data/users.ts";
const config = Config.getConfig();
const log = new Logging('APIUtils'); const log = new Logging('APIUtils');
@@ -29,8 +34,6 @@ export function generateRandomString(length: number) {
return randomString; return randomString;
} }
const instanceId = generateRandomString(128);
export function checkQueryTypes<T>(typeDef: T) { export function checkQueryTypes<T>(typeDef: T) {
return (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { return (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
for (const key in typeDef) { for (const key in typeDef) {
@@ -58,11 +61,11 @@ export function checkBodyTypes<T>(typeDef: T) {
} }
export function genericResponseFormat(failure: boolean, msg: string | null = null, data: object | null = null) { export function genericResponseFormat(failure: boolean, msg: string | null = null, data: object | null = null) {
return { failed: failure, instance: instanceId, message: msg, data: data }; return { failed: failure, message: msg, data: data };
} }
export function genericResponse(failure: boolean, msg: string | null = null, data: object | null = null) { export function genericResponse(failure: boolean, msg: string | null = null, data: object | null = null) {
return (_rq: express.Request, rs: express.Response) => { return (_rq: express.Request, rs: express.Response) => {
rs.json({ failed: failure, instance: instanceId, message: msg, data: data }); rs.json({ failed: failure, message: msg, data: data });
}; };
} }
type RecNetResponse = { type RecNetResponse = {
@@ -165,4 +168,45 @@ export class RateLimiter {
} }
export async function UserAuthentication(rq: express.Request, rs: express.Response, nxt: express.NextFunction) {
function returnUnauthorized() {
rs.statusCode = 401;
rs.json(genericResponseFormat(true, 'Authorization required.'));
}
const token: string | undefined = rq.header('GalvanicAuth');
if (typeof token == 'undefined') {
returnUnauthorized();
return;
}
try {
const decodedToken = await decode<UserTokenFormat>(token, config.auth.secret, { algorithm: "HS512" });
const valid = ![
decodedToken.iss == config.web.publichost,
decodedToken.nbf < Math.round(Date.now() / 1000),
decodedToken.exp > Math.round(Date.now() / 1000),
decodedToken.typ == AuthType.Web
].includes(false);
if (valid) {
rs.locals.user = new User(decodedToken.sub);
nxt();
}
else {
returnUnauthorized();
return;
}
} catch (err) {
returnUnauthorized();
log.w(`User Authentication failed: ${err}`);
}
}
export type NoBody = Record<string | number | symbol, never>
export * as APIUtils from "./apiutils.ts" export * as APIUtils from "./apiutils.ts"

View File

@@ -14,19 +14,22 @@ type RedisConfiguration = {
type WebConfiguration = { type WebConfiguration = {
port: number, port: number,
host: string, host: string,
nameserverHost: string, publichost: string,
secureNameserverHost: boolean securepublichost: boolean
} }
type PublicConfiguration = { type PublicConfiguration = {
serverName: string, serverName: string,
serverId: string,
owner: string, owner: string,
motd: string, motd: string,
levelScale: number, levelScale: number,
maxLevels: number maxLevels: number
patches: string[],
} }
type LoggingConfiguration = { type LoggingConfiguration = {
notfound: boolean,
debug: boolean, debug: boolean,
network: boolean network: boolean
} }
@@ -37,13 +40,12 @@ type DiscordConfiguration = {
guildId: string guildId: string
} }
type SecretConfiguration = { type AuthConfiguration = {
authSecret: string secret: string,
} /**
* In Hours
type RecaptchaConfiguration = { */
sitekey: string, timeout: number
secret: string
} }
export type GalvanicConfiguration = { export type GalvanicConfiguration = {
@@ -52,8 +54,7 @@ export type GalvanicConfiguration = {
public: PublicConfiguration, public: PublicConfiguration,
logging: LoggingConfiguration, logging: LoggingConfiguration,
discord: DiscordConfiguration | null, discord: DiscordConfiguration | null,
secrets: SecretConfiguration, auth: AuthConfiguration
recaptcha: RecaptchaConfiguration | null
} }
export const defaultConfig: GalvanicConfiguration = { export const defaultConfig: GalvanicConfiguration = {
@@ -67,29 +68,32 @@ export const defaultConfig: GalvanicConfiguration = {
web: { web: {
port: 3000, port: 3000,
host: "127.0.0.1", host: "127.0.0.1",
nameserverHost: "127.0.0.1:3000", publichost: "127.0.0.1:3000",
secureNameserverHost: false securepublichost: false,
}, },
public: { public: {
serverName: "Galvanic Corrosion", serverName: "Galvanic Corrosion",
serverId: "galvanic-corrosion-default",
owner: "John Doe", owner: "John Doe",
motd: "The narwhal bacons at midnight", motd: "A Galvanic Corrosion server.",
levelScale: 1, levelScale: 1,
maxLevels: 30 maxLevels: 30,
patches: [],
}, },
logging: { logging: {
notfound: false,
debug: false, debug: false,
network: false network: false
}, },
discord: null, discord: null,
secrets: { auth: {
authSecret: "CHANGE-ME-PLEASE" secret: "CHANGE-ME-PLEASE",
}, timeout: 48
recaptcha: null }
} }
/** The current configuration. Read and parsed only during startup. */ /** The current configuration. Read and parsed only during startup. */
let config: GalvanicConfiguration | undefined; let config: GalvanicConfiguration;
try { try {
if (!configurationExists()) generateDefaultConfig(); if (!configurationExists()) generateDefaultConfig();
config = JSON.parse(fs.readFileSync('./config.json').toString()); config = JSON.parse(fs.readFileSync('./config.json').toString());
@@ -113,4 +117,6 @@ export function getConfig() {
return config; return config;
} }
export const devMode = Deno.args.includes('--dev');
export * as Config from './config.ts'; export * as Config from './config.ts';

View File

@@ -1,48 +0,0 @@
import { encode, decode } from "@gz/jwt";
import { Config, GalvanicConfiguration } from "../config.ts";
import Logging from "@proxnet/undead-logging";
const log = new Logging("Auth");
const config = Config.getConfig() as GalvanicConfiguration;
type TokenFormat = {
iss: string;
sub: number;
nbf: number;
iat: number;
exp: number;
}
export class GameAuthContext {
valid: boolean | null = null;
#rawToken: string
playerId: number | null = null;
constructor(token: string) {
this.#rawToken = token;
}
async decode() {
try {
const decoded = await decode(this.#rawToken, config.secrets.authSecret) as TokenFormat;
this.playerId = decoded.sub || null;
const now = Math.round(Date.now() / 1000);
this.valid = true;
if (decoded.exp < now) this.valid = false;
if (decoded.nbf > now) this.valid = false;
} catch (e) {
this.valid = false;
log.w(`Token decode failed: ${(e as Error).stack}`);
}
}
}
export * as Authentication from "./auth.ts";

View File

@@ -59,7 +59,7 @@ export async function getAllGameConfigs() {
} }
export function setGameConfig(key: string, value: string) { export function setGameConfig(key: string, value: string) {
return Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Config.Root, Redis.KeyGroups.Config.Game)); return Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Config.Root, Redis.KeyGroups.Config.Game), key, value);
} }
export function getGameConfig(key: string) { export function getGameConfig(key: string) {
return Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Config.Root, Redis.KeyGroups.Config.Game), key); return Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Config.Root, Redis.KeyGroups.Config.Game), key);

128
src/data/profiles.ts Normal file
View File

@@ -0,0 +1,128 @@
import { Redis } from "../db.ts";
import Dictionary from "./usernames.ts";
import { Config } from "../config.ts";
import { AuthType } from "./users.ts";
import * as JsonWebToken from "@gz/jwt";
const config = Config.getConfig();
interface ProfileInitOptions {
username: string
}
interface AccountExport {
accountId: number,
profileImage: string,
isJunior: boolean,
platforms: number,
username: string,
displayName: string
}
export type ProfileTokenFormat = {
iss: string;
sub: number;
nbf: number;
iat: number;
exp: number;
typ: AuthType;
}
class Profile {
static async getUniqueId() {
let id = Math.round(Math.random() * Math.pow(2, 31));
while ((await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username))) >= 1)
id = await this.getUniqueId();
return id;
}
static async byName(name: string) {
const id = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profile_Usernames, name));
if (id == null) return null;
else return new Profile(parseInt(id, 10));
}
static async getUniqueUsername() {
let username = `${Dictionary.Adjectives[Math.floor(Math.random() * Dictionary.Adjectives.length)]}${Dictionary.Nouns[Math.floor(Math.random() * Dictionary.Nouns.length)]}${Math.round(Math.random() * 10000)}`
while ((await Profile.byName(username)) !== null) username = await this.getUniqueUsername();
return username;
}
static async init(options?: ProfileInitOptions) {
const optionsSpecified = typeof options !== 'undefined';
const newId = await this.getUniqueId();
const newUsername = optionsSpecified ? options.username : await this.getUniqueUsername();
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profile_Usernames, newUsername), newId);
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, newId.toString(), Redis.KeyGroups.Profiles.Username), newUsername);
return new Profile(newId);
}
// surely this can be written better
static getExportAccount(id: number): Promise<AccountExport | null> {
return new Promise((resolve, _reject) => {
Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username)).then(val => {
if (val == null) resolve(null);
else {
const promises = {
profileImage: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.ProfileImage)),
isJunior: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Junior)),
platforms: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Platforms)),
displayName: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.DisplayName)),
username: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username)),
}
Promise.all(Object.values(promises)).then((values) => {
resolve({
accountId: id,
profileImage: values[0] == null ? "DefaultProfileImage" : values[0],
isJunior: values[1] == null ? false : JSON.parse(values[1]),
platforms: values[2] == null ? 1 : JSON.parse(values[2]),
displayName: values[3] == null ? (values[4] == null ? "DATABASEERROR" : values[4]) : values[3],
username: values[4] == null ? "DATABASEERROR" : values[4],
});
});
}
});
});
}
static async getExportAccountsBulk(ids: number[]) {
const accs = await Promise.all(ids.map(val => this.getExportAccount(val)));
return accs.filter(val => val !== null);
}
#id: number;
constructor(id: number) {
this.#id = id;
}
getId() {
return this.#id;
}
async export() {
return await Profile.getExportAccount(this.#id);
}
async getToken() {
const payload: ProfileTokenFormat = {
iss: config.web.publichost,
sub: this.#id,
nbf: Math.round(Date.now() / 1000) - 200,
iat: Math.round(Date.now() / 1000),
exp: Math.round(Date.now() / 1000) + (config.auth.timeout * 60 * 60),
typ: AuthType.Web
};
return await JsonWebToken.encode(payload, config.auth.secret, { algorithm: "HS512" });
}
}
export default Profile;

View File

@@ -1,45 +0,0 @@
import Logging from "@proxnet/undead-logging";
import { Config } from "../config.ts";
const log = new Logging("ReCAPTCHA");
type SiteVerifyParams = {
secret: string,
response: string,
remoteip?: string
}
type SiteVerifyResponse = {
success: boolean,
challenge_ts: string,
hostname: string,
"error-codes": string[]
}
class ReCAPTCHABase {
async siteVerify(response: string, remoteip?: string) {
const config = Config.getConfig();
if (typeof config == 'undefined') return null;
if (config.recaptcha == null) {
log.e("Tried to verify ReCAPTCHA, but the config is null!");
return null;
}
const body: SiteVerifyParams = {
secret: config.recaptcha.secret,
response: response,
remoteip: remoteip
}
const res = await fetch('https://google.com/recaptcha/api/siteverify', {
method: "POST",
body: JSON.stringify(body)
});
const resBody = await res.json() as SiteVerifyResponse;
return resBody.success
}
}
const Recaptcha = new ReCAPTCHABase();
export default Recaptcha;

6
src/data/usernames.ts Normal file
View File

@@ -0,0 +1,6 @@
const Dictionary = {
Adjectives: ["Amazing","Adventurous","Affable","Agreeable","Ambitious","Amicable","Animated","Approachable","Articulate","Astute","Attractive","Authentic","Benevolent","Blissful","Bold","Bright","Buoyant","Calm","Captivating","Charismatic","Cheerful","Clever","Compassionate","Confident","Considerate","Content","Cooperative","Courageous","Creative","Cultured","Curious","Dashing","Dazzling","Dedicated","Delightful","Dependable","Determined","Diligent","Dynamic","Earnest","Easygoing","Ebullient","Effervescent","Empathetic","Enchanting","Endearing","Energetic","Engaging","Enthusiastic","Exuberant","Fantastic","Fearless","Fervent","Friendly","Funny","Generous","Gentle","Genuine","Gracious","Grateful","Harmonious","Heartwarming","Helpful","Honest","Humble","Humorous","Imaginative","Impeccable","Incisive","Incredible","Independent","Industrious","Ingenious","Insightful","Intelligent","Intuitive","Invigorating","Jovial","Jubilant","Just","Kind","Knowledgeable","Likable","Lively","Lovable","Loving","Loyal","Luminous","Magnetic","Marvelous","Masterful","Mature","Merciful","Methodical","Meticulous","Mindful","Motivated","Natural","Nurturing","Observant","Optimistic","Outgoing","Passionate","Patient","Peaceful","Perceptive","Perseverant","Persistent","Persuasive","Personable","Philanthropic","Placid","Playful","Pleasant","Poised","Positive","Powerful","Pragmatic","Proactive","Proficient","Prudent","Punctual","Purposeful","Radiant","Rational","Real","Receptive","Reflective","Reliable","Resilient","Resourceful","Respectful","Responsible","Robust","Sagacious","Serene","Sincere","Skillful","Smart","Sociable","Spirited","Splendid","Spontaneous","Steady","Sterling","Strong","Sublime","Successful","Supportive","Sympathetic","Talented","Tenacious","Thoughtful","Tireless","Tolerant","Tough","Tranquil","Trustworthy","Unassuming","Understanding","Unique","Unpretentious","Upbeat","Valiant","Vibrant","Virtuous","Visionary","Vivacious","Warmhearted","Welcoming","Wise","Witty","Wonderful","Zealous"],
Nouns: ["Nomad","Solstice","Elysium","Horizon","Catalyst","Luminescence","Utopia","Eclipse","Nebula","Arcadia","Apex","Harmony","Zenith","Radiant","Infinity","Echo","Quasar","Cascade","Empyrean","Nebula","Odyssey","Aether","Empower","Zephyr","Vibrance","Astral","Jubilant","Ascendancy","Zen","Nebulous","Ecliptic","Stellar","Quantum","Ethereal","Nexus","Synergy","Quantum","Enigma","Luminous","Epoch","Serendipity","Zenithal","Paragon","Panorama","Maverick","Voyager","Luminary","Catalyst","Phoenix","Dynamo","Zenith","Nexus","Pinnacle","Rhapsody","Serenity","Quantum","Apex","Harmony","Odyssey","Endeavor","Visionary","Epoch","Renaissance","Panache","Jubilee","Resonance","Zen","Nimbus","Ethereal","Cascade","Radiance","Synchronicity","Nebula","Equinox","Pulsar","Apex","Ethos","Wanderlust","Zenith","Nebula","Vertex","Equinox","Odyssey","Pantheon","Elysian","Nebulous","Quantum","Harmonic","Luminance","Paragon","Radiant","Epoch","Vortex","Celestia","Infinitum","Empyrean","Zephyr","Nimbus","Seraphic","Enigma","Synergy","Ecliptic","Utopian","Phoenix","Catalyst","Euphoria","Astral","Nebula","Ethereal","Zenith","Nexus","Empower","Panorama","Cascade","Quantum","Jubilant","Zen","Radiance","Labyrinth"]
}
export default Dictionary;

View File

@@ -1,101 +1,106 @@
import * as bcrypt from "bcrypt";
import { Redis } from "../db.ts"; import { Redis } from "../db.ts";
import Logging from "@proxnet/undead-logging"; import * as JsonWebToken from "@gz/jwt";
import { Config } from "../config.ts";
const log = new Logging("UserConstruct"); import Profile from "./profiles.ts";
type UserInitOptions = { type UserInitOptions = {
username: string, client_id: string,
password: string, pubkey: string
} }
type UserCreatedObj = { type UserCreatedObj = {
user: User, user: User
backupcode: string
} }
function randomASCII() { export enum AuthType {
const codes = crypto.getRandomValues(new Uint8Array(512)); Game,
const filteredCodes = codes.filter(val => (val >= 48 && val <= 57) || (val >= 65 && val <= 90) || (val >= 97 && val <= 122) ); Web
let str = String.fromCharCode(...filteredCodes);
if (str.length < 32) str = randomASCII();
return str.substring(0, 32);
} }
export type UserTokenFormat = {
iss: string;
sub: string;
nbf: number;
iat: number;
exp: number;
typ: AuthType;
}
const config = Config.getConfig();
export class User { export class User {
static async exists(username: string) { static async exists(id: string) {
return (await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Usernames, username))) == 1; return (await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Users.Root, id, Redis.KeyGroups.Users.Pubkey))) == 1;
} }
/** /**
* Create a user * Create a user
* @returns A `UserCreatedObj` with a reference to the new user if one was created, else `null` if the username already exists. * @returns A `User` if one was created, else `null` if the `client_id` already exists.
*/ */
static async init(options: UserInitOptions) { static async init(options: UserInitOptions) {
if (await User.exists(options.username)) return null; if (await User.exists(options.client_id)) return null;
const uuid = crypto.randomUUID(); Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Users.Root, options.client_id, Redis.KeyGroups.Users.Pubkey), options.pubkey);
const backup = randomASCII();
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Usernames, options.username), uuid);
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Users.Root, uuid, Redis.KeyGroups.Users.Username), options.username);
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Users.Root, uuid, Redis.KeyGroups.Users.BackupCode), backup);
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Users.Root, uuid, Redis.KeyGroups.Users.Password), await bcrypt.hash(options.password));
const user = new User(uuid); const user = new User(options.client_id);
const res: UserCreatedObj = { return user;
user: user,
backupcode: backup
}
return res;
} }
/** #client_id: string;
* Get a User by their username
* @returns A `User` is one was found, else `null`
*/
static async byName(username: string) {
const uuid = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Usernames, username));
if (uuid == null) return null;
else return new User(uuid);
}
#uuid: string; constructor(client_id: string) {
this.#client_id = client_id;
constructor(uuid: string) {
this.#uuid = uuid;
} }
getUuid() { getUuid() {
return this.#uuid; return this.#client_id;
} }
async validatePassword(password: string) { async exists() {
const hash = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#uuid, Redis.KeyGroups.Users.Password)); return (await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Pubkey))) >= 1;
if (hash == null) throw new Error(`Hash for user ${this.#uuid} was not found`);
return await bcrypt.compare(password, hash);
} }
async setPassword(password: string) { async getToken() {
const hash = await bcrypt.hash(password); const payload: UserTokenFormat = {
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#uuid, Redis.KeyGroups.Users.Password), hash); iss: config.web.publichost,
sub: this.#client_id,
nbf: Math.round(Date.now() / 1000) - 200,
iat: Math.round(Date.now() / 1000),
exp: Math.round(Date.now() / 1000) + (config.auth.timeout * 60 * 60),
typ: AuthType.Web
};
return await JsonWebToken.encode(payload, config.auth.secret, {algorithm: "HS512"});
} }
async getBackupCode() { async exportAssociatedProfiles() {
return await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#uuid, Redis.KeyGroups.Users.BackupCode)); const profiles = await this.getAssociatedProfiles();
return await Profile.getExportAccountsBulk(profiles.values().toArray());
} }
async getAssociatedProfiles() { async getAssociatedProfiles() {
const list = await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#uuid, Redis.KeyGroups.Users.Profiles)); const list = await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Profiles));
return new Set<number>(list.filter(val => !Number.isNaN(parseInt(val, 10))).map(val => parseInt(val, 10))); return new Set<number>(list.filter(val => !Number.isNaN(parseInt(val, 10))).map(val => parseInt(val, 10)));
} }
async removeAssociatedProfile(id: number) { async removeAssociatedProfile(id: number) {
await Redis.Database.srem(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#uuid, Redis.KeyGroups.Users.Profiles), id); await Redis.Database.srem(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Profiles), id);
} }
async addAssociatedProfile(id: number) { async addAssociatedProfile(id: number) {
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#uuid, Redis.KeyGroups.Users.Profiles), id); await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Profiles), id);
}
async addAssociatedPlatformId(id: string) {
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.PlatformIds), id);
}
async addNonce(str: string) {
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Nonces), str);
}
async hasNonce(str: string) {
return (await Redis.Database.sismember(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Nonces), str)) >= 1;
} }
} }

View File

@@ -20,11 +20,11 @@ Deno.addSignalListener('SIGINT', () => {
}); });
export const Database = new Redis({ export const Database = new Redis({
port: config?.redis.port, port: config.redis.port,
host: config?.redis.host, host: config.redis.host,
username: config?.redis.username == "" ? undefined : config?.redis.username, username: config.redis.username == "" ? undefined : config.redis.username,
password: config?.redis.password == "" ? undefined : config?.redis.password, password: config.redis.password == "" ? undefined : config.redis.password,
db: config?.redis.db, db: config.redis.db,
lazyConnect: true lazyConnect: true
}); });
Database.on('connect', async () => { Database.on('connect', async () => {
@@ -35,7 +35,7 @@ Database.on('connect', async () => {
}); });
}); });
Database.on('connecting', () => { Database.on('connecting', () => {
log.i('Connecting to Redis..'); log.n('Connecting to Redis..');
}); });
Database.on('error', (err) => { Database.on('error', (err) => {
log.e(`Redis error: ${err.stack}`); log.e(`Redis error: ${err.stack}`);
@@ -50,18 +50,21 @@ export const KeyGroups = {
Dynamic: "dynamic", Dynamic: "dynamic",
Game: "game" Game: "game"
}, },
Ids: "profile-ids", Profile_Usernames: "profile-usernames",
Profiles: { Profiles: {
Root: "profiles" Root: "profiles",
Username: "username",
ProfileImage: "profileImage",
Junior: "isJunior",
Platforms: "platforms",
DisplayName: "displayname"
}, },
Usernames: "usernames",
Users: { Users: {
Root: "users", Root: "users",
Username: "username",
Password: "password",
BackupCode: "backupcode",
Profiles: "profiles", Profiles: "profiles",
Meta: "meta" Pubkey: "pubkey",
Nonces: "nonces",
PlatformIds: "associatedPlatforms"
} }
} }
export * as Redis from "./db.ts"; export * as Redis from "./db.ts";

View File

@@ -14,7 +14,7 @@ export const client = new discord.Client({ intents: [discord.GatewayIntentBits.G
client.once(discord.Events.ClientReady, client => { client.once(discord.Events.ClientReady, client => {
log.i(`Logged in to Discord as "${client.user.tag}"`); log.i(`Logged in to Discord as "${client.user.tag}"`);
client.user?.setActivity(config?.public.motd, { type: discord.ActivityType.Custom }); client.user?.setActivity(config.public.motd, { type: discord.ActivityType.Custom });
}); });
let shuttingDown = false; let shuttingDown = false;
@@ -27,12 +27,12 @@ Deno.addSignalListener('SIGINT', () => {
}); });
export function login() { export function login() {
if (config?.discord?.token == Config.defaultConfig.discord?.token) { if (config.discord?.token == Config.defaultConfig.discord?.token) {
log.i('Discord not configured, ignoring'); log.i('Discord not configured, ignoring');
return; return;
} }
log.i(`Creating Discord connection..`); log.i(`Creating Discord connection..`);
client.login(config?.discord?.token); client.login(config.discord?.token);
} }
export * as Discord from "./discord.ts"; export * as Discord from "./discord.ts";

View File

@@ -1,48 +0,0 @@
import { KeyGroups, Redis } from "./db.ts";
export enum ResultType {
Found,
NotFound
}
type ConfigResult = {
Status: ResultType,
Data: string | null
}
type ConfigMResult = {
Status: ResultType,
Data: (string | null)[] | null
}
/** Get a dyamic config. */
export async function getConfig(key: string) {
const res: ConfigResult = {
Status: ResultType.Found,
Data: null
}
const data = await Redis.Database.get(`${KeyGroups.Config.Root}:${KeyGroups.Config.Dynamic}:${key}`);
if (data == null) res.Status = ResultType.NotFound;
else res.Data = data;
return res;
}
export async function mgetConfig(...keys: string[]) {
const res: ConfigMResult = {
Status: ResultType.Found,
Data: null
}
const data = await Redis.Database.mget(...keys);
res.Data = data;
return res;
}
/** Set a dynamic config. */
export async function setConfig(key: string, value: string) {
await Redis.Database.set(`${KeyGroups.Config.Root}:${KeyGroups.Config.Dynamic}:${key}`, value);
}
export * as DynamicConfig from "./dynamicconfig.ts";

View File

@@ -1,11 +1,13 @@
import * as Log from "@proxnet/undead-logging"; import * as Log from "@proxnet/undead-logging";
import * as Config from "./config.ts"; import * as Config from "./config.ts";
// @ts-types = 'npm:@types/express'
import express from "express";
import { Database } from "./db.ts"; import { Database } from "./db.ts";
import { APIUtils } from "./apiutils.ts"; import { APIUtils } from "./apiutils.ts";
import { Discord } from "./discord.ts"; import { Discord } from "./discord.ts";
import { User } from "./data/users.ts"; import { generateRandomString } from "./apiutils.ts";
// @ts-types = "npm:@types/express"
import express from "express";
const instanceId = generateRandomString(64);
const log = new Log.default("Main"); const log = new Log.default("Main");
@@ -17,54 +19,18 @@ if (typeof config == 'undefined') {
log.e('Cannot start: Configuration is undefined'); log.e('Cannot start: Configuration is undefined');
Deno.exit(1); Deno.exit(1);
} }
if (config.secrets.authSecret == Config.defaultConfig.secrets.authSecret) { if (config.auth.secret == Config.defaultConfig.auth.secret) {
log.e(`Cannot start: Auth secret is default. Please change 'secrets.authSecret' in 'config.json'`); log.e(`Cannot start: Auth secret is default. Please change 'secrets.authSecret' in 'config.json'`);
Deno.exit(1); Deno.exit(1);
} }
if (config.public.serverId == Config.defaultConfig.public.serverId) {
log.e(`Cannot start: Server ID is default. Please change 'public.serverId' in 'config.json'`);
Deno.exit(1);
}
Log.MessageTypeVisibility.Network = config.logging.network; Log.MessageTypeVisibility.Network = config.logging.network;
Log.MessageTypeVisibility.Debug = config.logging.debug; Log.MessageTypeVisibility.Debug = config.logging.debug;
const port = config.web.port;
const host = config.web.host;
log.i(`Starting HTTP server on http://${host}:${port}`);
const app = express();
app.disable('etag');
app.disable('x-powered-by');
app.use((rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
rs.locals.auth = null;
log.n(`${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`);
nxt();
});
app.get('/', APIUtils.genericResponse(false, `${config?.public.serverName} - ${config?.public.motd}`));
app.get('/debug', async (_rq, rs) => {
const user = await User.init({ username: "testuser123", password: "foopass123" });
log.i(String(user == null));
rs.sendStatus(200);
});
// content routes
const nameserverRouter = await import('./routes/nameserver.ts');
const apiRouter = await import('./routes/api.ts');
const userRouter = await import('./routes/user.ts');
app.use(nameserverRouter.route.path, nameserverRouter.route.router);
app.use(apiRouter.route.path, apiRouter.route.router);
app.use(userRouter.route.path, userRouter.route.router);
app.use((rq: express.Request, rs: express.Response) => {
log.e(`${APIUtils.getSrcIpDefault(rq)} 404 ${rq.method} ${rq.url.toString()}`);
rs.statusCode = 404;
rs.json(APIUtils.genericResponseFormat(true, 'Endpoint not found. Check your syntax and/or method.'));
});
try { try {
Database.connect(); Database.connect();
} catch (err) { } catch (err) {
@@ -72,9 +38,53 @@ try {
Deno.exit(1); Deno.exit(1);
} }
const port = config.web.port;
const host = config.web.host;
log.n(`Starting HTTP server on http://${host}:${port}`);
const app = express();
app.disable('etag');
app.disable('x-powered-by');
app.use((rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
rs.setHeader('Instance', instanceId)
log.n(`${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`);
nxt();
});
app.get('/info', (_rq, rs) => {
rs.json({
name: config.public.serverName,
id: config.public.serverId,
motd: config.public.motd,
patches: config.public.patches
});
});
// content routes
const nameserverRouter = await import('./routes/nameserver.ts');
const apiRouter = await import('./routes/api.ts');
const userRouter = await import('./routes/user.ts');
const authRouter = await import('./routes/auth.ts');
const accountRouter = await import('./routes/account.ts');
app.use(nameserverRouter.route.path, nameserverRouter.route.router);
app.use(apiRouter.route.path, apiRouter.route.router);
app.use(userRouter.route.path, userRouter.route.router);
app.use(authRouter.route.path, authRouter.route.router);
app.use(accountRouter.route.path, accountRouter.route.router);
app.use((rq: express.Request, rs: express.Response) => {
log.e(`${APIUtils.getSrcIpDefault(rq)} 404 ${rq.method} ${rq.url.toString()}`);
rs.statusCode = 404;
rs.json(APIUtils.genericResponseFormat(true, 'Endpoint not found. Check your syntax and/or method.'));
});
try { try {
const http = app.listen(config.web.port, config.web.host, () => { const http = app.listen(config.web.port, config.web.host, () => {
log.n(`Listening on http://${config.web.host}`); log.n(`Listening on http://${config.web.host}:${config.web.port}`);
let shuttingDown = false; let shuttingDown = false;
Deno.addSignalListener('SIGINT', () => { Deno.addSignalListener('SIGINT', () => {
@@ -83,7 +93,6 @@ try {
log.i(`Shutting down`); log.i(`Shutting down`);
http.close(); http.close();
http.removeAllListeners();
}); });
}); });
} catch (err) { } catch (err) {

6
src/routes/account.ts Normal file
View File

@@ -0,0 +1,6 @@
import { APIUtils } from "../apiutils.ts";
import { route as AccountRoute } from "./account/account.ts";
export const route = APIUtils.createRouter('/accountservice');
route.router.use(AccountRoute.path, AccountRoute.router);

View File

@@ -0,0 +1,30 @@
import { APIUtils } from "../../apiutils.ts";
import express from "express";
import Profile from "../../data/profiles.ts";
export const route = APIUtils.createRouter("/account");
interface CreateAccountRequestBody {
platform: string,
platformId: string,
deviceId: string
}
route.router.post('/create',
APIUtils.UserAuthentication,
express.urlencoded({ extended: true }),
APIUtils.checkBodyTypes<CreateAccountRequestBody>({platform: "", platformId: "", deviceId: ""}),
async (_rq, rs) => {
const newAcc = await Profile.init();
rs.locals.user.addAssociatedProfile(newAcc.getId());
rs.json({
success: true,
value: await newAcc.export()
});
},
);

View File

@@ -1,8 +1,10 @@
import { route as VersionCheckRoute } from "./api/versioncheck.ts"; import { route as VersionCheckRoute } from "./api/versioncheck.ts";
import { route as ConfigRoute } from "./api/config.ts"; import { route as ConfigRoute } from "./api/config.ts";
import { route as GameConfig } from "./api/gameconfigs.ts";
import { APIUtils } from "../apiutils.ts"; import { APIUtils } from "../apiutils.ts";
export const route = APIUtils.createRouter('/api'); export const route = APIUtils.createRouter('/api');
route.router.use(VersionCheckRoute.path, VersionCheckRoute.router); route.router.use(VersionCheckRoute.path, VersionCheckRoute.router);
route.router.use(ConfigRoute.path, ConfigRoute.router); route.router.use(ConfigRoute.path, ConfigRoute.router);
route.router.use(GameConfig.path, GameConfig.router);

View File

@@ -5,9 +5,9 @@ export const route = APIUtils.createRouter('/versioncheck');
const validVersion = '20191120'; const validVersion = '20191120';
enum VersionStatus { enum VersionStatus {
UpdateRequired, ValidForPlay,
ValidForMenu, ValidForMenu,
ValidForPlay UpdateRequired
} }
type ValidVersionResponse = { type ValidVersionResponse = {
VersionStatus: VersionStatus VersionStatus: VersionStatus
@@ -15,7 +15,8 @@ type ValidVersionResponse = {
route.router.get('/v4', (rq, rs) => { route.router.get('/v4', (rq, rs) => {
const requestedVer = rq.query['v']; const requestedVer = rq.query['v'];
if (typeof requestedVer == 'undefined') { const pQuery = rq.query['p'];
if (typeof requestedVer == 'undefined' || typeof pQuery == 'undefined') {
rs.statusCode = 400; rs.statusCode = 400;
rs.json(APIUtils.genericResponseFormat(true, 'One or more query parameters were not found.')); rs.json(APIUtils.genericResponseFormat(true, 'One or more query parameters were not found.'));
} }

8
src/routes/auth.ts Normal file
View File

@@ -0,0 +1,8 @@
import { APIUtils } from "../apiutils.ts";
import { route as CachedLoginRoute } from "./auth/cachedlogin.ts";
import { route as ConnectRoute } from "./auth/connect.ts";
export const route = APIUtils.createRouter('/authservice');
route.router.use(CachedLoginRoute.path, CachedLoginRoute.router);
route.router.use(ConnectRoute.path, ConnectRoute.router);

View File

@@ -0,0 +1,15 @@
import { APIUtils } from "../../apiutils.ts";
export const route = APIUtils.createRouter("/cachedlogin");
route.router.get('/forplatformid/:platformtype/:platformid',
APIUtils.UserAuthentication,
async (_rq, rs) => {
rs.json(await rs.locals.user.exportAssociatedProfiles());
}
);

View File

@@ -0,0 +1,5 @@
import { APIUtils } from "../../apiutils.ts";
export const route = APIUtils.createRouter("/connect");
//route.router.post()

View File

@@ -2,7 +2,7 @@ import { APIUtils } from "../apiutils.ts";
import { Config } from "../config.ts"; import { Config } from "../config.ts";
const config = Config.getConfig() as Config.GalvanicConfiguration; const config = Config.getConfig() as Config.GalvanicConfiguration;
const protocol = config.web.secureNameserverHost ? 'https' : 'http'; const protocol = config.web.securepublichost ? 'https' : 'http';
export const route = APIUtils.createRouter('/ns'); export const route = APIUtils.createRouter('/ns');
@@ -21,17 +21,17 @@ type NameserverHosts = {
} }
const nameserver: NameserverHosts = { const nameserver: NameserverHosts = {
Auth: `${protocol}://${config.web.nameserverHost}/auth`, Auth: `${protocol}://${config.web.publichost}/auth`,
API: `${protocol}://${config.web.nameserverHost}`, API: `${protocol}://${config.web.publichost}`,
WWW: `${protocol}://${config.web.nameserverHost}`, WWW: `${protocol}://${config.web.publichost}`,
Notifications: `${protocol}://${config.web.nameserverHost}/notify`, Notifications: `${protocol}://${config.web.publichost}/notify`,
Images: `${protocol}://${config.web.nameserverHost}/img`, Images: `${protocol}://${config.web.publichost}/img`,
CDN: `${protocol}://${config.web.nameserverHost}/cdn`, CDN: `${protocol}://${config.web.publichost}/cdn`,
Commerce: `${protocol}://${config.web.nameserverHost}/commerce`, Commerce: `${protocol}://${config.web.publichost}/commerce`,
Matchmaking: `${protocol}://${config.web.nameserverHost}/match`, Matchmaking: `${protocol}://${config.web.publichost}/match`,
Storage: `${protocol}://${config.web.nameserverHost}/storage`, Storage: `${protocol}://${config.web.publichost}/storage`,
Chat: `${protocol}://${config.web.nameserverHost}/chat`, Chat: `${protocol}://${config.web.publichost}/chat`,
Leaderboard: `${protocol}://${config.web.nameserverHost}/leaderboard` Leaderboard: `${protocol}://${config.web.publichost}/leaderboard`
} }
route.router.get('*', (_rq, rs) => { route.router.get('*', (_rq, rs) => {

View File

@@ -1,53 +1,107 @@
import { APIUtils, getSrcIpDefault } from "../apiutils.ts"; import { APIUtils, NoBody } from "../apiutils.ts";
// @ts-types = "npm:@types/express" // @ts-types = "npm:@types/express"
import express from "express"; import express from "express";
import { User } from "../data/users.ts"; import { User } from "../data/users.ts";
import Recaptcha from "../data/recaptcha.ts"; import { Config } from "../config.ts";
import crypto from "node:crypto";
import Logging from "@proxnet/undead-logging";
const log = new Logging("UserRoute");
const config = Config.getConfig();
export const route = APIUtils.createRouter('/user'); export const route = APIUtils.createRouter('/user');
type CreateUserBody = { interface AuthRequestSec {
username: string, timestamp: number,
password: string, nonce: string,
recaptcha: string server_id: string
} }
type CreatedUserResponse = { interface AuthRequestRoot {
uuid: string, client_id: string,
backupcode: string message: AuthRequestSec,
signature: string,
pubkey: string
} }
const rateLimit = new APIUtils.RateLimiter(10, 1); const rateLimit = new APIUtils.RateLimiter(60, 1);
route.router.post('/create', route.router.post('/auth',
rateLimit.middle(), rateLimit.middle(),
express.json(), express.json(),
APIUtils.checkBodyTypes<CreateUserBody>({ username: "test", password: "test", recaptcha: "test" }), APIUtils.checkBodyTypes<AuthRequestRoot>({
client_id: "asdf",
message: {
timestamp: 0,
nonce: "asdf",
server_id: "asdf"
},
signature: "asdf",
pubkey: "asdf"
}),
async (rq, rs) => { async (rq: express.Request<NoBody, NoBody, AuthRequestRoot>, rs: express.Response) => {
const body = rq.body as CreateUserBody;
const recaptchaStatus = await Recaptcha.siteVerify(body.recaptcha, getSrcIpDefault(rq)); function authFailed(msg: string) {
if (recaptchaStatus) { rs.json(APIUtils.genericResponseFormat(true, msg));
const userinit = await User.init({ username: body.username, password: body.password }); }
if (userinit == null) {
rs.statusCode = 400;
rs.json(APIUtils.genericResponseFormat(true, "Username is already taken"));
} else {
const res: CreatedUserResponse = { if (rq.body.message.server_id !== config.public.serverId) {
uuid: userinit.user.getUuid(), log.w(`Auth request failed (serverId mismatch), config error?\n given ID: '${rq.body.message.server_id}'\n our ID: '${config.public.serverId}'`);
backupcode: userinit.backupcode authFailed('Authentication request not intended for this server.');
return;
} }
rs.json(APIUtils.genericResponseFormat(false, "User created successfully", res));
try {
const verify = crypto.createVerify('SHA256');
verify.update(JSON.stringify(rq.body.message));
verify.end();
const publicKey = await crypto.subtle.importKey(
"spki",
(Uint8Array.from(atob(rq.body.pubkey), c => c.charCodeAt(0))).buffer,
{ name: "ECDSA", namedCurve: "P-256" },
false,
["verify"]
);
const messageBytes = new TextEncoder().encode(JSON.stringify(rq.body.message));
const signatureBytes = Uint8Array.from(atob(rq.body.signature), c => c.charCodeAt(0));
const isValid = await crypto.subtle.verify(
{ name: "ECDSA", hash: "SHA-256" },
publicKey,
signatureBytes.buffer,
messageBytes
);
if (!isValid) {
log.w(`Auth failed for clientId '${rq.body.client_id}'`);
authFailed('Authentication request failed.');
return;
} }
} catch (err) {
log.d(`Error when verifying auth request: ${err}`);
authFailed('Authentication request failed.');
return;
} }
else {
rs.statusCode = 400; let user = new User(rq.body.client_id);
rs.json(APIUtils.genericResponseFormat(true, "ReCAPTCHA error")); if (!(await user.exists())) {
const obj = await User.init({ client_id: rq.body.client_id, pubkey: rq.body.pubkey });
if (obj == null) {
rs.sendStatus(500);
return;
} else user = obj;
} }
if (await user.hasNonce(rq.body.message.nonce)) {
log.w(`Client '${rq.body.client_id}' has already used nonce. Replay attack?`);
authFailed('Authentication request failed.');
return;
} else user.addNonce(rq.body.message.nonce);
const token = await user.getToken();
rs.json({ token: token });
} }
); );

View File

@@ -1,9 +1,11 @@
import { Authentication } from "../data/auth.ts"; import Profile from "../data/profiles.ts";
import { User } from "../data/users.ts";
declare global { declare global {
namespace Express { namespace Express {
interface Locals { interface Locals {
auth: Authentication.GameAuthContext | null profile: Profile
user: User
} }
} }
} }