Sessions Route + Password Auth (#649)
* Sessions Route + Password Auth * run prep + fix clippy * changing passwords + logging in * register login
This commit is contained in:
parent
ef9c90a43a
commit
6c0ad7fe1a
2
.env
2
.env
@ -71,3 +71,5 @@ GOOGLE_CLIENT_ID=none
|
|||||||
GOOGLE_CLIENT_SECRET=none
|
GOOGLE_CLIENT_SECRET=none
|
||||||
|
|
||||||
STEAM_API_KEY=none
|
STEAM_API_KEY=none
|
||||||
|
|
||||||
|
TURNSTILE_SECRET=none
|
||||||
|
|||||||
141
Cargo.lock
generated
141
Cargo.lock
generated
@ -366,6 +366,17 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"password-hash 0.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@ -482,6 +493,12 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base32"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@ -509,6 +526,21 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-set"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||||
|
dependencies = [
|
||||||
|
"bit-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@ -539,6 +571,15 @@ dependencies = [
|
|||||||
"wyz",
|
"wyz",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -818,6 +859,12 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -1128,6 +1175,37 @@ dependencies = [
|
|||||||
"uuid 1.4.0",
|
"uuid 1.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder_core"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
|
||||||
|
dependencies = [
|
||||||
|
"darling 0.14.4",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder_macro"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder_core",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.17"
|
version = "0.99.17"
|
||||||
@ -1267,6 +1345,16 @@ dependencies = [
|
|||||||
"zune-inflate",
|
"zune-inflate",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fancy-regex"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
|
||||||
|
dependencies = [
|
||||||
|
"bit-set",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@ -1986,6 +2074,7 @@ dependencies = [
|
|||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"argon2",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
@ -2025,6 +2114,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
"totp-rs",
|
||||||
"url",
|
"url",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"validator",
|
"validator",
|
||||||
@ -2033,6 +2123,7 @@ dependencies = [
|
|||||||
"yaserde",
|
"yaserde",
|
||||||
"yaserde_derive",
|
"yaserde_derive",
|
||||||
"zip",
|
"zip",
|
||||||
|
"zxcvbn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2622,6 +2713,17 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.13"
|
version = "1.0.13"
|
||||||
@ -2636,7 +2738,7 @@ checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
"hmac 0.12.1",
|
"hmac 0.12.1",
|
||||||
"password-hash",
|
"password-hash 0.4.2",
|
||||||
"sha2 0.10.7",
|
"sha2 0.10.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2842,6 +2944,12 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.26.0"
|
version = "0.26.0"
|
||||||
@ -4128,6 +4236,19 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "totp-rs"
|
||||||
|
version = "5.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ad5e73765ff14ae797c1a61ee0c7beaf21b4e4a0047844300e332c6c24df1fc"
|
||||||
|
dependencies = [
|
||||||
|
"base32",
|
||||||
|
"constant_time_eq 0.2.6",
|
||||||
|
"hmac 0.12.1",
|
||||||
|
"sha1 0.10.5",
|
||||||
|
"sha2 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -4714,7 +4835,7 @@ dependencies = [
|
|||||||
"aes",
|
"aes",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"constant_time_eq",
|
"constant_time_eq 0.1.5",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"flate2",
|
"flate2",
|
||||||
@ -4782,3 +4903,19 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zxcvbn"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "103fa851fff70ea29af380e87c25c48ff7faac5c530c70bd0e65366d4e0c94e4"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder",
|
||||||
|
"fancy-regex",
|
||||||
|
"itertools 0.10.5",
|
||||||
|
"js-sys",
|
||||||
|
"lazy_static",
|
||||||
|
"quick-error",
|
||||||
|
"regex",
|
||||||
|
"time 0.3.22",
|
||||||
|
]
|
||||||
|
|||||||
@ -45,8 +45,11 @@ base64 = "0.21.2"
|
|||||||
sha1 = { version = "0.6.1", features = ["std"] }
|
sha1 = { version = "0.6.1", features = ["std"] }
|
||||||
sha2 = "0.9.9"
|
sha2 = "0.9.9"
|
||||||
hmac = "0.11.0"
|
hmac = "0.11.0"
|
||||||
|
argon2 = { version = "0.5.0", features = ["std"] }
|
||||||
bitflags = "1.3.2"
|
bitflags = "1.3.2"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
zxcvbn = "2.2.2"
|
||||||
|
totp-rs = "5.0.2"
|
||||||
|
|
||||||
url = "2.4.0"
|
url = "2.4.0"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
|
|||||||
873
sqlx-data.json
873
sqlx-data.json
@ -397,19 +397,67 @@
|
|||||||
},
|
},
|
||||||
"query": "\n INSERT INTO files (id, version_id, url, filename, is_primary, size, file_type)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n "
|
"query": "\n INSERT INTO files (id, version_id, url, filename, is_primary, size, file_type)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n "
|
||||||
},
|
},
|
||||||
"0d737f82a07a1cd578c4c14c9f718d872068d17e4b122c60765cbb3328b2378a": {
|
"0d91a3a73844f46ef00d8d45a0d028f1c4c1da016044f63f21d96707eafec858": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [
|
||||||
"nullable": [],
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "team_id",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "member_role",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "permissions",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "accepted",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payouts_split",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Numeric"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ordering",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Left": [
|
"Left": [
|
||||||
"Varchar",
|
"Int8Array"
|
||||||
"Timestamp",
|
|
||||||
"Int8"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n UPDATE pats SET\n name = $1,\n expires_at = $2\n WHERE id = $3\n "
|
"query": "\n SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split, tm.ordering,\n tm.user_id user_id\n FROM team_members tm\n WHERE tm.team_id = ANY($1)\n ORDER BY tm.team_id, tm.ordering\n "
|
||||||
},
|
},
|
||||||
"0f29bb5ba767ebd0669c860994e48e3cb2674f0d53f6c4ab85c79d46b04cbb40": {
|
"0f29bb5ba767ebd0669c860994e48e3cb2674f0d53f6c4ab85c79d46b04cbb40": {
|
||||||
"describe": {
|
"describe": {
|
||||||
@ -919,23 +967,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT id FROM side_types\n WHERE name = $1\n "
|
"query": "\n SELECT id FROM side_types\n WHERE name = $1\n "
|
||||||
},
|
},
|
||||||
"1ee84c22602345af913657875dfde3208d71101de75955cbb6f18214d16c3d2f": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"Varchar",
|
|
||||||
"Varchar",
|
|
||||||
"Int8",
|
|
||||||
"Int8",
|
|
||||||
"Timestamp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n INSERT INTO pats (id, name, access_token, user_id, scope, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6)\n "
|
|
||||||
},
|
|
||||||
"1ffce9b2d5c9fa6c8b9abce4bad9f9419c44ad6367b7463b979c91b9b5b4fea1": {
|
"1ffce9b2d5c9fa6c8b9abce4bad9f9419c44ad6367b7463b979c91b9b5b4fea1": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@ -1259,6 +1290,58 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1\n "
|
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1\n "
|
||||||
},
|
},
|
||||||
|
"2e5ddc7876d8041fec781893027f84b49b5794c85fa442296c35156d0a72464a": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Timestamptz",
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Varchar",
|
||||||
|
"Int8",
|
||||||
|
"Varchar",
|
||||||
|
"Bool",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n INSERT INTO users (\n id, username, name, email,\n avatar_url, bio, created,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9, $10, $11, $12, $13,\n $14, $15\n )\n "
|
||||||
|
},
|
||||||
|
"2eeb8e6fe76c13bcab19ec983234d6fc10a57ea4452740c01504ea4443c18b83": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT id, password FROM users\n WHERE email = $1\n "
|
||||||
|
},
|
||||||
"2f4a620f954c7488e8bdb94a3d6968cec6d1332942b9e9f60925d14a8c2040f7": {
|
"2f4a620f954c7488e8bdb94a3d6968cec6d1332942b9e9f60925d14a8c2040f7": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@ -1522,140 +1605,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT id FROM mods\n WHERE status = $1\n ORDER BY queued ASC\n LIMIT $2;\n "
|
"query": "\n SELECT id FROM mods\n WHERE status = $1\n ORDER BY queued ASC\n LIMIT $2;\n "
|
||||||
},
|
},
|
||||||
"3bb3bcad044ebb7f94e9a73661295345f413c65b15e27b98fc9481caac46b48e": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "expires_at",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Timestamp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "avatar_url",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bio",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "role",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "badges",
|
|
||||||
"ordinal": 9,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "balance",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Numeric"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payout_wallet",
|
|
||||||
"ordinal": 11,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payout_wallet_type",
|
|
||||||
"ordinal": 12,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payout_address",
|
|
||||||
"ordinal": 13,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "github_id",
|
|
||||||
"ordinal": 14,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "discord_id",
|
|
||||||
"ordinal": 15,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gitlab_id",
|
|
||||||
"ordinal": 16,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "google_id",
|
|
||||||
"ordinal": 17,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "steam_id",
|
|
||||||
"ordinal": 18,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "microsoft_id",
|
|
||||||
"ordinal": 19,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT pats.expires_at,\n u.id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type, u.payout_address,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id\n FROM pats LEFT OUTER JOIN users u ON pats.user_id = u.id\n WHERE access_token = $1\n "
|
|
||||||
},
|
|
||||||
"3bdcbfa5abe43cc9b4f996f147277a7f6921cca00f82cad0ef5d85032c761a36": {
|
"3bdcbfa5abe43cc9b4f996f147277a7f6921cca00f82cad0ef5d85032c761a36": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -1971,135 +1920,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM mods_gallery\n WHERE id = $1\n "
|
"query": "\n DELETE FROM mods_gallery\n WHERE id = $1\n "
|
||||||
},
|
},
|
||||||
"4dfc14e7ba6fe3a8e0e078d91efef33743be2939838a6e621c8abeaadc12ff29": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "avatar_url",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bio",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "role",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "badges",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "balance",
|
|
||||||
"ordinal": 9,
|
|
||||||
"type_info": "Numeric"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payout_wallet",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payout_wallet_type",
|
|
||||||
"ordinal": 11,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payout_address",
|
|
||||||
"ordinal": 12,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "github_id",
|
|
||||||
"ordinal": 13,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "discord_id",
|
|
||||||
"ordinal": 14,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gitlab_id",
|
|
||||||
"ordinal": 15,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "google_id",
|
|
||||||
"ordinal": 16,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "steam_id",
|
|
||||||
"ordinal": 17,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "microsoft_id",
|
|
||||||
"ordinal": 18,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8Array",
|
|
||||||
"TextArray"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id, name, email,\n avatar_url, username, bio,\n created, role, badges,\n balance, payout_wallet, payout_wallet_type, payout_address,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id\n FROM users\n WHERE id = ANY($1) OR username = ANY($2)\n "
|
|
||||||
},
|
|
||||||
"4e9f9eafbfd705dfc94571018cb747245a98ea61bad3fae4b3ce284229d99955": {
|
"4e9f9eafbfd705dfc94571018cb747245a98ea61bad3fae4b3ce284229d99955": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -2553,27 +2373,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n INSERT INTO dependencies (dependent_id, dependency_type, dependency_id, mod_dependency_id, dependency_file_name)\n VALUES ($1, $2, $3, $4, $5)\n "
|
"query": "\n INSERT INTO dependencies (dependent_id, dependency_type, dependency_id, mod_dependency_id, dependency_file_name)\n VALUES ($1, $2, $3, $4, $5)\n "
|
||||||
},
|
},
|
||||||
"603ce7394c4b3e0790bf6a590199c72c50a54304112dc0b7cd91654f6aebf37c": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id FROM pats\n WHERE id = $1 AND user_id = $2\n "
|
|
||||||
},
|
|
||||||
"61a7f29e024bf2f1368370e3f6e8ef70317c7e8545b5b6d4235f21164948ba27": {
|
"61a7f29e024bf2f1368370e3f6e8ef70317c7e8545b5b6d4235f21164948ba27": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -2587,109 +2386,24 @@
|
|||||||
},
|
},
|
||||||
"query": "\n UPDATE mods_gallery\n SET featured = $2\n WHERE mod_id = $1\n "
|
"query": "\n UPDATE mods_gallery\n SET featured = $2\n WHERE mod_id = $1\n "
|
||||||
},
|
},
|
||||||
"61ebde440ef7ce59c38d92725b93b666ee22e04d6237438c2579d771f5b51240": {
|
"64d5e7cfb8472fbedcd06143db0db2f4c9677c42f73c540e85ccb5aee1a7b6f9": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [],
|
||||||
{
|
"nullable": [],
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "team_id",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "member_role",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "permissions",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "accepted",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payouts_split",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Numeric"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ordering",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user_id",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user_name",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "avatar_url",
|
|
||||||
"ordinal": 9,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bio",
|
|
||||||
"ordinal": 11,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created",
|
|
||||||
"ordinal": 12,
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user_role",
|
|
||||||
"ordinal": 13,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "badges",
|
|
||||||
"ordinal": 14,
|
|
||||||
"type_info": "Int8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Left": [
|
"Left": [
|
||||||
"Int8Array"
|
"Int8",
|
||||||
|
"Timestamptz",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split, tm.ordering,\n u.id user_id, u.name user_name,\n u.avatar_url avatar_url, u.username username, u.bio bio,\n u.created created, u.role user_role, u.badges badges\n FROM team_members tm\n INNER JOIN users u ON u.id = tm.user_id\n WHERE tm.team_id = ANY($1)\n ORDER BY tm.team_id, tm.ordering\n "
|
"query": "\n UPDATE sessions\n SET last_login = $2, city = $3, country = $4, ip = $5, os = $6, platform = $7, user_agent = $8\n WHERE (id = $1)\n "
|
||||||
},
|
},
|
||||||
"665e294e9737fd0299fc4639127d56811485dc8a5a4e08a4e7292044d8a2fb7a": {
|
"665e294e9737fd0299fc4639127d56811485dc8a5a4e08a4e7292044d8a2fb7a": {
|
||||||
"describe": {
|
"describe": {
|
||||||
@ -2951,6 +2665,36 @@
|
|||||||
},
|
},
|
||||||
"query": "\n INSERT INTO team_members (\n id, team_id, user_id, role, permissions, accepted\n )\n VALUES (\n $1, $2, $3, $4, $5, $6\n )\n "
|
"query": "\n INSERT INTO team_members (\n id, team_id, user_id, role, permissions, accepted\n )\n VALUES (\n $1, $2, $3, $4, $5, $6\n )\n "
|
||||||
},
|
},
|
||||||
|
"7028615c2af313f48ce68addef860b6e15a9736117cd64ec96277487d54d5964": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "session",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT id, session, user_id\n FROM sessions\n WHERE refresh_expires >= NOW()\n "
|
||||||
|
},
|
||||||
"70b510956a40583eef8c57dcced71c67f525eee455ae8b09e9b2403668068751": {
|
"70b510956a40583eef8c57dcced71c67f525eee455ae8b09e9b2403668068751": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -3284,6 +3028,19 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT n.id FROM notifications n\n WHERE n.user_id = $1\n "
|
"query": "\n SELECT n.id FROM notifications n\n WHERE n.user_id = $1\n "
|
||||||
},
|
},
|
||||||
|
"7b284f2c766ab64c57309f903d2c456ff74e06ea5f8454f0303215a5ad2cc93f": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n UPDATE users\n SET password = $1\n WHERE (id = $2)\n "
|
||||||
|
},
|
||||||
"7c0cdacf0898155c94008a96a0b918550df4475b9e3362a926d4d00e001880c1": {
|
"7c0cdacf0898155c94008a96a0b918550df4475b9e3362a926d4d00e001880c1": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@ -3832,6 +3589,147 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM payouts_values\n WHERE user_id = $1\n "
|
"query": "\n DELETE FROM payouts_values\n WHERE user_id = $1\n "
|
||||||
},
|
},
|
||||||
|
"95c131d3ea36d53f9dccc6ff8bb7efd3fb571e4175857178c24f5c841a1ec7ed": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "email",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "avatar_url",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bio",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "role",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "badges",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "balance",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Numeric"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payout_wallet",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payout_wallet_type",
|
||||||
|
"ordinal": 11,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payout_address",
|
||||||
|
"ordinal": 12,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "github_id",
|
||||||
|
"ordinal": 13,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "discord_id",
|
||||||
|
"ordinal": 14,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gitlab_id",
|
||||||
|
"ordinal": 15,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "google_id",
|
||||||
|
"ordinal": 16,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "steam_id",
|
||||||
|
"ordinal": 17,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "microsoft_id",
|
||||||
|
"ordinal": 18,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "email_verified",
|
||||||
|
"ordinal": 19,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"ordinal": 20,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8Array",
|
||||||
|
"TextArray"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT id, name, email,\n avatar_url, username, bio,\n created, role, badges,\n balance, payout_wallet, payout_wallet_type, payout_address,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n "
|
||||||
|
},
|
||||||
"97690dda7edea8c985891cae5ad405f628ed81e333bc88df5493c928a4324d43": {
|
"97690dda7edea8c985891cae5ad405f628ed81e333bc88df5493c928a4324d43": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@ -3937,26 +3835,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n INSERT INTO team_members (id, team_id, user_id, role, permissions, accepted, payouts_split, ordering)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n "
|
"query": "\n INSERT INTO team_members (id, team_id, user_id, role, permissions, accepted, payouts_split, ordering)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n "
|
||||||
},
|
},
|
||||||
"9d74e3d45e49dc2a7ac50b4ab233f96dbf39f6fa112df94e991b00444e9ab6ca": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "exists",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT EXISTS(SELECT 1 FROM pats WHERE access_token=$1)\n "
|
|
||||||
},
|
|
||||||
"a0148ff25855202e7bb220b6a2bc9220a95e309fb0dae41d9a05afa86e6b33af": {
|
"a0148ff25855202e7bb220b6a2bc9220a95e309fb0dae41d9a05afa86e6b33af": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -4374,26 +4252,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM mods_donations\n WHERE joining_mod_id = $1\n "
|
"query": "\n DELETE FROM mods_donations\n WHERE joining_mod_id = $1\n "
|
||||||
},
|
},
|
||||||
"ad27195af9964c34803343c22abcb9aa6b52f2d1a370550ed4fb68bce2297e71": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "exists",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "SELECT EXISTS(SELECT 1 FROM pats WHERE id=$1)"
|
|
||||||
},
|
|
||||||
"ae1686b8b566dd7ecc57c653c9313a4b324a2ec3a63aa6a44ed1d8ea7999b115": {
|
"ae1686b8b566dd7ecc57c653c9313a4b324a2ec3a63aa6a44ed1d8ea7999b115": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -4622,30 +4480,6 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)"
|
"query": "SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)"
|
||||||
},
|
},
|
||||||
"bedb0bdf803671138449d3e46e6dc5c63f9d01ea93e447ee69c99d3f29c89ab3": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"Varchar",
|
|
||||||
"Varchar",
|
|
||||||
"Varchar",
|
|
||||||
"Varchar",
|
|
||||||
"Varchar",
|
|
||||||
"Timestamptz",
|
|
||||||
"Int8",
|
|
||||||
"Int8",
|
|
||||||
"Int8",
|
|
||||||
"Varchar",
|
|
||||||
"Int8",
|
|
||||||
"Varchar"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n INSERT INTO users (\n id, username, name, email,\n avatar_url, bio, created,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9, $10, $11, $12, $13\n )\n "
|
|
||||||
},
|
|
||||||
"bee1abe8313d17a56d93b06a31240e338c3973bc7a7374799ced3df5e38d3134": {
|
"bee1abe8313d17a56d93b06a31240e338c3973bc7a7374799ced3df5e38d3134": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -5078,50 +4912,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n INSERT INTO hashes (file_id, algorithm, hash)\n VALUES ($1, $2, $3)\n "
|
"query": "\n INSERT INTO hashes (file_id, algorithm, hash)\n VALUES ($1, $2, $3)\n "
|
||||||
},
|
},
|
||||||
"cc58fd2c0aca4576a0fda37e7b2f183a6cda482f24e3cca7b2c6e31cb8d0d728": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user_id",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "scope",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "expires_at",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Timestamp"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id, name, user_id, scope, expires_at\n FROM pats\n WHERE user_id = $1\n "
|
|
||||||
},
|
|
||||||
"ccd913bb2f3006ffe881ce2fc4ef1e721d18fe2eed6ac62627046c955129610c": {
|
"ccd913bb2f3006ffe881ce2fc4ef1e721d18fe2eed6ac62627046c955129610c": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@ -5782,51 +5572,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT tm.user_id id\n FROM team_members tm\n WHERE tm.team_id = $1 AND tm.accepted\n "
|
"query": "\n SELECT tm.user_id id\n FROM team_members tm\n WHERE tm.team_id = $1 AND tm.accepted\n "
|
||||||
},
|
},
|
||||||
"ebbd2105c456a9b462a4e5ace356345ff18ad6cbcdfc053afe86948f8f3ae092": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "scope",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user_id",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "expires_at",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Timestamp"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id, name, scope, user_id, expires_at FROM pats\n WHERE id = $1 AND user_id = $2\n "
|
|
||||||
},
|
|
||||||
"ed1d5d9433bc7f4a360431ecfdd9430c5e58cd6d1c623c187d8661200400b1a4": {
|
"ed1d5d9433bc7f4a360431ecfdd9430c5e58cd6d1c623c187d8661200400b1a4": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -6302,17 +6047,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n SELECT follower_id FROM mod_follows\n WHERE mod_id = $1\n "
|
"query": "\n SELECT follower_id FROM mod_follows\n WHERE mod_id = $1\n "
|
||||||
},
|
|
||||||
"fe42090b9d81a660c36500eb3c017c1794839d206550239fd1322019c042b3d9": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n DELETE FROM pats\n WHERE id = $1\n "
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,26 +1,33 @@
|
|||||||
use crate::database::models::{generate_state_id, StateId};
|
|
||||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::parse_strings_from_var;
|
|
||||||
|
|
||||||
use actix_web::web::{scope, Data, Query, ServiceConfig};
|
|
||||||
use actix_web::{get, HttpRequest, HttpResponse};
|
|
||||||
use chrono::Utc;
|
|
||||||
use reqwest::header::AUTHORIZATION;
|
|
||||||
use rust_decimal::Decimal;
|
|
||||||
|
|
||||||
use crate::auth::session::issue_session;
|
use crate::auth::session::issue_session;
|
||||||
use crate::auth::AuthenticationError;
|
use crate::auth::AuthenticationError;
|
||||||
|
use crate::database::models::{generate_state_id, StateId};
|
||||||
use crate::file_hosting::FileHost;
|
use crate::file_hosting::FileHost;
|
||||||
|
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||||
use crate::models::users::{Badges, Role};
|
use crate::models::users::{Badges, Role};
|
||||||
|
use crate::parse_strings_from_var;
|
||||||
|
use crate::routes::ApiError;
|
||||||
|
use crate::util::captcha::check_turnstile_captcha;
|
||||||
use crate::util::ext::{get_image_content_type, get_image_ext};
|
use crate::util::ext::{get_image_content_type, get_image_ext};
|
||||||
|
use crate::util::validate::{validation_errors_to_string, RE_URL_SAFE};
|
||||||
|
use actix_web::web::{scope, Data, Query, ServiceConfig};
|
||||||
|
use actix_web::{get, post, web, HttpRequest, HttpResponse};
|
||||||
|
use argon2::password_hash::SaltString;
|
||||||
|
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||||
|
use chrono::Utc;
|
||||||
|
use rand_chacha::rand_core::SeedableRng;
|
||||||
|
use rand_chacha::ChaCha20Rng;
|
||||||
|
use reqwest::header::AUTHORIZATION;
|
||||||
|
use rust_decimal::Decimal;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
pub fn config(cfg: &mut ServiceConfig) {
|
pub fn config(cfg: &mut ServiceConfig) {
|
||||||
cfg.service(scope("auth").service(auth_callback).service(init));
|
cfg.service(scope("auth").service(auth_callback).service(init))
|
||||||
|
.service(create_account_with_password)
|
||||||
|
.service(login_password);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Default, Eq, PartialEq)]
|
||||||
@ -849,9 +856,11 @@ pub async fn auth_callback(
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
password: None,
|
||||||
username,
|
username,
|
||||||
name: oauth_user.name,
|
name: oauth_user.name,
|
||||||
email: oauth_user.email,
|
email: oauth_user.email,
|
||||||
|
email_verified: true,
|
||||||
avatar_url,
|
avatar_url,
|
||||||
bio: oauth_user.bio,
|
bio: oauth_user.bio,
|
||||||
created: Utc::now(),
|
created: Utc::now(),
|
||||||
@ -887,3 +896,155 @@ pub async fn auth_callback(
|
|||||||
Err(AuthenticationError::InvalidCredentials)
|
Err(AuthenticationError::InvalidCredentials)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Validate)]
|
||||||
|
pub struct NewAccount {
|
||||||
|
#[validate(length(min = 1, max = 39), regex = "RE_URL_SAFE")]
|
||||||
|
pub username: String,
|
||||||
|
#[validate(length(min = 8, max = 256))]
|
||||||
|
pub password: String,
|
||||||
|
#[validate(email)]
|
||||||
|
pub email: String,
|
||||||
|
pub challenge: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("create")]
|
||||||
|
pub async fn create_account_with_password(
|
||||||
|
req: HttpRequest,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
new_account: web::Json<NewAccount>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
new_account
|
||||||
|
.0
|
||||||
|
.validate()
|
||||||
|
.map_err(|err| ApiError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
|
if check_turnstile_captcha(&req, &new_account.challenge).await? {
|
||||||
|
return Err(ApiError::Turnstile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if crate::database::models::User::get(&new_account.username, &**pool, &redis)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(ApiError::InvalidInput("Username is taken!".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
let user_id = crate::database::models::generate_user_id(&mut transaction).await?;
|
||||||
|
|
||||||
|
let new_account = new_account.0;
|
||||||
|
|
||||||
|
let score = zxcvbn::zxcvbn(
|
||||||
|
&new_account.password,
|
||||||
|
&[&new_account.username, &new_account.email],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if score.score() < 3 {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
if let Some(feedback) = score.feedback().clone().and_then(|x| x.warning()) {
|
||||||
|
format!("Password too weak: {}", feedback)
|
||||||
|
} else {
|
||||||
|
"Specified password is too weak! Please improve its strength.".to_string()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasher = Argon2::default();
|
||||||
|
let salt = SaltString::generate(&mut ChaCha20Rng::from_entropy());
|
||||||
|
let password_hash = hasher
|
||||||
|
.hash_password(new_account.password.as_bytes(), &salt)?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
crate::database::models::User {
|
||||||
|
id: user_id,
|
||||||
|
github_id: None,
|
||||||
|
discord_id: None,
|
||||||
|
gitlab_id: None,
|
||||||
|
google_id: None,
|
||||||
|
steam_id: None,
|
||||||
|
microsoft_id: None,
|
||||||
|
password: Some(password_hash),
|
||||||
|
username: new_account.username.clone(),
|
||||||
|
name: Some(new_account.username),
|
||||||
|
email: Some(new_account.email),
|
||||||
|
email_verified: false,
|
||||||
|
avatar_url: None,
|
||||||
|
bio: None,
|
||||||
|
created: Utc::now(),
|
||||||
|
role: Role::Developer.to_string(),
|
||||||
|
badges: Badges::default(),
|
||||||
|
balance: Decimal::ZERO,
|
||||||
|
payout_wallet: None,
|
||||||
|
payout_wallet_type: None,
|
||||||
|
payout_address: None,
|
||||||
|
}
|
||||||
|
.insert(&mut transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let session = issue_session(req, user_id, &mut transaction, &redis).await?;
|
||||||
|
let res = crate::models::sessions::Session::from(session, true);
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Validate)]
|
||||||
|
pub struct Login {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
pub challenge: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("login")]
|
||||||
|
pub async fn login_password(
|
||||||
|
req: HttpRequest,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
login: web::Json<Login>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
if check_turnstile_captcha(&req, &login.challenge).await? {
|
||||||
|
return Err(ApiError::Turnstile);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (user_id, password) = if let Some(user) =
|
||||||
|
crate::database::models::User::get(&login.username, &**pool, &redis).await?
|
||||||
|
{
|
||||||
|
(
|
||||||
|
user.id,
|
||||||
|
user.password
|
||||||
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let user_pass = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT id, password FROM users
|
||||||
|
WHERE email = $1
|
||||||
|
",
|
||||||
|
login.username
|
||||||
|
)
|
||||||
|
.fetch_one(&**pool)
|
||||||
|
.await
|
||||||
|
.map_err(|_| AuthenticationError::InvalidCredentials)?;
|
||||||
|
|
||||||
|
(
|
||||||
|
crate::database::models::UserId(user_pass.id),
|
||||||
|
user_pass
|
||||||
|
.password
|
||||||
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let hasher = Argon2::default();
|
||||||
|
hasher
|
||||||
|
.verify_password(login.password.as_bytes(), &PasswordHash::new(&password)?)
|
||||||
|
.map_err(|_| AuthenticationError::InvalidCredentials)?;
|
||||||
|
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
let session = issue_session(req, user_id, &mut transaction, &redis).await?;
|
||||||
|
let res = crate::models::sessions::Session::from(session, true);
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(res))
|
||||||
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
pub mod checks;
|
pub mod checks;
|
||||||
pub mod flows;
|
pub mod flows;
|
||||||
pub mod pat;
|
pub mod pat;
|
||||||
mod session;
|
pub mod session;
|
||||||
pub mod validate;
|
pub mod validate;
|
||||||
|
|
||||||
pub use checks::{
|
pub use checks::{
|
||||||
filter_authorized_projects, filter_authorized_versions, is_authorized, is_authorized_version,
|
filter_authorized_projects, filter_authorized_versions, is_authorized, is_authorized_version,
|
||||||
};
|
};
|
||||||
pub use flows::config;
|
// pub use pat::{generate_pat, PersonalAccessToken};
|
||||||
pub use pat::{generate_pat, get_user_from_pat, PersonalAccessToken};
|
|
||||||
pub use validate::{check_is_moderator_from_headers, get_user_from_headers};
|
pub use validate::{check_is_moderator_from_headers, get_user_from_headers};
|
||||||
|
|
||||||
use crate::file_hosting::FileHostingError;
|
use crate::file_hosting::FileHostingError;
|
||||||
@ -29,6 +28,8 @@ pub enum AuthenticationError {
|
|||||||
SerDe(#[from] serde_json::Error),
|
SerDe(#[from] serde_json::Error),
|
||||||
#[error("Error while communicating to external oauth provider")]
|
#[error("Error while communicating to external oauth provider")]
|
||||||
Reqwest(#[from] reqwest::Error),
|
Reqwest(#[from] reqwest::Error),
|
||||||
|
#[error("Error uploading user profile picture")]
|
||||||
|
FileHosting(#[from] FileHostingError),
|
||||||
#[error("Error while decoding PAT: {0}")]
|
#[error("Error while decoding PAT: {0}")]
|
||||||
Decoding(#[from] crate::models::ids::DecodingError),
|
Decoding(#[from] crate::models::ids::DecodingError),
|
||||||
#[error("Invalid Authentication Credentials")]
|
#[error("Invalid Authentication Credentials")]
|
||||||
@ -39,8 +40,6 @@ pub enum AuthenticationError {
|
|||||||
InvalidClientId,
|
InvalidClientId,
|
||||||
#[error("Invalid callback URL specified")]
|
#[error("Invalid callback URL specified")]
|
||||||
Url,
|
Url,
|
||||||
#[error("Error uploading user profile picture")]
|
|
||||||
FileHosting(#[from] FileHostingError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl actix_web::ResponseError for AuthenticationError {
|
impl actix_web::ResponseError for AuthenticationError {
|
||||||
|
|||||||
230
src/auth/pat.rs
230
src/auth/pat.rs
@ -1,115 +1,115 @@
|
|||||||
use crate::auth::AuthenticationError;
|
// use crate::auth::AuthenticationError;
|
||||||
use crate::database;
|
// use crate::database;
|
||||||
use crate::database::models::{DatabaseError, UserId};
|
// use crate::database::models::{DatabaseError, UserId};
|
||||||
use crate::models::users::{self, Badges, RecipientType, RecipientWallet};
|
// use crate::models::users::{self, Badges, RecipientType, RecipientWallet};
|
||||||
use censor::Censor;
|
// use censor::Censor;
|
||||||
use chrono::{NaiveDateTime, Utc};
|
// use chrono::{NaiveDateTime, Utc};
|
||||||
use rand::Rng;
|
// use rand::Rng;
|
||||||
use serde::{Deserialize, Serialize};
|
// use serde::{Deserialize, Serialize};
|
||||||
|
//
|
||||||
#[derive(Serialize, Deserialize)]
|
// #[derive(Serialize, Deserialize)]
|
||||||
pub struct PersonalAccessToken {
|
// pub struct PersonalAccessToken {
|
||||||
pub id: String,
|
// pub id: String,
|
||||||
pub name: Option<String>,
|
// pub name: Option<String>,
|
||||||
pub access_token: Option<String>,
|
// pub access_token: Option<String>,
|
||||||
pub scope: i64,
|
// pub scope: i64,
|
||||||
pub user_id: users::UserId,
|
// pub user_id: users::UserId,
|
||||||
pub expires_at: NaiveDateTime,
|
// pub expires_at: NaiveDateTime,
|
||||||
}
|
// }
|
||||||
// Find database user from PAT token
|
// // Find database user from PAT token
|
||||||
// Separate to user_items as it may yet include further behaviour.
|
// // Separate to user_items as it may yet include further behaviour.
|
||||||
pub async fn get_user_from_pat<'a, E>(
|
// pub async fn get_user_from_pat<'a, E>(
|
||||||
access_token: &str,
|
// access_token: &str,
|
||||||
executor: E,
|
// executor: E,
|
||||||
) -> Result<Option<database::models::User>, AuthenticationError>
|
// ) -> Result<Option<database::models::User>, AuthenticationError>
|
||||||
where
|
// where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
// E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
// {
|
||||||
let row = sqlx::query!(
|
// let row = sqlx::query!(
|
||||||
"
|
// "
|
||||||
SELECT pats.expires_at,
|
// SELECT pats.expires_at,
|
||||||
u.id, u.name, u.email,
|
// u.id, u.name, u.email,
|
||||||
u.avatar_url, u.username, u.bio,
|
// u.avatar_url, u.username, u.bio,
|
||||||
u.created, u.role, u.badges,
|
// u.created, u.role, u.badges,
|
||||||
u.balance, u.payout_wallet, u.payout_wallet_type, u.payout_address,
|
// u.balance, u.payout_wallet, u.payout_wallet_type, u.payout_address,
|
||||||
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id
|
// github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id
|
||||||
FROM pats LEFT OUTER JOIN users u ON pats.user_id = u.id
|
// FROM pats LEFT OUTER JOIN users u ON pats.user_id = u.id
|
||||||
WHERE access_token = $1
|
// WHERE access_token = $1
|
||||||
",
|
// ",
|
||||||
access_token
|
// access_token
|
||||||
)
|
// )
|
||||||
.fetch_optional(executor)
|
// .fetch_optional(executor)
|
||||||
.await?;
|
// .await?;
|
||||||
if let Some(row) = row {
|
// if let Some(row) = row {
|
||||||
if row.expires_at < Utc::now().naive_utc() {
|
// if row.expires_at < Utc::now().naive_utc() {
|
||||||
return Ok(None);
|
// return Ok(None);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return Ok(Some(database::models::User {
|
// return Ok(Some(database::models::User {
|
||||||
id: UserId(row.id),
|
// id: UserId(row.id),
|
||||||
name: row.name,
|
// name: row.name,
|
||||||
github_id: row.github_id,
|
// github_id: row.github_id,
|
||||||
discord_id: row.discord_id,
|
// discord_id: row.discord_id,
|
||||||
gitlab_id: row.gitlab_id,
|
// gitlab_id: row.gitlab_id,
|
||||||
google_id: row.google_id,
|
// google_id: row.google_id,
|
||||||
steam_id: row.steam_id,
|
// steam_id: row.steam_id,
|
||||||
microsoft_id: row.microsoft_id,
|
// microsoft_id: row.microsoft_id,
|
||||||
email: row.email,
|
// email: row.email,
|
||||||
avatar_url: row.avatar_url,
|
// avatar_url: row.avatar_url,
|
||||||
username: row.username,
|
// username: row.username,
|
||||||
bio: row.bio,
|
// bio: row.bio,
|
||||||
created: row.created,
|
// created: row.created,
|
||||||
role: row.role,
|
// role: row.role,
|
||||||
badges: Badges::from_bits(row.badges as u64).unwrap_or_default(),
|
// badges: Badges::from_bits(row.badges as u64).unwrap_or_default(),
|
||||||
balance: row.balance,
|
// balance: row.balance,
|
||||||
payout_wallet: row.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
|
// payout_wallet: row.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
|
||||||
payout_wallet_type: row
|
// payout_wallet_type: row
|
||||||
.payout_wallet_type
|
// .payout_wallet_type
|
||||||
.map(|x| RecipientType::from_string(&x)),
|
// .map(|x| RecipientType::from_string(&x)),
|
||||||
payout_address: row.payout_address,
|
// payout_address: row.payout_address,
|
||||||
}));
|
// }));
|
||||||
}
|
// }
|
||||||
Ok(None)
|
// Ok(None)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Generate a new 128 char PAT token starting with 'modrinth_pat_'
|
// // Generate a new 128 char PAT token starting with 'modrinth_pat_'
|
||||||
pub async fn generate_pat(
|
// pub async fn generate_pat(
|
||||||
con: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
// con: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<String, DatabaseError> {
|
// ) -> Result<String, DatabaseError> {
|
||||||
let mut rng = rand::thread_rng();
|
// let mut rng = rand::thread_rng();
|
||||||
let mut retry_count = 0;
|
// let mut retry_count = 0;
|
||||||
let censor = Censor::Standard + Censor::Sex;
|
// let censor = Censor::Standard + Censor::Sex;
|
||||||
|
//
|
||||||
// First generate the PAT token as a random 128 char string. This may include uppercase and lowercase and numbers only.
|
// // First generate the PAT token as a random 128 char string. This may include uppercase and lowercase and numbers only.
|
||||||
loop {
|
// loop {
|
||||||
let mut access_token = String::with_capacity(63);
|
// let mut access_token = String::with_capacity(63);
|
||||||
access_token.push_str("modrinth_pat_");
|
// access_token.push_str("modrinth_pat_");
|
||||||
for _ in 0..51 {
|
// for _ in 0..51 {
|
||||||
let c = rng.gen_range(0..62);
|
// let c = rng.gen_range(0..62);
|
||||||
if c < 10 {
|
// if c < 10 {
|
||||||
access_token.push(char::from_u32(c + 48).unwrap()); // 0-9
|
// access_token.push(char::from_u32(c + 48).unwrap()); // 0-9
|
||||||
} else if c < 36 {
|
// } else if c < 36 {
|
||||||
access_token.push(char::from_u32(c + 55).unwrap()); // A-Z
|
// access_token.push(char::from_u32(c + 55).unwrap()); // A-Z
|
||||||
} else {
|
// } else {
|
||||||
access_token.push(char::from_u32(c + 61).unwrap()); // a-z
|
// access_token.push(char::from_u32(c + 61).unwrap()); // a-z
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
let results = sqlx::query!(
|
// let results = sqlx::query!(
|
||||||
"
|
// "
|
||||||
SELECT EXISTS(SELECT 1 FROM pats WHERE access_token=$1)
|
// SELECT EXISTS(SELECT 1 FROM pats WHERE access_token=$1)
|
||||||
",
|
// ",
|
||||||
access_token
|
// access_token
|
||||||
)
|
// )
|
||||||
.fetch_one(&mut *con)
|
// .fetch_one(&mut *con)
|
||||||
.await?;
|
// .await?;
|
||||||
|
//
|
||||||
if !results.exists.unwrap_or(true) && !censor.check(&access_token) {
|
// if !results.exists.unwrap_or(true) && !censor.check(&access_token) {
|
||||||
break Ok(access_token);
|
// break Ok(access_token);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
retry_count += 1;
|
// retry_count += 1;
|
||||||
if retry_count > 15 {
|
// if retry_count > 15 {
|
||||||
return Err(DatabaseError::RandomId);
|
// return Err(DatabaseError::RandomId);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -1,19 +1,43 @@
|
|||||||
use crate::auth::AuthenticationError;
|
use crate::auth::{get_user_from_headers, AuthenticationError};
|
||||||
use crate::database::models::session_item::{Session, SessionBuilder};
|
use crate::database::models::session_item::Session as DBSession;
|
||||||
|
use crate::database::models::session_item::SessionBuilder;
|
||||||
use crate::database::models::UserId;
|
use crate::database::models::UserId;
|
||||||
|
use crate::models::sessions::Session;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
|
use crate::routes::ApiError;
|
||||||
use crate::util::env::parse_var;
|
use crate::util::env::parse_var;
|
||||||
use actix_web::HttpRequest;
|
use actix_web::http::header::AUTHORIZATION;
|
||||||
|
use actix_web::web::{scope, Data, ServiceConfig};
|
||||||
|
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||||
|
use chrono::Utc;
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
|
use sqlx::PgPool;
|
||||||
use woothee::parser::Parser;
|
use woothee::parser::Parser;
|
||||||
|
|
||||||
pub async fn issue_session(
|
pub fn config(cfg: &mut ServiceConfig) {
|
||||||
req: HttpRequest,
|
cfg.service(
|
||||||
user_id: UserId,
|
scope("session")
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
.service(list)
|
||||||
redis: &deadpool_redis::Pool,
|
.service(delete)
|
||||||
) -> Result<Session, AuthenticationError> {
|
.service(refresh),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SessionMetadata {
|
||||||
|
pub city: Option<String>,
|
||||||
|
pub country: Option<String>,
|
||||||
|
pub ip: String,
|
||||||
|
|
||||||
|
pub os: Option<String>,
|
||||||
|
pub platform: Option<String>,
|
||||||
|
pub user_agent: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_session_metadata(
|
||||||
|
req: &HttpRequest,
|
||||||
|
) -> Result<SessionMetadata, AuthenticationError> {
|
||||||
let conn_info = req.connection_info().clone();
|
let conn_info = req.connection_info().clone();
|
||||||
let ip_addr = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
let ip_addr = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
||||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||||
@ -45,6 +69,26 @@ pub async fn issue_session(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Ok(SessionMetadata {
|
||||||
|
os: os.map(|x| x.0.to_string()),
|
||||||
|
platform: os.map(|x| x.1.to_string()),
|
||||||
|
city: city.map(|x| x.to_string()),
|
||||||
|
country: country.map(|x| x.to_string()),
|
||||||
|
ip: ip_addr
|
||||||
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?
|
||||||
|
.to_string(),
|
||||||
|
user_agent: user_agent.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn issue_session(
|
||||||
|
req: HttpRequest,
|
||||||
|
user_id: UserId,
|
||||||
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
redis: &deadpool_redis::Pool,
|
||||||
|
) -> Result<DBSession, AuthenticationError> {
|
||||||
|
let metadata = get_session_metadata(&req).await?;
|
||||||
|
|
||||||
let session = ChaCha20Rng::from_entropy()
|
let session = ChaCha20Rng::from_entropy()
|
||||||
.sample_iter(&Alphanumeric)
|
.sample_iter(&Alphanumeric)
|
||||||
.take(60)
|
.take(60)
|
||||||
@ -56,25 +100,118 @@ pub async fn issue_session(
|
|||||||
let id = SessionBuilder {
|
let id = SessionBuilder {
|
||||||
session,
|
session,
|
||||||
user_id,
|
user_id,
|
||||||
os: os.map(|x| x.0.to_string()),
|
os: metadata.os,
|
||||||
platform: os.map(|x| x.1.to_string()),
|
platform: metadata.platform,
|
||||||
city: city.map(|x| x.to_string()),
|
city: metadata.city,
|
||||||
country: country.map(|x| x.to_string()),
|
country: metadata.country,
|
||||||
ip: ip_addr
|
ip: metadata.ip,
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?
|
user_agent: metadata.user_agent,
|
||||||
.to_string(),
|
|
||||||
user_agent: user_agent.to_string(),
|
|
||||||
}
|
}
|
||||||
.insert(transaction)
|
.insert(transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let session = Session::get_id(id, &mut *transaction, redis)
|
let session = DBSession::get_id(id, &mut *transaction, redis)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
|
|
||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: List user sessions route
|
#[get("list")]
|
||||||
// TODO: Delete User Session Route / logout
|
pub async fn list(
|
||||||
// TODO: Refresh session route
|
req: HttpRequest,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: Data<SessionQueue>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
|
let session_ids = DBSession::get_user_sessions(current_user.id.into(), &**pool, &redis).await?;
|
||||||
|
let sessions = DBSession::get_many_ids(&session_ids, &**pool, &redis)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|x| x.expires > Utc::now())
|
||||||
|
.map(|x| Session::from(x, false))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(sessions))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("{id}")]
|
||||||
|
pub async fn delete(
|
||||||
|
info: web::Path<(String,)>,
|
||||||
|
req: HttpRequest,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: Data<SessionQueue>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
|
let session = DBSession::get(info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
|
if let Some(session) = session {
|
||||||
|
if session.user_id != current_user.id.into() {
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
DBSession::remove(session.id, &mut transaction).await?;
|
||||||
|
DBSession::clear_cache(
|
||||||
|
vec![(
|
||||||
|
Some(session.id),
|
||||||
|
Some(session.session),
|
||||||
|
Some(session.user_id),
|
||||||
|
)],
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
transaction.commit().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("refresh")]
|
||||||
|
pub async fn refresh(
|
||||||
|
req: HttpRequest,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
redis: Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: Data<SessionQueue>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
let session = req
|
||||||
|
.headers()
|
||||||
|
.get(AUTHORIZATION)
|
||||||
|
.and_then(|x| x.to_str().ok())
|
||||||
|
.ok_or_else(|| ApiError::Authentication(AuthenticationError::InvalidCredentials))?;
|
||||||
|
|
||||||
|
let session = DBSession::get(session, &**pool, &redis).await?;
|
||||||
|
|
||||||
|
if let Some(session) = session {
|
||||||
|
if current_user.id != session.user_id.into() || session.refresh_expires < Utc::now() {
|
||||||
|
return Err(ApiError::Authentication(
|
||||||
|
AuthenticationError::InvalidCredentials,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
|
DBSession::remove(session.id, &mut transaction).await?;
|
||||||
|
let new_session = issue_session(req, session.user_id, &mut transaction, &redis).await?;
|
||||||
|
DBSession::clear_cache(
|
||||||
|
vec![(
|
||||||
|
Some(session.id),
|
||||||
|
Some(session.session),
|
||||||
|
Some(session.user_id),
|
||||||
|
)],
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(Session::from(new_session, true)))
|
||||||
|
} else {
|
||||||
|
Err(ApiError::Authentication(
|
||||||
|
AuthenticationError::InvalidCredentials,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,29 +1,35 @@
|
|||||||
use crate::auth::flows::AuthProvider;
|
use crate::auth::flows::AuthProvider;
|
||||||
use crate::auth::get_user_from_pat;
|
use crate::auth::session::get_session_metadata;
|
||||||
use crate::auth::AuthenticationError;
|
use crate::auth::AuthenticationError;
|
||||||
use crate::database::models::user_item;
|
use crate::database::models::user_item;
|
||||||
use crate::models::users::{Role, User, UserId, UserPayoutData};
|
use crate::models::users::{Role, User, UserId, UserPayoutData};
|
||||||
use actix_web::http::header::HeaderMap;
|
use crate::queue::session::SessionQueue;
|
||||||
|
use actix_web::HttpRequest;
|
||||||
|
use chrono::Utc;
|
||||||
use reqwest::header::{HeaderValue, AUTHORIZATION};
|
use reqwest::header::{HeaderValue, AUTHORIZATION};
|
||||||
|
|
||||||
pub async fn get_user_from_headers<'a, E>(
|
pub async fn get_user_from_headers<'a, E>(
|
||||||
headers: &HeaderMap,
|
req: &HttpRequest,
|
||||||
executor: E,
|
executor: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
|
session_queue: &SessionQueue,
|
||||||
) -> Result<User, AuthenticationError>
|
) -> Result<User, AuthenticationError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
|
let headers = req.headers();
|
||||||
let token: Option<&HeaderValue> = headers.get(AUTHORIZATION);
|
let token: Option<&HeaderValue> = headers.get(AUTHORIZATION);
|
||||||
|
|
||||||
// Fetch DB user record and minos user from headers
|
// Fetch DB user record and minos user from headers
|
||||||
let db_user = get_user_record_from_bearer_token(
|
let db_user = get_user_record_from_bearer_token(
|
||||||
|
req,
|
||||||
token
|
token
|
||||||
.ok_or_else(|| AuthenticationError::InvalidAuthMethod)?
|
.ok_or_else(|| AuthenticationError::InvalidAuthMethod)?
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(|_| AuthenticationError::InvalidCredentials)?,
|
.map_err(|_| AuthenticationError::InvalidCredentials)?,
|
||||||
executor,
|
executor,
|
||||||
redis,
|
redis,
|
||||||
|
session_queue,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
@ -55,24 +61,33 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
||||||
|
req: &HttpRequest,
|
||||||
token: &str,
|
token: &str,
|
||||||
executor: E,
|
executor: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
|
session_queue: &SessionQueue,
|
||||||
) -> Result<Option<user_item::User>, AuthenticationError>
|
) -> Result<Option<user_item::User>, AuthenticationError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let token: &str = token.trim_start_matches("Bearer ");
|
|
||||||
|
|
||||||
let possible_user = match token.split_once('_') {
|
let possible_user = match token.split_once('_') {
|
||||||
Some(("modrinth", _)) => get_user_from_pat(token, executor).await?,
|
//Some(("modrinth", _)) => get_user_from_pat(token, executor).await?,
|
||||||
Some(("mra", _)) => {
|
Some(("mra", _)) => {
|
||||||
let session =
|
let session =
|
||||||
crate::database::models::session_item::Session::get(token, executor, redis)
|
crate::database::models::session_item::Session::get(token, executor, redis)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||||
|
|
||||||
user_item::User::get_id(session.user_id, executor, redis).await?
|
if session.expires < Utc::now() {
|
||||||
|
return Err(AuthenticationError::InvalidCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = user_item::User::get_id(session.user_id, executor, redis).await?;
|
||||||
|
|
||||||
|
let metadata = get_session_metadata(req).await?;
|
||||||
|
session_queue.add(session.id, metadata).await;
|
||||||
|
|
||||||
|
user
|
||||||
}
|
}
|
||||||
Some(("github", _)) | Some(("gho", _)) | Some(("ghp", _)) => {
|
Some(("github", _)) | Some(("gho", _)) | Some(("ghp", _)) => {
|
||||||
let user = AuthProvider::GitHub.get_user(token).await?;
|
let user = AuthProvider::GitHub.get_user(token).await?;
|
||||||
@ -91,14 +106,15 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_is_moderator_from_headers<'a, 'b, E>(
|
pub async fn check_is_moderator_from_headers<'a, 'b, E>(
|
||||||
headers: &HeaderMap,
|
req: &HttpRequest,
|
||||||
executor: E,
|
executor: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
|
session_queue: &SessionQueue,
|
||||||
) -> Result<User, AuthenticationError>
|
) -> Result<User, AuthenticationError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let user = get_user_from_headers(headers, executor, redis).await?;
|
let user = get_user_from_headers(req, executor, redis, session_queue).await?;
|
||||||
|
|
||||||
if user.role.is_mod() {
|
if user.role.is_mod() {
|
||||||
Ok(user)
|
Ok(user)
|
||||||
|
|||||||
@ -83,13 +83,13 @@ generate_ids!(
|
|||||||
"SELECT EXISTS(SELECT 1 FROM states WHERE id=$1)",
|
"SELECT EXISTS(SELECT 1 FROM states WHERE id=$1)",
|
||||||
StateId
|
StateId
|
||||||
);
|
);
|
||||||
generate_ids!(
|
// generate_ids!(
|
||||||
pub generate_pat_id,
|
// pub generate_pat_id,
|
||||||
PatId,
|
// PatId,
|
||||||
8,
|
// 8,
|
||||||
"SELECT EXISTS(SELECT 1 FROM pats WHERE id=$1)",
|
// "SELECT EXISTS(SELECT 1 FROM pats WHERE id=$1)",
|
||||||
PatId
|
// PatId
|
||||||
);
|
// );
|
||||||
|
|
||||||
generate_ids!(
|
generate_ids!(
|
||||||
pub generate_user_id,
|
pub generate_user_id,
|
||||||
|
|||||||
@ -10,8 +10,6 @@ const SESSIONS_IDS_NAMESPACE: &str = "sessions_ids";
|
|||||||
const SESSIONS_USERS_NAMESPACE: &str = "sessions_users";
|
const SESSIONS_USERS_NAMESPACE: &str = "sessions_users";
|
||||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||||
|
|
||||||
// TODO: Manage sessions cache + clear cache when needed
|
|
||||||
|
|
||||||
pub struct SessionBuilder {
|
pub struct SessionBuilder {
|
||||||
pub session: String,
|
pub session: String,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
@ -293,10 +291,33 @@ impl Session {
|
|||||||
Ok(db_sessions)
|
Ok(db_sessions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn clear_cache(
|
||||||
|
clear_sessions: Vec<(Option<SessionId>, Option<String>, Option<UserId>)>,
|
||||||
|
redis: &deadpool_redis::Pool,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
let mut redis = redis.get().await?;
|
||||||
|
let mut cmd = cmd("DEL");
|
||||||
|
|
||||||
|
for (id, session, user_id) in clear_sessions {
|
||||||
|
if let Some(id) = id {
|
||||||
|
cmd.arg(format!("{}:{}", SESSIONS_NAMESPACE, id.0));
|
||||||
|
}
|
||||||
|
if let Some(session) = session {
|
||||||
|
cmd.arg(format!("{}:{}", SESSIONS_IDS_NAMESPACE, session));
|
||||||
|
}
|
||||||
|
if let Some(user_id) = user_id {
|
||||||
|
cmd.arg(format!("{}:{}", SESSIONS_USERS_NAMESPACE, user_id.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.query_async::<_, ()>(&mut redis).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn remove(
|
pub async fn remove(
|
||||||
id: SessionId,
|
id: SessionId,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
// redis: &deadpool_redis::Pool,
|
|
||||||
) -> Result<Option<()>, sqlx::error::Error> {
|
) -> Result<Option<()>, sqlx::error::Error> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
use super::ids::*;
|
use super::ids::*;
|
||||||
use crate::database::models::User;
|
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::models::users::Badges;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use redis::cmd;
|
use redis::cmd;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
@ -83,6 +81,7 @@ pub struct Team {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A member of a team
|
/// A member of a team
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct TeamMember {
|
pub struct TeamMember {
|
||||||
pub id: TeamMemberId,
|
pub id: TeamMemberId,
|
||||||
pub team_id: TeamId,
|
pub team_id: TeamId,
|
||||||
@ -95,27 +94,13 @@ pub struct TeamMember {
|
|||||||
pub ordering: i64,
|
pub ordering: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A member of a team
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct QueryTeamMember {
|
|
||||||
pub id: TeamMemberId,
|
|
||||||
pub team_id: TeamId,
|
|
||||||
/// The user associated with the member
|
|
||||||
pub user: User,
|
|
||||||
pub role: String,
|
|
||||||
pub permissions: Permissions,
|
|
||||||
pub accepted: bool,
|
|
||||||
pub payouts_split: Decimal,
|
|
||||||
pub ordering: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TeamMember {
|
impl TeamMember {
|
||||||
// Lists the full members of a team
|
// Lists the full members of a team
|
||||||
pub async fn get_from_team_full<'a, 'b, E>(
|
pub async fn get_from_team_full<'a, 'b, E>(
|
||||||
id: TeamId,
|
id: TeamId,
|
||||||
executor: E,
|
executor: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
) -> Result<Vec<QueryTeamMember>, super::DatabaseError>
|
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
@ -126,7 +111,7 @@ impl TeamMember {
|
|||||||
team_ids: &[TeamId],
|
team_ids: &[TeamId],
|
||||||
exec: E,
|
exec: E,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
) -> Result<Vec<QueryTeamMember>, super::DatabaseError>
|
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
@ -155,7 +140,7 @@ impl TeamMember {
|
|||||||
for team_raw in teams {
|
for team_raw in teams {
|
||||||
if let Some(mut team) = team_raw
|
if let Some(mut team) = team_raw
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|x| serde_json::from_str::<Vec<QueryTeamMember>>(&x).ok())
|
.and_then(|x| serde_json::from_str::<Vec<TeamMember>>(&x).ok())
|
||||||
{
|
{
|
||||||
if let Some(team_id) = team.first().map(|x| x.team_id) {
|
if let Some(team_id) = team.first().map(|x| x.team_id) {
|
||||||
team_ids_parsed.retain(|x| &team_id.0 != x);
|
team_ids_parsed.retain(|x| &team_id.0 != x);
|
||||||
@ -167,14 +152,11 @@ impl TeamMember {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !team_ids_parsed.is_empty() {
|
if !team_ids_parsed.is_empty() {
|
||||||
let teams: Vec<QueryTeamMember> = sqlx::query!(
|
let teams: Vec<TeamMember> = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split, tm.ordering,
|
SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split, tm.ordering,
|
||||||
u.id user_id, u.name user_name,
|
tm.user_id user_id
|
||||||
u.avatar_url avatar_url, u.username username, u.bio bio,
|
|
||||||
u.created created, u.role user_role, u.badges badges
|
|
||||||
FROM team_members tm
|
FROM team_members tm
|
||||||
INNER JOIN users u ON u.id = tm.user_id
|
|
||||||
WHERE tm.team_id = ANY($1)
|
WHERE tm.team_id = ANY($1)
|
||||||
ORDER BY tm.team_id, tm.ordering
|
ORDER BY tm.team_id, tm.ordering
|
||||||
",
|
",
|
||||||
@ -183,39 +165,19 @@ impl TeamMember {
|
|||||||
.fetch_many(exec)
|
.fetch_many(exec)
|
||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
Ok(e.right().map(|m|
|
Ok(e.right().map(|m|
|
||||||
QueryTeamMember {
|
TeamMember {
|
||||||
id: TeamMemberId(m.id),
|
id: TeamMemberId(m.id),
|
||||||
team_id: TeamId(m.team_id),
|
team_id: TeamId(m.team_id),
|
||||||
role: m.member_role,
|
role: m.member_role,
|
||||||
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
|
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
|
||||||
accepted: m.accepted,
|
accepted: m.accepted,
|
||||||
user: User {
|
user_id: UserId(m.user_id),
|
||||||
id: UserId(m.user_id),
|
|
||||||
github_id: None,
|
|
||||||
discord_id: None,
|
|
||||||
gitlab_id: None,
|
|
||||||
google_id: None,
|
|
||||||
steam_id: None,
|
|
||||||
name: m.user_name,
|
|
||||||
email: None,
|
|
||||||
avatar_url: m.avatar_url,
|
|
||||||
username: m.username,
|
|
||||||
bio: m.bio,
|
|
||||||
created: m.created,
|
|
||||||
role: m.user_role,
|
|
||||||
badges: Badges::from_bits(m.badges as u64).unwrap_or_default(),
|
|
||||||
balance: Decimal::ZERO,
|
|
||||||
payout_wallet: None,
|
|
||||||
payout_wallet_type: None,
|
|
||||||
payout_address: None,
|
|
||||||
microsoft_id: None,
|
|
||||||
},
|
|
||||||
payouts_split: m.payouts_split,
|
payouts_split: m.payouts_split,
|
||||||
ordering: m.ordering,
|
ordering: m.ordering,
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.try_collect::<Vec<QueryTeamMember>>()
|
.try_collect::<Vec<TeamMember>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for (id, members) in &teams.into_iter().group_by(|x| x.team_id) {
|
for (id, members) in &teams.into_iter().group_by(|x| x.team_id) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const USER_USERNAMES_NAMESPACE: &str = "users_usernames";
|
|||||||
// const USERS_PROJECTS_NAMESPACE: &str = "users_projects";
|
// const USERS_PROJECTS_NAMESPACE: &str = "users_projects";
|
||||||
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: UserId,
|
pub id: UserId,
|
||||||
|
|
||||||
@ -22,10 +22,12 @@ pub struct User {
|
|||||||
pub google_id: Option<String>,
|
pub google_id: Option<String>,
|
||||||
pub steam_id: Option<i64>,
|
pub steam_id: Option<i64>,
|
||||||
pub microsoft_id: Option<String>,
|
pub microsoft_id: Option<String>,
|
||||||
|
pub password: Option<String>,
|
||||||
|
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
|
pub email_verified: bool,
|
||||||
pub avatar_url: Option<String>,
|
pub avatar_url: Option<String>,
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
@ -47,12 +49,14 @@ impl User {
|
|||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
id, username, name, email,
|
id, username, name, email,
|
||||||
avatar_url, bio, created,
|
avatar_url, bio, created,
|
||||||
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id
|
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,
|
||||||
|
email_verified, password
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5,
|
||||||
$6, $7,
|
$6, $7,
|
||||||
$8, $9, $10, $11, $12, $13
|
$8, $9, $10, $11, $12, $13,
|
||||||
|
$14, $15
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.id as UserId,
|
self.id as UserId,
|
||||||
@ -68,6 +72,8 @@ impl User {
|
|||||||
self.google_id,
|
self.google_id,
|
||||||
self.steam_id,
|
self.steam_id,
|
||||||
self.microsoft_id,
|
self.microsoft_id,
|
||||||
|
self.email_verified,
|
||||||
|
self.password,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -197,9 +203,10 @@ impl User {
|
|||||||
avatar_url, username, bio,
|
avatar_url, username, bio,
|
||||||
created, role, badges,
|
created, role, badges,
|
||||||
balance, payout_wallet, payout_wallet_type, payout_address,
|
balance, payout_wallet, payout_wallet_type, payout_address,
|
||||||
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id
|
github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,
|
||||||
|
email_verified, password
|
||||||
FROM users
|
FROM users
|
||||||
WHERE id = ANY($1) OR username = ANY($2)
|
WHERE id = ANY($1) OR LOWER(username) = ANY($2)
|
||||||
",
|
",
|
||||||
&user_ids_parsed,
|
&user_ids_parsed,
|
||||||
&remaining_strings
|
&remaining_strings
|
||||||
@ -219,6 +226,7 @@ impl User {
|
|||||||
microsoft_id: u.microsoft_id,
|
microsoft_id: u.microsoft_id,
|
||||||
name: u.name,
|
name: u.name,
|
||||||
email: u.email,
|
email: u.email,
|
||||||
|
email_verified: u.email_verified,
|
||||||
avatar_url: u.avatar_url,
|
avatar_url: u.avatar_url,
|
||||||
username: u.username,
|
username: u.username,
|
||||||
bio: u.bio,
|
bio: u.bio,
|
||||||
@ -231,6 +239,7 @@ impl User {
|
|||||||
.payout_wallet_type
|
.payout_wallet_type
|
||||||
.map(|x| RecipientType::from_string(&x)),
|
.map(|x| RecipientType::from_string(&x)),
|
||||||
payout_address: u.payout_address,
|
payout_address: u.payout_address,
|
||||||
|
password: u.password,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.try_collect::<Vec<User>>()
|
.try_collect::<Vec<User>>()
|
||||||
|
|||||||
42
src/main.rs
42
src/main.rs
@ -1,6 +1,7 @@
|
|||||||
use crate::file_hosting::S3Host;
|
use crate::file_hosting::S3Host;
|
||||||
use crate::queue::download::DownloadQueue;
|
use crate::queue::download::DownloadQueue;
|
||||||
use crate::queue::payouts::PayoutsQueue;
|
use crate::queue::payouts::PayoutsQueue;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::ratelimit::errors::ARError;
|
use crate::ratelimit::errors::ARError;
|
||||||
use crate::ratelimit::memory::{MemoryStore, MemoryStoreActor};
|
use crate::ratelimit::memory::{MemoryStore, MemoryStoreActor};
|
||||||
use crate::ratelimit::middleware::RateLimiter;
|
use crate::ratelimit::middleware::RateLimiter;
|
||||||
@ -269,7 +270,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
scheduler::schedule_versions(&mut scheduler, pool.clone());
|
scheduler::schedule_versions(&mut scheduler, pool.clone());
|
||||||
|
|
||||||
let download_queue = Arc::new(DownloadQueue::new());
|
let download_queue = web::Data::new(DownloadQueue::new());
|
||||||
|
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
let download_queue_ref = download_queue.clone();
|
let download_queue_ref = download_queue.clone();
|
||||||
@ -287,11 +288,31 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let session_queue = web::Data::new(SessionQueue::new());
|
||||||
|
|
||||||
|
let pool_ref = pool.clone();
|
||||||
|
let redis_ref = redis_pool.clone();
|
||||||
|
let session_queue_ref = session_queue.clone();
|
||||||
|
scheduler.run(std::time::Duration::from_secs(60), move || {
|
||||||
|
let pool_ref = pool_ref.clone();
|
||||||
|
let redis_ref = redis_ref.clone();
|
||||||
|
let session_queue_ref = session_queue_ref.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
info!("Indexing sessions queue");
|
||||||
|
let result = session_queue_ref.index(&pool_ref, &redis_ref).await;
|
||||||
|
if let Err(e) = result {
|
||||||
|
warn!("Indexing sessions queue failed: {:?}", e);
|
||||||
|
}
|
||||||
|
info!("Done indexing sessions queue");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let ip_salt = Pepper {
|
let ip_salt = Pepper {
|
||||||
pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(),
|
pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let payouts_queue = Arc::new(Mutex::new(PayoutsQueue::new()));
|
let payouts_queue = web::Data::new(Mutex::new(PayoutsQueue::new()));
|
||||||
|
|
||||||
let store = MemoryStore::new();
|
let store = MemoryStore::new();
|
||||||
|
|
||||||
@ -354,14 +375,14 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.app_data(web::Data::new(pool.clone()))
|
.app_data(web::Data::new(pool.clone()))
|
||||||
.app_data(web::Data::new(file_host.clone()))
|
.app_data(web::Data::new(file_host.clone()))
|
||||||
.app_data(web::Data::new(search_config.clone()))
|
.app_data(web::Data::new(search_config.clone()))
|
||||||
.app_data(web::Data::new(download_queue.clone()))
|
.app_data(download_queue.clone())
|
||||||
.app_data(web::Data::new(payouts_queue.clone()))
|
.app_data(session_queue.clone())
|
||||||
|
.app_data(payouts_queue.clone())
|
||||||
.app_data(web::Data::new(ip_salt.clone()))
|
.app_data(web::Data::new(ip_salt.clone()))
|
||||||
.wrap(sentry_actix::Sentry::new())
|
.wrap(sentry_actix::Sentry::new())
|
||||||
.configure(routes::root_config)
|
.configure(routes::root_config)
|
||||||
.configure(routes::v2::config)
|
.configure(routes::v2::config)
|
||||||
.configure(routes::v3::config)
|
.configure(routes::v3::config)
|
||||||
.configure(auth::config)
|
|
||||||
.default_service(web::get().to(routes::not_found))
|
.default_service(web::get().to(routes::not_found))
|
||||||
})
|
})
|
||||||
.bind(dotenvy::var("BIND_ADDR").unwrap())?
|
.bind(dotenvy::var("BIND_ADDR").unwrap())?
|
||||||
@ -441,6 +462,17 @@ fn check_env_vars() -> bool {
|
|||||||
|
|
||||||
failed |= check_var::<String>("GITHUB_CLIENT_ID");
|
failed |= check_var::<String>("GITHUB_CLIENT_ID");
|
||||||
failed |= check_var::<String>("GITHUB_CLIENT_SECRET");
|
failed |= check_var::<String>("GITHUB_CLIENT_SECRET");
|
||||||
|
failed |= check_var::<String>("GITLAB_CLIENT_ID");
|
||||||
|
failed |= check_var::<String>("GITLAB_CLIENT_SECRET");
|
||||||
|
failed |= check_var::<String>("DISCORD_CLIENT_ID");
|
||||||
|
failed |= check_var::<String>("DISCORD_CLIENT_SECRET");
|
||||||
|
failed |= check_var::<String>("MICROSOFT_CLIENT_ID");
|
||||||
|
failed |= check_var::<String>("MICROSOFT_CLIENT_SECRET");
|
||||||
|
failed |= check_var::<String>("GOOGLE_CLIENT_ID");
|
||||||
|
failed |= check_var::<String>("GOOGLE_CLIENT_SECRET");
|
||||||
|
failed |= check_var::<String>("STEAM_API_KEY");
|
||||||
|
|
||||||
|
failed |= check_var::<String>("TURNSTILE_SECRET");
|
||||||
|
|
||||||
failed |= check_var::<String>("ARIADNE_ADMIN_KEY");
|
failed |= check_var::<String>("ARIADNE_ADMIN_KEY");
|
||||||
failed |= check_var::<String>("ARIADNE_URL");
|
failed |= check_var::<String>("ARIADNE_URL");
|
||||||
|
|||||||
@ -11,7 +11,7 @@ pub struct SessionId(pub u64);
|
|||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub id: SessionId,
|
pub id: SessionId,
|
||||||
pub session: String,
|
pub session: Option<String>,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
|
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
@ -27,3 +27,30 @@ pub struct Session {
|
|||||||
pub country: Option<String>,
|
pub country: Option<String>,
|
||||||
pub ip: String,
|
pub ip: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
pub fn from(
|
||||||
|
data: crate::database::models::session_item::Session,
|
||||||
|
include_session: bool,
|
||||||
|
) -> Self {
|
||||||
|
Session {
|
||||||
|
id: data.id.into(),
|
||||||
|
session: if include_session {
|
||||||
|
Some(data.session)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
user_id: data.user_id.into(),
|
||||||
|
created: data.created,
|
||||||
|
last_login: data.last_login,
|
||||||
|
expires: data.expires,
|
||||||
|
refresh_expires: data.refresh_expires,
|
||||||
|
os: data.os,
|
||||||
|
platform: data.platform,
|
||||||
|
user_agent: data.user_agent,
|
||||||
|
city: data.city,
|
||||||
|
country: data.country,
|
||||||
|
ip: data.ip,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::ids::Base62Id;
|
use super::ids::Base62Id;
|
||||||
use crate::database::models::team_item::QueryTeamMember;
|
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -70,8 +69,12 @@ pub struct TeamMember {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TeamMember {
|
impl TeamMember {
|
||||||
pub fn from(data: QueryTeamMember, override_permissions: bool) -> Self {
|
pub fn from(
|
||||||
let user: User = data.user.into();
|
data: crate::database::models::team_item::TeamMember,
|
||||||
|
user: crate::database::models::User,
|
||||||
|
override_permissions: bool,
|
||||||
|
) -> Self {
|
||||||
|
let user: User = user.into();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
team_id: data.team_id.into(),
|
team_id: data.team_id.into(),
|
||||||
|
|||||||
@ -62,7 +62,7 @@ pub struct UserPayoutData {
|
|||||||
pub payout_address: Option<String>,
|
pub payout_address: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RecipientType {
|
pub enum RecipientType {
|
||||||
Email,
|
Email,
|
||||||
@ -94,7 +94,7 @@ impl RecipientType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RecipientWallet {
|
pub enum RecipientWallet {
|
||||||
Venmo,
|
Venmo,
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
pub mod download;
|
pub mod download;
|
||||||
pub mod payouts;
|
pub mod payouts;
|
||||||
|
pub mod session;
|
||||||
|
|||||||
91
src/queue/session.rs
Normal file
91
src/queue/session.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use crate::auth::session::SessionMetadata;
|
||||||
|
use crate::database::models::session_item::Session;
|
||||||
|
use crate::database::models::{DatabaseError, SessionId, UserId};
|
||||||
|
use chrono::Utc;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
pub struct SessionQueue {
|
||||||
|
queue: Mutex<Vec<(SessionId, SessionMetadata)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batches session accessing transactions every 30 seconds
|
||||||
|
impl SessionQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
SessionQueue {
|
||||||
|
queue: Mutex::new(Vec::with_capacity(1000)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn add(&self, id: SessionId, metadata: SessionMetadata) {
|
||||||
|
self.queue.lock().await.push((id, metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn take(&self) -> Vec<(SessionId, SessionMetadata)> {
|
||||||
|
let mut queue = self.queue.lock().await;
|
||||||
|
let len = queue.len();
|
||||||
|
|
||||||
|
std::mem::replace(&mut queue, Vec::with_capacity(len))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn index(
|
||||||
|
&self,
|
||||||
|
pool: &PgPool,
|
||||||
|
redis: &deadpool_redis::Pool,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
let queue = self.take().await;
|
||||||
|
|
||||||
|
if !queue.is_empty() {
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
let mut clear_cache_sessions = Vec::new();
|
||||||
|
|
||||||
|
for (id, metadata) in queue {
|
||||||
|
clear_cache_sessions.push((Some(id), None, None));
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE sessions
|
||||||
|
SET last_login = $2, city = $3, country = $4, ip = $5, os = $6, platform = $7, user_agent = $8
|
||||||
|
WHERE (id = $1)
|
||||||
|
",
|
||||||
|
id as SessionId,
|
||||||
|
Utc::now(),
|
||||||
|
metadata.city,
|
||||||
|
metadata.country,
|
||||||
|
metadata.ip,
|
||||||
|
metadata.os,
|
||||||
|
metadata.platform,
|
||||||
|
metadata.user_agent,
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
let expired_ids = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT id, session, user_id
|
||||||
|
FROM sessions
|
||||||
|
WHERE refresh_expires >= NOW()
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.fetch_many(&mut *transaction)
|
||||||
|
.try_filter_map(|e| async {
|
||||||
|
Ok(e.right()
|
||||||
|
.map(|x| (SessionId(x.id), x.session, UserId(x.user_id))))
|
||||||
|
})
|
||||||
|
.try_collect::<Vec<(SessionId, String, UserId)>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for (id, session, user_id) in expired_ids {
|
||||||
|
clear_cache_sessions.push((Some(id), Some(session), Some(user_id)));
|
||||||
|
Session::remove(id, &mut transaction).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Session::clear_cache(clear_cache_sessions, redis).await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ use crate::auth::{get_user_from_headers, is_authorized_version};
|
|||||||
use crate::database::models::project_item::QueryProject;
|
use crate::database::models::project_item::QueryProject;
|
||||||
use crate::database::models::version_item::{QueryFile, QueryVersion};
|
use crate::database::models::version_item::{QueryFile, QueryVersion};
|
||||||
use crate::models::projects::{ProjectId, VersionId};
|
use crate::models::projects::{ProjectId, VersionId};
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::{auth::is_authorized, database};
|
use crate::{auth::is_authorized, database};
|
||||||
use actix_web::{get, route, web, HttpRequest, HttpResponse};
|
use actix_web::{get, route, web, HttpRequest, HttpResponse};
|
||||||
@ -67,6 +68,7 @@ pub async fn maven_metadata(
|
|||||||
params: web::Path<(String,)>,
|
params: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_id = params.into_inner().0;
|
let project_id = params.into_inner().0;
|
||||||
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||||
@ -77,7 +79,7 @@ pub async fn maven_metadata(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -188,6 +190,7 @@ pub async fn version_file(
|
|||||||
params: web::Path<(String, String, String)>,
|
params: web::Path<(String, String, String)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||||
@ -198,7 +201,7 @@ pub async fn version_file(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -271,6 +274,7 @@ pub async fn version_file_sha1(
|
|||||||
params: web::Path<(String, String, String)>,
|
params: web::Path<(String, String, String)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||||
@ -281,7 +285,7 @@ pub async fn version_file_sha1(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -328,6 +332,7 @@ pub async fn version_file_sha512(
|
|||||||
params: web::Path<(String, String, String)>,
|
params: web::Path<(String, String, String)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||||
@ -338,7 +343,7 @@ pub async fn version_file_sha512(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
|||||||
@ -61,11 +61,17 @@ pub enum ApiError {
|
|||||||
#[error("Payments Error: {0}")]
|
#[error("Payments Error: {0}")]
|
||||||
Payments(String),
|
Payments(String),
|
||||||
#[error("Discord Error: {0}")]
|
#[error("Discord Error: {0}")]
|
||||||
DiscordError(String),
|
Discord(String),
|
||||||
|
#[error("Captcha Error. Try resubmitting the form.")]
|
||||||
|
Turnstile,
|
||||||
#[error("Error while decoding Base62: {0}")]
|
#[error("Error while decoding Base62: {0}")]
|
||||||
Decoding(#[from] crate::models::ids::DecodingError),
|
Decoding(#[from] crate::models::ids::DecodingError),
|
||||||
#[error("Image Parsing Error: {0}")]
|
#[error("Image Parsing Error: {0}")]
|
||||||
ImageError(#[from] image::ImageError),
|
ImageParse(#[from] image::ImageError),
|
||||||
|
#[error("Password Hashing Error: {0}")]
|
||||||
|
PasswordHashing(#[from] argon2::password_hash::Error),
|
||||||
|
#[error("Password strength checking error: {0}")]
|
||||||
|
PasswordStrengthCheck(#[from] zxcvbn::ZxcvbnError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl actix_web::ResponseError for ApiError {
|
impl actix_web::ResponseError for ApiError {
|
||||||
@ -85,9 +91,12 @@ impl actix_web::ResponseError for ApiError {
|
|||||||
ApiError::Validation(..) => StatusCode::BAD_REQUEST,
|
ApiError::Validation(..) => StatusCode::BAD_REQUEST,
|
||||||
ApiError::Analytics(..) => StatusCode::FAILED_DEPENDENCY,
|
ApiError::Analytics(..) => StatusCode::FAILED_DEPENDENCY,
|
||||||
ApiError::Payments(..) => StatusCode::FAILED_DEPENDENCY,
|
ApiError::Payments(..) => StatusCode::FAILED_DEPENDENCY,
|
||||||
ApiError::DiscordError(..) => StatusCode::FAILED_DEPENDENCY,
|
ApiError::Discord(..) => StatusCode::FAILED_DEPENDENCY,
|
||||||
|
ApiError::Turnstile => StatusCode::BAD_REQUEST,
|
||||||
ApiError::Decoding(..) => StatusCode::BAD_REQUEST,
|
ApiError::Decoding(..) => StatusCode::BAD_REQUEST,
|
||||||
ApiError::ImageError(..) => StatusCode::BAD_REQUEST,
|
ApiError::ImageParse(..) => StatusCode::BAD_REQUEST,
|
||||||
|
ApiError::PasswordHashing(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ApiError::PasswordStrengthCheck(..) => StatusCode::BAD_REQUEST,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,9 +117,12 @@ impl actix_web::ResponseError for ApiError {
|
|||||||
ApiError::Validation(..) => "invalid_input",
|
ApiError::Validation(..) => "invalid_input",
|
||||||
ApiError::Analytics(..) => "analytics_error",
|
ApiError::Analytics(..) => "analytics_error",
|
||||||
ApiError::Payments(..) => "payments_error",
|
ApiError::Payments(..) => "payments_error",
|
||||||
ApiError::DiscordError(..) => "discord_error",
|
ApiError::Discord(..) => "discord_error",
|
||||||
|
ApiError::Turnstile => "turnstile_error",
|
||||||
ApiError::Decoding(..) => "decoding_error",
|
ApiError::Decoding(..) => "decoding_error",
|
||||||
ApiError::ImageError(..) => "invalid_image",
|
ApiError::ImageParse(..) => "invalid_image",
|
||||||
|
ApiError::PasswordHashing(..) => "password_hashing_error",
|
||||||
|
ApiError::PasswordStrengthCheck(..) => "strength_check_error",
|
||||||
},
|
},
|
||||||
description: &self.to_string(),
|
description: &self.to_string(),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -7,6 +7,7 @@ use sqlx::PgPool;
|
|||||||
use crate::auth::{filter_authorized_versions, get_user_from_headers, is_authorized};
|
use crate::auth::{filter_authorized_versions, get_user_from_headers, is_authorized};
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models::projects::VersionType;
|
use crate::models::projects::VersionType;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
|
|
||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ pub async fn forge_updates(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
const ERROR: &str = "The specified project does not exist!";
|
const ERROR: &str = "The specified project does not exist!";
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ pub async fn forge_updates(
|
|||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@ use serde::Deserialize;
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
@ -36,7 +35,7 @@ pub struct DownloadBody {
|
|||||||
pub async fn count_download(
|
pub async fn count_download(
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
download_body: web::Json<DownloadBody>,
|
download_body: web::Json<DownloadBody>,
|
||||||
download_queue: web::Data<Arc<DownloadQueue>>,
|
download_queue: web::Data<DownloadQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_id: crate::database::models::ids::ProjectId = download_body.project_id.into();
|
let project_id: crate::database::models::ids::ProjectId = download_body.project_id.into();
|
||||||
|
|
||||||
|
|||||||
@ -20,26 +20,20 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
|||||||
cfg.service(
|
cfg.service(
|
||||||
actix_web::web::scope("v2")
|
actix_web::web::scope("v2")
|
||||||
.configure(admin::config)
|
.configure(admin::config)
|
||||||
.configure(crate::auth::config)
|
.configure(crate::auth::session::config)
|
||||||
|
.configure(crate::auth::flows::config)
|
||||||
.configure(moderation::config)
|
.configure(moderation::config)
|
||||||
.configure(notifications::config)
|
.configure(notifications::config)
|
||||||
.configure(pats::config)
|
//.configure(pats::config)
|
||||||
.configure(project_creation::config)
|
.configure(project_creation::config)
|
||||||
// SHOULD CACHE
|
|
||||||
.configure(projects::config)
|
.configure(projects::config)
|
||||||
.configure(reports::config)
|
.configure(reports::config)
|
||||||
// should cache in future
|
|
||||||
.configure(statistics::config)
|
.configure(statistics::config)
|
||||||
// should cache in future
|
|
||||||
.configure(tags::config)
|
.configure(tags::config)
|
||||||
// should cache
|
|
||||||
.configure(teams::config)
|
.configure(teams::config)
|
||||||
.configure(threads::config)
|
.configure(threads::config)
|
||||||
// should cache
|
|
||||||
.configure(users::config)
|
.configure(users::config)
|
||||||
// should cache in future
|
|
||||||
.configure(version_file::config)
|
.configure(version_file::config)
|
||||||
// SHOULD CACHE
|
|
||||||
.configure(versions::config),
|
.configure(versions::config),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use super::ApiError;
|
|||||||
use crate::auth::check_is_moderator_from_headers;
|
use crate::auth::check_is_moderator_from_headers;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models::projects::ProjectStatus;
|
use crate::models::projects::ProjectStatus;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
@ -26,8 +27,9 @@ pub async fn get_projects(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
count: web::Query<ResultCount>,
|
count: web::Query<ResultCount>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
check_is_moderator_from_headers(req.headers(), &**pool, &redis).await?;
|
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use crate::auth::get_user_from_headers;
|
|||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models::ids::NotificationId;
|
use crate::models::ids::NotificationId;
|
||||||
use crate::models::notifications::Notification;
|
use crate::models::notifications::Notification;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -31,8 +32,9 @@ pub async fn notifications_get(
|
|||||||
web::Query(ids): web::Query<NotificationIds>,
|
web::Query(ids): web::Query<NotificationIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
use database::models::notification_item::Notification as DBNotification;
|
use database::models::notification_item::Notification as DBNotification;
|
||||||
use database::models::NotificationId as DBNotificationId;
|
use database::models::NotificationId as DBNotificationId;
|
||||||
@ -62,8 +64,9 @@ pub async fn notification_get(
|
|||||||
info: web::Path<(NotificationId,)>,
|
info: web::Path<(NotificationId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
@ -87,8 +90,9 @@ pub async fn notification_read(
|
|||||||
info: web::Path<(NotificationId,)>,
|
info: web::Path<(NotificationId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
@ -121,8 +125,9 @@ pub async fn notification_delete(
|
|||||||
info: web::Path<(NotificationId,)>,
|
info: web::Path<(NotificationId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
@ -155,8 +160,9 @@ pub async fn notifications_read(
|
|||||||
web::Query(ids): web::Query<NotificationIds>,
|
web::Query(ids): web::Query<NotificationIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -191,8 +197,9 @@ pub async fn notifications_delete(
|
|||||||
web::Query(ids): web::Query<NotificationIds>,
|
web::Query(ids): web::Query<NotificationIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@ -1,244 +1,249 @@
|
|||||||
/*!
|
// /*!
|
||||||
Current edition of Ory kratos does not support PAT access of data, so this module is how we allow for PAT authentication.
|
// Current edition of Ory kratos does not support PAT access of data, so this module is how we allow for PAT authentication.
|
||||||
|
//
|
||||||
|
//
|
||||||
Just as a summary: Don't implement this flow in your application!
|
// Just as a summary: Don't implement this flow in your application!
|
||||||
*/
|
// */
|
||||||
|
//
|
||||||
use crate::database;
|
// use crate::database;
|
||||||
use crate::database::models::generate_pat_id;
|
// use crate::database::models::generate_pat_id;
|
||||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
// use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||||
|
//
|
||||||
use crate::auth::get_user_from_headers;
|
// use crate::auth::get_user_from_headers;
|
||||||
use crate::auth::{generate_pat, PersonalAccessToken};
|
// use crate::auth::{generate_pat, PersonalAccessToken};
|
||||||
use crate::models::users::UserId;
|
// use crate::models::users::UserId;
|
||||||
use crate::routes::ApiError;
|
// use crate::routes::ApiError;
|
||||||
|
//
|
||||||
use actix_web::web::{self, Data, Query};
|
// use actix_web::web::{self, Data, Query};
|
||||||
use actix_web::{delete, get, patch, post, HttpRequest, HttpResponse};
|
// use actix_web::{delete, get, patch, post, HttpRequest, HttpResponse};
|
||||||
use chrono::{Duration, Utc};
|
// use chrono::{Duration, Utc};
|
||||||
|
//
|
||||||
use serde::Deserialize;
|
// use crate::queue::session::SessionQueue;
|
||||||
use sqlx::postgres::PgPool;
|
// use serde::Deserialize;
|
||||||
|
// use sqlx::postgres::PgPool;
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
//
|
||||||
cfg.service(get_pats);
|
// pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(create_pat);
|
// cfg.service(get_pats);
|
||||||
cfg.service(edit_pat);
|
// cfg.service(create_pat);
|
||||||
cfg.service(delete_pat);
|
// cfg.service(edit_pat);
|
||||||
}
|
// cfg.service(delete_pat);
|
||||||
|
// }
|
||||||
#[derive(Deserialize)]
|
//
|
||||||
pub struct CreatePersonalAccessToken {
|
// #[derive(Deserialize)]
|
||||||
pub scope: i64, // todo: should be a vec of enum
|
// pub struct CreatePersonalAccessToken {
|
||||||
pub name: Option<String>,
|
// pub scope: i64, // todo: should be a vec of enum
|
||||||
pub expire_in_days: i64, // resets expiry to expire_in_days days from now
|
// pub name: Option<String>,
|
||||||
}
|
// pub expire_in_days: i64, // resets expiry to expire_in_days days from now
|
||||||
|
// }
|
||||||
#[derive(Deserialize)]
|
//
|
||||||
pub struct ModifyPersonalAccessToken {
|
// #[derive(Deserialize)]
|
||||||
#[serde(default, with = "::serde_with::rust::double_option")]
|
// pub struct ModifyPersonalAccessToken {
|
||||||
pub name: Option<Option<String>>,
|
// #[serde(default, with = "::serde_with::rust::double_option")]
|
||||||
pub expire_in_days: Option<i64>, // resets expiry to expire_in_days days from now
|
// pub name: Option<Option<String>>,
|
||||||
}
|
// pub expire_in_days: Option<i64>, // resets expiry to expire_in_days days from now
|
||||||
|
// }
|
||||||
// GET /pat
|
//
|
||||||
// Get all personal access tokens for the given user. Minos/Kratos cookie must be attached for it to work.
|
// // GET /pat
|
||||||
// Does not return the actual access token, only the ID + metadata.
|
// // Get all personal access tokens for the given user. Minos/Kratos cookie must be attached for it to work.
|
||||||
#[get("pat")]
|
// // Does not return the actual access token, only the ID + metadata.
|
||||||
pub async fn get_pats(
|
// #[get("pat")]
|
||||||
req: HttpRequest,
|
// pub async fn get_pats(
|
||||||
pool: Data<PgPool>,
|
// req: HttpRequest,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
// pool: Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
// redis: Data<deadpool_redis::Pool>,
|
||||||
let user: crate::models::users::User =
|
// session_queue: web::Data<SessionQueue>,
|
||||||
get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
// ) -> Result<HttpResponse, ApiError> {
|
||||||
let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
// let user: crate::models::users::User =
|
||||||
|
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let pats = sqlx::query!(
|
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
||||||
"
|
//
|
||||||
SELECT id, name, user_id, scope, expires_at
|
// let pats = sqlx::query!(
|
||||||
FROM pats
|
// "
|
||||||
WHERE user_id = $1
|
// SELECT id, name, user_id, scope, expires_at
|
||||||
",
|
// FROM pats
|
||||||
db_user_id.0
|
// WHERE user_id = $1
|
||||||
)
|
// ",
|
||||||
.fetch_all(&**pool)
|
// db_user_id.0
|
||||||
.await?;
|
// )
|
||||||
|
// .fetch_all(&**pool)
|
||||||
let pats = pats
|
// .await?;
|
||||||
.into_iter()
|
//
|
||||||
.map(|pat| PersonalAccessToken {
|
// let pats = pats
|
||||||
id: to_base62(pat.id as u64),
|
// .into_iter()
|
||||||
scope: pat.scope,
|
// .map(|pat| PersonalAccessToken {
|
||||||
name: pat.name,
|
// id: to_base62(pat.id as u64),
|
||||||
expires_at: pat.expires_at,
|
// scope: pat.scope,
|
||||||
access_token: None,
|
// name: pat.name,
|
||||||
user_id: UserId(pat.user_id as u64),
|
// expires_at: pat.expires_at,
|
||||||
})
|
// access_token: None,
|
||||||
.collect::<Vec<_>>();
|
// user_id: UserId(pat.user_id as u64),
|
||||||
|
// })
|
||||||
Ok(HttpResponse::Ok().json(pats))
|
// .collect::<Vec<_>>();
|
||||||
}
|
//
|
||||||
|
// Ok(HttpResponse::Ok().json(pats))
|
||||||
// POST /pat
|
// }
|
||||||
// Create a new personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
|
//
|
||||||
// All PAT tokens are base62 encoded, and are prefixed with "modrinth_pat_"
|
// // POST /pat
|
||||||
#[post("pat")]
|
// // Create a new personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
|
||||||
pub async fn create_pat(
|
// // All PAT tokens are base62 encoded, and are prefixed with "modrinth_pat_"
|
||||||
req: HttpRequest,
|
// #[post("pat")]
|
||||||
Query(info): Query<CreatePersonalAccessToken>, // callback url
|
// pub async fn create_pat(
|
||||||
pool: Data<PgPool>,
|
// req: HttpRequest,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
// Query(info): Query<CreatePersonalAccessToken>, // callback url
|
||||||
) -> Result<HttpResponse, ApiError> {
|
// pool: Data<PgPool>,
|
||||||
let user: crate::models::users::User =
|
// redis: web::Data<deadpool_redis::Pool>,
|
||||||
get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
// session_queue: web::Data<SessionQueue>,
|
||||||
let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
// ) -> Result<HttpResponse, ApiError> {
|
||||||
|
// let user: crate::models::users::User =
|
||||||
let mut transaction: sqlx::Transaction<sqlx::Postgres> = pool.begin().await?;
|
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
||||||
let pat = generate_pat_id(&mut transaction).await?;
|
//
|
||||||
let access_token = generate_pat(&mut transaction).await?;
|
// let mut transaction: sqlx::Transaction<sqlx::Postgres> = pool.begin().await?;
|
||||||
let expiry = Utc::now().naive_utc() + Duration::days(info.expire_in_days);
|
//
|
||||||
if info.expire_in_days <= 0 {
|
// let pat = generate_pat_id(&mut transaction).await?;
|
||||||
return Err(ApiError::InvalidInput(
|
// let access_token = generate_pat(&mut transaction).await?;
|
||||||
"'expire_in_days' must be greater than 0".to_string(),
|
// let expiry = Utc::now().naive_utc() + Duration::days(info.expire_in_days);
|
||||||
));
|
// if info.expire_in_days <= 0 {
|
||||||
}
|
// return Err(ApiError::InvalidInput(
|
||||||
|
// "'expire_in_days' must be greater than 0".to_string(),
|
||||||
sqlx::query!(
|
// ));
|
||||||
"
|
// }
|
||||||
INSERT INTO pats (id, name, access_token, user_id, scope, expires_at)
|
//
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
// sqlx::query!(
|
||||||
",
|
// "
|
||||||
pat.0,
|
// INSERT INTO pats (id, name, access_token, user_id, scope, expires_at)
|
||||||
info.name,
|
// VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
access_token,
|
// ",
|
||||||
db_user_id.0,
|
// pat.0,
|
||||||
info.scope,
|
// info.name,
|
||||||
expiry
|
// access_token,
|
||||||
)
|
// db_user_id.0,
|
||||||
.execute(&mut *transaction)
|
// info.scope,
|
||||||
.await?;
|
// expiry
|
||||||
|
// )
|
||||||
transaction.commit().await?;
|
// .execute(&mut *transaction)
|
||||||
|
// .await?;
|
||||||
Ok(HttpResponse::Ok().json(PersonalAccessToken {
|
//
|
||||||
id: to_base62(pat.0 as u64),
|
// transaction.commit().await?;
|
||||||
access_token: Some(access_token),
|
//
|
||||||
name: info.name,
|
// Ok(HttpResponse::Ok().json(PersonalAccessToken {
|
||||||
scope: info.scope,
|
// id: to_base62(pat.0 as u64),
|
||||||
user_id: user.id,
|
// access_token: Some(access_token),
|
||||||
expires_at: expiry,
|
// name: info.name,
|
||||||
}))
|
// scope: info.scope,
|
||||||
}
|
// user_id: user.id,
|
||||||
|
// expires_at: expiry,
|
||||||
// PATCH /pat/(id)
|
// }))
|
||||||
// Edit an access token of id "id" for the given user.
|
// }
|
||||||
// 'None' will mean not edited. Minos/Kratos cookie or PAT must be attached for it to work.
|
//
|
||||||
#[patch("pat/{id}")]
|
// // PATCH /pat/(id)
|
||||||
pub async fn edit_pat(
|
// // Edit an access token of id "id" for the given user.
|
||||||
req: HttpRequest,
|
// // 'None' will mean not edited. Minos/Kratos cookie or PAT must be attached for it to work.
|
||||||
id: web::Path<String>,
|
// #[patch("pat/{id}")]
|
||||||
Query(info): Query<ModifyPersonalAccessToken>, // callback url
|
// pub async fn edit_pat(
|
||||||
pool: Data<PgPool>,
|
// req: HttpRequest,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
// id: web::Path<String>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
// Query(info): Query<ModifyPersonalAccessToken>, // callback url
|
||||||
let user: crate::models::users::User =
|
// pool: Data<PgPool>,
|
||||||
get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
// redis: web::Data<deadpool_redis::Pool>,
|
||||||
let pat_id = database::models::PatId(parse_base62(&id)? as i64);
|
// session_queue: web::Data<SessionQueue>,
|
||||||
let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
// ) -> Result<HttpResponse, ApiError> {
|
||||||
|
// let user: crate::models::users::User =
|
||||||
if let Some(expire_in_days) = info.expire_in_days {
|
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
if expire_in_days <= 0 {
|
// let pat_id = database::models::PatId(parse_base62(&id)? as i64);
|
||||||
return Err(ApiError::InvalidInput(
|
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
||||||
"'expire_in_days' must be greater than 0".to_string(),
|
//
|
||||||
));
|
// if let Some(expire_in_days) = info.expire_in_days {
|
||||||
}
|
// if expire_in_days <= 0 {
|
||||||
}
|
// return Err(ApiError::InvalidInput(
|
||||||
|
// "'expire_in_days' must be greater than 0".to_string(),
|
||||||
// Get the singular PAT and user combination (failing immediately if it doesn't exist)
|
// ));
|
||||||
let mut transaction = pool.begin().await?;
|
// }
|
||||||
let row = sqlx::query!(
|
// }
|
||||||
"
|
//
|
||||||
SELECT id, name, scope, user_id, expires_at FROM pats
|
// // Get the singular PAT and user combination (failing immediately if it doesn't exist)
|
||||||
WHERE id = $1 AND user_id = $2
|
// let mut transaction = pool.begin().await?;
|
||||||
",
|
// let row = sqlx::query!(
|
||||||
pat_id.0,
|
// "
|
||||||
db_user_id.0 // included for safety
|
// SELECT id, name, scope, user_id, expires_at FROM pats
|
||||||
)
|
// WHERE id = $1 AND user_id = $2
|
||||||
.fetch_one(&**pool)
|
// ",
|
||||||
.await?;
|
// pat_id.0,
|
||||||
|
// db_user_id.0 // included for safety
|
||||||
let pat = PersonalAccessToken {
|
// )
|
||||||
id: to_base62(row.id as u64),
|
// .fetch_one(&**pool)
|
||||||
access_token: None,
|
// .await?;
|
||||||
user_id: UserId::from(db_user_id),
|
//
|
||||||
name: info.name.unwrap_or(row.name),
|
// let pat = PersonalAccessToken {
|
||||||
scope: row.scope,
|
// id: to_base62(row.id as u64),
|
||||||
expires_at: info
|
// access_token: None,
|
||||||
.expire_in_days
|
// user_id: UserId::from(db_user_id),
|
||||||
.map(|d| Utc::now().naive_utc() + Duration::days(d))
|
// name: info.name.unwrap_or(row.name),
|
||||||
.unwrap_or(row.expires_at),
|
// scope: row.scope,
|
||||||
};
|
// expires_at: info
|
||||||
|
// .expire_in_days
|
||||||
sqlx::query!(
|
// .map(|d| Utc::now().naive_utc() + Duration::days(d))
|
||||||
"
|
// .unwrap_or(row.expires_at),
|
||||||
UPDATE pats SET
|
// };
|
||||||
name = $1,
|
//
|
||||||
expires_at = $2
|
// sqlx::query!(
|
||||||
WHERE id = $3
|
// "
|
||||||
",
|
// UPDATE pats SET
|
||||||
pat.name,
|
// name = $1,
|
||||||
pat.expires_at,
|
// expires_at = $2
|
||||||
parse_base62(&pat.id)? as i64
|
// WHERE id = $3
|
||||||
)
|
// ",
|
||||||
.execute(&mut *transaction)
|
// pat.name,
|
||||||
.await?;
|
// pat.expires_at,
|
||||||
transaction.commit().await?;
|
// parse_base62(&pat.id)? as i64
|
||||||
|
// )
|
||||||
Ok(HttpResponse::Ok().json(pat))
|
// .execute(&mut *transaction)
|
||||||
}
|
// .await?;
|
||||||
|
// transaction.commit().await?;
|
||||||
// DELETE /pat
|
//
|
||||||
// Delete a personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
|
// Ok(HttpResponse::Ok().json(pat))
|
||||||
#[delete("pat/{id}")]
|
// }
|
||||||
pub async fn delete_pat(
|
//
|
||||||
req: HttpRequest,
|
// // DELETE /pat
|
||||||
id: web::Path<String>,
|
// // Delete a personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
|
||||||
pool: Data<PgPool>,
|
// #[delete("pat/{id}")]
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
// pub async fn delete_pat(
|
||||||
) -> Result<HttpResponse, ApiError> {
|
// req: HttpRequest,
|
||||||
let user: crate::models::users::User =
|
// id: web::Path<String>,
|
||||||
get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
// pool: Data<PgPool>,
|
||||||
let pat_id = database::models::PatId(parse_base62(&id)? as i64);
|
// redis: web::Data<deadpool_redis::Pool>,
|
||||||
let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
// session_queue: web::Data<SessionQueue>,
|
||||||
|
// ) -> Result<HttpResponse, ApiError> {
|
||||||
// Get the singular PAT and user combination (failing immediately if it doesn't exist)
|
// let user: crate::models::users::User =
|
||||||
// This is to prevent users from deleting other users' PATs
|
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let pat_id = sqlx::query!(
|
// let pat_id = database::models::PatId(parse_base62(&id)? as i64);
|
||||||
"
|
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
|
||||||
SELECT id FROM pats
|
//
|
||||||
WHERE id = $1 AND user_id = $2
|
// // Get the singular PAT and user combination (failing immediately if it doesn't exist)
|
||||||
",
|
// // This is to prevent users from deleting other users' PATs
|
||||||
pat_id.0,
|
// let pat_id = sqlx::query!(
|
||||||
db_user_id.0
|
// "
|
||||||
)
|
// SELECT id FROM pats
|
||||||
.fetch_one(&**pool)
|
// WHERE id = $1 AND user_id = $2
|
||||||
.await?
|
// ",
|
||||||
.id;
|
// pat_id.0,
|
||||||
|
// db_user_id.0
|
||||||
let mut transaction = pool.begin().await?;
|
// )
|
||||||
sqlx::query!(
|
// .fetch_one(&**pool)
|
||||||
"
|
// .await?
|
||||||
DELETE FROM pats
|
// .id;
|
||||||
WHERE id = $1
|
//
|
||||||
",
|
// let mut transaction = pool.begin().await?;
|
||||||
pat_id,
|
// sqlx::query!(
|
||||||
)
|
// "
|
||||||
.execute(&mut *transaction)
|
// DELETE FROM pats
|
||||||
.await?;
|
// WHERE id = $1
|
||||||
transaction.commit().await?;
|
// ",
|
||||||
|
// pat_id,
|
||||||
Ok(HttpResponse::Ok().finish())
|
// )
|
||||||
}
|
// .execute(&mut *transaction)
|
||||||
|
// .await?;
|
||||||
|
// transaction.commit().await?;
|
||||||
|
//
|
||||||
|
// Ok(HttpResponse::Ok().finish())
|
||||||
|
// }
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use crate::models::projects::{
|
|||||||
};
|
};
|
||||||
use crate::models::threads::ThreadType;
|
use crate::models::threads::ThreadType;
|
||||||
use crate::models::users::UserId;
|
use crate::models::users::UserId;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::search::indexing::IndexingError;
|
use crate::search::indexing::IndexingError;
|
||||||
use crate::util::routes::read_from_field;
|
use crate::util::routes::read_from_field;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
@ -272,6 +273,7 @@ pub async fn project_create(
|
|||||||
client: Data<PgPool>,
|
client: Data<PgPool>,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
|
session_queue: Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let mut transaction = client.begin().await?;
|
let mut transaction = client.begin().await?;
|
||||||
let mut uploaded_files = Vec::new();
|
let mut uploaded_files = Vec::new();
|
||||||
@ -284,6 +286,7 @@ pub async fn project_create(
|
|||||||
&mut uploaded_files,
|
&mut uploaded_files,
|
||||||
&client,
|
&client,
|
||||||
&redis,
|
&redis,
|
||||||
|
&session_queue,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -331,6 +334,7 @@ Get logged in user
|
|||||||
- Add project data to indexing queue
|
- Add project data to indexing queue
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn project_create_inner(
|
async fn project_create_inner(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
payload: &mut Multipart,
|
payload: &mut Multipart,
|
||||||
@ -339,12 +343,13 @@ async fn project_create_inner(
|
|||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
redis: &deadpool_redis::Pool,
|
redis: &deadpool_redis::Pool,
|
||||||
|
session_queue: &SessionQueue,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
// The base URL for files uploaded to backblaze
|
// The base URL for files uploaded to backblaze
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
|
|
||||||
// The currently logged in user
|
// The currently logged in user
|
||||||
let current_user = get_user_from_headers(req.headers(), pool, redis).await?;
|
let current_user = get_user_from_headers(&req, pool, redis, session_queue).await?;
|
||||||
|
|
||||||
let project_id: ProjectId = models::generate_project_id(transaction).await?.into();
|
let project_id: ProjectId = models::generate_project_id(transaction).await?.into();
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ use crate::models::projects::{
|
|||||||
};
|
};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::models::threads::MessageBody;
|
use crate::models::threads::MessageBody;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||||
use crate::util::routes::read_from_payload;
|
use crate::util::routes::read_from_payload;
|
||||||
@ -115,11 +116,12 @@ pub async fn projects_get(
|
|||||||
web::Query(ids): web::Query<ProjectIds>,
|
web::Query(ids): web::Query<ProjectIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
||||||
let projects_data = database::models::Project::get_many(&ids, &**pool, &redis).await?;
|
let projects_data = database::models::Project::get_many(&ids, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -134,12 +136,13 @@ pub async fn project_get(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_data = database::models::Project::get(&string, &**pool, &redis).await?;
|
let project_data = database::models::Project::get(&string, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -183,12 +186,13 @@ pub async fn dependency_list(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -353,8 +357,9 @@ pub async fn project_edit(
|
|||||||
config: web::Data<SearchConfig>,
|
config: web::Data<SearchConfig>,
|
||||||
new_project: web::Json<EditProject>,
|
new_project: web::Json<EditProject>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
new_project
|
new_project
|
||||||
.validate()
|
.validate()
|
||||||
@ -1176,8 +1181,9 @@ pub async fn projects_edit(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
bulk_edit_project: web::Json<BulkEditProject>,
|
bulk_edit_project: web::Json<BulkEditProject>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
bulk_edit_project
|
bulk_edit_project
|
||||||
.validate()
|
.validate()
|
||||||
@ -1218,7 +1224,7 @@ pub async fn projects_edit(
|
|||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
if let Some(member) = team_members
|
if let Some(member) = team_members
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.team_id == project.inner.team_id && x.user.id == user.id.into())
|
.find(|x| x.team_id == project.inner.team_id && x.user_id == user.id.into())
|
||||||
{
|
{
|
||||||
if !member.permissions.contains(Permissions::EDIT_DETAILS) {
|
if !member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(format!(
|
return Err(ApiError::CustomAuthentication(format!(
|
||||||
@ -1505,9 +1511,10 @@ pub async fn project_schedule(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
scheduling_data: web::Json<SchedulingData>,
|
scheduling_data: web::Json<SchedulingData>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
if scheduling_data.time < Utc::now() {
|
if scheduling_data.time < Utc::now() {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
@ -1575,6 +1582,7 @@ pub struct Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[patch("{id}/icon")]
|
#[patch("{id}/icon")]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn project_icon_edit(
|
pub async fn project_icon_edit(
|
||||||
web::Query(ext): web::Query<Extension>,
|
web::Query(ext): web::Query<Extension>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@ -1583,10 +1591,11 @@ pub async fn project_icon_edit(
|
|||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@ -1678,8 +1687,9 @@ pub async fn delete_project_icon(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@ -1763,13 +1773,14 @@ pub async fn add_gallery_item(
|
|||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
item.validate()
|
item.validate()
|
||||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@ -1904,8 +1915,9 @@ pub async fn edit_gallery_item(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
item.validate()
|
item.validate()
|
||||||
@ -2049,8 +2061,9 @@ pub async fn delete_gallery_item(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
let project_item = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@ -2135,8 +2148,9 @@ pub async fn project_delete(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
config: web::Data<SearchConfig>,
|
config: web::Data<SearchConfig>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project = database::models::Project::get(&string, &**pool, &redis)
|
let project = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@ -2189,8 +2203,9 @@ pub async fn project_follow(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get(&string, &**pool, &redis)
|
let result = database::models::Project::get(&string, &**pool, &redis)
|
||||||
@ -2259,8 +2274,9 @@ pub async fn project_unfollow(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get(&string, &**pool, &redis)
|
let result = database::models::Project::get(&string, &**pool, &redis)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder};
|
|||||||
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
|
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
|
||||||
use crate::models::reports::{ItemType, Report};
|
use crate::models::reports::{ItemType, Report};
|
||||||
use crate::models::threads::{MessageBody, ThreadType};
|
use crate::models::threads::{MessageBody, ThreadType};
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@ -34,10 +35,11 @@ pub async fn report_create(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
mut body: web::Payload,
|
mut body: web::Payload,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let mut bytes = web::BytesMut::new();
|
let mut bytes = web::BytesMut::new();
|
||||||
while let Some(item) = body.next().await {
|
while let Some(item) = body.next().await {
|
||||||
@ -180,8 +182,9 @@ pub async fn reports(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
count: web::Query<ReportsRequestOptions>,
|
count: web::Query<ReportsRequestOptions>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
@ -245,6 +248,7 @@ pub async fn reports_get(
|
|||||||
web::Query(ids): web::Query<ReportIds>,
|
web::Query(ids): web::Query<ReportIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let report_ids: Vec<crate::database::models::ids::ReportId> =
|
let report_ids: Vec<crate::database::models::ids::ReportId> =
|
||||||
serde_json::from_str::<Vec<crate::models::ids::ReportId>>(&ids.ids)?
|
serde_json::from_str::<Vec<crate::models::ids::ReportId>>(&ids.ids)?
|
||||||
@ -255,7 +259,7 @@ pub async fn reports_get(
|
|||||||
let reports_data =
|
let reports_data =
|
||||||
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
|
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
|
||||||
|
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let all_reports = reports_data
|
let all_reports = reports_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -272,8 +276,9 @@ pub async fn report_get(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id = info.into_inner().0.into();
|
let id = info.into_inner().0.into();
|
||||||
|
|
||||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||||
@ -303,9 +308,10 @@ pub async fn report_edit(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
edit_report: web::Json<EditReport>,
|
edit_report: web::Json<EditReport>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id = info.into_inner().0.into();
|
let id = info.into_inner().0.into();
|
||||||
|
|
||||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||||
@ -379,8 +385,9 @@ pub async fn report_delete(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
check_is_moderator_from_headers(req.headers(), &**pool, &redis).await?;
|
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
let result = crate::database::models::report_item::Report::remove_full(
|
let result = crate::database::models::report_item::Report::remove_full(
|
||||||
|
|||||||
@ -5,6 +5,7 @@ use crate::models::ids::ProjectId;
|
|||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
use crate::models::teams::{Permissions, TeamId};
|
use crate::models::teams::{Permissions, TeamId};
|
||||||
use crate::models::users::UserId;
|
use crate::models::users::UserId;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
@ -31,47 +32,53 @@ pub async fn team_members_get_project(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let project_data = crate::database::models::Project::get(&string, &**pool, &redis).await?;
|
let project_data = crate::database::models::Project::get(&string, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(project) = project_data {
|
if let Some(project) = project_data {
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis)
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
let members_data =
|
|
||||||
TeamMember::get_from_team_full(project.inner.team_id, &**pool, &redis).await?;
|
|
||||||
|
|
||||||
if !is_authorized(&project.inner, ¤t_user, &pool).await? {
|
if !is_authorized(&project.inner, ¤t_user, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(user) = ¤t_user {
|
let members_data =
|
||||||
let team_member = members_data
|
TeamMember::get_from_team_full(project.inner.team_id, &**pool, &redis).await?;
|
||||||
|
let users = crate::database::models::User::get_many_ids(
|
||||||
|
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let user_id = current_user.as_ref().map(|x| x.id.into());
|
||||||
|
|
||||||
|
let logged_in = current_user
|
||||||
|
.and_then(|user| {
|
||||||
|
members_data
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.user.id == user.id.into() && x.accepted);
|
.find(|x| x.user_id == user.id.into() && x.accepted)
|
||||||
|
})
|
||||||
|
.is_some();
|
||||||
|
|
||||||
if team_member.is_some() {
|
|
||||||
let team_members: Vec<_> = members_data
|
|
||||||
.into_iter()
|
|
||||||
.map(|data| crate::models::teams::TeamMember::from(data, false))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
return Ok(HttpResponse::Ok().json(team_members));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let user_id = current_user.map(|x| x.id.into());
|
|
||||||
let team_members: Vec<_> = members_data
|
let team_members: Vec<_> = members_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| {
|
.filter(|x| {
|
||||||
x.accepted
|
logged_in
|
||||||
|
|| x.accepted
|
||||||
|| user_id
|
|| user_id
|
||||||
.map(|y: crate::database::models::UserId| y == x.user.id)
|
.map(|y: crate::database::models::UserId| y == x.user_id)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
})
|
})
|
||||||
.map(|data| crate::models::teams::TeamMember::from(data, true))
|
.flat_map(|data| {
|
||||||
|
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||||
|
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||||
|
})
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(team_members))
|
Ok(HttpResponse::Ok().json(team_members))
|
||||||
@ -86,39 +93,45 @@ pub async fn team_members_get(
|
|||||||
info: web::Path<(TeamId,)>,
|
info: web::Path<(TeamId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
let members_data = TeamMember::get_from_team_full(id.into(), &**pool, &redis).await?;
|
let members_data = TeamMember::get_from_team_full(id.into(), &**pool, &redis).await?;
|
||||||
|
let users = crate::database::models::User::get_many_ids(
|
||||||
|
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis)
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
let user_id = current_user.as_ref().map(|x| x.id.into());
|
||||||
|
|
||||||
if let Some(user) = ¤t_user {
|
let logged_in = current_user
|
||||||
let team_member = members_data
|
.and_then(|user| {
|
||||||
|
members_data
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.user.id == user.id.into() && x.accepted);
|
.find(|x| x.user_id == user.id.into() && x.accepted)
|
||||||
|
})
|
||||||
|
.is_some();
|
||||||
|
|
||||||
if team_member.is_some() {
|
|
||||||
let team_members: Vec<_> = members_data
|
|
||||||
.into_iter()
|
|
||||||
.map(|data| crate::models::teams::TeamMember::from(data, false))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
return Ok(HttpResponse::Ok().json(team_members));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let user_id = current_user.map(|x| x.id.into());
|
|
||||||
let team_members: Vec<_> = members_data
|
let team_members: Vec<_> = members_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| {
|
.filter(|x| {
|
||||||
x.accepted
|
logged_in
|
||||||
|
|| x.accepted
|
||||||
|| user_id
|
|| user_id
|
||||||
.map(|y: crate::database::models::UserId| y == x.user.id)
|
.map(|y: crate::database::models::UserId| y == x.user_id)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
})
|
})
|
||||||
.map(|data| crate::models::teams::TeamMember::from(data, true))
|
.flat_map(|data| {
|
||||||
|
users
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.id == data.user_id)
|
||||||
|
.map(|user| crate::models::teams::TeamMember::from(data, user.clone(), !logged_in))
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(team_members))
|
Ok(HttpResponse::Ok().json(team_members))
|
||||||
@ -135,6 +148,7 @@ pub async fn teams_get(
|
|||||||
web::Query(ids): web::Query<TeamIds>,
|
web::Query(ids): web::Query<TeamIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
@ -144,8 +158,14 @@ pub async fn teams_get(
|
|||||||
.collect::<Vec<crate::database::models::ids::TeamId>>();
|
.collect::<Vec<crate::database::models::ids::TeamId>>();
|
||||||
|
|
||||||
let teams_data = TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?;
|
let teams_data = TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?;
|
||||||
|
let users = crate::database::models::User::get_many_ids(
|
||||||
|
&teams_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis)
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -156,28 +176,23 @@ pub async fn teams_get(
|
|||||||
for (_, member_data) in &teams_groups {
|
for (_, member_data) in &teams_groups {
|
||||||
let members = member_data.collect::<Vec<_>>();
|
let members = member_data.collect::<Vec<_>>();
|
||||||
|
|
||||||
let team_member = if let Some(user) = ¤t_user {
|
let logged_in = current_user
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|user| {
|
||||||
members
|
members
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.user.id == user.id.into() && x.accepted)
|
.find(|x| x.user_id == user.id.into() && x.accepted)
|
||||||
} else {
|
})
|
||||||
None
|
.is_some();
|
||||||
};
|
|
||||||
|
|
||||||
if team_member.is_some() {
|
|
||||||
let team_members = members
|
|
||||||
.into_iter()
|
|
||||||
.map(|data| crate::models::teams::TeamMember::from(data, false));
|
|
||||||
|
|
||||||
teams.push(team_members.collect());
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let team_members = members
|
let team_members = members
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| x.accepted)
|
.filter(|x| logged_in || x.accepted)
|
||||||
.map(|data| crate::models::teams::TeamMember::from(data, true));
|
.flat_map(|data| {
|
||||||
|
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||||
|
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
teams.push(team_members.collect());
|
teams.push(team_members.collect());
|
||||||
}
|
}
|
||||||
@ -191,9 +206,10 @@ pub async fn join_team(
|
|||||||
info: web::Path<(TeamId,)>,
|
info: web::Path<(TeamId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let team_id = info.into_inner().0.into();
|
let team_id = info.into_inner().0.into();
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let member =
|
let member =
|
||||||
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
||||||
@ -259,12 +275,13 @@ pub async fn add_team_member(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
new_member: web::Json<NewTeamMember>,
|
new_member: web::Json<NewTeamMember>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let team_id = info.into_inner().0.into();
|
let team_id = info.into_inner().0.into();
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
@ -373,12 +390,13 @@ pub async fn edit_team_member(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
edit_member: web::Json<EditTeamMember>,
|
edit_member: web::Json<EditTeamMember>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let ids = info.into_inner();
|
let ids = info.into_inner();
|
||||||
let id = ids.0.into();
|
let id = ids.0.into();
|
||||||
let user_id = ids.1.into();
|
let user_id = ids.1.into();
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
@ -463,10 +481,11 @@ pub async fn transfer_ownership(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
new_owner: web::Json<TransferOwnership>,
|
new_owner: web::Json<TransferOwnership>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
if !current_user.role.is_admin() {
|
if !current_user.role.is_admin() {
|
||||||
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
|
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
|
||||||
@ -535,12 +554,13 @@ pub async fn remove_team_member(
|
|||||||
info: web::Path<(TeamId, UserId)>,
|
info: web::Path<(TeamId, UserId)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let ids = info.into_inner();
|
let ids = info.into_inner();
|
||||||
let id = ids.0.into();
|
let id = ids.0.into();
|
||||||
let user_id = ids.1.into();
|
let user_id = ids.1.into();
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ use crate::models::notifications::NotificationBody;
|
|||||||
use crate::models::projects::ProjectStatus;
|
use crate::models::projects::ProjectStatus;
|
||||||
use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadType};
|
use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadType};
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
@ -211,12 +212,13 @@ pub async fn thread_get(
|
|||||||
info: web::Path<(ThreadId,)>,
|
info: web::Path<(ThreadId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0.into();
|
let string = info.into_inner().0.into();
|
||||||
|
|
||||||
let thread_data = database::models::Thread::get(string, &**pool).await?;
|
let thread_data = database::models::Thread::get(string, &**pool).await?;
|
||||||
|
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
if let Some(mut data) = thread_data {
|
if let Some(mut data) = thread_data {
|
||||||
if is_authorized_thread(&data, &user, &pool).await? {
|
if is_authorized_thread(&data, &user, &pool).await? {
|
||||||
@ -253,8 +255,9 @@ pub async fn threads_get(
|
|||||||
web::Query(ids): web::Query<ThreadIds>,
|
web::Query(ids): web::Query<ThreadIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let thread_ids: Vec<database::models::ids::ThreadId> =
|
let thread_ids: Vec<database::models::ids::ThreadId> =
|
||||||
serde_json::from_str::<Vec<ThreadId>>(&ids.ids)?
|
serde_json::from_str::<Vec<ThreadId>>(&ids.ids)?
|
||||||
@ -281,8 +284,9 @@ pub async fn thread_send_message(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
new_message: web::Json<NewThreadMessage>,
|
new_message: web::Json<NewThreadMessage>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let string: database::models::ThreadId = info.into_inner().0.into();
|
let string: database::models::ThreadId = info.into_inner().0.into();
|
||||||
|
|
||||||
@ -370,7 +374,7 @@ pub async fn thread_send_message(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
.insert_many(
|
.insert_many(
|
||||||
members.into_iter().map(|x| x.user.id).collect(),
|
members.into_iter().map(|x| x.user_id).collect(),
|
||||||
&mut transaction,
|
&mut transaction,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@ -442,8 +446,9 @@ pub async fn moderation_inbox(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = check_is_moderator_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let ids = sqlx::query!(
|
let ids = sqlx::query!(
|
||||||
"
|
"
|
||||||
@ -469,8 +474,9 @@ pub async fn thread_read(
|
|||||||
info: web::Path<(ThreadId,)>,
|
info: web::Path<(ThreadId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
check_is_moderator_from_headers(req.headers(), &**pool, &redis).await?;
|
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
@ -497,8 +503,9 @@ pub async fn message_delete(
|
|||||||
info: web::Path<(ThreadMessageId,)>,
|
info: web::Path<(ThreadMessageId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?;
|
let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?;
|
||||||
|
|
||||||
|
|||||||
@ -5,12 +5,17 @@ use crate::models::notifications::Notification;
|
|||||||
use crate::models::projects::Project;
|
use crate::models::projects::Project;
|
||||||
use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId};
|
use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId};
|
||||||
use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue};
|
use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue};
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::routes::read_from_payload;
|
use crate::util::routes::read_from_payload;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
|
use argon2::password_hash::SaltString;
|
||||||
|
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use rand_chacha::rand_core::SeedableRng;
|
||||||
|
use rand_chacha::ChaCha20Rng;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -44,8 +49,12 @@ pub async fn user_auth_get(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
Ok(HttpResponse::Ok().json(get_user_from_headers(req.headers(), &**pool, &redis).await?))
|
Ok(
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.json(get_user_from_headers(&req, &**pool, &redis, &session_queue).await?),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@ -59,8 +68,9 @@ pub async fn user_data_get(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let data = sqlx::query!(
|
let data = sqlx::query!(
|
||||||
"
|
"
|
||||||
@ -128,8 +138,9 @@ pub async fn projects_list(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -189,6 +200,7 @@ pub struct EditUser {
|
|||||||
)]
|
)]
|
||||||
#[validate]
|
#[validate]
|
||||||
pub payout_data: Option<Option<EditPayoutData>>,
|
pub payout_data: Option<Option<EditPayoutData>>,
|
||||||
|
pub password: Option<(Option<String>, Option<String>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Validate)]
|
#[derive(Serialize, Deserialize, Validate)]
|
||||||
@ -206,8 +218,9 @@ pub async fn user_edit(
|
|||||||
new_user: web::Json<EditUser>,
|
new_user: web::Json<EditUser>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
new_user
|
new_user
|
||||||
.validate()
|
.validate()
|
||||||
@ -387,6 +400,78 @@ pub async fn user_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some((old_password, new_password)) = &new_user.password {
|
||||||
|
if let Some(pass) = actual_user.password {
|
||||||
|
let old_password = old_password.as_ref().ok_or_else(|| {
|
||||||
|
ApiError::CustomAuthentication(
|
||||||
|
"You must specify the old password to change your password!"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let hasher = Argon2::default();
|
||||||
|
hasher.verify_password(old_password.as_bytes(), &PasswordHash::new(&pass)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let update_password = if let Some(new_password) = new_password {
|
||||||
|
let score = zxcvbn::zxcvbn(
|
||||||
|
new_password,
|
||||||
|
&[
|
||||||
|
&actual_user.username,
|
||||||
|
&actual_user.email.unwrap_or_default(),
|
||||||
|
&actual_user.name.unwrap_or_default(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if score.score() < 3 {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
if let Some(feedback) =
|
||||||
|
score.feedback().clone().and_then(|x| x.warning())
|
||||||
|
{
|
||||||
|
format!("Password too weak: {}", feedback)
|
||||||
|
} else {
|
||||||
|
"Specified password is too weak! Please improve its strength."
|
||||||
|
.to_string()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasher = Argon2::default();
|
||||||
|
let salt = SaltString::generate(&mut ChaCha20Rng::from_entropy());
|
||||||
|
let password_hash = hasher
|
||||||
|
.hash_password(new_password.as_bytes(), &salt)?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Some(password_hash)
|
||||||
|
} else {
|
||||||
|
if !(actual_user.github_id.is_some()
|
||||||
|
|| actual_user.gitlab_id.is_some()
|
||||||
|
|| actual_user.microsoft_id.is_some()
|
||||||
|
|| actual_user.google_id.is_some()
|
||||||
|
|| actual_user.steam_id.is_some()
|
||||||
|
|| actual_user.discord_id.is_some())
|
||||||
|
{
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"You must have another authentication method added to remove password authentication!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE users
|
||||||
|
SET password = $1
|
||||||
|
WHERE (id = $2)
|
||||||
|
",
|
||||||
|
update_password,
|
||||||
|
id as crate::database::models::ids::UserId,
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?;
|
User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?;
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
@ -406,6 +491,7 @@ pub struct Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[patch("{id}/icon")]
|
#[patch("{id}/icon")]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn user_icon_edit(
|
pub async fn user_icon_edit(
|
||||||
web::Query(ext): web::Query<Extension>,
|
web::Query(ext): web::Query<Extension>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@ -414,10 +500,11 @@ pub async fn user_icon_edit(
|
|||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(actual_user) = id_option {
|
if let Some(actual_user) = id_option {
|
||||||
@ -492,8 +579,9 @@ pub async fn user_delete(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
removal_type: web::Query<RemovalType>,
|
removal_type: web::Query<RemovalType>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
@ -531,8 +619,9 @@ pub async fn user_follows(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
@ -578,8 +667,9 @@ pub async fn user_notifications(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
@ -617,8 +707,9 @@ pub async fn user_payouts(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
@ -691,12 +782,13 @@ pub async fn user_payouts_request(
|
|||||||
info: web::Path<(String,)>,
|
info: web::Path<(String,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
data: web::Json<PayoutData>,
|
data: web::Json<PayoutData>,
|
||||||
payouts_queue: web::Data<Arc<Mutex<PayoutsQueue>>>,
|
payouts_queue: web::Data<Mutex<PayoutsQueue>>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let mut payouts_queue = payouts_queue.lock().await;
|
let mut payouts_queue = payouts_queue.lock().await;
|
||||||
|
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(id) = id_option.map(|x| x.id) {
|
if let Some(id) = id_option.map(|x| x.id) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ use crate::models::projects::{
|
|||||||
VersionId, VersionStatus, VersionType,
|
VersionId, VersionStatus, VersionType,
|
||||||
};
|
};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::util::routes::read_from_field;
|
use crate::util::routes::read_from_field;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use crate::validate::{validate_file, ValidationResult};
|
use crate::validate::{validate_file, ValidationResult};
|
||||||
@ -84,6 +85,7 @@ pub async fn version_create(
|
|||||||
client: Data<PgPool>,
|
client: Data<PgPool>,
|
||||||
redis: Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
|
session_queue: Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let mut transaction = client.begin().await?;
|
let mut transaction = client.begin().await?;
|
||||||
let mut uploaded_files = Vec::new();
|
let mut uploaded_files = Vec::new();
|
||||||
@ -96,6 +98,7 @@ pub async fn version_create(
|
|||||||
&***file_host,
|
&***file_host,
|
||||||
&mut uploaded_files,
|
&mut uploaded_files,
|
||||||
&client,
|
&client,
|
||||||
|
&session_queue,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -115,6 +118,7 @@ pub async fn version_create(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn version_create_inner(
|
async fn version_create_inner(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
payload: &mut Multipart,
|
payload: &mut Multipart,
|
||||||
@ -123,6 +127,7 @@ async fn version_create_inner(
|
|||||||
file_host: &dyn FileHost,
|
file_host: &dyn FileHost,
|
||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
|
session_queue: &SessionQueue,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
|
|
||||||
@ -132,7 +137,7 @@ async fn version_create_inner(
|
|||||||
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||||
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
||||||
|
|
||||||
let user = get_user_from_headers(req.headers(), pool, redis).await?;
|
let user = get_user_from_headers(&req, pool, redis, session_queue).await?;
|
||||||
|
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
while let Some(item) = payload.next().await {
|
while let Some(item) = payload.next().await {
|
||||||
@ -434,8 +439,9 @@ pub async fn upload_file_to_version(
|
|||||||
url_data: web::Path<(VersionId,)>,
|
url_data: web::Path<(VersionId,)>,
|
||||||
mut payload: Multipart,
|
mut payload: Multipart,
|
||||||
client: Data<PgPool>,
|
client: Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: Data<deadpool_redis::Pool>,
|
||||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let mut transaction = client.begin().await?;
|
let mut transaction = client.begin().await?;
|
||||||
let mut uploaded_files = Vec::new();
|
let mut uploaded_files = Vec::new();
|
||||||
@ -451,6 +457,7 @@ pub async fn upload_file_to_version(
|
|||||||
&***file_host,
|
&***file_host,
|
||||||
&mut uploaded_files,
|
&mut uploaded_files,
|
||||||
version_id,
|
version_id,
|
||||||
|
&session_queue,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -480,13 +487,14 @@ async fn upload_file_to_version_inner(
|
|||||||
file_host: &dyn FileHost,
|
file_host: &dyn FileHost,
|
||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
version_id: models::VersionId,
|
version_id: models::VersionId,
|
||||||
|
session_queue: &SessionQueue,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
|
|
||||||
let mut initial_file_data: Option<InitialFileData> = None;
|
let mut initial_file_data: Option<InitialFileData> = None;
|
||||||
let mut file_builders: Vec<VersionFileBuilder> = Vec::new();
|
let mut file_builders: Vec<VersionFileBuilder> = Vec::new();
|
||||||
|
|
||||||
let user = get_user_from_headers(req.headers(), &**client, &redis).await?;
|
let user = get_user_from_headers(&req, &**client, &redis, session_queue).await?;
|
||||||
|
|
||||||
let result = models::Version::get(version_id, &**client, &redis).await?;
|
let result = models::Version::get(version_id, &**client, &redis).await?;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ use crate::auth::{
|
|||||||
use crate::models::ids::VersionId;
|
use crate::models::ids::VersionId;
|
||||||
use crate::models::projects::VersionType;
|
use crate::models::projects::VersionType;
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::{database, models};
|
use crate::{database, models};
|
||||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -48,8 +49,9 @@ pub async fn get_version_from_hash(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
hash_query: web::Query<HashQuery>,
|
hash_query: web::Query<HashQuery>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -93,8 +95,9 @@ pub async fn download_version(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
hash_query: web::Query<HashQuery>,
|
hash_query: web::Query<HashQuery>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -135,8 +138,9 @@ pub async fn delete_file(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
hash_query: web::Query<HashQuery>,
|
hash_query: web::Query<HashQuery>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
let hash = info.into_inner().0.to_lowercase();
|
let hash = info.into_inner().0.to_lowercase();
|
||||||
|
|
||||||
@ -230,8 +234,9 @@ pub async fn get_update_from_hash(
|
|||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
hash_query: web::Query<HashQuery>,
|
hash_query: web::Query<HashQuery>,
|
||||||
update_data: web::Json<UpdateData>,
|
update_data: web::Json<UpdateData>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
let hash = info.into_inner().0.to_lowercase();
|
let hash = info.into_inner().0.to_lowercase();
|
||||||
@ -299,8 +304,9 @@ pub async fn get_versions_from_hashes(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_data: web::Json<FileHashes>,
|
file_data: web::Json<FileHashes>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -339,8 +345,9 @@ pub async fn get_projects_from_hashes(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
file_data: web::Json<FileHashes>,
|
file_data: web::Json<FileHashes>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -389,8 +396,9 @@ pub async fn update_files(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
update_data: web::Json<ManyUpdateData>,
|
update_data: web::Json<ManyUpdateData>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ use crate::database;
|
|||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType};
|
use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
|
use crate::queue::session::SessionQueue;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@ -44,12 +45,13 @@ pub async fn version_list(
|
|||||||
web::Query(filters): web::Query<VersionListFilters>,
|
web::Query(filters): web::Query<VersionListFilters>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -152,12 +154,13 @@ pub async fn version_project_get(
|
|||||||
info: web::Path<(String, String)>,
|
info: web::Path<(String, String)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner();
|
let id = info.into_inner();
|
||||||
let version_data =
|
let version_data =
|
||||||
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool, &redis).await?;
|
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -181,6 +184,7 @@ pub async fn versions_get(
|
|||||||
web::Query(ids): web::Query<VersionIds>,
|
web::Query(ids): web::Query<VersionIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -188,7 +192,7 @@ pub async fn versions_get(
|
|||||||
.collect::<Vec<database::models::VersionId>>();
|
.collect::<Vec<database::models::VersionId>>();
|
||||||
let versions_data = database::models::Version::get_many(&version_ids, &**pool, &redis).await?;
|
let versions_data = database::models::Version::get_many(&version_ids, &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -203,11 +207,12 @@ pub async fn version_get(
|
|||||||
info: web::Path<(models::ids::VersionId,)>,
|
info: web::Path<(models::ids::VersionId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
let version_data = database::models::Version::get(id.into(), &**pool, &redis).await?;
|
let version_data = database::models::Version::get(id.into(), &**pool, &redis).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
|
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -263,8 +268,9 @@ pub async fn version_edit(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
new_version: web::Json<EditVersion>,
|
new_version: web::Json<EditVersion>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
new_version
|
new_version
|
||||||
.validate()
|
.validate()
|
||||||
@ -639,8 +645,9 @@ pub async fn version_schedule(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
scheduling_data: web::Json<SchedulingData>,
|
scheduling_data: web::Json<SchedulingData>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
|
|
||||||
if scheduling_data.time < Utc::now() {
|
if scheduling_data.time < Utc::now() {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
@ -704,8 +711,9 @@ pub async fn version_delete(
|
|||||||
info: web::Path<(models::ids::VersionId,)>,
|
info: web::Path<(models::ids::VersionId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<deadpool_redis::Pool>,
|
redis: web::Data<deadpool_redis::Pool>,
|
||||||
|
session_queue: web::Data<SessionQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
|
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
let version = database::models::Version::get(id.into(), &**pool, &redis)
|
let version = database::models::Version::get(id.into(), &**pool, &redis)
|
||||||
|
|||||||
41
src/util/captcha.rs
Normal file
41
src/util/captcha.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use crate::routes::ApiError;
|
||||||
|
use crate::util::env::parse_var;
|
||||||
|
use actix_web::HttpRequest;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn check_turnstile_captcha(req: &HttpRequest, challenge: &str) -> Result<bool, ApiError> {
|
||||||
|
let conn_info = req.connection_info().clone();
|
||||||
|
let ip_addr = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
||||||
|
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||||
|
header.to_str().ok()
|
||||||
|
} else {
|
||||||
|
conn_info.peer_addr()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conn_info.peer_addr()
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Response {
|
||||||
|
success: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
let val: Response = client
|
||||||
|
.post("https://challenges.cloudflare.com/turnstile/v0/siteverify")
|
||||||
|
.json(&json!({
|
||||||
|
"secret": dotenvy::var("TURNSTILE_SECRET")?,
|
||||||
|
"response": challenge,
|
||||||
|
"remoteip": ip_addr,
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|_| ApiError::Turnstile)?
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|_| ApiError::Turnstile)?;
|
||||||
|
|
||||||
|
Ok(val.success)
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
pub mod captcha;
|
||||||
pub mod env;
|
pub mod env;
|
||||||
pub mod ext;
|
pub mod ext;
|
||||||
pub mod guards;
|
pub mod guards;
|
||||||
|
|||||||
@ -260,9 +260,7 @@ pub async fn send_discord_webhook(
|
|||||||
})
|
})
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| ApiError::Discord("Error while sending projects webhook".to_string()))?;
|
||||||
ApiError::DiscordError("Error while sending projects webhook".to_string())
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user