From d2c4e9a06b16003c52d914ea8ca9a9c090605559 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Mon, 11 Oct 2021 09:56:15 +0530 Subject: [PATCH] admin authentication --- Cargo.lock | 359 +++++++++++++++++-- Cargo.toml | 2 + migrations/20211001071311_survey_users.sql | 2 +- migrations/20211011041634_survey_admins.sql | 17 + sqlx-data.json | 154 ++++++-- src/api/v1/auth.rs | 199 +++++++++-- src/api/v1/bench.rs | 54 ++- src/api/v1/challenges.rs | 85 +++++ src/data.rs | 26 +- src/errors.rs | 62 ++++ src/main.rs | 21 +- src/tests.rs | 372 ++++++++++---------- 12 files changed, 1089 insertions(+), 264 deletions(-) create mode 100644 migrations/20211011041634_survey_admins.sql create mode 100644 src/api/v1/challenges.rs diff --git a/Cargo.lock b/Cargo.lock index a0a2f74..acb5e29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index cc67599..2d80bc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/migrations/20211001071311_survey_users.sql b/migrations/20211001071311_survey_users.sql index 20dfbd4..027bab1 100644 --- a/migrations/20211001071311_survey_users.sql +++ b/migrations/20211001071311_survey_users.sql @@ -1,4 +1,4 @@ CREATE TABLE IF NOT EXISTS survey_users ( ID UUID PRIMARY KEY NOT NULL UNIQUE, created_at TIMESTAMPTZ NOT NULL -) +); diff --git a/migrations/20211011041634_survey_admins.sql b/migrations/20211011041634_survey_admins.sql new file mode 100644 index 0000000..c5bfada --- /dev/null +++ b/migrations/20211011041634_survey_admins.sql @@ -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 +) diff --git a/sqlx-data.json b/sqlx-data.json index 5c5d8a6..0c26f94 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -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": [] + } } } \ No newline at end of file diff --git a/src/api/v1/auth.rs b/src/api/v1/auth.rs index f127482..50b80c5 100644 --- a/src/api/v1/auth.rs +++ b/src/api/v1/auth.rs @@ -14,72 +14,221 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -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 { - 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, + } - 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 { + 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 { - let uuid = runners::register_runner(&data).await?; - id.remember(uuid.to_string()); +async fn register( + payload: web::Json, + data: AppData, +) -> ServiceResult { + 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, + data: AppData, +) -> ServiceResult { + 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() +} + diff --git a/src/api/v1/bench.rs b/src/api/v1/bench.rs index 712a4c2..97a3761 100644 --- a/src/api/v1/bench.rs +++ b/src/api/v1/bench.rs @@ -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 { + 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 { + let uuid = runners::register_runner(&data).await?; + id.remember(uuid.to_string()); + Ok(HttpResponse::Ok()) +} + + #[derive(Serialize, Deserialize)] struct Bench { duration: f32, diff --git a/src/api/v1/challenges.rs b/src/api/v1/challenges.rs new file mode 100644 index 0000000..e4466e3 --- /dev/null +++ b/src/api/v1/challenges.rs @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * 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 . + */ +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 { + 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 { + let uuid = runners::add_runner(&data).await?; + id.remember(uuid.to_string()); + Ok(HttpResponse::Ok()) +} diff --git a/src/data.rs b/src/data.rs index a86d86e..6ed9bd5 100644 --- a/src/data.rs +++ b/src/data.rs @@ -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 { + 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) } diff --git a/src/errors.rs b/src/errors.rs index 38ed977..3eb6f07 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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 for ServiceError { } } +impl From 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 for ServiceError { #[cfg(not(tarpaulin_include))] diff --git a/src/main.rs b/src/main.rs index de27168..3446863 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 { +pub fn get_survey_identity_service() -> IdentityService { 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 { + 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); diff --git a/src/tests.rs b/src/tests.rs index 098af8a..c462ea1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * 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 . + */ 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, 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, 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( - 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, - 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, - 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, - cookies: Cookie<'_>, -) -> Vec { - 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, -) -> 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, - 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, 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, 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( +// 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, +// 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, +// 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, +// cookies: Cookie<'_>, +//) -> Vec { +// 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, +//) -> 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, +// 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 +//}