mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Merge pull request 'Create society service with basic functionality and auth #118' (#120) from feat-society into main
Reviewed-on: https://git.ngram.ca/OpenJam/rc-servers/pulls/120
This commit is contained in:
185
Cargo.lock
generated
185
Cargo.lock
generated
@@ -52,7 +52,7 @@ dependencies = [
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bitflags",
|
||||
"brotli",
|
||||
"bytes",
|
||||
@@ -80,6 +80,22 @@ dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-identity"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "810f47733f956175bd5b2ae17ae5237fa92bd1b6a4a65f646a7240dbe9ff2728"
|
||||
dependencies = [
|
||||
"actix-service",
|
||||
"actix-session",
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"derive_more",
|
||||
"futures-core",
|
||||
"serde",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-macros"
|
||||
version = "0.2.4"
|
||||
@@ -141,6 +157,23 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-session"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "400c27fd4cdbe0082b7bbd29ac44a3070cbda1b2114138dc106ba39fe2f90dff"
|
||||
dependencies = [
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"derive_more",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-utils"
|
||||
version = "3.0.1"
|
||||
@@ -169,6 +202,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
"cookie",
|
||||
"derive_more",
|
||||
"encoding_rs",
|
||||
"foldhash",
|
||||
@@ -224,6 +258,16 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
@@ -235,6 +279,20 @@ dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -341,6 +399,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.1.1"
|
||||
@@ -467,6 +531,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@@ -800,6 +870,24 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"base64 0.20.0",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@@ -879,9 +967,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
@@ -1422,6 +1520,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-version"
|
||||
version = "0.3.9"
|
||||
@@ -1678,7 +1786,7 @@ version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -1955,7 +2063,7 @@ version = "10.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
"getrandom 0.2.17",
|
||||
"hmac",
|
||||
@@ -2006,7 +2114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68fff2e457fb18346f6de760a0bbfe2de69febc5ab1782c5ff470db14de2241d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"cgmath 0.18.0",
|
||||
"chrono",
|
||||
"genmesh",
|
||||
@@ -2522,7 +2630,7 @@ version = "1.2.2"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"futures",
|
||||
"hex",
|
||||
@@ -2563,7 +2671,7 @@ name = "oj_rc_factory"
|
||||
version = "1.2.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"hex",
|
||||
"libfj",
|
||||
@@ -2579,7 +2687,7 @@ version = "1.2.2"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-web",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"git-version",
|
||||
@@ -2772,6 +2880,29 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oj_rc_society"
|
||||
version = "1.2.2"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-identity",
|
||||
"actix-session",
|
||||
"actix-web",
|
||||
"actix-ws",
|
||||
"chrono",
|
||||
"clap",
|
||||
"cookie",
|
||||
"env_logger",
|
||||
"git-version",
|
||||
"handlebars",
|
||||
"libfj",
|
||||
"log",
|
||||
"oj_rc_core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oj_serdes"
|
||||
version = "0.3.0"
|
||||
@@ -2794,6 +2925,12 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "4.6.0"
|
||||
@@ -2907,7 +3044,7 @@ version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
@@ -3038,6 +3175,18 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.1"
|
||||
@@ -3429,7 +3578,7 @@ version = "0.12.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
@@ -4074,7 +4223,7 @@ version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bigdecimal",
|
||||
"bytes",
|
||||
"chrono",
|
||||
@@ -4154,7 +4303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bigdecimal",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
@@ -4201,7 +4350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bigdecimal",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
@@ -4748,6 +4897,16 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -4760,7 +4919,7 @@ version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
|
||||
@@ -26,6 +26,7 @@ members = [
|
||||
"rc_factory", "rc_factory_web",
|
||||
"rc_multiplayer",
|
||||
"rc_plugins",
|
||||
"rc_society",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
273
assets/templates/rc_society/dashboard.html.hbs
Normal file
273
assets/templates/rc_society/dashboard.html.hbs
Normal file
@@ -0,0 +1,273 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Dashboard - Openjam</title>
|
||||
<meta name="title" content="Dashboard - Openjam" />
|
||||
<meta name="description" content="User dashboard for third-party Robocraft servers" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://git.ngram.ca/OpenJam/rc-servers" />
|
||||
<meta property="og:title" content="Dashboard - Openjam" />
|
||||
<meta property="og:description" content="Create an account for third-party Robocraft servers" />
|
||||
<meta property="og:image" content="https://git.ngram.ca/OpenJam/rc-servers/raw/branch/main/assets/robocraft/favicon.jpg" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary" />
|
||||
<meta property="twitter:url" content="https://git.ngram.ca/OpenJam/rc-servers" />
|
||||
<meta property="twitter:title" content="Dashboard - Openjam" />
|
||||
<meta property="twitter:description" content="Create an account for third-party Robocraft servers" />
|
||||
<meta property="twitter:image" content="https://git.ngram.ca/OpenJam/rc-servers/raw/branch/main/assets/robocraft/favicon.jpg" />
|
||||
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<style>
|
||||
body {
|
||||
background-color: lightgrey;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.form-stripe {
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
max-width: 90%;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: grey;
|
||||
padding: 0 0 25px 0;
|
||||
box-shadow: 0px 0px 50px 5px grey;
|
||||
}
|
||||
.form-header-right {
|
||||
position: relative;
|
||||
width: 25%;
|
||||
text-align: right;
|
||||
margin: 0 0 0 auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0 25px;
|
||||
}
|
||||
.form-banner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
background-color: dimgrey;
|
||||
color: black;
|
||||
padding: 25px;
|
||||
}
|
||||
.form-padded {
|
||||
padding: 0 25px;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
text-align: center;
|
||||
}
|
||||
a { text-decoration-line: none; }
|
||||
a:link { color: darkgrey; }
|
||||
a:hover { color: white; }
|
||||
a:visited { color: lightgrey; }
|
||||
a:active { color: white; }
|
||||
form {
|
||||
padding-top: 25px;
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
padding-bottom: 250px;
|
||||
}
|
||||
.form-error {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
background-color: orange;
|
||||
}
|
||||
.form-error-message {
|
||||
padding: 25px;
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
}
|
||||
.form-text-block {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.access-grid {
|
||||
width: 80%;
|
||||
display: grid;
|
||||
margin: 0 auto;
|
||||
padding: 5px;
|
||||
grid-template-columns: auto auto auto auto;
|
||||
gap: 5px;
|
||||
}
|
||||
.access-box {
|
||||
background-color: dimgrey;
|
||||
color: black;
|
||||
padding: 5px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.access-box-title {
|
||||
padding: 5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.access-box-banner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
background-color: black;
|
||||
color: lightgrey;
|
||||
padding: 5px;
|
||||
}
|
||||
.box-details {
|
||||
text-align: left;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
gap: 2px;
|
||||
padding: 5px;
|
||||
}
|
||||
.box-details-key {
|
||||
padding: 0 5px;
|
||||
background-color: black;
|
||||
color: lightgrey;
|
||||
text-align: left;
|
||||
}
|
||||
.box-details-value {
|
||||
padding: 0 5px;
|
||||
background-color: lightgrey;
|
||||
color: black;
|
||||
text-align: left;
|
||||
}
|
||||
footer {
|
||||
position: relative;
|
||||
width: 100%
|
||||
margin: 0 auto;
|
||||
background-color: black;
|
||||
}
|
||||
.footer {
|
||||
width: 95%;
|
||||
color: lightgrey;
|
||||
box-sizing: border-box;
|
||||
padding: 25px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="form-stripe">
|
||||
<div class="form-header-right">
|
||||
Welcome {{form.display_name}}! |
|
||||
<a href="/">Home</a>
|
||||
</div>
|
||||
<h1 class="form-banner">{{form.display_name}} Dashboard</h1>
|
||||
{{#if error}}
|
||||
<div class="form-error">
|
||||
<p class="form-error-message">{{error}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="access-grid">
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Details</h4>
|
||||
<div class="box-details">
|
||||
<div class="box-details-key">Name</div>
|
||||
<div class="box-details-value">{{form.display_name}}</div>
|
||||
<div class="box-details-key">Public ID</div>
|
||||
<div class="box-details-value">{{form.public_id}}</div>
|
||||
<div class="box-details-key">Database ID</div>
|
||||
<div class="box-details-value">{{form.debug.user_id}}</div>
|
||||
<div class="box-details-key">Last Seen</div> <!-- TODO -->
|
||||
<div class="box-details-value"><time datetime="{{form.debug.creation_time_iso}}">{{form.debug.creation_time_iso}}</time></div>
|
||||
<div class="box-details-key">Creation</div>
|
||||
<div class="box-details-value"><time datetime="{{form.debug.creation_time_iso}}">{{form.debug.creation_time_iso}}</time></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Permissions</h4>
|
||||
<div class="box-details">
|
||||
<div class="box-details-key">Moderator</div>
|
||||
<div class="box-details-value">{{form.perms.mod}}</div>
|
||||
<div class="box-details-key">Administrator</div>
|
||||
<div class="box-details-value">{{form.perms.admin}}</div>
|
||||
<div class="box-details-key">Developer</div>
|
||||
<div class="box-details-value">{{form.perms.dev}}</div>
|
||||
<div class="box-details-key">Royal</div>
|
||||
<div class="box-details-value">{{form.perms.royal}}</div>
|
||||
<div class="box-details-key">Banned</div>
|
||||
<div class="box-details-value">{{form.perms.banned}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Garages</h4>
|
||||
<div class="box-details">
|
||||
<div class="box-details-key">Total</div> <!-- TODO -->
|
||||
<div class="box-details-value">{{form.debug.user_id}}/{{form.debug.user_id}}</div>
|
||||
</div>
|
||||
<a href="#"> <!-- TODO -->
|
||||
<div class="access-box-banner">
|
||||
View My Garages
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Factory</h4>
|
||||
<div class="box-details">
|
||||
<div class="box-details-key">Total</div> <!-- TODO -->
|
||||
<div class="box-details-value">{{form.debug.user_id}}/{{form.debug.user_id}}</div>
|
||||
</div>
|
||||
<a href="#"> <!-- TODO -->
|
||||
<div class="access-box-banner">
|
||||
View My Uploads
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Account</h4>
|
||||
<a href="#"> <!-- TODO -->
|
||||
<div class="access-box-banner">
|
||||
Manage My Account
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Sanctions</h4>
|
||||
<div class="box-details">
|
||||
<div class="box-details-key">Total</div> <!-- TODO -->
|
||||
<div class="box-details-value">{{form.debug.user_id}}</div>
|
||||
</div>
|
||||
<a href="#"> <!-- TODO -->
|
||||
<div class="access-box-banner">
|
||||
View My Sanctions
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Social</h4>
|
||||
<div class="box-details">
|
||||
<div class="box-details-key">Clan</div> <!-- TODO -->
|
||||
<div class="box-details-value">{{form.display_name}}</div>
|
||||
<div class="box-details-key">Friends</div> <!-- TODO -->
|
||||
<div class="box-details-value">{{form.debug.user_id}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Federation</h4>
|
||||
<div class="box-details">
|
||||
<div class="box-details-key">Enabled</div> <!-- TODO -->
|
||||
<div class="box-details-value">{{form.perms.royal}}</div>
|
||||
</div>
|
||||
<a href="#"> <!-- TODO -->
|
||||
<div class="access-box-banner">
|
||||
Manage My Federation
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="footer">
|
||||
{{version}}
|
||||
| <a href="{{source_url}}#readme">About</a>
|
||||
| <a href="https://liberapay.com/NGram">Support Me</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
233
assets/templates/rc_society/index.html.hbs
Normal file
233
assets/templates/rc_society/index.html.hbs
Normal file
@@ -0,0 +1,233 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Home - Openjam</title>
|
||||
<meta name="title" content="Home - Openjam" />
|
||||
<meta name="description" content="OpenJam third-party Robocraft servers federation" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://git.ngram.ca/OpenJam/rc-servers" />
|
||||
<meta property="og:title" content="Home - Openjam" />
|
||||
<meta property="og:description" content="OpenJam third-party Robocraft servers federation" />
|
||||
<meta property="og:image" content="https://git.ngram.ca/OpenJam/rc-servers/raw/branch/main/assets/robocraft/favicon.jpg" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary" />
|
||||
<meta property="twitter:url" content="https://git.ngram.ca/OpenJam/rc-servers" />
|
||||
<meta property="twitter:title" content="Home - Openjam" />
|
||||
<meta property="twitter:description" content="OpenJam third-party Robocraft servers federation" />
|
||||
<meta property="twitter:image" content="https://git.ngram.ca/OpenJam/rc-servers/raw/branch/main/assets/robocraft/favicon.jpg" />
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<style>
|
||||
body {
|
||||
background-color: lightgrey;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.form-stripe {
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
max-width: 90%;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: grey;
|
||||
padding: 0 0 25px 0;
|
||||
box-shadow: 0px 0px 50px 5px grey;
|
||||
}
|
||||
.form-header-right {
|
||||
position: relative;
|
||||
width: 25%;
|
||||
text-align: right;
|
||||
margin: 0 0 0 auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0 25px;
|
||||
}
|
||||
.form-banner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
background-color: dimgrey;
|
||||
color: black;
|
||||
padding: 25px;
|
||||
}
|
||||
.form-padded {
|
||||
padding: 0 25px;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
text-align: center;
|
||||
}
|
||||
a { text-decoration-line: none; }
|
||||
a:link { color: darkgrey; }
|
||||
a:hover { color: white; }
|
||||
a:visited { color: lightgrey; }
|
||||
a:active { color: white; }
|
||||
form {
|
||||
padding-top: 25px;
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
padding-bottom: 250px;
|
||||
}
|
||||
.form-error {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
background-color: orange;
|
||||
}
|
||||
.form-error-message {
|
||||
padding: 25px;
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
}
|
||||
.form-text-block {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.access-grid {
|
||||
width: 80%;
|
||||
display: grid;
|
||||
margin: 0 auto;
|
||||
padding: 5px;
|
||||
grid-template-columns: auto auto auto auto;
|
||||
gap: 5px;
|
||||
}
|
||||
.access-box {
|
||||
background-color: dimgrey;
|
||||
color: black;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.access-box-title {
|
||||
padding: 5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.access-box-banner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
background-color: black;
|
||||
color: lightgrey;
|
||||
padding: 5px;
|
||||
}
|
||||
.box-details {
|
||||
text-align: left;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
gap: 2px;
|
||||
padding: 5px;
|
||||
}
|
||||
.box-details-key {
|
||||
padding: 0 5px;
|
||||
background-color: black;
|
||||
color: lightgrey;
|
||||
text-align: left;
|
||||
}
|
||||
.box-details-value {
|
||||
padding: 0 5px;
|
||||
background-color: lightgrey;
|
||||
color: black;
|
||||
text-align: left;
|
||||
}
|
||||
footer {
|
||||
position: relative;
|
||||
width: 100%
|
||||
margin: 0 auto;
|
||||
background-color: black;
|
||||
}
|
||||
.footer {
|
||||
width: 95%;
|
||||
color: lightgrey;
|
||||
box-sizing: border-box;
|
||||
padding: 25px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="form-stripe">
|
||||
<div class="form-header-right">
|
||||
{{#if form.is_logged_in}}
|
||||
Welcome {{form.display_name}}! |
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
{{else}}
|
||||
<a href="/login">Login</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<h1 class="form-banner">Welcome to <a href="https://git.ngram.ca/OpenJam">OpenJam</a> servers</h1>
|
||||
<h3 class="form-padded">FOSSifying proprietary games since 2025</h3>
|
||||
{{#if error}}
|
||||
<div class="form-error">
|
||||
<p class="form-error-message">{{error}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="form-text-block">
|
||||
<p>
|
||||
This service allows you to manage your account settings, import and export your garage bays, view the server config, and will eventually (not yet) federate with other OpenJam rc-servers instances.
|
||||
You can view other known instances <a href="/federation">here</a>.
|
||||
This service is intended to have stable public APIs for expanding existing functionality.
|
||||
If you do not want any of this functionality in your instance, simply do not run this service with your instance.
|
||||
</p>
|
||||
<p>
|
||||
Interested in running your own server instance? Check out the <a href="https://git.ngram.ca/OpenJam/rc-servers/wiki/Server-Quickstart">quickstart guide</a> in the wiki.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="access-grid">
|
||||
<div class="access-box">
|
||||
<h4 class="access-box-title">Server Details</h4>
|
||||
<div class="box-details">
|
||||
<div class="box-details-key">Domain</div>
|
||||
<div class="box-details-value">{{form.server.domain}}</div>
|
||||
<div class="box-details-key">CDN</div>
|
||||
<div class="box-details-value">{{form.server.cdn}}</div>
|
||||
<div class="box-details-key">Auth</div>
|
||||
<div class="box-details-value">{{form.server.auth}}</div>
|
||||
<div class="box-details-key">Client Version</div>
|
||||
<div class="box-details-value">>={{form.server.min_version}}</div>
|
||||
<div class="box-details-key">Server Version</div>
|
||||
<div class="box-details-value">{{form.server.server_version}}</div>
|
||||
<div class="box-details-key">Start Time</div>
|
||||
<div class="box-details-value"><time datetime="{{form.server.start_time_iso}}">{{form.server.start_time_iso}}</time></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="access-box">
|
||||
Grid item 2
|
||||
</div>
|
||||
<div class="access-box">
|
||||
Grid item 3
|
||||
</div>
|
||||
<div class="access-box">
|
||||
Grid item 4
|
||||
</div>
|
||||
<div class="access-box">
|
||||
Grid item 5
|
||||
</div>
|
||||
<div class="access-box">
|
||||
Grid item 6
|
||||
</div>
|
||||
<div class="access-box">
|
||||
Grid item 7
|
||||
</div>
|
||||
<div class="access-box">
|
||||
Grid item 8
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<footer>
|
||||
<div class="footer">
|
||||
{{version}}
|
||||
| <a href="{{source_url}}#readme">About</a>
|
||||
| <a href="https://liberapay.com/NGram">Support Me</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
197
assets/templates/rc_society/login.html.hbs
Normal file
197
assets/templates/rc_society/login.html.hbs
Normal file
@@ -0,0 +1,197 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Login - Openjam</title>
|
||||
<meta name="title" content="Login - Openjam" />
|
||||
<meta name="description" content="Connect to a third-party Robocraft servers account" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://git.ngram.ca/OpenJam/rc-servers" />
|
||||
<meta property="og:title" content="Login - Openjam" />
|
||||
<meta property="og:description" content="Connect to a third-party Robocraft servers account" />
|
||||
<meta property="og:image" content="https://git.ngram.ca/OpenJam/rc-servers/raw/branch/main/assets/robocraft/favicon.jpg" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary" />
|
||||
<meta property="twitter:url" content="https://git.ngram.ca/OpenJam/rc-servers" />
|
||||
<meta property="twitter:title" content="Login - Openjam" />
|
||||
<meta property="twitter:description" content="Connect to a third-party Robocraft servers account" />
|
||||
<meta property="twitter:image" content="https://git.ngram.ca/OpenJam/rc-servers/raw/branch/main/assets/robocraft/favicon.jpg" />
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<style>
|
||||
body {
|
||||
background-color: lightgrey;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.form-stripe {
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: grey;
|
||||
padding: 25px 0;
|
||||
box-shadow: 0px 0px 50px 5px grey;
|
||||
}
|
||||
.form-banner {
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
background-color: dimgrey;
|
||||
color: black;
|
||||
padding: 25px;
|
||||
}
|
||||
.form-padded {
|
||||
padding: 0 25px;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
text-align: center;
|
||||
}
|
||||
a { text-decoration-line: none; }
|
||||
a:link { color: darkgrey; }
|
||||
a:hover { color: white; }
|
||||
a:visited { color: lightgrey; }
|
||||
a:active { color: white; }
|
||||
form {
|
||||
padding-top: 25px;
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
padding-bottom: 250px;
|
||||
}
|
||||
.form-error {
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
background-color: orange;
|
||||
}
|
||||
.form-error-message {
|
||||
padding: 25px;
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
}
|
||||
.form-input {
|
||||
padding-top: 5px;
|
||||
}
|
||||
.form-input-label { display: none; }
|
||||
input:required:invalid.form-input-box {
|
||||
border-color: red;
|
||||
}
|
||||
input:required:invalid.form-input-box::after {
|
||||
content: "*required";
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border: thin solid white;
|
||||
background-color: lightgrey;
|
||||
font-size: 20px;
|
||||
}
|
||||
.input-required-hint::after {
|
||||
color: red;
|
||||
/*content: "*required";*/
|
||||
font-size: 12px;
|
||||
}
|
||||
.form-disclaimer {
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
}
|
||||
.form-submit {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.form-submit-button {
|
||||
background-color: black;
|
||||
color: lightgrey;
|
||||
}
|
||||
.fake-button {
|
||||
padding-top: 25px;
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
.fake-button-anchor {
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
color: lightgrey;
|
||||
border: 1px solid white;
|
||||
display: inline-block;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
footer {
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
background-color: black;
|
||||
}
|
||||
.footer {
|
||||
width: 95%;
|
||||
color: lightgrey;
|
||||
box-sizing: border-box;
|
||||
padding: 25px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="form-stripe">
|
||||
<h1 class="form-banner">Login to <a href="https://git.ngram.ca/OpenJam">OpenJam</a> servers</h1>
|
||||
<h3 class="form-padded">FOSSifying proprietary games since 2025</h3>
|
||||
{{#if error}}
|
||||
<div class="form-error">
|
||||
<p class="form-error-message">{{error}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
<form action="/login" method="post">
|
||||
<div class="form-input">
|
||||
<div class="form-input-label">
|
||||
<label for="username">Username</label>
|
||||
</div>
|
||||
<input class="form-input-box" type="text" id="username" name="username" value="{{form.username}}" placeholder="username" required/>
|
||||
<span class="input-required-hint"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
<div class="form-input-label">
|
||||
<label for="password">Password</label>
|
||||
</div>
|
||||
<input class="form-input-box" type="password" id="password" name="password" value="{{form.password}}" placeholder="password" required/>
|
||||
<span class="input-required-hint"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-disclaimer">
|
||||
<p>By submitting, you agree to the <a href="https://git.ngram.ca/OpenJam/rc-servers#privacy">privacy policy</a>.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-submit">
|
||||
<input class="form-submit-button" type="submit" value="Submit">
|
||||
</div>
|
||||
|
||||
<div class="form-disclaimer">
|
||||
<p>This project is not affiliated with FreeJam, Cardlife or Robocraft. We're just fixing their """sunset""".</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<footer>
|
||||
<div class="footer">
|
||||
{{version}}
|
||||
| <a href="{{source_url}}#readme">About</a>
|
||||
| <a href="https://liberapay.com/NGram">Support Me</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -81,16 +81,12 @@ impl AccountProvider {
|
||||
db: self.db.clone(),
|
||||
})
|
||||
}*/
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
|
||||
async fn authenticate(&self, token: super::UserToken) -> Result<Box<dyn super::User<C> + Send + Sync>, super::AuthError> {
|
||||
//let new_root = self.root.join(&token.uuid);
|
||||
async fn auth_internal(&self, token: &str) -> Result<UserData, super::AuthError> {
|
||||
let secret = jsonwebtoken::DecodingKey::from_secret(&self.secret);
|
||||
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
|
||||
validation.set_required_spec_claims::<&str>(&[]);
|
||||
let token_data = jsonwebtoken::decode::<crate::auth::Token>(&token.token, &secret, &validation).map_err(|e| super::AuthError {
|
||||
let token_data = jsonwebtoken::decode::<crate::auth::Token>(&token, &secret, &validation).map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
})?;
|
||||
@@ -119,8 +115,7 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
log::info!("Authenticated user {} with flags {:?}", display_name, token_data.claims.client_details.flags.as_slice());
|
||||
//let account_info = AccountInfo::load(&new_root).map_err(|e| e.to_string())?;
|
||||
Ok(Box::new(UserData {
|
||||
Ok(UserData {
|
||||
account: user_info,
|
||||
perms: user_perms,
|
||||
cubes: self.cubes.clone(),
|
||||
@@ -133,7 +128,14 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
|
||||
http_client: std::sync::Arc::new(reqwest::Client::new()),
|
||||
db: self.db.clone(),
|
||||
secret: self.secret.clone(),
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
|
||||
async fn authenticate(&self, token: super::UserToken) -> Result<Box<dyn super::User<C> + Send + Sync>, super::AuthError> {
|
||||
Ok(Box::new(self.auth_internal(&token.token).await?))
|
||||
}
|
||||
|
||||
async fn multiplayer_authenticate(&self, user: String) -> Result<Box<dyn super::User<C> + Send + Sync>, super::AuthError> {
|
||||
@@ -174,6 +176,10 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
|
||||
secret: self.secret.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn web_authenticate(&self, token: String) -> Result<Box<dyn super::WebUser>, super::AuthError> {
|
||||
Ok(Box::new(self.auth_internal(&token).await?))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -349,6 +355,18 @@ impl super::UserAuthenticator for AccountProvider {
|
||||
async fn register(&self, info: super::RegistrationInfo) -> Result<i32, String> {
|
||||
super::register_new_user(&info, &self.db).await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
async fn verify(&self, token: String) -> Result<crate::auth::Token, super::AuthError> {
|
||||
let secret = jsonwebtoken::DecodingKey::from_secret(&self.secret);
|
||||
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
|
||||
validation.set_required_spec_claims::<&str>(&[]);
|
||||
jsonwebtoken::decode::<crate::auth::Token>(&token, &secret, &validation)
|
||||
.map_err(|e| super::AuthError {
|
||||
message: e.to_string(),
|
||||
code: crate::data::error_codes::AuthErrorCode::BadCredentials,
|
||||
})
|
||||
.map(|decoded| decoded.claims)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct UserData {
|
||||
|
||||
@@ -14,6 +14,14 @@ impl super::CommonUser for UserData {
|
||||
&self.account.public_id
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &'_ str {
|
||||
&self.account.display_name
|
||||
}
|
||||
|
||||
fn creation(&self) -> i64 {
|
||||
self.account.creation_time
|
||||
}
|
||||
|
||||
fn is_mod(&self) -> bool {
|
||||
self.perms.moderator
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ mod inventory;
|
||||
pub use inventory::{UnlockedParts, UnlockOverride};
|
||||
|
||||
mod traits;
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides};
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides, WebUser};
|
||||
|
||||
pub mod intercom;
|
||||
pub use intercom::generate_token as generate_intercom_token;
|
||||
@@ -26,6 +26,7 @@ mod factory;
|
||||
mod userless;
|
||||
mod team;
|
||||
pub use team::{TeamChooser, StandardTeamChooser};
|
||||
mod web;
|
||||
|
||||
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
||||
|
||||
|
||||
@@ -106,18 +106,6 @@ impl UserData {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::MultiplayerUser for UserData {
|
||||
fn user_id(&self) -> i32 {
|
||||
self.account.id
|
||||
}
|
||||
|
||||
fn user_name(&self) -> &'_ str {
|
||||
&self.account.public_id
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &'_ str {
|
||||
&self.account.display_name
|
||||
}
|
||||
|
||||
async fn current_game(&self) -> Result<Option<super::GameDescriptor>, super::MultiplayerError> {
|
||||
Ok(self.db.game_by_user_id_and_completion(self.account.id, false).await
|
||||
.map_err(|e| {
|
||||
|
||||
@@ -63,6 +63,8 @@ pub trait UserProvider<C> {
|
||||
async fn authenticate(&self, user: UserToken) -> Result<Box<dyn User<C> + Send + Sync>, AuthError>;
|
||||
|
||||
async fn multiplayer_authenticate(&self, user: String) -> Result<Box<dyn User<C> + Send + Sync>, AuthError>;
|
||||
|
||||
async fn web_authenticate(&self, token: String) -> Result<Box<dyn WebUser>, AuthError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -70,6 +72,7 @@ pub trait UserAuthenticator {
|
||||
async fn login(&self, info: UserAuthInfo) -> Result<UserLoginInfo, AuthError>;
|
||||
async fn user_exists(&self, user: UserId) -> Result<bool, String>;
|
||||
async fn register(&self, info: RegistrationInfo) -> Result<i32, String>;
|
||||
async fn verify(&self, token: String) -> Result<crate::auth::Token, AuthError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -385,9 +388,6 @@ pub enum MultiplayerErrorCode {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait MultiplayerUser: IntercomUser + CommonUser {
|
||||
fn user_id(&self) -> i32;
|
||||
fn user_name(&self) -> &'_ str;
|
||||
fn display_name(&self) -> &'_ str;
|
||||
async fn current_game(&self) -> Result<Option<GameDescriptor>, MultiplayerError>;
|
||||
async fn game_players(&self, guid: &str) -> Result<Vec<PlayerDescriptor>, MultiplayerError>;
|
||||
async fn complete_game(&self, guid: &str) -> Result<(), MultiplayerError>;
|
||||
@@ -455,6 +455,9 @@ pub trait CommonUser: Send + Sync {
|
||||
fn account_id(&self) -> i32;
|
||||
async fn resolve_config_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<ResolvedVehicle, polariton_server::operations::SimpleOpError>;
|
||||
fn public_id(&self) -> &'_ str;
|
||||
fn display_name(&self) -> &'_ str;
|
||||
/// Seconds since Unix epoch
|
||||
fn creation(&self) -> i64;
|
||||
fn is_mod(&self) -> bool;
|
||||
fn is_admin(&self) -> bool;
|
||||
fn is_dev(&self) -> bool;
|
||||
@@ -671,6 +674,11 @@ pub trait FactoryUser {
|
||||
async fn rate_vehicle(&self, slot: i32, combat: i32, cosmetic: i32) -> Result<Option<i32>, polariton_server::operations::SimpleOpError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait WebUser: CommonUser {
|
||||
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Userless: Send + Sync {
|
||||
async fn lobby_state_listener(&self) -> Result<super::IntercomListener<super::intercom::IntercomLobbyStateMessage>, reqwest_websocket::Error>;
|
||||
|
||||
4
rc_core/src/persist/user/web.rs
Normal file
4
rc_core/src/persist/user/web.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[async_trait::async_trait]
|
||||
impl super::WebUser for super::account_json::UserData {
|
||||
// TODO
|
||||
}
|
||||
@@ -19,7 +19,7 @@ impl crate::DisconnectHandler for ClientDisconnecter {
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::EndConnection {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
is_unregister: false,
|
||||
}).await);
|
||||
} else {
|
||||
|
||||
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for RequestLoadingSync {
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RequestLoadingSync {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
}).await);
|
||||
} else {
|
||||
log::error!("Failed to handle sync loading request for unknown user");
|
||||
|
||||
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for RequestAllLoadingProgress {
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RequestLoadingProgress {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
}).await);
|
||||
} else {
|
||||
log::error!("Failed to broadcast loading progress for unknown user");
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for AssistBonus {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::AssistBonus {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
shootee: data.requester_player_id as u8,
|
||||
shooters: data.player_ids.into_iter().map(|x| x.player).collect(),
|
||||
}).await);
|
||||
|
||||
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for ClientUnregisterer {
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::EndConnection {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
is_unregister: true,
|
||||
}).await);
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for DamageBonus {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::DestroyCubesBonus {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
info: data,
|
||||
}).await);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for RectifierStart {
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::FlippingStarted {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
}).await);
|
||||
} else {
|
||||
log::error!("Failed to handle sync loading request for unknown user");
|
||||
|
||||
@@ -23,7 +23,7 @@ impl crate::handlers::RlnlEventCodeHandler for HealAssistBonus {
|
||||
if let Some(user_info) = user.user().await {
|
||||
//log::info!("Heal assist for player {}", data.healing_player_id);
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::HealAssistBonus {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
healer: data.healing_player_id,
|
||||
healee: data.healed_player_id,
|
||||
}).await);
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for HealBonus {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::HealCubesBonus {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
info: data,
|
||||
}).await);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for KillEnemyBonus {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::KillBonus {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
shootee: data.killee_player_id,
|
||||
shooter: data.killer_player_id,
|
||||
}).await);
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for KillEnemy {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::DestroyVehicle {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
remote_player: data.killee_player_id,
|
||||
killer_player: data.killer_player_id,
|
||||
}).await);
|
||||
|
||||
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for LoadComplete {
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::LoadComplete {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
}).await);
|
||||
} else {
|
||||
log::error!("Failed to handle sync loading request for unknown user");
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for GameLoadingProgress {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::LoadingProgress {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
user_name: data.user_name.0,
|
||||
progress: data.progress,
|
||||
}).await);
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for PingMap {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::MapPing {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
ping: data,
|
||||
}).await);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for PlayerInput {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::PlayerInputChanged {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
data,
|
||||
}).await);
|
||||
} else {
|
||||
|
||||
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for PlayerQuit {
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RequestLeave {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
}).await);
|
||||
} else {
|
||||
log::error!("Failed to handle sync loading request for unknown user");
|
||||
|
||||
@@ -21,7 +21,7 @@ impl crate::handlers::DatalessEventCodeHandler for SelfDestructElim {
|
||||
async fn handle(&self, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::SelfDestruct {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
is_classic: true,
|
||||
}).await);
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,7 @@ impl crate::handlers::RlnlEventCodeHandler for SpotEnemy {
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::SpotVehicle {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
remote_player: data.player_id,
|
||||
}).await);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ impl crate::handlers::RlnlEventCodeHandler for AuthUserGame {
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
log::warn!("Cannot validate game guid for user {} with no ongoing game [disconnecting...]", user_info.user_id());
|
||||
log::warn!("Cannot validate game guid for user {} with no ongoing game [disconnecting...]", user_info.account_id());
|
||||
super::log_lnl_send_failure(crate::handlers::RlnlSender::new(sender)
|
||||
.send_data(&rlnl::types::StringCode {
|
||||
ty: rlnl::types::GameServerErrorCodes::StrErrIncorrectGameGuid,
|
||||
@@ -76,7 +76,7 @@ impl crate::handlers::RlnlEventCodeHandler for AuthUserGame {
|
||||
peer.disconnect();
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to get current game for user {}: {} [disconnecting...]", user_info.user_id(), e.message);
|
||||
log::error!("Failed to get current game for user {}: {} [disconnecting...]", user_info.account_id(), e.message);
|
||||
super::log_lnl_send_failure(crate::handlers::RlnlSender::new(sender)
|
||||
.send_data(&rlnl::types::StringCode {
|
||||
ty: core_to_rlnl_mp_error_code(e.code),
|
||||
|
||||
@@ -24,7 +24,7 @@ impl crate::handlers::RlnlEventCodeHandler for WeaponSelect {
|
||||
if let Some(category) = oj_rc_core::data::weapon_list::ItemCategory::from_smaller(data.item_category as _) {
|
||||
if let Some(tier) = oj_rc_core::data::cube_list::ItemTier::from_u32(data.item_size as _) {
|
||||
super::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::WeaponSelect {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
machine_id: data.machine_id,
|
||||
category,
|
||||
size: tier,
|
||||
|
||||
@@ -79,7 +79,7 @@ impl literustlib_server::EventHandler for LnlEventHandler {
|
||||
async fn on_disconnect(&self, peer: &std::sync::Arc<literustlib_server::Connection<Self::PacketData>>, user: &Self::UserData) {
|
||||
self.disconnect_handler.handle(peer, user).await;
|
||||
if let Some(user_info) = user.user().await {
|
||||
log::info!("Disconnect from user {} ({})", user_info.user_id(), peer.id());
|
||||
log::info!("Disconnect from user {} ({})", user_info.account_id(), peer.id());
|
||||
} else {
|
||||
log::debug!("Disconnect from connection {}", peer.id());
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ impl <const EVENT: i16, const PROPERTY: u8, InOut: byteserde::des_slice::ByteDes
|
||||
async fn handle(&self, data: Self::In, _peer: &std::sync::Arc<literustlib_server::Connection<crate::PacketData>>, user: &crate::UserData, _sender: &std::sync::Arc<literustlib_server::DataSender<crate::PacketData>>) {
|
||||
if let Some(user_info) = user.user().await {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::CustomLogicRlnl {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
event: self.event,
|
||||
property: self.property,
|
||||
data: Box::new(data),
|
||||
|
||||
@@ -30,7 +30,7 @@ impl <const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const
|
||||
|
||||
if EXCLUDE_SENDER {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RebroadcastRlnl {
|
||||
skip_user_id: user_info.user_id(),
|
||||
skip_user_id: user_info.account_id(),
|
||||
event: self.code_out,
|
||||
event_in: Self::CODE,
|
||||
property: self.property,
|
||||
@@ -38,7 +38,7 @@ impl <const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const
|
||||
}).await);
|
||||
} else {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::BroadcastRlnl {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
event: self.code_out,
|
||||
event_in: Self::CODE,
|
||||
property: self.property,
|
||||
|
||||
@@ -27,7 +27,7 @@ impl <const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const
|
||||
if let Some(user_info) = user.user().await {
|
||||
if EXCLUDE_SENDER {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::RebroadcastRlnl {
|
||||
skip_user_id: user_info.user_id(),
|
||||
skip_user_id: user_info.account_id(),
|
||||
event: self.code_out,
|
||||
event_in: Self::CODE,
|
||||
property: self.property,
|
||||
@@ -35,7 +35,7 @@ impl <const EXCLUDE_SENDER: bool, const CODE_IN: i16, const CODE_OUT: i16, const
|
||||
}).await);
|
||||
} else {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::BroadcastRlnl {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
event: self.code_out,
|
||||
event_in: Self::CODE,
|
||||
property: self.property,
|
||||
|
||||
@@ -260,7 +260,7 @@ impl GameMatches {
|
||||
}
|
||||
};
|
||||
self.matches.insert(game_guid.clone(), tx.clone());
|
||||
self.routing.insert(user.user_id(), game_guid.clone());
|
||||
self.routing.insert(user.account_id(), game_guid.clone());
|
||||
if !self.is_crystal_regen_running.swap(true, std::sync::atomic::Ordering::SeqCst) {
|
||||
tokio::task::spawn(Self::regenerate_crystal_order_task(
|
||||
self.ba_sorted_crystals.clone(),
|
||||
@@ -304,7 +304,7 @@ impl GameMatches {
|
||||
self.do_game_cleanup(&game_guid);
|
||||
self.create_new_game(user.clone(), game_guid, connection, response, sender).await;
|
||||
} else {
|
||||
self.routing.insert(user.user_id(), game_guid.clone());
|
||||
self.routing.insert(user.account_id(), game_guid.clone());
|
||||
if tx.send(super::GameMessage::NewConnection { user: user.clone(), game_guid, connection, response, sender }).await.is_err() {
|
||||
log::error!("Failed to send NewConnection game message to existing match");
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
|
||||
pub(super) async fn rebroadcast<T: byteserde::ser_heap::ByteSerializeHeap + ?Sized>(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T, in_game: bool) {
|
||||
for (player_id, conn) in self.users.read().await.iter() {
|
||||
if user_id == conn.user.user_id() { continue; }
|
||||
if user_id == conn.user.account_id() { continue; }
|
||||
if conn.aliases.contains(player_id) { continue; }
|
||||
if in_game {
|
||||
let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||
@@ -388,7 +388,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
|
||||
pub(super) async fn rebroadcast_dataless(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, in_game: bool) {
|
||||
for (player_id, conn) in self.users.read().await.iter() {
|
||||
if user_id == conn.user.user_id() { continue; }
|
||||
if user_id == conn.user.account_id() { continue; }
|
||||
if conn.aliases.contains(player_id) { continue; }
|
||||
if in_game {
|
||||
let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||
@@ -586,7 +586,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
} else {
|
||||
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
//let id = users.len() as u8;
|
||||
let user_id = user.user_id();
|
||||
let user_id = user.account_id();
|
||||
let player_info_opt = self.descriptors.values().find(|p| p.descriptor.user_id == Some(user_id));
|
||||
if player_info_opt.is_none() {
|
||||
log::warn!("User {} tried to connect to match {} which they are not in", user_id, self.game_guid());
|
||||
@@ -617,7 +617,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
literustlib::packet::Property::ReliableOrdered,
|
||||
&new_user.connection.connection
|
||||
).await);
|
||||
log::debug!("User {} is validated to play game {}", new_user.user.user_id(), game_guid);
|
||||
log::debug!("User {} is validated to play game {}", new_user.user.account_id(), game_guid);
|
||||
let new_user = std::sync::Arc::new(new_user);
|
||||
if let Err(e) = new_user.user.save_player_connected_status(self.game_guid(), true).await {
|
||||
log::error!("Failed to mark player {} (user {}) as connected to game {}: {}", id, user_id, self.game_guid(), e);
|
||||
@@ -757,7 +757,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
};
|
||||
for (player_id, conn) in self.users.read().await.iter() {
|
||||
let user_desc = self.user_descriptor(*player_id).unwrap();
|
||||
if user_id == conn.user.user_id() {
|
||||
if user_id == conn.user.account_id() {
|
||||
let progress_percent = ((progress * 100.0).ceil() as u8).clamp(0, 100);
|
||||
log::debug!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid());
|
||||
user_desc.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed);
|
||||
@@ -894,7 +894,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
for (player_id, user) in self.users.read().await.iter() {
|
||||
let user_desc = self.user_descriptor(*player_id).unwrap();
|
||||
if user.aliases.contains(player_id) { continue; }
|
||||
if user.user.user_id() == user_id {
|
||||
if user.user.account_id() == user_id {
|
||||
if !matches!(ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) {
|
||||
log::warn!("Got RequestLoadingSync after user {} was already in/past WaitingForSync stage", user_id);
|
||||
continue;
|
||||
@@ -1362,7 +1362,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
|
||||
fn spawn_send_loading_events(&self, user: &UserConnection, player_id: u8, players: Vec<std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>>, client_ais: Vec<u8>) {
|
||||
let connection = user.connection.clone();
|
||||
let user_id = user.user.user_id();
|
||||
let user_id = user.user.account_id();
|
||||
tokio::spawn(Self::send_loading_events_wrapper(connection, player_id, user_id, players, client_ais));
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ impl GameMessage {
|
||||
pub fn user_id(&self) -> i32 {
|
||||
match self {
|
||||
Self::NewConnection { user, .. } => {
|
||||
user.user_id()
|
||||
user.account_id()
|
||||
}
|
||||
Self::EndConnection { user_id, .. } => *user_id,
|
||||
Self::RequestLeave { user_id, .. } => *user_id,
|
||||
|
||||
@@ -411,7 +411,7 @@ impl CustomGameLogic for PitLogic {
|
||||
}
|
||||
}
|
||||
if single_client.is_some() {
|
||||
let user_id = read_lock.values().next().unwrap().user.user_id();
|
||||
let user_id = read_lock.values().next().unwrap().user.account_id();
|
||||
let player_id = generic.user_key_by_user_id(user_id).unwrap();
|
||||
let user_info = generic.user_descriptor(player_id).unwrap();
|
||||
WinTracker::do_win(generic, self, user_info.descriptor.team as u8).await;
|
||||
|
||||
@@ -24,7 +24,7 @@ impl crate::RobotMotionHandler for VehicleMotionHandler {
|
||||
match rlnl::machine_motion::MachineMotion::byte_deserialize(&mut des) {
|
||||
Ok(motion_data) => {
|
||||
crate::events::log_channel_send_failure(self.msg_router.send(crate::matches::GameMessage::Motion {
|
||||
user_id: user_info.user_id(),
|
||||
user_id: user_info.account_id(),
|
||||
motion: motion_data,
|
||||
}).await);
|
||||
},
|
||||
|
||||
28
rc_society/Cargo.toml
Normal file
28
rc_society/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "oj_rc_society"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-web = { workspace = true, features = [ "secure-cookies" ] }
|
||||
actix-session = { version = "0.11", features = [ "cookie-session" ], default-features = false }
|
||||
actix-identity = "0.9"
|
||||
cookie = "0.16"
|
||||
actix-files.workspace = true
|
||||
actix-ws = "0.3"
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
tokio = { version = "1.43", features = [ "rt-multi-thread" ] }
|
||||
#futures.workspace = true
|
||||
clap.workspace = true
|
||||
handlebars = { version = "6", features = ["dir_source"] }
|
||||
oj_rc_core = { version = "*", path = "../rc_core" }
|
||||
libfj.workspace = true
|
||||
git-version.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
chrono.workspace = true
|
||||
0
rc_society/README.md
Normal file
0
rc_society/README.md
Normal file
3
rc_society/run_debug.sh
Executable file
3
rc_society/run_debug.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
RUST_LOG=debug cargo run
|
||||
0
rc_society/src/api/mod.rs
Normal file
0
rc_society/src/api/mod.rs
Normal file
58
rc_society/src/cli.rs
Normal file
58
rc_society/src/cli.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct CliArgs {
|
||||
/// TCP port on which to accept connections
|
||||
#[arg(short, long, default_value_t = 8002)]
|
||||
pub port: u16,
|
||||
|
||||
/// IP Address on which to accept connections
|
||||
#[arg(long, default_value_t = {"127.0.0.1".to_string()})]
|
||||
pub ip: String,
|
||||
|
||||
/// Assets root
|
||||
#[arg(long, default_value_t = {"../assets/robocraft".to_string()})]
|
||||
pub assets_robocraft: String,
|
||||
|
||||
/// User data root
|
||||
#[arg(long, default_value_t = {"../data/robocraft".to_string()})]
|
||||
pub data_robocraft: String,
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
pub fn get() -> Self {
|
||||
Self::parse()
|
||||
}
|
||||
|
||||
pub fn loaded(&self) -> LoadedArgs {
|
||||
let assets_path = std::path::PathBuf::from(&self.assets_robocraft);
|
||||
let data_path = std::path::PathBuf::from(&self.data_robocraft);
|
||||
let token_path = data_path.join(oj_rc_core::persist::user::TOKEN_SECRET_FILENAME);
|
||||
let secret = std::fs::read(&token_path).expect("Bad token");
|
||||
let cookie_key = if secret.len() < 32 {
|
||||
log::warn!("{} should be >= 32 bytes (extending with zeroes)", token_path.display());
|
||||
let mut secret_ext = secret.clone();
|
||||
while secret_ext.len() < 32 {
|
||||
secret_ext.push(0);
|
||||
}
|
||||
actix_web::cookie::Key::derive_from(&secret_ext)
|
||||
} else {
|
||||
actix_web::cookie::Key::derive_from(&secret)
|
||||
};
|
||||
LoadedArgs {
|
||||
secret: std::sync::Arc::new(secret.clone()),
|
||||
cookie_key,
|
||||
assets: assets_path,
|
||||
data: data_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct LoadedArgs {
|
||||
pub secret: std::sync::Arc<Vec<u8>>,
|
||||
pub cookie_key: actix_web::cookie::Key,
|
||||
pub assets: std::path::PathBuf,
|
||||
pub data: std::path::PathBuf,
|
||||
}
|
||||
78
rc_society/src/main.rs
Normal file
78
rc_society/src/main.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
#![forbid(unsafe_code)]
|
||||
mod cli;
|
||||
mod api;
|
||||
mod web;
|
||||
|
||||
use actix_web::{App, HttpServer, Responder};
|
||||
|
||||
pub static START_TIME: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1);
|
||||
|
||||
#[actix_web::get("/version")]
|
||||
async fn version_info() -> impl Responder {
|
||||
let name = env!("CARGO_PKG_NAME");
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let git_version = git_version::git_version!(args = ["--always", "--dirty=+"]);
|
||||
let authors = env!("CARGO_PKG_AUTHORS");
|
||||
let license = env!("CARGO_PKG_LICENSE");
|
||||
let repo = env!("CARGO_PKG_REPOSITORY");
|
||||
format!("{} {}:{} by [{}]\n{}\n{}", name, version, git_version, authors, license, repo)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init();
|
||||
let cli_args = cli::CliArgs::get();
|
||||
|
||||
let config = oj_rc_core::ConfigImpl::load(&cli_args.assets_robocraft)?;
|
||||
|
||||
let server_settings = actix_web::web::Data::new(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::server_config(&config));
|
||||
|
||||
let users = oj_rc_core::UserImpl::load(&cli_args.data_robocraft, &config).await.expect("Bad user data");
|
||||
let auth_ref = actix_web::web::Data::new(Box::new(users));
|
||||
|
||||
let cli_args2 = actix_web::web::Data::new(cli_args.clone());
|
||||
let loadeds_args = actix_web::web::Data::new(cli_args.loaded());
|
||||
|
||||
let mut handlebars_conf = handlebars::Handlebars::new();
|
||||
let mut dir_conf = handlebars::DirectorySourceOptions::default();
|
||||
dir_conf.tpl_extension = ".html.hbs".to_owned();
|
||||
dir_conf.hidden = false;
|
||||
dir_conf.temporary = false;
|
||||
handlebars_conf
|
||||
.register_templates_directory(
|
||||
std::path::PathBuf::from(&cli_args.assets_robocraft).parent().expect("Bad robocraft asset path").join("templates/rc_society"),
|
||||
dir_conf,
|
||||
)
|
||||
.unwrap();
|
||||
let handlebars_ref = actix_web::web::Data::new(handlebars_conf);
|
||||
|
||||
START_TIME.store(chrono::Utc::now().timestamp(), std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap_fn(|req, srv| {
|
||||
use actix_web::dev::Service;
|
||||
log::trace!("Request {} {}", req.method(), req.path());
|
||||
srv.call(req)
|
||||
})
|
||||
.wrap(actix_identity::IdentityMiddleware::default())
|
||||
.wrap(actix_session::SessionMiddleware::new(
|
||||
actix_session::storage::CookieSessionStore::default(),
|
||||
loadeds_args.cookie_key.clone(),
|
||||
))
|
||||
.app_data(cli_args2.clone())
|
||||
.app_data(loadeds_args.clone())
|
||||
.app_data(handlebars_ref.clone())
|
||||
.app_data(server_settings.clone())
|
||||
.app_data(auth_ref.clone())
|
||||
.service(version_info)
|
||||
.service(web::login::form_submit)
|
||||
.service(web::login::form_load)
|
||||
.service(web::favicon::favicon_standard)
|
||||
.service(web::dashboard::get)
|
||||
.service(web::index::get)
|
||||
})
|
||||
.bind((cli_args.ip, cli_args.port))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
65
rc_society/src/web/dashboard.rs
Normal file
65
rc_society/src/web/dashboard.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use actix_web::{get, web::Data, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
const FORM_NAME: &str = "dashboard";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
// TODO
|
||||
display_name: String,
|
||||
public_id: String,
|
||||
debug: DebugData,
|
||||
perms: PermissionData,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DebugData {
|
||||
user_id: i32,
|
||||
creation_time_unix: i64,
|
||||
creation_time_iso: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct PermissionData {
|
||||
r#mod: bool,
|
||||
admin: bool,
|
||||
dev: bool,
|
||||
royal: bool,
|
||||
banned: bool,
|
||||
}
|
||||
|
||||
#[get("/dashboard")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match super::try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
super::LoginReturn::Success(user) => {
|
||||
// TODO
|
||||
let creation_time = user.creation();
|
||||
let creation_time_chrono = chrono::DateTime::<chrono::Utc>::from_timestamp_secs(creation_time).unwrap_or_default();
|
||||
Ok(super::render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
debug: DebugData {
|
||||
user_id: user.account_id(),
|
||||
creation_time_unix: creation_time,
|
||||
creation_time_iso: creation_time_chrono.to_rfc3339(),
|
||||
},
|
||||
perms: PermissionData {
|
||||
r#mod: user.is_mod(),
|
||||
admin: user.is_admin(),
|
||||
dev: user.is_dev(),
|
||||
royal: user.is_royal(),
|
||||
banned: user.is_banned(),
|
||||
}
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
11
rc_society/src/web/favicon.rs
Normal file
11
rc_society/src/web/favicon.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use actix_web::{get, web::Data, Responder};
|
||||
|
||||
async fn favicon_impl(cli_args: Data<crate::cli::LoadedArgs>) -> impl Responder {
|
||||
let path = std::path::PathBuf::from(&cli_args.assets).join("favicon.jpg");
|
||||
actix_files::NamedFile::open_async(path).await
|
||||
}
|
||||
|
||||
#[get("/favicon.ico")]
|
||||
pub async fn favicon_standard(cli_args: Data<crate::cli::LoadedArgs>) -> impl Responder {
|
||||
favicon_impl(cli_args).await
|
||||
}
|
||||
77
rc_society/src/web/index.rs
Normal file
77
rc_society/src/web/index.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use actix_web::{get, web::Data, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
pub const FORM_NAME: &str = "index";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
is_logged_in: bool,
|
||||
display_name: Option<String>,
|
||||
server: ServerDetails,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ServerDetails {
|
||||
domain: String,
|
||||
cdn: String,
|
||||
auth: String,
|
||||
min_version: i32,
|
||||
server_version: String,
|
||||
start_time_iso: String,
|
||||
start_time_unix: i64,
|
||||
}
|
||||
|
||||
fn server_details(conf: &oj_rc_core::persist::config::ServerConfig) -> ServerDetails {
|
||||
let start_time_unix = crate::START_TIME.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let start_time_chrono = chrono::DateTime::<chrono::Utc>::from_timestamp_secs(start_time_unix).unwrap_or_default();
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let git_version = git_version::git_version!(args = ["--always", "--dirty=+"]);
|
||||
let server_version = format!("{}:{}", version, git_version);
|
||||
ServerDetails {
|
||||
domain: conf.domain.clone(),
|
||||
cdn: conf.cdn_url.clone(),
|
||||
auth: conf.auth_url.clone(),
|
||||
min_version: conf.minimum_version,
|
||||
server_version,
|
||||
start_time_unix,
|
||||
start_time_iso: start_time_chrono.to_rfc3339(),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
let server_info = server_details(server_config.as_ref());
|
||||
if let Some(user) = user_opt {
|
||||
match super::try_auth_user(Some(user), auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
super::LoginReturn::Success(user) => {
|
||||
Ok(super::render_ok(
|
||||
RenderData {
|
||||
is_logged_in: true,
|
||||
display_name: Some(user.display_name().to_owned()),
|
||||
server: server_info,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(super::render_ok(
|
||||
RenderData {
|
||||
is_logged_in: false,
|
||||
display_name: None,
|
||||
server: server_info,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
66
rc_society/src/web/login.rs
Normal file
66
rc_society/src/web/login.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use actix_web::{get, post, web::{Data, Form, Redirect}, Responder, HttpRequest, HttpMessage};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
const FORM_NAME: &str = "login";
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
struct LoginForm {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[post("/login")]
|
||||
pub async fn form_submit(form: Form<LoginForm>, auth: Data<Box<oj_rc_core::UserImpl>>, handlebars_ref: Data<handlebars::Handlebars<'_>>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
let auth_result = auth.login(oj_rc_core::persist::user::UserAuthInfo::Username {
|
||||
username: form.username.to_owned(),
|
||||
password: form.password.to_owned(),
|
||||
}).await;
|
||||
match auth_result {
|
||||
Ok(user) => {
|
||||
let resp = Redirect::to("/dashboard")
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body();
|
||||
Identity::login(&req.extensions(), user.response.token)?;
|
||||
Ok(resp)
|
||||
},
|
||||
Err(e) => {
|
||||
Ok(super::render_err(form.0, e.message, handlebars_ref.as_ref(), FORM_NAME)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/login")]
|
||||
pub async fn form_load(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
if let Some(user) = user_opt {
|
||||
let user_id = user.id()?;
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
if auth.verify(user_id).await.is_ok() {
|
||||
Ok(Redirect::to("/dashboard")
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body())
|
||||
} else {
|
||||
Ok(super::render_err(
|
||||
LoginForm::default(),
|
||||
"Invalid login token".to_owned(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Ok(super::render_ok(
|
||||
LoginForm::default(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
71
rc_society/src/web/mod.rs
Normal file
71
rc_society/src/web/mod.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
pub mod dashboard;
|
||||
pub mod login;
|
||||
pub mod favicon;
|
||||
pub mod index;
|
||||
|
||||
use serde::Serialize;
|
||||
use actix_web::{web::{Html, Redirect}, Responder};
|
||||
|
||||
fn version_string() -> String {
|
||||
let name = env!("CARGO_PKG_NAME");
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
//let license = env!("CARGO_PKG_LICENSE");
|
||||
//let repo = env!("CARGO_PKG_REPOSITORY");
|
||||
format!("OpenJam {} {}", name, version)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Context<T: Serialize> {
|
||||
form: T,
|
||||
error: Option<String>,
|
||||
version: String,
|
||||
source_url: String,
|
||||
}
|
||||
|
||||
fn render_ok<T: Serialize>(form: T, renderer: &handlebars::Handlebars<'_>, form_name: &str) -> Html {
|
||||
let rendered = renderer.render(form_name, &Context {
|
||||
form,
|
||||
error: None,
|
||||
version: version_string(),
|
||||
source_url: env!("CARGO_PKG_REPOSITORY").to_owned(),
|
||||
}).unwrap();
|
||||
Html::new(rendered)
|
||||
}
|
||||
|
||||
fn render_err<T: Serialize>(form: T, error: String , renderer: &handlebars::Handlebars<'_>, form_name: &str) -> Html {
|
||||
let rendered = renderer.render(form_name, &Context {
|
||||
form,
|
||||
error: Some(error),
|
||||
version: version_string(),
|
||||
source_url: env!("CARGO_PKG_REPOSITORY").to_owned(),
|
||||
}).unwrap();
|
||||
Html::new(rendered)
|
||||
}
|
||||
|
||||
enum LoginReturn {
|
||||
Success(Box<dyn oj_rc_core::persist::user::WebUser>),
|
||||
AuthFail(actix_web::HttpResponse<actix_web::body::BoxBody>),
|
||||
}
|
||||
|
||||
async fn try_auth_user(user_opt: Option<actix_identity::Identity>, auth: &oj_rc_core::UserImpl, req: &actix_web::HttpRequest) -> Result<LoginReturn, actix_web::Error> {
|
||||
if let Some(user) = user_opt {
|
||||
let user_id = user.id()?;
|
||||
match <oj_rc_core::UserImpl as oj_rc_core::UserProvider<()>>::web_authenticate(auth, user_id.clone()).await {
|
||||
Ok(user) => Ok(LoginReturn::Success(user)),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to login with token {}: {} ({:?})", user_id, e.message, e.code);
|
||||
Ok(LoginReturn::AuthFail(
|
||||
Redirect::to("/login")
|
||||
.respond_to(req)
|
||||
.map_into_boxed_body()
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(LoginReturn::AuthFail(
|
||||
Redirect::to("/login")
|
||||
.respond_to(req)
|
||||
.map_into_boxed_body()
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user