admin authentication

This commit is contained in:
Aravinth Manivannan 2021-10-11 09:56:15 +05:30
parent 71199aa9ce
commit d2c4e9a06b
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
12 changed files with 1089 additions and 264 deletions

359
Cargo.lock generated
View File

@ -91,12 +91,12 @@ dependencies = [
"percent-encoding",
"pin-project",
"pin-project-lite",
"rand",
"rand 0.8.4",
"regex",
"serde 1.0.130",
"sha-1",
"smallvec",
"time",
"time 0.2.27",
"tokio",
"zstd",
]
@ -112,7 +112,7 @@ dependencies = [
"futures-util",
"serde 1.0.130",
"serde_json",
"time",
"time 0.2.27",
]
[[package]]
@ -242,7 +242,7 @@ dependencies = [
"serde_urlencoded",
"smallvec",
"socket2",
"time",
"time 0.2.27",
"url",
]
@ -346,7 +346,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
dependencies = [
"getrandom",
"getrandom 0.2.3",
"once_cell",
"version_check",
]
@ -360,6 +360,43 @@ dependencies = [
"memchr",
]
[[package]]
name = "ammonia"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e445c26125ff80316eaea16e812d717b147b82a68682bd4730f74d4845c8b35"
dependencies = [
"html5ever",
"lazy_static",
"maplit",
"markup5ever_rcdom",
"matches",
"tendril",
"url",
]
[[package]]
name = "argon2-creds"
version = "0.2.1"
source = "git+https://github.com/realaravinth/argon2-creds?branch=master#2a3df16a6148ac1f48121b87232f24975f45a9c0"
dependencies = [
"ammonia",
"derive_builder",
"derive_more",
"lazy_static",
"rand 0.8.4",
"regex",
"rust-argon2",
"unicode-normalization",
"validator",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.5.2"
@ -430,6 +467,17 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2b_simd"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
@ -561,6 +609,12 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "convert_case"
version = "0.4.0"
@ -578,10 +632,10 @@ dependencies = [
"hkdf",
"hmac",
"percent-encoding",
"rand",
"rand 0.8.4",
"sha2",
"subtle",
"time",
"time 0.2.27",
"version_check",
]
@ -903,6 +957,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
dependencies = [
"mac",
"new_debug_unreachable",
]
[[package]]
name = "futures"
version = "0.3.17"
@ -1018,6 +1082,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.3"
@ -1026,7 +1101,7 @@ checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
@ -1129,6 +1204,20 @@ dependencies = [
"winapi",
]
[[package]]
name = "html5ever"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b"
dependencies = [
"log",
"mac",
"markup5ever",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "http"
version = "0.2.5"
@ -1278,7 +1367,7 @@ dependencies = [
"log",
"pow_sha256",
"pretty_env_logger",
"rand",
"rand 0.8.4",
"redis",
"serde 1.0.130",
"serde_json",
@ -1327,6 +1416,44 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "mac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "markup5ever"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
dependencies = [
"log",
"phf",
"phf_codegen",
"string_cache",
"string_cache_codegen",
"tendril",
]
[[package]]
name = "markup5ever_rcdom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b"
dependencies = [
"html5ever",
"markup5ever",
"tendril",
"xml5ever",
]
[[package]]
name = "matches"
version = "0.1.9"
@ -1404,6 +1531,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nom"
version = "5.1.2"
@ -1627,6 +1760,44 @@ dependencies = [
"ucd-trie",
]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand 0.7.3",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.0.8"
@ -1694,6 +1865,12 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
@ -1764,6 +1941,20 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
"rand_pcg",
]
[[package]]
name = "rand"
version = "0.8.4"
@ -1771,9 +1962,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_hc 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
@ -1783,7 +1984,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.3",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
@ -1792,7 +2002,16 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
"getrandom 0.2.3",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
@ -1801,7 +2020,16 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
"rand_core 0.6.3",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
@ -1819,7 +2047,7 @@ dependencies = [
"itoa",
"percent-encoding",
"pin-project-lite",
"rand",
"rand 0.8.4",
"sha1",
"tokio",
"tokio-util",
@ -1841,7 +2069,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"getrandom 0.2.3",
"redox_syscall",
]
@ -1877,6 +2105,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "rust-argon2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
dependencies = [
"base64",
"blake2b_simd",
"constant_time_eq",
"crossbeam-utils",
]
[[package]]
name = "rust-embed"
version = "6.2.0"
@ -2152,6 +2392,12 @@ dependencies = [
"libc",
]
[[package]]
name = "siphasher"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
[[package]]
name = "slab"
version = "0.4.4"
@ -2234,7 +2480,7 @@ dependencies = [
"once_cell",
"parking_lot",
"percent-encoding",
"rand",
"rand 0.8.4",
"rustls",
"serde 1.0.130",
"serde_json",
@ -2245,7 +2491,7 @@ dependencies = [
"sqlx-rt",
"stringprep",
"thiserror",
"time",
"time 0.2.27",
"tokio-stream",
"url",
"uuid",
@ -2353,6 +2599,31 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "string_cache"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"phf_shared",
"precomputed-hash",
"serde 1.0.130",
]
[[package]]
name = "string_cache_codegen"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
]
[[package]]
name = "stringprep"
version = "0.1.2"
@ -2387,6 +2658,7 @@ dependencies = [
"actix-service",
"actix-web",
"actix-web-codegen 0.5.0-beta.4 (git+https://github.com/realaravinth/actix-web)",
"argon2-creds",
"cache-buster",
"config",
"derive_builder",
@ -2399,7 +2671,7 @@ dependencies = [
"mime_guess",
"openssl",
"pretty_env_logger",
"rand",
"rand 0.8.4",
"rust-embed",
"sailfish",
"serde 1.0.130",
@ -2422,6 +2694,17 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tendril"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33"
dependencies = [
"futf",
"mac",
"utf-8",
]
[[package]]
name = "termcolor"
version = "1.1.2"
@ -2451,6 +2734,16 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "time"
version = "0.2.27"
@ -2670,13 +2963,19 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom",
"getrandom 0.2.3",
]
[[package]]
@ -2745,6 +3044,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
@ -2875,6 +3180,18 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "xml5ever"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59"
dependencies = [
"log",
"mac",
"markup5ever",
"time 0.1.43",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"

View File

@ -37,6 +37,8 @@ futures = "0.3.15"
sqlx = { version = "0.5.5", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] }
argon2-creds = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"}
derive_builder = "0.10"
validator = { version = "0.14", features = ["derive"]}
derive_more = "0.99"

View File

@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS survey_users (
ID UUID PRIMARY KEY NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL
)
);

View File

@ -0,0 +1,17 @@
-- Add migration script here
CREATE TABLE IF NOT EXISTS survey_admins (
name VARCHAR(100) NOT NULL UNIQUE,
email VARCHAR(100) UNIQUE DEFAULT NULL,
email_verified BOOLEAN DEFAULT NULL,
secret varchar(50) NOT NULL UNIQUE,
password TEXT NOT NULL,
ID SERIAL PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS survey_challenges (
ID UUID PRIMARY KEY NOT NULL UNIQUE,
user_id INTEGER NOT NULL references survey_admins(ID) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,
difficulties INTEGER[] NOT NULL,
created_at TIMESTAMPTZ NOT NULL
)

View File

@ -1,7 +1,110 @@
{
"db": "PostgreSQL",
"0d27c6787e145f980063f008266ec00bd96f5a761d6f23bbe2bdc63a5163ba69": {
"query": "SELECT ID FROM survey_responses \n WHERE user_id = $1 AND device_software_recognised = $2",
"07dd9f4c2edd99714b3de90365fdae4f874c66a736c308df6e668ef9b86737dc": {
"query": "INSERT INTO survey_responses (\n user_id, \n device_user_provided,\n device_software_recognised,\n threads\n ) VALUES ($1, $2, $3, $4);",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Varchar",
"Varchar",
"Int4"
]
},
"nullable": []
}
},
"0d22134cc5076304b7895827f006ee8269cc500f400114a7472b83f0f1c568b5": {
"query": "INSERT INTO survey_admins \n (name , password, secret) VALUES ($1, $2, $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Varchar"
]
},
"nullable": []
}
},
"1373df097fa0e58b23a374753318ae53a44559aa0e7eb64680185baf1c481723": {
"query": "SELECT password FROM survey_admins WHERE name = ($1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "password",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
}
},
"19686bfe8772cbc6831d46d18994e2b9aa40c7181eae9a31e51451cce95f04e8": {
"query": "SELECT name, password FROM survey_admins WHERE email = ($1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "password",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false
]
}
},
"1b7e17bfc949fa97e8dec1f95e35a02bcf3aa1aa72a1f6f6c8884e885fc3b953": {
"query": "insert into survey_admins \n (name , password, email, secret) values ($1, $2, $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Varchar",
"Varchar"
]
},
"nullable": []
}
},
"43b3e771f38bf8059832169227705be06a28925af1b3799ffef5371d511fd138": {
"query": "\n INSERT INTO survey_users (created_at, id) VALUES($1, $2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Timestamptz",
"Uuid"
]
},
"nullable": []
}
},
"8320dda2b3e107d1451fdfb35eb2a4b8e97364e7b1b74ffe4d6913faf132fb61": {
"query": "SELECT ID \n FROM survey_responses \n WHERE \n user_id = $1 \n AND \n device_software_recognised = $2;",
"describe": {
"columns": [
{
@ -21,37 +124,8 @@
]
}
},
"b50210741c2fa689a8bd7f211a650c6616178f927c2c9320e5b6e25dc7c3cc1a": {
"query": "INSERT INTO survey_response_tokens (resp_id, user_id, id)\n VALUES ($1, $2, $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Uuid"
]
},
"nullable": []
}
},
"cbf1bac274a6a1b1339615b406b9127c4f314080450e7bd5a5652f5febfb0d21": {
"query": "INSERT INTO survey_responses (user_id, device_user_provided, device_software_recognised,\n threads) VALUES ($1, $2, $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Varchar",
"Varchar",
"Int4"
]
},
"nullable": []
}
},
"d958feaec808739d8fddc2d6a3b560fae2e5aa496c44b24a6f70e73ebb291568": {
"query": "INSERT INTO survey_benches \n (resp_id, difficulty, duration) VALUES ($1, $2, $3)",
"a721cfa249acf328c2f29c4cf8c2aeba1a635bcf49d18ced5474caa10b7cae4f": {
"query": "INSERT INTO survey_benches \n (resp_id, difficulty, duration) \n VALUES ($1, $2, $3);",
"describe": {
"columns": [],
"parameters": {
@ -63,5 +137,19 @@
},
"nullable": []
}
},
"fcdc5fe5d496eb516c805e64ec96d9626b74ab33cd6e75e5a08ae88967403b72": {
"query": "INSERT INTO survey_response_tokens \n (resp_id, user_id, id)\n VALUES ($1, $2, $3);",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Uuid"
]
},
"nullable": []
}
}
}

