mirror of https://github.com/realaravinth/gitpad
feat: bootstarp gists
AUTHENTICATION - Sign Up - Sign IN ACCOUNT - Username Exists - Email Exists - Account delete - Password update - Email update - Username update - Get account secret - Update secret All routes are implemented with proper error handling and testing CONFIGURATION See ./config/default.toml for full listmaster
parent
3447a3a45c
commit
34a67a5535
|
@ -2,6 +2,18 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "actix-auth-middleware"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/realaravinth/actix-auth-middleware?branch=v4#29737aeb5594487e4192b5f2fb1a76a15c36c8f2"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-identity",
|
||||
"actix-service",
|
||||
"actix-web",
|
||||
"futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-codec"
|
||||
version = "0.4.2"
|
||||
|
@ -56,6 +68,21 @@ dependencies = [
|
|||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-identity"
|
||||
version = "0.4.0-beta.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f084963856cf7990b1f21d6298626de4ae6178385cadece312e12c9f7a9f432"
|
||||
dependencies = [
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"futures-util",
|
||||
"serde 1.0.136",
|
||||
"serde_json",
|
||||
"time 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-macros"
|
||||
version = "0.2.3"
|
||||
|
@ -198,6 +225,41 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
|
@ -429,6 +491,15 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.11.0"
|
||||
|
@ -469,7 +540,14 @@ version = "0.16.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"base64",
|
||||
"hkdf",
|
||||
"hmac 0.12.0",
|
||||
"percent-encoding",
|
||||
"rand 0.8.4",
|
||||
"sha2 0.10.1",
|
||||
"subtle",
|
||||
"time 0.3.7",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -556,6 +634,15 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.12.4"
|
||||
|
@ -680,6 +767,7 @@ checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
|
|||
dependencies = [
|
||||
"block-buffer 0.10.2",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -801,6 +889,21 @@ dependencies = [
|
|||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.21"
|
||||
|
@ -839,6 +942,23 @@ dependencies = [
|
|||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.21"
|
||||
|
@ -857,9 +977,13 @@ version = "0.3.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
|
@ -897,11 +1021,23 @@ dependencies = [
|
|||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gists"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-auth-middleware",
|
||||
"actix-http",
|
||||
"actix-identity",
|
||||
"actix-rt",
|
||||
"actix-web",
|
||||
"actix-web-codegen 0.5.0-rc.2 (git+https://github.com/realaravinth/actix-web)",
|
||||
|
@ -911,6 +1047,7 @@ dependencies = [
|
|||
"db-sqlx-postgres",
|
||||
"db-sqlx-sqlite",
|
||||
"derive_more",
|
||||
"futures",
|
||||
"git2",
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
@ -922,6 +1059,7 @@ dependencies = [
|
|||
"sqlx",
|
||||
"tokio",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"validator",
|
||||
]
|
||||
|
||||
|
@ -1001,6 +1139,15 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "158bc31e00a68e380286904cc598715f861f2b0ccf7aa6fe20c6d0c49ca5d0f6"
|
||||
dependencies = [
|
||||
"hmac 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.11.0"
|
||||
|
@ -1011,6 +1158,15 @@ dependencies = [
|
|||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2"
|
||||
dependencies = [
|
||||
"digest 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.25.1"
|
||||
|
@ -1601,6 +1757,18 @@ version = "0.3.24"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
|
@ -2025,6 +2193,17 @@ dependencies = [
|
|||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
|
@ -2124,7 +2303,7 @@ dependencies = [
|
|||
"futures-util",
|
||||
"hashlink",
|
||||
"hex",
|
||||
"hmac",
|
||||
"hmac 0.11.0",
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"libc",
|
||||
|
@ -2140,7 +2319,7 @@ dependencies = [
|
|||
"serde 1.0.136",
|
||||
"serde_json",
|
||||
"sha-1 0.9.8",
|
||||
"sha2",
|
||||
"sha2 0.9.9",
|
||||
"smallvec",
|
||||
"sqlformat",
|
||||
"sqlx-rt",
|
||||
|
@ -2170,7 +2349,7 @@ dependencies = [
|
|||
"quote",
|
||||
"serde 1.0.136",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.9.9",
|
||||
"sqlx-core",
|
||||
"sqlx-rt",
|
||||
"syn",
|
||||
|
@ -2555,6 +2734,16 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
|
@ -2573,6 +2762,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -21,13 +21,18 @@ members = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
actix-auth-middleware = { branch="v4", version = "0.2", git = "https://github.com/realaravinth/actix-auth-middleware", features = ["actix_identity_backend"] }
|
||||
actix-web = "4.0.0-rc.3"
|
||||
actix-http = "3.0.0-rc.2"
|
||||
actix-identity = "0.4.0-beta.8"
|
||||
actix-rt = "2.6.0"
|
||||
argon2-creds = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"}
|
||||
config = "0.11"
|
||||
db-core = {path = "./database/db-core"}
|
||||
db-sqlx-postgres = {path = "./database/db-sqlx-postgres"}
|
||||
db-sqlx-sqlite = {path = "./database/db-sqlx-sqlite"}
|
||||
derive_more = "0.99"
|
||||
futures = "0.3.21"
|
||||
git2 = "0.13.25"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
|
@ -37,12 +42,13 @@ pretty_env_logger = "0.4"
|
|||
rand = "0.8.4"
|
||||
serde = { version = "1", features = ["derive"]}
|
||||
serde_json = "1"
|
||||
sqlx = { version = "0.5.10", features = [ "runtime-actix-rustls", "uuid", "postgres", "time", "offline", "sqlite" ] }
|
||||
tokio = "1.16.1"
|
||||
url = "2.2"
|
||||
urlencoding = "2.1.0"
|
||||
validator = { version = "0.14.0", features = ["derive"] }
|
||||
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
db-sqlx-postgres = {path = "./database/db-sqlx-postgres"}
|
||||
db-sqlx-sqlite = {path = "./database/db-sqlx-sqlite"}
|
||||
actix-rt = "2"
|
||||
sqlx = { version = "0.5.10", features = [ "runtime-actix-rustls", "uuid", "postgres", "time", "offline", "sqlite" ] }
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
log = "info" # possible values: "info", "warn", "trace", "error", "debug"
|
||||
source_code = "https://github.com/realaravinth/gists"
|
||||
allow_registration = true # allow registration on server
|
||||
allow_demo = true # allow demo on server
|
||||
|
||||
[server]
|
||||
# The port at which you want authentication to listen to
|
||||
# takes a number, choose from 1000-10000 if you dont know what you are doing
|
||||
port = 7000
|
||||
#IP address. Enter 0.0.0.0 to listen on all availale addresses
|
||||
ip= "0.0.0.0"
|
||||
# enter your hostname, eg: example.com
|
||||
domain = "localhost"
|
||||
proxy_has_tls = false
|
||||
cookie_secret = "k&y8G#J&2gesW&N6hNauy63vgRzq9ZLPb39"
|
||||
#workers = 2
|
||||
|
||||
[database]
|
||||
# This section deals with the database location and how to access it
|
||||
# Please note that at the moment, we have support for only postgresqa.
|
||||
# Example, if you are Batman, your config would be:
|
||||
# hostname = "batcave.org"
|
||||
# port = "5432"
|
||||
# username = "batman"
|
||||
# password = "somereallycomplicatedBatmanpassword"
|
||||
hostname = "localhost"
|
||||
port = "5432"
|
||||
username = "postgres"
|
||||
password = "password"
|
||||
name = "postgres"
|
||||
pool = 4
|
||||
database_type = "postgres"
|
||||
|
||||
|
||||
[repository]
|
||||
root = "/tmp/gists.batsense.net"
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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/>.
|
||||
*/
|
||||
pub mod v1;
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* 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 actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::data::api::v1::account::*;
|
||||
use crate::data::api::v1::auth::Password;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test;
|
||||
|
||||
pub use super::auth;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct AccountCheckPayload {
|
||||
pub val: String,
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(username_exists);
|
||||
cfg.service(set_username);
|
||||
cfg.service(email_exists);
|
||||
cfg.service(set_email);
|
||||
cfg.service(delete_account);
|
||||
cfg.service(update_user_password);
|
||||
cfg.service(get_secret);
|
||||
cfg.service(update_user_secret);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Email {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Username {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
/// update username
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.account.update_username",
|
||||
wrap = "super::get_auth_middleware()"
|
||||
)]
|
||||
async fn set_username(
|
||||
id: Identity,
|
||||
payload: web::Json<Username>,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
|
||||
let new_name = data
|
||||
.update_username(&(**db), &username, &payload.username)
|
||||
.await?;
|
||||
|
||||
id.forget();
|
||||
id.remember(new_name);
|
||||
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.account.username_exists")]
|
||||
async fn username_exists(
|
||||
payload: web::Json<AccountCheckPayload>,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
Ok(HttpResponse::Ok().json(data.username_exists(&(**db), &payload.val).await?))
|
||||
}
|
||||
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.account.email_exists")]
|
||||
pub async fn email_exists(
|
||||
payload: web::Json<AccountCheckPayload>,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
Ok(HttpResponse::Ok().json(data.email_exists(&(**db), &payload.val).await?))
|
||||
}
|
||||
|
||||
/// update email
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.account.update_email",
|
||||
wrap = "super::get_auth_middleware()"
|
||||
)]
|
||||
async fn set_email(
|
||||
id: Identity,
|
||||
payload: web::Json<Email>,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
data.set_email(&(**db), &username, &payload.email).await?;
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.account.delete",
|
||||
wrap = "super::get_auth_middleware()"
|
||||
)]
|
||||
async fn delete_account(
|
||||
id: Identity,
|
||||
payload: web::Json<Password>,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
|
||||
data.delete_user(&(**db), &username, &payload.password)
|
||||
.await?;
|
||||
id.forget();
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.account.update_password",
|
||||
wrap = "super::get_auth_middleware()"
|
||||
)]
|
||||
async fn update_user_password(
|
||||
id: Identity,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
payload: web::Json<ChangePasswordReqest>,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let payload = payload.into_inner();
|
||||
data.change_password(&(**db), &username, &payload).await?;
|
||||
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
#[my_codegen::get(
|
||||
path = "crate::V1_API_ROUTES.account.get_secret",
|
||||
wrap = "super::get_auth_middleware()"
|
||||
)]
|
||||
async fn get_secret(id: Identity, data: AppData, db: crate::DB) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let secret = data.get_secret(&(**db), &username).await?;
|
||||
Ok(HttpResponse::Ok().json(secret))
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.account.update_secret",
|
||||
wrap = "super::get_auth_middleware()"
|
||||
)]
|
||||
async fn update_user_secret(
|
||||
id: Identity,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let _secret = data.update_user_secret(&(**db), &username).await?;
|
||||
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
/*
|
||||
* 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::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::data::api::v1::account::*;
|
||||
use crate::data::api::v1::auth::Password;
|
||||
use crate::data::Data;
|
||||
use crate::errors::*;
|
||||
use crate::*;
|
||||
|
||||
use crate::tests::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn postgrest_account_works() {
|
||||
let (db, data) = sqlx_postgres::get_data().await;
|
||||
uname_email_exists_works(data.clone(), db.clone()).await;
|
||||
email_udpate_password_validation_del_userworks(data.clone(), db.clone()).await;
|
||||
username_update_works(data.clone(), db.clone()).await;
|
||||
update_password_works(data.clone(), db.clone()).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn sqlite_account_works() {
|
||||
let (db, data) = sqlx_sqlite::get_data().await;
|
||||
uname_email_exists_works(data.clone(), db.clone()).await;
|
||||
email_udpate_password_validation_del_userworks(data.clone(), db.clone()).await;
|
||||
username_update_works(data.clone(), db.clone()).await;
|
||||
update_password_works(data.clone(), db.clone()).await;
|
||||
}
|
||||
|
||||
async fn uname_email_exists_works(data: Arc<Data>, db: BoxDB) {
|
||||
const NAME: &str = "testuserexists";
|
||||
const PASSWORD: &str = "longpassword2";
|
||||
const EMAIL: &str = "testuserexists@a.com2";
|
||||
let db = &db;
|
||||
|
||||
let _ = data.delete_user(db, NAME, PASSWORD).await;
|
||||
|
||||
let (_, signin_resp) = data.register_and_signin(db, NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data, db).await;
|
||||
|
||||
// chech if get user secret works
|
||||
let resp = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::get()
|
||||
.cookie(cookies.clone())
|
||||
.uri(ROUTES.account.get_secret)
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// chech if get user secret works
|
||||
let resp = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::post()
|
||||
.cookie(cookies.clone())
|
||||
.uri(ROUTES.account.update_secret)
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let mut payload = AccountCheckPayload { val: NAME.into() };
|
||||
|
||||
let user_exists_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, ROUTES.account.username_exists)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(user_exists_resp.status(), StatusCode::OK);
|
||||
let mut resp: AccountCheckResp = test::read_body_json(user_exists_resp).await;
|
||||
assert!(resp.exists);
|
||||
|
||||
payload.val = PASSWORD.into();
|
||||
|
||||
let user_doesnt_exist = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, ROUTES.account.username_exists)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(user_doesnt_exist.status(), StatusCode::OK);
|
||||
resp = test::read_body_json(user_doesnt_exist).await;
|
||||
assert!(!resp.exists);
|
||||
|
||||
let email_doesnt_exist = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, ROUTES.account.email_exists)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(email_doesnt_exist.status(), StatusCode::OK);
|
||||
resp = test::read_body_json(email_doesnt_exist).await;
|
||||
assert!(!resp.exists);
|
||||
|
||||
payload.val = EMAIL.into();
|
||||
|
||||
let email_exist = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, ROUTES.account.email_exists)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(email_exist.status(), StatusCode::OK);
|
||||
resp = test::read_body_json(email_exist).await;
|
||||
assert!(resp.exists);
|
||||
}
|
||||
|
||||
async fn email_udpate_password_validation_del_userworks(data: Arc<Data>, db: BoxDB) {
|
||||
const NAME: &str = "testuser2";
|
||||
const PASSWORD: &str = "longpassword2";
|
||||
const EMAIL: &str = "testuser1@a.com2";
|
||||
const NAME2: &str = "eupdauser";
|
||||
const EMAIL2: &str = "eupdauser@a.com";
|
||||
|
||||
let _ = data.delete_user(&db, NAME, PASSWORD).await;
|
||||
let _ = data.delete_user(&db, NAME2, PASSWORD).await;
|
||||
|
||||
let _ = data.register_and_signin(&db, NAME2, EMAIL2, PASSWORD).await;
|
||||
let (_creds, signin_resp) = data.register_and_signin(&db, NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data, db).await;
|
||||
|
||||
// update email
|
||||
let mut email_payload = Email {
|
||||
email: EMAIL.into(),
|
||||
};
|
||||
let email_update_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&email_payload, ROUTES.account.update_email)
|
||||
//post_request!(&email_payload, EMAIL_UPDATE)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(email_update_resp.status(), StatusCode::OK);
|
||||
|
||||
// check duplicate email while duplicate email
|
||||
email_payload.email = EMAIL2.into();
|
||||
data.bad_post_req_test(
|
||||
&db,
|
||||
NAME,
|
||||
PASSWORD,
|
||||
ROUTES.account.update_email,
|
||||
&email_payload,
|
||||
ServiceError::EmailTaken,
|
||||
)
|
||||
.await;
|
||||
|
||||
// wrong password while deleteing account
|
||||
let mut payload = Password {
|
||||
password: NAME.into(),
|
||||
};
|
||||
data.bad_post_req_test(
|
||||
&db,
|
||||
NAME,
|
||||
PASSWORD,
|
||||
ROUTES.account.delete,
|
||||
&payload,
|
||||
ServiceError::WrongPassword,
|
||||
)
|
||||
.await;
|
||||
|
||||
// delete account
|
||||
payload.password = PASSWORD.into();
|
||||
let delete_user_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, ROUTES.account.delete)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(delete_user_resp.status(), StatusCode::OK);
|
||||
|
||||
// try to delete an account that doesn't exist
|
||||
let account_not_found_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, ROUTES.account.delete)
|
||||
.cookie(cookies)
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(account_not_found_resp.status(), StatusCode::NOT_FOUND);
|
||||
let txt: ErrorToResponse = test::read_body_json(account_not_found_resp).await;
|
||||
assert_eq!(txt.error, format!("{}", ServiceError::AccountNotFound));
|
||||
}
|
||||
|
||||
async fn username_update_works(data: Arc<Data>, db: BoxDB) {
|
||||
const NAME: &str = "testuserupda";
|
||||
const EMAIL: &str = "testuserupda@sss.com";
|
||||
const EMAIL2: &str = "testuserupda2@sss.com";
|
||||
const PASSWORD: &str = "longpassword2";
|
||||
const NAME2: &str = "terstusrtds";
|
||||
const NAME_CHANGE: &str = "terstusrtdsxx";
|
||||
|
||||
let db = &db;
|
||||
|
||||
let _ = futures::join!(
|
||||
data.delete_user(db, NAME, PASSWORD),
|
||||
data.delete_user(db, NAME2, PASSWORD),
|
||||
data.delete_user(db, NAME_CHANGE, PASSWORD)
|
||||
);
|
||||
|
||||
let _ = data.register_and_signin(db, NAME2, EMAIL2, PASSWORD).await;
|
||||
let (_creds, signin_resp) = data.register_and_signin(db, NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data, db).await;
|
||||
|
||||
// update username
|
||||
let mut username_udpate = Username {
|
||||
username: NAME_CHANGE.into(),
|
||||
};
|
||||
let username_update_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&username_udpate, ROUTES.account.update_username)
|
||||
.cookie(cookies)
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(username_update_resp.status(), StatusCode::OK);
|
||||
|
||||
// check duplicate username with duplicate username
|
||||
username_udpate.username = NAME2.into();
|
||||
data.bad_post_req_test(
|
||||
db,
|
||||
NAME_CHANGE,
|
||||
PASSWORD,
|
||||
ROUTES.account.update_username,
|
||||
&username_udpate,
|
||||
ServiceError::UsernameTaken,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn update_password_works(data: Arc<Data>, db: BoxDB) {
|
||||
const NAME: &str = "updatepassuser";
|
||||
const PASSWORD: &str = "longpassword2";
|
||||
const EMAIL: &str = "updatepassuser@a.com";
|
||||
|
||||
let db = &db;
|
||||
|
||||
let _ = data.delete_user(db, NAME, PASSWORD).await;
|
||||
|
||||
let (_, signin_resp) = data.register_and_signin(db, NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data, db).await;
|
||||
|
||||
let new_password = "newpassword";
|
||||
|
||||
let update_password = ChangePasswordReqest {
|
||||
password: PASSWORD.into(),
|
||||
new_password: new_password.into(),
|
||||
confirm_new_password: PASSWORD.into(),
|
||||
};
|
||||
|
||||
let res = data.change_password(db, NAME, &update_password).await;
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res, Err(ServiceError::PasswordsDontMatch));
|
||||
|
||||
let update_password = ChangePasswordReqest {
|
||||
password: PASSWORD.into(),
|
||||
new_password: new_password.into(),
|
||||
confirm_new_password: new_password.into(),
|
||||
};
|
||||
|
||||
assert!(data
|
||||
.change_password(db, NAME, &update_password)
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
let update_password = ChangePasswordReqest {
|
||||
password: new_password.into(),
|
||||
new_password: new_password.into(),
|
||||
confirm_new_password: PASSWORD.into(),
|
||||
};
|
||||
|
||||
data.bad_post_req_test(
|
||||
db,
|
||||
NAME,
|
||||
new_password,
|
||||
ROUTES.account.update_password,
|
||||
&update_password,
|
||||
ServiceError::PasswordsDontMatch,
|
||||
)
|
||||
.await;
|
||||
|
||||
let update_password = ChangePasswordReqest {
|
||||
password: PASSWORD.into(),
|
||||
new_password: PASSWORD.into(),
|
||||
confirm_new_password: PASSWORD.into(),
|
||||
};
|
||||
|
||||
data.bad_post_req_test(
|
||||
db,
|
||||
NAME,
|
||||
new_password,
|
||||
ROUTES.account.update_password,
|
||||
&update_password,
|
||||
ServiceError::WrongPassword,
|
||||
)
|
||||
.await;
|
||||
|
||||
let update_password = ChangePasswordReqest {
|
||||
password: new_password.into(),
|
||||
new_password: PASSWORD.into(),
|
||||
confirm_new_password: PASSWORD.into(),
|
||||
};
|
||||
|
||||
let update_password_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&update_password, ROUTES.account.update_password)
|
||||
.cookie(cookies)
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(update_password_resp.status(), StatusCode::OK);
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 crate::data::api::v1::auth::{Login, Register};
|
||||
use actix_identity::Identity;
|
||||
use actix_web::http::header;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
|
||||
use super::RedirectQuery;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
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(
|
||||
payload: web::Json<Register>,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
data.register(&(**db), &payload).await?;
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.login")]
|
||||
async fn login(
|
||||
id: Identity,
|
||||
payload: web::Json<Login>,
|
||||
query: web::Query<RedirectQuery>,
|
||||
data: AppData,
|
||||
db: crate::DB,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let payload = payload.into_inner();
|
||||
let username = data.login(&(**db), &payload).await?;
|
||||
id.remember(username);
|
||||
let query = query.into_inner();
|
||||
if let Some(redirect_to) = query.redirect_to {
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header((header::LOCATION, redirect_to))
|
||||
.finish())
|
||||
} else {
|
||||
Ok(HttpResponse::Ok().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[my_codegen::get(
|
||||
path = "crate::V1_API_ROUTES.auth.logout",
|
||||
wrap = "super::get_auth_middleware()"
|
||||
)]
|
||||
async fn signout(id: Identity) -> impl Responder {
|
||||
use actix_auth_middleware::GetLoginRoute;
|
||||
|
||||
if id.identity().is_some() {
|
||||
id.forget();
|
||||
}
|
||||
HttpResponse::Found()
|
||||
.append_header((header::LOCATION, crate::V1_API_ROUTES.get_login_route(None)))
|
||||
.finish()
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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 actix_web::{web, HttpResponse, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{DB, GIT_COMMIT_HASH, VERSION};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct BuildDetails {
|
||||
pub version: &'static str,
|
||||
pub git_commit_hash: &'static str,
|
||||
}
|
||||
|
||||
pub mod routes {
|
||||
pub struct Meta {
|
||||
pub build_details: &'static str,
|
||||
pub health: &'static str,
|
||||
}
|
||||
|
||||
impl Meta {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
build_details: "/api/v1/meta/build",
|
||||
health: "/api/v1/meta/health",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// emmits build details of the bninary
|
||||
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")]
|
||||
async fn build_details() -> impl Responder {
|
||||
let build = BuildDetails {
|
||||
version: VERSION,
|
||||
git_commit_hash: GIT_COMMIT_HASH,
|
||||
};
|
||||
HttpResponse::Ok().json(build)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
/// Health check return datatype
|
||||
pub struct Health {
|
||||
db: bool,
|
||||
}
|
||||
|
||||
/// emmits build details of the bninary
|
||||
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.health")]
|
||||
async fn health(db: DB) -> impl Responder {
|
||||
let health = Health {
|
||||
db: db.ping().await,
|
||||
};
|
||||
HttpResponse::Ok().json(health)
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(build_details);
|
||||
cfg.service(health);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::{http::StatusCode, test, App};
|
||||
|
||||
use crate::routes::services;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn build_details_works() {
|
||||
let app = test::init_service(App::new().configure(services)).await;
|
||||
|
||||
let resp = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::get()
|
||||
.uri(V1_API_ROUTES.meta.build_details)
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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 actix_auth_middleware::Authentication;
|
||||
use actix_web::web::ServiceConfig;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub mod account;
|
||||
pub mod auth;
|
||||
pub mod meta;
|
||||
pub mod routes;
|
||||
|
||||
pub use routes::ROUTES;
|
||||
|
||||
pub fn services(cfg: &mut ServiceConfig) {
|
||||
auth::services(cfg);
|
||||
account::services(cfg);
|
||||
meta::services(cfg);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RedirectQuery {
|
||||
pub redirect_to: Option<String>,
|
||||
}
|
||||
|
||||
pub fn get_auth_middleware() -> Authentication<routes::Routes> {
|
||||
Authentication::with_identity(ROUTES)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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/>.
|
||||
*/
|
||||
//! V1 API Routes
|
||||
use actix_auth_middleware::{Authentication, GetLoginRoute};
|
||||
|
||||
use super::meta::routes::Meta;
|
||||
|
||||
/// constant [Routes](Routes) instance
|
||||
pub const ROUTES: Routes = Routes::new();
|
||||
|
||||
/// Authentication routes
|
||||
pub struct Auth {
|
||||
/// logout route
|
||||
pub logout: &'static str,
|
||||
/// login route
|
||||
pub login: &'static str,
|
||||
/// registration route
|
||||
pub register: &'static str,
|
||||
}
|
||||
impl Auth {
|
||||
/// create new instance of Authentication route
|
||||
pub const fn new() -> Auth {
|
||||
let login = "/api/v1/signin";
|
||||
let logout = "/logout";
|
||||
let register = "/api/v1/signup";
|
||||
Auth {
|
||||
logout,
|
||||
login,
|
||||
register,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Account management routes
|
||||
pub struct Account {
|
||||
/// delete account route
|
||||
pub delete: &'static str,
|
||||
/// route to check if an email exists
|
||||
pub email_exists: &'static str,
|
||||
/// route to fetch account secret
|
||||
pub get_secret: &'static str,
|
||||
/// route to update a user's email
|
||||
pub update_email: &'static str,
|
||||
/// route to update password
|
||||
pub update_password: &'static str,
|
||||
/// route to update secret
|
||||
pub update_secret: &'static str,
|
||||
/// route to check if a username is already registered
|
||||
pub username_exists: &'static str,
|
||||
/// route to change username
|
||||
pub update_username: &'static str,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
/// create a new instance of [Account][Account] routes
|
||||
pub const fn new() -> Account {
|
||||
let get_secret = "/api/v1/account/secret/get";
|
||||
let update_secret = "/api/v1/account/secret/update";
|
||||
let delete = "/api/v1/account/delete";
|
||||
let email_exists = "/api/v1/account/email/exists";
|
||||
let username_exists = "/api/v1/account/username/exists";
|
||||
let update_username = "/api/v1/account/username/update";
|
||||
let update_email = "/api/v1/account/email/update";
|
||||
let update_password = "/api/v1/account/password/update";
|
||||
Account {
|
||||
delete,
|
||||
email_exists,
|
||||
get_secret,
|
||||
update_email,
|
||||
update_password,
|
||||
update_secret,
|
||||
username_exists,
|
||||
update_username,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Top-level routes data structure for V1 AP1
|
||||
pub struct Routes {
|
||||
/// Authentication routes
|
||||
pub auth: Auth,
|
||||
/// Account routes
|
||||
pub account: Account,
|
||||
|
||||
/// Meta routes
|
||||
pub meta: Meta,
|
||||
}
|
||||
|
||||
impl Routes {
|
||||
/// create new instance of Routes
|
||||
const fn new() -> Routes {
|
||||
Routes {
|
||||
auth: Auth::new(),
|
||||
account: Account::new(),
|
||||
meta: Meta::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_auth_middleware() -> Authentication<Routes> {
|
||||
Authentication::with_identity(ROUTES)
|
||||
}
|
||||
|
||||
impl GetLoginRoute for Routes {
|
||||
fn get_login_route(&self, src: Option<&str>) -> String {
|
||||
if let Some(redirect_to) = src {
|
||||
format!(
|
||||
"{}?redirect_to={}",
|
||||
self.auth.login,
|
||||
urlencoding::encode(redirect_to)
|
||||
)
|
||||
} else {
|
||||
self.auth.register.to_string()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* 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_auth_middleware::GetLoginRoute;
|
||||
use actix_web::http::{header, StatusCode};
|
||||
use actix_web::test;
|
||||
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::data::api::v1::auth::{Login, Register};
|
||||
use crate::data::Data;
|
||||
use crate::db::BoxDB;
|
||||
use crate::errors::*;
|
||||
use crate::*;
|
||||
|
||||
use crate::tests::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn postgrest_auth_works() {
|
||||
let (db, data) = sqlx_postgres::get_data().await;
|
||||
auth_works(data.clone(), db.clone()).await;
|
||||
serverside_password_validation_works(data, db).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn sqlite_auth_works() {
|
||||
let (db, data) = sqlx_sqlite::get_data().await;
|
||||
auth_works(data.clone(), db.clone()).await;
|
||||
serverside_password_validation_works(data, db).await;
|
||||
}
|
||||
|
||||
async fn auth_works(data: Arc<Data>, db: BoxDB) {
|
||||
const NAME: &str = "testuserfoo";
|
||||
const PASSWORD: &str = "longpassword";
|
||||
const EMAIL: &str = "testuser1foo@a.com";
|
||||
|
||||
let _ = data.delete_user(&db, NAME, PASSWORD).await;
|
||||
let app = get_app!(data, db).await;
|
||||
|
||||
// 1. Register with email == None
|
||||
let msg = Register {
|
||||
username: NAME.into(),
|
||||
password: PASSWORD.into(),
|
||||
confirm_password: PASSWORD.into(),
|
||||
email: None,
|
||||
};
|
||||
let resp =
|
||||
test::call_service(&app, post_request!(&msg, ROUTES.auth.register).to_request()).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
// delete user TODO
|
||||
let _ = data.delete_user(&db, NAME, PASSWORD).await;
|
||||
|
||||
// 1. Register and signin
|
||||
let (_, signin_resp) = data.register_and_signin(&db, NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
|
||||
// Sign in with email
|
||||
data.signin_test(&db, EMAIL, PASSWORD).await;
|
||||
|
||||
// 2. check if duplicate username is allowed
|
||||
let mut msg = Register {
|
||||
username: NAME.into(),
|
||||
password: PASSWORD.into(),
|
||||
confirm_password: PASSWORD.into(),
|
||||
email: Some(EMAIL.into()),
|
||||
};
|
||||
|
||||
msg.username = format!("asdfasd{}", msg.username);
|
||||
data.bad_post_req_test(
|
||||
&db,
|
||||
NAME,
|
||||
PASSWORD,
|
||||
ROUTES.auth.register,
|
||||
&msg,
|
||||
ServiceError::EmailTaken,
|
||||
)
|
||||
.await;
|
||||
|
||||
msg.email = Some(format! |