aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtkay123 <dev@kanjala.com>2026-02-10 23:38:02 +0200
committerrtkay123 <dev@kanjala.com>2026-02-10 23:38:02 +0200
commit4f30128feb0715f05c103fec20aa6cba61e60984 (patch)
tree8291d4accb1bdf98c9afb0dca9686aa34880c62f
parent375da0e07f2b3e88c2f6db0e6f4565b3ad555b95 (diff)
downloadsellershut-4f30128feb0715f05c103fec20aa6cba61e60984.tar.bz2
sellershut-4f30128feb0715f05c103fec20aa6cba61e60984.zip
feat: db create account
-rw-r--r--Cargo.lock336
-rw-r--r--Cargo.toml1
-rw-r--r--lib/auth-service/Cargo.toml1
-rw-r--r--lib/auth-service/src/client/http.rs56
-rw-r--r--lib/auth-service/src/client/mod.rs29
-rw-r--r--lib/auth-service/src/lib.rs3
-rw-r--r--lib/auth-service/src/service/mod.rs71
-rw-r--r--migrations/20260210193544_profile.sql26
-rw-r--r--migrations/20260210194218_oauth_account.sql11
-rw-r--r--sellershut/Cargo.toml2
-rw-r--r--sellershut/sellershut.toml2
-rw-r--r--sellershut/src/server/entity/mod.rs1
-rw-r--r--sellershut/src/server/entity/user.rs6
-rw-r--r--sellershut/src/server/mod.rs4
-rw-r--r--sellershut/src/server/routes/auth/discord.rs47
-rw-r--r--sellershut/src/server/routes/auth/mod.rs141
-rw-r--r--sellershut/src/state/mod.rs4
17 files changed, 723 insertions, 18 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0034898..7315a4c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -187,6 +187,7 @@ dependencies = [
"async-session",
"async-trait",
"oauth2",
+ "reqwest 0.13.2",
"secrecy",
"serde_json",
"shared-svc",
@@ -204,6 +205,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
+name = "aws-lc-rs"
+version = "1.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
+dependencies = [
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+]
+
+[[package]]
name = "axum"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -256,6 +279,28 @@ dependencies = [
]
[[package]]
+name = "axum-extra"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fef252edff26ddba56bbcdf2ee3307b8129acb86f5749b68990c168a6fcc9c76"
+dependencies = [
+ "axum",
+ "axum-core",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "headers",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
name = "backon"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -380,10 +425,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
dependencies = [
"find-msvc-tools",
+ "jobserver",
+ "libc",
"shlex",
]
[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -456,6 +509,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
[[package]]
+name = "cmake"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
+dependencies = [
+ "cc",
+]
+
+[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -497,6 +559,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -665,6 +737,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -766,6 +844,12 @@ dependencies = [
]
[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -901,6 +985,30 @@ dependencies = [
]
[[package]]
+name = "headers"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "headers-core",
+ "http",
+ "httpdate",
+ "mime",
+ "sha1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
+dependencies = [
+ "http",
+]
+
+[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1223,6 +1331,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if 1.0.4",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.4",
+ "libc",
+]
+
+[[package]]
name = "js-sys"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1451,7 +1591,7 @@ dependencies = [
"getrandom 0.2.17",
"http",
"rand 0.8.5",
- "reqwest",
+ "reqwest 0.12.28",
"serde",
"serde_json",
"serde_path_to_error",
@@ -1479,6 +1619,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
+name = "openssl-probe"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
+
+[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1632,6 +1778,7 @@ version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [
+ "aws-lc-rs",
"bytes",
"getrandom 0.3.4",
"lru-slab",
@@ -1851,6 +1998,43 @@ dependencies = [
]
[[package]]
+name = "reqwest"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "rustls-platform-verifier",
+ "serde",
+ "serde_json",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1930,6 +2114,7 @@ version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
+ "aws-lc-rs",
"once_cell",
"ring",
"rustls-pki-types",
@@ -1939,6 +2124,18 @@ dependencies = [
]
[[package]]
+name = "rustls-native-certs"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
+dependencies = [
+ "openssl-probe",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1949,11 +2146,39 @@ dependencies = [
]
[[package]]
+name = "rustls-platform-verifier"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
+dependencies = [
+ "core-foundation",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
+[[package]]
name = "rustls-webpki"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
+ "aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -1981,6 +2206,15 @@ dependencies = [
]
[[package]]
+name = "schannel"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1997,6 +2231,29 @@ dependencies = [
]
[[package]]
+name = "security-framework"
+version = "3.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
name = "sellershut"
version = "0.1.0"
dependencies = [
@@ -2004,9 +2261,11 @@ dependencies = [
"async-session",
"auth-service",
"axum",
+ "axum-extra",
"clap",
"dotenvy",
"http-body-util",
+ "reqwest 0.13.2",
"secrecy",
"serde",
"serde_json",
@@ -3135,6 +3394,15 @@ dependencies = [
]
[[package]]
+name = "webpki-root-certs"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
name = "webpki-roots"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3232,6 +3500,15 @@ dependencies = [
[[package]]
name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
@@ -3268,6 +3545,21 @@ dependencies = [
[[package]]
name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
@@ -3316,6 +3608,12 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
@@ -3334,6 +3632,12 @@ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
@@ -3352,6 +3656,12 @@ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
@@ -3382,6 +3692,12 @@ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
@@ -3400,6 +3716,12 @@ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
@@ -3418,6 +3740,12 @@ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
@@ -3436,6 +3764,12 @@ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/Cargo.toml b/Cargo.toml
index bbe0fa7..dd33222 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,7 @@ documentation = "https://books.kanjala.com/sellershut"
[workspace.dependencies]
async-session = "3.0.0"
async-trait = "0.1.89"
+reqwest = { version = "0.13.2", default-features = false }
secrecy = "0.10.3"
serde = "1.0.228"
serde_json = "1.0.149"
diff --git a/lib/auth-service/Cargo.toml b/lib/auth-service/Cargo.toml
index c3b9be7..2cea550 100644
--- a/lib/auth-service/Cargo.toml
+++ b/lib/auth-service/Cargo.toml
@@ -10,6 +10,7 @@ documentation.workspace = true
async-session.workspace = true
async-trait.workspace = true
oauth2 = "5.0.0"
+reqwest = { workspace = true, features = ["rustls"] }
secrecy = "0.10.3"
serde_json = "1.0.149"
shared-svc = { workspace = true, features = ["cache"] }
diff --git a/lib/auth-service/src/client/http.rs b/lib/auth-service/src/client/http.rs
new file mode 100644
index 0000000..5621fb0
--- /dev/null
+++ b/lib/auth-service/src/client/http.rs
@@ -0,0 +1,56 @@
+use std::ops::Deref;
+
+use oauth2::http;
+
+#[derive(Clone, Debug)]
+pub struct HttpAuthClient(reqwest::Client);
+
+impl From<reqwest::Client> for HttpAuthClient {
+ fn from(value: reqwest::Client) -> Self {
+ Self(value)
+ }
+}
+
+impl Deref for HttpAuthClient {
+ type Target = reqwest::Client;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<'c> oauth2::AsyncHttpClient<'c> for HttpAuthClient {
+ type Error = oauth2::HttpClientError<reqwest::Error>;
+
+ #[cfg(target_arch = "wasm32")]
+ type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Self::Error>> + 'c>>;
+ #[cfg(not(target_arch = "wasm32"))]
+ type Future = std::pin::Pin<
+ Box<dyn Future<Output = Result<oauth2::HttpResponse, Self::Error>> + Send + Sync + 'c>,
+ >;
+
+ fn call(&'c self, request: oauth2::HttpRequest) -> Self::Future {
+ Box::pin(async move {
+ let response = self
+ .0
+ .execute(request.try_into().map_err(Box::new)?)
+ .await
+ .map_err(Box::new)?;
+
+ let mut builder = http::Response::builder().status(response.status());
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ builder = builder.version(response.version());
+ }
+
+ for (name, value) in response.headers().iter() {
+ builder = builder.header(name, value);
+ }
+
+ builder
+ .body(response.bytes().await.map_err(Box::new)?.to_vec())
+ .map_err(oauth2::HttpClientError::Http)
+ })
+ }
+}
diff --git a/lib/auth-service/src/client/mod.rs b/lib/auth-service/src/client/mod.rs
index 45260fb..e02672b 100644
--- a/lib/auth-service/src/client/mod.rs
+++ b/lib/auth-service/src/client/mod.rs
@@ -1,3 +1,6 @@
+pub(crate) mod http;
+use std::ops::Deref;
+
use oauth2::{
AuthUrl, ClientId, ClientSecret, CsrfToken, EndpointNotSet, EndpointSet, RedirectUrl, Scope,
TokenUrl,
@@ -8,16 +11,24 @@ use url::Url;
use crate::{AuthServiceError, Provider};
+type Inner = oauth2::basic::BasicClient<
+ EndpointSet,
+ EndpointNotSet,
+ EndpointNotSet,
+ EndpointNotSet,
+ EndpointSet,
+>;
+
#[derive(Debug, Clone)]
-pub struct OauthClient(
- oauth2::basic::BasicClient<
- EndpointSet,
- EndpointNotSet,
- EndpointNotSet,
- EndpointNotSet,
- EndpointSet,
- >,
-);
+pub struct OauthClient(Inner);
+
+impl Deref for OauthClient {
+ type Target = Inner;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
#[derive(Debug)]
pub struct ClientConfig {
diff --git a/lib/auth-service/src/lib.rs b/lib/auth-service/src/lib.rs
index 0965f86..61ff230 100644
--- a/lib/auth-service/src/lib.rs
+++ b/lib/auth-service/src/lib.rs
@@ -1,9 +1,12 @@
+pub use oauth2;
pub mod client;
mod service;
+pub use client::http::HttpAuthClient;
pub use service::*;
use thiserror::Error;
+#[derive(Debug)]
pub enum Provider {
Discord,
}
diff --git a/lib/auth-service/src/service/mod.rs b/lib/auth-service/src/service/mod.rs
index 5150221..80b29de 100644
--- a/lib/auth-service/src/service/mod.rs
+++ b/lib/auth-service/src/service/mod.rs
@@ -1,15 +1,71 @@
use async_session::{Result, Session, SessionStore};
use async_trait::async_trait;
use shared_svc::cache::{CacheKey, RedisManager, redis::AsyncCommands};
-use sqlx::PgPool;
+use sqlx::{PgPool, Postgres, Transaction};
use tracing::{debug, instrument};
+use crate::Provider;
+
+#[async_trait]
+pub trait AccountMgr {
+ async fn get_apid_by_email(&self, email: &str) -> Result<Option<String>>;
+ async fn create_account(&self, provider: Provider, provider_user_id: &str, ap_id: &str);
+ async fn create_account_step(
+ &self,
+ provider: Provider,
+ provider_user_id: &str,
+ ap_id: &str,
+ email: &str,
+ transaction: &mut Transaction<'_, Postgres>,
+ ) -> Result;
+ async fn persist_session(&self);
+}
+
#[derive(Debug, Clone)]
pub struct AuthService {
cache: RedisManager,
database: PgPool,
}
+#[async_trait]
+impl AccountMgr for AuthService {
+ #[instrument(skip(self))]
+ async fn get_apid_by_email(&self, email: &str) -> Result<Option<String>> {
+ todo!()
+ }
+
+ #[instrument(skip(self))]
+ async fn create_account(&self, provider: Provider, provider_user_id: &str, ap_id: &str) {
+ todo!()
+ }
+ #[instrument(skip(self, transaction))]
+ async fn create_account_step(
+ &self,
+ provider: Provider,
+ provider_user_id: &str,
+ ap_id: &str,
+ email: &str,
+ transaction: &mut Transaction<'_, Postgres>,
+ ) -> Result {
+ sqlx::query!(
+ "insert into account
+ (provider_id, provider_user_id, email, ap_id)
+ values ($1, $2, $3, $4)
+ ",
+ "",
+ provider_user_id,
+ "",
+ ap_id
+ )
+ .execute(&mut **transaction)
+ .await?;
+ todo!()
+ }
+
+ #[instrument(skip(self))]
+ async fn persist_session(&self) {}
+}
+
impl AuthService {
pub fn new(cache: &RedisManager, database: &PgPool) -> Self {
Self {
@@ -26,9 +82,18 @@ impl SessionStore for AuthService {
#[doc = " The input is expected to be the value of an identifying"]
#[doc = " cookie. This will then be parsed by the session middleware"]
#[doc = " into a session if possible"]
- #[instrument(skip(self))]
+ #[instrument(skip(self, cookie_value))]
async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
- todo!()
+ debug!("getting session");
+ let id = Session::id_from_cookie_value(&cookie_value)?;
+ let mut client = self.cache.get().await?;
+ let session = client
+ .get::<_, Option<Vec<u8>>>(CacheKey::Session(&id))
+ .await?;
+ match session {
+ Some(value) => Ok(Some(serde_json::from_slice(&value)?)),
+ None => Ok(None),
+ }
}
#[doc = " Store a session on the storage backend."]
diff --git a/migrations/20260210193544_profile.sql b/migrations/20260210193544_profile.sql
new file mode 100644
index 0000000..f47d034
--- /dev/null
+++ b/migrations/20260210193544_profile.sql
@@ -0,0 +1,26 @@
+create type actor_kind as enum (
+ 'application',
+ 'group',
+ 'organization',
+ 'person',
+ 'service'
+);
+
+create table profile (
+ ap_id text primary key,
+ username varchar(15),
+ description varchar(255),
+ inbox text not null,
+ role actor_kind not null default 'person',
+ outbox text,
+ picture text,
+ public_key text not null,
+ private_key text,
+ created_at timestamptz not null default now(),
+ last_refreshed_at timestamptz not null default now()
+);
+
+create index user_inbox_idx on profile (inbox);
+create index user_outbox_idx on profile (outbox);
+create index user_role_idx on profile (role);
+create index user_username_idx on profile (username);
diff --git a/migrations/20260210194218_oauth_account.sql b/migrations/20260210194218_oauth_account.sql
new file mode 100644
index 0000000..ce660fe
--- /dev/null
+++ b/migrations/20260210194218_oauth_account.sql
@@ -0,0 +1,11 @@
+create extension if not exists citext;
+
+create table account (
+ provider_id text not null,
+ provider_user_id text not null,
+ email citext not null,
+ ap_id text not null references profile(ap_id) on delete cascade,
+ primary key (provider_id, provider_user_id)
+);
+
+create index account_email_idx on account (email);
diff --git a/sellershut/Cargo.toml b/sellershut/Cargo.toml
index 74dfd5d..2a4de31 100644
--- a/sellershut/Cargo.toml
+++ b/sellershut/Cargo.toml
@@ -11,8 +11,10 @@ anyhow = "1.0.101"
async-session.workspace = true
auth-service = { path = "../lib/auth-service" }
axum = "0.8.8"
+axum-extra = { version = "0.12.5", features = ["typed-header"] }
clap = { version = "4.5.57", features = ["derive", "env"] }
dotenvy = "0.15.7"
+reqwest = { version = "0.13.2", default-features = false, features = ["json", "rustls"] }
secrecy = { workspace = true, features = ["serde"] }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
diff --git a/sellershut/sellershut.toml b/sellershut/sellershut.toml
index ce2f4ff..01b9481 100644
--- a/sellershut/sellershut.toml
+++ b/sellershut/sellershut.toml
@@ -11,7 +11,7 @@ pool-size = 10
url = "redis://localhost:6379"
[oauth]
-redirect-url = "http://localhost:2210"
+redirect-url = "http://localhost:2210/auth/authorised"
[oauth.discord]
client-id = ""
diff --git a/sellershut/src/server/entity/mod.rs b/sellershut/src/server/entity/mod.rs
new file mode 100644
index 0000000..22d12a3
--- /dev/null
+++ b/sellershut/src/server/entity/mod.rs
@@ -0,0 +1 @@
+pub mod user;
diff --git a/sellershut/src/server/entity/user.rs b/sellershut/src/server/entity/user.rs
new file mode 100644
index 0000000..91da03a
--- /dev/null
+++ b/sellershut/src/server/entity/user.rs
@@ -0,0 +1,6 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize)]
+pub struct User {
+ pub ap_id: String,
+}
diff --git a/sellershut/src/server/mod.rs b/sellershut/src/server/mod.rs
index cc96c84..9818ce5 100644
--- a/sellershut/src/server/mod.rs
+++ b/sellershut/src/server/mod.rs
@@ -1,3 +1,4 @@
+mod entity;
pub mod error;
mod middleware;
mod routes;
@@ -35,7 +36,8 @@ pub async fn router(state: Arc<AppState>) -> Router<()> {
let stubs = OpenApiRouter::with_openapi(doc)
.routes(utoipa_axum::routes!(routes::health))
- .routes(utoipa_axum::routes!(routes::auth::auth));
+ .routes(utoipa_axum::routes!(routes::auth::auth))
+ .routes(utoipa_axum::routes!(routes::auth::authorised));
let (router, _api) = stubs.split_for_parts();
diff --git a/sellershut/src/server/routes/auth/discord.rs b/sellershut/src/server/routes/auth/discord.rs
new file mode 100644
index 0000000..e6c086a
--- /dev/null
+++ b/sellershut/src/server/routes/auth/discord.rs
@@ -0,0 +1,47 @@
+use anyhow::Context as _;
+use auth_service::HttpAuthClient;
+use serde::{Deserialize, Serialize};
+use tracing::instrument;
+
+use crate::server::error::AppError;
+
+// https://discord.com/developers/docs/resources/user#user-object-user-structure
+#[derive(Debug, Serialize, Deserialize)]
+struct User {
+ id: String,
+ username: String,
+ email: String,
+ verified: bool,
+}
+
+impl From<User> for super::OauthUser {
+ fn from(value: User) -> Self {
+ Self {
+ id: value.id,
+ username: value.username,
+ email: value.email,
+ }
+ }
+}
+
+#[instrument(skip(client, token))]
+pub(super) async fn get_data(
+ client: &HttpAuthClient,
+ token: &str,
+) -> Result<super::OauthUser, AppError> {
+ let user_data: User = client
+ .get("https://discordapp.com/api/users/@me")
+ .bearer_auth(token)
+ .send()
+ .await
+ .context("failed in sending request to target Url")?
+ .json::<User>()
+ .await
+ .context("failed to deserialize response as JSON")?;
+
+ if !user_data.verified {
+ return Err(anyhow::anyhow!("user must be verified").into());
+ }
+
+ Ok(user_data.into())
+}
diff --git a/sellershut/src/server/routes/auth/mod.rs b/sellershut/src/server/routes/auth/mod.rs
index bfc045f..04b6a2c 100644
--- a/sellershut/src/server/routes/auth/mod.rs
+++ b/sellershut/src/server/routes/auth/mod.rs
@@ -1,13 +1,22 @@
+mod discord;
use std::sync::Arc;
use anyhow::Context as _;
-use async_session::{Session, SessionStore};
-use auth_service::Provider;
+use async_session::{Session, SessionStore, log::debug};
+use auth_service::{
+ AccountMgr, HttpAuthClient, Provider,
+ client::OauthClient,
+ oauth2::{AuthorizationCode, CsrfToken, TokenResponse},
+};
use axum::{
extract::{Query, State},
http::{HeaderMap, header::SET_COOKIE},
response::{IntoResponse, Redirect},
};
+use axum_extra::{
+ TypedHeader,
+ headers::{self},
+};
use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, OpenApi, ToSchema};
@@ -91,3 +100,131 @@ pub async fn auth(
Ok((headers, Redirect::to(auth_url.as_ref())))
}
+
+#[derive(Debug, Deserialize, IntoParams)]
+#[into_params(parameter_in = Query)]
+pub struct AuthRequest {
+ code: String,
+ state: String,
+}
+
+#[utoipa::path(
+ method(get),
+ path = "/auth/authorised",
+ params(
+ AuthRequest
+ ),
+ tag=AUTH,
+ responses(
+ (
+ status = 200,
+ description = "OAuth authorised callback",
+ headers(
+ (
+ "set-cookie" = String,
+ description = "Session cookie"
+ )
+ )
+ )
+ )
+)]
+pub async fn authorised(
+ TypedHeader(cookies): TypedHeader<headers::Cookie>,
+ Query(params): Query<AuthRequest>,
+ State(data): State<Arc<AppState>>,
+) -> Result<impl IntoResponse, AppError> {
+ let provider = csrf_token_validation_workflow(&params, &cookies, &data).await?;
+
+ // Get an auth token
+
+ let user = match provider {
+ OauthProvider::Discord => {
+ let token = get_token(&data.discord_client, &data.http_client, &params.code).await?;
+ discord::get_data(&data.http_client, token.access_token().secret()).await?
+ }
+ };
+
+ if let Some(ap_id) = data.auth_service.get_apid_by_email(&user.email).await? {
+ debug!("user exists");
+ data.auth_service
+ .create_account(provider.into(), &user.id, "")
+ .await;
+ } else {
+ debug!("user does not exist, creating");
+ // create account and user in a transaction
+ let mut transaction = data.database.begin().await?;
+
+ data.auth_service
+ .create_account_step(provider.into(), &user.id, "", &user.email, &mut transaction)
+ .await?;
+
+ transaction.commit().await?;
+ }
+
+ Ok(String::default())
+}
+
+async fn csrf_token_validation_workflow(
+ auth_request: &AuthRequest,
+ cookies: &headers::Cookie,
+ data: &Arc<AppState>,
+) -> Result<OauthProvider, AppError> {
+ let cookie = cookies
+ .get(COOKIE_NAME)
+ .context("unexpected error getting cookie name")?
+ .to_string();
+
+ let session = data
+ .auth_service
+ .load_session(cookie)
+ .await?
+ .context("Session not found")?;
+
+ let stored_csrf_token = session
+ .get::<CsrfToken>(CSRF_TOKEN)
+ .context("CSRF token not found in session")?
+ .to_owned();
+
+ let provider = session
+ .get(OAUTH_PROVIDER)
+ .context("provider not found in session")?;
+
+ data.auth_service
+ .destroy_session(session)
+ .await
+ .context("Failed to destroy old session")?;
+
+ // Validate CSRF token is the same as the one in the auth request
+ if *stored_csrf_token.secret() != auth_request.state {
+ return Err(anyhow::anyhow!("CSRF token mismatch").into());
+ }
+
+ Ok(provider)
+}
+
+async fn get_token(
+ oauth_client: &OauthClient,
+ client: &HttpAuthClient,
+ code: &str,
+) -> Result<
+ auth_service::oauth2::StandardTokenResponse<
+ auth_service::oauth2::EmptyExtraTokenFields,
+ auth_service::oauth2::basic::BasicTokenType,
+ >,
+ AppError,
+> {
+ // Get an auth token
+ let token = oauth_client
+ .exchange_code(AuthorizationCode::new(code.to_owned()))
+ .request_async(client)
+ .await
+ .context("failed in sending request request to authorization server")?;
+ Ok(token)
+}
+
+#[derive(Debug, Deserialize)]
+struct OauthUser {
+ pub id: String,
+ pub username: String,
+ pub email: String,
+}
diff --git a/sellershut/src/state/mod.rs b/sellershut/src/state/mod.rs
index be20e13..ab0262e 100644
--- a/sellershut/src/state/mod.rs
+++ b/sellershut/src/state/mod.rs
@@ -1,4 +1,4 @@
-use auth_service::{AuthService, client::OauthClient};
+use auth_service::{AuthService, HttpAuthClient, client::OauthClient};
use shared_svc::cache::RedisManager;
use sqlx::PgPool;
use tracing::{debug, error};
@@ -11,6 +11,7 @@ pub struct AppState {
pub cache: RedisManager,
pub auth_service: AuthService,
pub database: PgPool,
+ pub http_client: HttpAuthClient,
}
impl AppState {
@@ -43,6 +44,7 @@ impl AppState {
cache,
auth_service,
database,
+ http_client: reqwest::Client::new().into(),
})
}
}