View File

@ -14,72 +14,221 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::borrow::Cow;
use actix_identity::Identity;
use actix_web::http::header;
use actix_web::{web, HttpResponse, Responder};
use sqlx::types::time::OffsetDateTime;
use serde::{Deserialize, Serialize};
use super::get_uuid;
use super::get_random;
use crate::errors::*;
use crate::AppData;
pub mod routes {
pub struct Auth {
pub logout: &'static str,
pub login: &'static str,
pub register: &'static str,
}
impl Auth {
pub const fn new() -> Auth {
let login = "/api/v1/signin";
let logout = "/logout";
let register = "/api/v1/signup";
Auth { register }
Auth {
logout,
login,
register,
}
}
}
}
pub mod runners {
// use std::borrow::Cow;
use std::borrow::Cow;
use super::*;
pub async fn register_runner(data: &AppData) -> ServiceResult<uuid::Uuid> {
let mut uuid;
let now = OffsetDateTime::now_utc();
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Register {
pub username: String,
pub password: String,
pub confirm_password: String,
pub email: Option<String>,
}
loop {
uuid = get_uuid();
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Login {
// login accepts both username and email under "username field"
// TODO update all instances where login is used
pub login: String,
pub password: String,
}
let res = sqlx::query!(
"
INSERT INTO survey_users (created_at, id) VALUES($1, $2)",
&now,
&uuid
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Password {
pub password: String,
}
/// returns Ok(()) when everything checks out and the user is authenticated. Erros otherwise
pub async fn login_runner(payload: &Login, data: &AppData) -> ServiceResult<String> {
use argon2_creds::Config;
use sqlx::Error::RowNotFound;
let verify = |stored: &str, received: &str| {
if Config::verify(stored, received)? {
Ok(())
} else {
Err(ServiceError::WrongPassword)
}
};
if payload.login.contains('@') {
#[derive(Clone, Debug)]
struct EmailLogin {
name: String,
password: String,
}
let email_fut = sqlx::query_as!(
EmailLogin,
r#"SELECT name, password FROM survey_admins WHERE email = ($1)"#,
&payload.login,
)
.execute(&data.db)
.fetch_one(&data.db)
.await;
match email_fut {
Ok(s) => {
verify(&s.password, &payload.password)?;
Ok(s.name)
}
Err(RowNotFound) => Err(ServiceError::AccountNotFound),
Err(_) => Err(ServiceError::InternalServerError),
}
} else {
let username_fut = sqlx::query_as!(
Password,
r#"SELECT password FROM survey_admins WHERE name = ($1)"#,
&payload.login,
)
.fetch_one(&data.db)
.await;
match username_fut {
Ok(s) => {
verify(&s.password, &payload.password)?;
Ok(payload.login.clone())
}
Err(RowNotFound) => Err(ServiceError::AccountNotFound),
Err(_) => Err(ServiceError::InternalServerError),
}
}
}
pub async fn register_runner(
payload: &Register,
data: &AppData,
) -> ServiceResult<()> {
if !crate::SETTINGS.allow_registration {
return Err(ServiceError::ClosedForRegistration);
}
if payload.password != payload.confirm_password {
return Err(ServiceError::PasswordsDontMatch);
}
let username = data.creds.username(&payload.username)?;
let hash = data.creds.password(&payload.password)?;
if let Some(email) = &payload.email {
data.creds.email(email)?;
}
let mut secret;
loop {
secret = get_random(32);
let res;
if let Some(email) = &payload.email {
res = sqlx::query!(
"insert into survey_admins
(name , password, email, secret) values ($1, $2, $3, $4)",
&username,
&hash,
&email,
&secret,
)
.execute(&data.db)
.await;
} else {
res = sqlx::query!(
"INSERT INTO survey_admins
(name , password, secret) VALUES ($1, $2, $3)",
&username,
&hash,
&secret,
)
.execute(&data.db)
.await;
}
if res.is_ok() {
break;
} else if let Err(sqlx::Error::Database(err)) = res {
if err.code() == Some(Cow::from("23505"))
&& err.message().contains("survey_users_id_key")
{
continue;
if err.code() == Some(Cow::from("23505")) {
let msg = err.message();
if msg.contains("survey_admins_name_key") {
return Err(ServiceError::UsernameTaken);
} else if msg.contains("survey_admins_email_key") {
return Err(ServiceError::EmailTaken);
} else if msg.contains("survey_admins_secret_key") {
continue;
} else {
return Err(ServiceError::InternalServerError);
}
} else {
return Err(sqlx::Error::Database(err).into());
}
}
};
}
Ok(uuid)
Ok(())
}
}
pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(register);
cfg.service(login);
cfg.service(signout);
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.register")]
async fn register(data: AppData, id: Identity) -> ServiceResult<impl Responder> {
let uuid = runners::register_runner(&data).await?;
id.remember(uuid.to_string());
async fn register(
payload: web::Json<runners::Register>,
data: AppData,
) -> ServiceResult<impl Responder> {
runners::register_runner(&payload, &data).await?;
Ok(HttpResponse::Ok())
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.login")]
async fn login(
id: Identity,
payload: web::Json<runners::Login>,
data: AppData,
) -> ServiceResult<impl Responder> {
let payload = payload.into_inner();
let username = runners::login_runner(&payload, &data).await?;
id.remember(username);
Ok(HttpResponse::Ok())
}
#[my_codegen::get(path = "crate::V1_API_ROUTES.auth.logout", wrap = "crate::CheckLogin")]
async fn signout(id: Identity) -> impl Responder {
if id.identity().is_some() {
id.forget();
}
HttpResponse::Found()
.append_header((header::LOCATION, crate::middleware::auth::AUTH))
.finish()
}

View File

@ -21,6 +21,7 @@ use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use futures::future::try_join_all;
use serde::{Deserialize, Serialize};
use sqlx::types::time::OffsetDateTime;
use uuid::Uuid;
use super::get_uuid;
@ -30,20 +31,69 @@ use crate::AppData;
pub mod routes {
pub struct Benches {
pub submit: &'static str,
pub register: &'static str,
pub scope: &'static str,
}
impl Benches {
pub const fn new() -> Benches {
let submit = "/api/v1/benches";
Benches { submit }
let submit = "/api/v1/benches/submit";
let register = "/api/v1/benches/register";
let scope = "/api/v1/benches/";
Benches { submit, register, scope }
}
}
}
pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(submit);
cfg.service(register);
}
pub mod runners {
use super::*;
pub async fn register_runner(data: &AppData) -> ServiceResult<uuid::Uuid> {
let mut uuid;
let now = OffsetDateTime::now_utc();
loop {
uuid = get_uuid();
let res = sqlx::query!(
"
INSERT INTO survey_users (created_at, id) VALUES($1, $2)",
&now,
&uuid
)
.execute(&data.db)
.await;
if res.is_ok() {
break;
} else if let Err(sqlx::Error::Database(err)) = res {
if err.code() == Some(Cow::from("23505"))
&& err.message().contains("survey_users_id_key")
{
continue;
} else {
return Err(sqlx::Error::Database(err).into());
}
}
}
Ok(uuid)
}
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.benches.register")]
async fn register(data: AppData, id: Identity) -> ServiceResult<impl Responder> {
let uuid = runners::register_runner(&data).await?;
id.remember(uuid.to_string());
Ok(HttpResponse::Ok())
}
#[derive(Serialize, Deserialize)]
struct Bench {
duration: f32,

85
src/api/v1/challenges.rs Normal file
View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::borrow::Cow;
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use sqlx::types::time::OffsetDateTime;
use super::get_uuid;
use crate::errors::*;
use crate::AppData;
pub mod routes {
pub struct Challenges {
pub add: &'static str,
}
impl Challenges {
pub const fn new() -> Challenges {
let add = "/api/v1/challenges/add";
Challenges { add }
}
}
}
pub mod runners {
// use std::borrow::Cow;
use super::*;
pub async fn add_runner(data: &AppData) -> ServiceResult<uuid::Uuid> {
let mut uuid;
let now = OffsetDateTime::now_utc();
loop {
uuid = get_uuid();
let res = sqlx::query!(
"
INSERT INTO survey_users (created_at, id) VALUES($1, $2)",
&now,
&uuid
)
.execute(&data.db)
.await;
if res.is_ok() {
break;
} else if let Err(sqlx::Error::Database(err)) = res {
if err.code() == Some(Cow::from("23505"))
&& err.message().contains("survey_users_id_key")
{
continue;
} else {
return Err(sqlx::Error::Database(err).into());
}
}
}
Ok(uuid)
}
}
pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(add);
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.add")]
async fn add(data: AppData, id: Identity) -> ServiceResult<impl Responder> {
let uuid = runners::add_runner(&data).await?;
id.remember(uuid.to_string());
Ok(HttpResponse::Ok())
}

View File

@ -16,9 +16,11 @@
*/
//! App data: database connections, etc.
use std::sync::Arc;
use std::thread;
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use argon2_creds::{Config, ConfigBuilder, PasswordPolicy};
use crate::SETTINGS;
@ -26,19 +28,41 @@ use crate::SETTINGS;
pub struct Data {
/// databse pool
pub db: PgPool,
pub creds: Config,
}
impl Data {
pub fn get_creds() -> Config {
ConfigBuilder::default()
.username_case_mapped(true)
.profanity(true)
.blacklist(true)
.password_policy(PasswordPolicy::default())
.build()
.unwrap()
}
#[cfg(not(tarpaulin_include))]
/// create new instance of app data
pub async fn new() -> Arc<Self> {
let creds = Self::get_creds();
let c = creds.clone();
#[allow(unused_variables)]
let init = thread::spawn(move || {
log::info!("Initializing credential manager");
c.init();
log::info!("Initialized credential manager");
});
let db = PgPoolOptions::new()
.max_connections(SETTINGS.database.pool)
.connect(&SETTINGS.database.url)
.await
.expect("Unable to form database pool");
let data = Data { db };
#[cfg(not(debug_assertions))]
init.join().unwrap();
let data = Data { db, creds };
Arc::new(data)
}

View File

@ -17,6 +17,8 @@
use std::convert::From;
use actix::MailboxError;
use argon2_creds::errors::CredsError;
use actix_web::{
error::ResponseError,
http::{header, StatusCode},
@ -39,15 +41,48 @@ pub enum ServiceError {
#[display(fmt = "Username not available")]
UsernameTaken,
#[display(
fmt = "This server is is closed for registration. Contact admin if this is unexpecter"
)]
ClosedForRegistration,
/// email is already taken
#[display(fmt = "Email not available")]
EmailTaken,
#[display(fmt = "The value you entered for email is not an email")] //405j
NotAnEmail,
/// when the a token name is already taken
/// token not found
#[display(fmt = "Token not found. Is token registered?")]
TokenNotFound,
#[display(fmt = "Wrong password")]
WrongPassword,
#[display(fmt = "Account not found")]
AccountNotFound,
/// when the value passed contains profainity
#[display(fmt = "Can't allow profanity in usernames")]
ProfainityError,
/// when the value passed contains blacklisted words
/// see [blacklist](https://github.com/shuttlecraft/The-Big-Username-Blacklist)
#[display(fmt = "Username contains blacklisted words")]
BlacklistError,
/// when the value passed contains characters not present
/// in [UsernameCaseMapped](https://tools.ietf.org/html/rfc8265#page-7)
/// profile
#[display(fmt = "username_case_mapped violation")]
UsernameCaseMappedError,
#[display(fmt = "Passsword too short")]
PasswordTooShort,
#[display(fmt = "Username too long")]
PasswordTooLong,
#[display(fmt = "Passwords don't match")]
PasswordsDontMatch,
#[display(fmt = "{}", _0)]
CaptchaError(CaptchaError),
}
@ -76,10 +111,22 @@ impl ResponseError for ServiceError {
#[cfg(not(tarpaulin_include))]
fn status_code(&self) -> StatusCode {
match self {
ServiceError::ClosedForRegistration => StatusCode::FORBIDDEN,
ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
ServiceError::UsernameTaken => StatusCode::BAD_REQUEST,
ServiceError::EmailTaken => StatusCode::BAD_REQUEST,
ServiceError::NotAnEmail => StatusCode::BAD_REQUEST,
ServiceError::WrongPassword => StatusCode::UNAUTHORIZED,
ServiceError::AccountNotFound => StatusCode::NOT_FOUND,
ServiceError::ProfainityError => StatusCode::BAD_REQUEST,
ServiceError::BlacklistError => StatusCode::BAD_REQUEST,
ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST,
ServiceError::PasswordTooShort => StatusCode::BAD_REQUEST,
ServiceError::PasswordTooLong => StatusCode::BAD_REQUEST,
ServiceError::PasswordsDontMatch => StatusCode::BAD_REQUEST,
ServiceError::TokenNotFound => StatusCode::NOT_FOUND,
ServiceError::CaptchaError(e) => {
@ -111,6 +158,21 @@ impl From<sqlx::Error> for ServiceError {
}
}
impl From<CredsError> for ServiceError {
#[cfg(not(tarpaulin_include))]
fn from(e: CredsError) -> ServiceError {
match e {
CredsError::UsernameCaseMappedError => ServiceError::UsernameCaseMappedError,
CredsError::ProfainityError => ServiceError::ProfainityError,
CredsError::BlacklistError => ServiceError::BlacklistError,
CredsError::NotAnEmail => ServiceError::NotAnEmail,
CredsError::Argon2Error(_) => ServiceError::InternalServerError,
CredsError::PasswordTooLong => ServiceError::PasswordTooLong,
CredsError::PasswordTooShort => ServiceError::PasswordTooShort,
}
}
}
#[cfg(not(tarpaulin_include))]
impl From<RecvError> for ServiceError {
#[cfg(not(tarpaulin_include))]

View File

@ -103,6 +103,7 @@ async fn main() -> std::io::Result<()> {
.header("Permissions-Policy", "interest-cohort=()"),
)
.wrap(get_identity_service())
.wrap(get_survey_identity_service())
.wrap(actix_middleware::NormalizePath::new(
actix_middleware::TrailingSlash::Trim,
))
@ -124,18 +125,32 @@ pub fn get_json_err() -> JsonConfig {
}
#[cfg(not(tarpaulin_include))]
pub fn get_identity_service() -> IdentityService<CookieIdentityPolicy> {
pub fn get_survey_identity_service() -> IdentityService<CookieIdentityPolicy> {
let cookie_secret = &SETTINGS.server.cookie_secret;
IdentityService::new(
CookieIdentityPolicy::new(cookie_secret.as_bytes())
.name("survey-id")
//TODO change cookie age
.max_age_secs(216000)
.path(V1_API_ROUTES.benches.scope)
.max_age_secs(30 * 60)
.domain(&SETTINGS.server.domain)
.secure(false),
)
}
#[cfg(not(tarpaulin_include))]
pub fn get_identity_service() -> IdentityService<CookieIdentityPolicy> {
let cookie_secret = &SETTINGS.server.cookie_secret;
IdentityService::new(
CookieIdentityPolicy::new(cookie_secret.as_bytes())
.path("/admin")
.name("survey-auth")
.max_age_secs(60 * 24)
.domain(&SETTINGS.server.domain)
.secure(false),
)
}
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
//pages::services(cfg);
api::v1::services(cfg);

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::sync::Arc;
use actix_web::cookie::Cookie;
@ -81,181 +97,181 @@ macro_rules! get_app {
};
}
/// register and signin utility
pub async fn register_and_signin(
name: &str,
email: &str,
password: &str,
) -> (Arc<data::Data>, Login, ServiceResponse) {
register(name, email, password).await;
signin(name, password).await
}
/// register utility
pub async fn register(name: &str, email: &str, password: &str) {
let data = Data::new().await;
let app = get_app!(data).await;
// 1. Register
let msg = Register {
username: name.into(),
password: password.into(),
confirm_password: password.into(),
email: Some(email.into()),
};
let resp =
test::call_service(&app, post_request!(&msg, ROUTES.auth.register).to_request())
.await;
assert_eq!(resp.status(), StatusCode::OK);
}
/// signin util
pub async fn signin(name: &str, password: &str) -> (Arc<Data>, Login, ServiceResponse) {
let data = Data::new().await;
let app = get_app!(data.clone()).await;
// 2. signin
let creds = Login {
login: name.into(),
password: password.into(),
};
let signin_resp =
test::call_service(&app, post_request!(&creds, ROUTES.auth.login).to_request())
.await;
assert_eq!(signin_resp.status(), StatusCode::OK);
(data, creds, signin_resp)
}
/// pub duplicate test
pub async fn bad_post_req_test<T: Serialize>(
name: &str,
password: &str,
url: &str,
payload: &T,
err: ServiceError,
) {
let (data, _, signin_resp) = signin(name, password).await;
let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await;
let resp = test::call_service(
&app,
post_request!(&payload, url)
.cookie(cookies.clone())
.to_request(),
)
.await;
assert_eq!(resp.status(), err.status_code());
let resp_err: ErrorToResponse = test::read_body_json(resp).await;
//println!("{}", txt.error);
assert_eq!(resp_err.error, format!("{}", err));
}
/// bad post req test without payload
pub async fn bad_post_req_test_witout_payload(
name: &str,
password: &str,
url: &str,
err: ServiceError,
) {
let (data, _, signin_resp) = signin(name, password).await;
let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await;
let resp = test::call_service(
&app,
post_request!(url).cookie(cookies.clone()).to_request(),
)
.await;
assert_eq!(resp.status(), err.status_code());
let resp_err: ErrorToResponse = test::read_body_json(resp).await;
//println!("{}", txt.error);
assert_eq!(resp_err.error, format!("{}", err));
}
pub async fn create_new_campaign(
campaign_name: &str,
data: Arc<Data>,
cookies: Cookie<'_>,
) -> CreateResp {
let new = CreateReq {
name: campaign_name.into(),
};
let app = get_app!(data).await;
let new_resp = test::call_service(
&app,
post_request!(&new, crate::V1_API_ROUTES.campaign.new)
.cookie(cookies)
.to_request(),
)
.await;
assert_eq!(new_resp.status(), StatusCode::OK);
let uuid: CreateResp = test::read_body_json(new_resp).await;
uuid
}
pub async fn delete_campaign(
camapign: &CreateResp,
data: Arc<Data>,
cookies: Cookie<'_>,
) {
let del_route = V1_API_ROUTES.campaign.get_delete_route(&camapign.uuid);
let app = get_app!(data).await;
let del_resp =
test::call_service(&app, post_request!(&del_route).cookie(cookies).to_request())
.await;
assert_eq!(del_resp.status(), StatusCode::OK);
}
pub async fn list_campaings(
data: Arc<Data>,
cookies: Cookie<'_>,
) -> Vec<ListCampaignResp> {
let app = get_app!(data).await;
let list_resp = test::call_service(
&app,
post_request!(crate::V1_API_ROUTES.campaign.list)
.cookie(cookies)
.to_request(),
)
.await;
assert_eq!(list_resp.status(), StatusCode::OK);
test::read_body_json(list_resp).await
}
pub async fn add_feedback(
rating: &RatingReq,
campaign: &CreateResp,
data: Arc<Data>,
) -> RatingResp {
let add_feedback_route = V1_API_ROUTES.feedback.add_feedback_route(&campaign.uuid);
let app = get_app!(data).await;
let add_feedback_resp = test::call_service(
&app,
post_request!(&rating, &add_feedback_route).to_request(),
)
.await;
assert_eq!(add_feedback_resp.status(), StatusCode::OK);
test::read_body_json(add_feedback_resp).await
}
pub async fn get_feedback(
campaign: &CreateResp,
data: Arc<Data>,
cookies: Cookie<'_>,
) -> GetFeedbackResp {
let get_feedback_route = V1_API_ROUTES.campaign.get_feedback_route(&campaign.uuid);
let app = get_app!(data).await;
let get_feedback_resp = test::call_service(
&app,
post_request!(&get_feedback_route)
.cookie(cookies)
.to_request(),
)
.await;
assert_eq!(get_feedback_resp.status(), StatusCode::OK);
test::read_body_json(get_feedback_resp).await
}
///// register and signin utility
//pub async fn register_and_signin(
// name: &str,
// email: &str,
// password: &str,
//) -> (Arc<data::Data>, Login, ServiceResponse) {
// register(name, email, password).await;
// signin(name, password).await
//}
//
///// register utility
//pub async fn register(name: &str, email: &str, password: &str) {
// let data = Data::new().await;
// let app = get_app!(data).await;
//
// // 1. Register
// let msg = Register {
// username: name.into(),
// password: password.into(),
// confirm_password: password.into(),
// email: Some(email.into()),
// };
// let resp =
// test::call_service(&app, post_request!(&msg, ROUTES.auth.register).to_request())
// .await;
// assert_eq!(resp.status(), StatusCode::OK);
//}
//
///// signin util
//pub async fn signin(name: &str, password: &str) -> (Arc<Data>, Login, ServiceResponse) {
// let data = Data::new().await;
// let app = get_app!(data.clone()).await;
//
// // 2. signin
// let creds = Login {
// login: name.into(),
// password: password.into(),
// };
// let signin_resp =
// test::call_service(&app, post_request!(&creds, ROUTES.auth.login).to_request())
// .await;
// assert_eq!(signin_resp.status(), StatusCode::OK);
// (data, creds, signin_resp)
//}
//
///// pub duplicate test
//pub async fn bad_post_req_test<T: Serialize>(
// name: &str,
// password: &str,
// url: &str,
// payload: &T,
// err: ServiceError,
//) {
// let (data, _, signin_resp) = signin(name, password).await;
// let cookies = get_cookie!(signin_resp);
// let app = get_app!(data).await;
//
// let resp = test::call_service(
// &app,
// post_request!(&payload, url)
// .cookie(cookies.clone())
// .to_request(),
// )
// .await;
// assert_eq!(resp.status(), err.status_code());
// let resp_err: ErrorToResponse = test::read_body_json(resp).await;
// //println!("{}", txt.error);
// assert_eq!(resp_err.error, format!("{}", err));
//}
//
///// bad post req test without payload
//pub async fn bad_post_req_test_witout_payload(
// name: &str,
// password: &str,
// url: &str,
// err: ServiceError,
//) {
// let (data, _, signin_resp) = signin(name, password).await;
// let cookies = get_cookie!(signin_resp);
// let app = get_app!(data).await;
//
// let resp = test::call_service(
// &app,
// post_request!(url).cookie(cookies.clone()).to_request(),
// )
// .await;
// assert_eq!(resp.status(), err.status_code());
// let resp_err: ErrorToResponse = test::read_body_json(resp).await;
// //println!("{}", txt.error);
// assert_eq!(resp_err.error, format!("{}", err));
//}
//
//pub async fn create_new_campaign(
// campaign_name: &str,
// data: Arc<Data>,
// cookies: Cookie<'_>,
//) -> CreateResp {
// let new = CreateReq {
// name: campaign_name.into(),
// };
//
// let app = get_app!(data).await;
// let new_resp = test::call_service(
// &app,
// post_request!(&new, crate::V1_API_ROUTES.campaign.new)
// .cookie(cookies)
// .to_request(),
// )
// .await;
// assert_eq!(new_resp.status(), StatusCode::OK);
// let uuid: CreateResp = test::read_body_json(new_resp).await;
// uuid
//}
//
//pub async fn delete_campaign(
// camapign: &CreateResp,
// data: Arc<Data>,
// cookies: Cookie<'_>,
//) {
// let del_route = V1_API_ROUTES.campaign.get_delete_route(&camapign.uuid);
// let app = get_app!(data).await;
// let del_resp =
// test::call_service(&app, post_request!(&del_route).cookie(cookies).to_request())
// .await;
// assert_eq!(del_resp.status(), StatusCode::OK);
//}
//
//pub async fn list_campaings(
// data: Arc<Data>,
// cookies: Cookie<'_>,
//) -> Vec<ListCampaignResp> {
// let app = get_app!(data).await;
// let list_resp = test::call_service(
// &app,
// post_request!(crate::V1_API_ROUTES.campaign.list)
// .cookie(cookies)
// .to_request(),
// )
// .await;
// assert_eq!(list_resp.status(), StatusCode::OK);
// test::read_body_json(list_resp).await
//}
//
//pub async fn add_feedback(
// rating: &RatingReq,
// campaign: &CreateResp,
// data: Arc<Data>,
//) -> RatingResp {
// let add_feedback_route = V1_API_ROUTES.feedback.add_feedback_route(&campaign.uuid);
// let app = get_app!(data).await;
// let add_feedback_resp = test::call_service(
// &app,
// post_request!(&rating, &add_feedback_route).to_request(),
// )
// .await;
// assert_eq!(add_feedback_resp.status(), StatusCode::OK);
//
// test::read_body_json(add_feedback_resp).await
//}
//
//pub async fn get_feedback(
// campaign: &CreateResp,
// data: Arc<Data>,
// cookies: Cookie<'_>,
//) -> GetFeedbackResp {
// let get_feedback_route = V1_API_ROUTES.campaign.get_feedback_route(&campaign.uuid);
// let app = get_app!(data).await;
//
// let get_feedback_resp = test::call_service(
// &app,
// post_request!(&get_feedback_route)
// .cookie(cookies)
// .to_request(),
// )
// .await;
// assert_eq!(get_feedback_resp.status(), StatusCode::OK);
// test::read_body_json(get_feedback_resp).await
//}