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:
16
README.md
16
README.md
@@ -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`.
|
||||||
24
data/galvanic-corrosion-web/.gitignore
vendored
24
data/galvanic-corrosion-web/.gitignore
vendored
@@ -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?
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
@@ -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 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
@@ -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>
|
|
||||||
3226
data/galvanic-corrosion-web/package-lock.json
generated
3226
data/galvanic-corrosion-web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 |
@@ -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
|
|
||||||
@@ -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 |
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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>,
|
|
||||||
)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
@@ -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"]
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"files": [],
|
|
||||||
"references": [
|
|
||||||
{ "path": "./tsconfig.app.json" },
|
|
||||||
{ "path": "./tsconfig.node.json" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"]
|
|
||||||
}
|
|
||||||
@@ -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')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
10
deno.json
10
deno.json
@@ -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
132
deno.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -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"
|
||||||
@@ -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';
|
||||||
@@ -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";
|
|
||||||
@@ -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
128
src/data/profiles.ts
Normal 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;
|
||||||
@@ -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
6
src/data/usernames.ts
Normal 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;
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
29
src/db.ts
29
src/db.ts
@@ -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";
|
||||||
@@ -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";
|
||||||
@@ -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";
|
|
||||||
101
src/main.ts
101
src/main.ts
@@ -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
6
src/routes/account.ts
Normal 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);
|
||||||
30
src/routes/account/account.ts
Normal file
30
src/routes/account/account.ts
Normal 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()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
@@ -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);
|
||||||
@@ -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
8
src/routes/auth.ts
Normal 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);
|
||||||
15
src/routes/auth/cachedlogin.ts
Normal file
15
src/routes/auth/cachedlogin.ts
Normal 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());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
5
src/routes/auth/connect.ts
Normal file
5
src/routes/auth/connect.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter("/connect");
|
||||||
|
|
||||||
|
//route.router.post()
|
||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user