Skip to content

Commit

Permalink
feat: add validations and change routes
Browse files Browse the repository at this point in the history
  • Loading branch information
luisfbl committed Feb 15, 2024
1 parent 0b877e9 commit 39aa9d8
Show file tree
Hide file tree
Showing 37 changed files with 499 additions and 356 deletions.
File renamed without changes.
8 changes: 5 additions & 3 deletions rest-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
edition.workspace = true

[dependencies]
common = { path = "../common"}
common = { path = "../common" }
app_core = { path = "../core" }

sqlx = { workspace = true, features = ["migrate"] }
Expand All @@ -18,11 +18,13 @@ serde_json = { workspace = true }
axum-login = { workspace = true }
tower-sessions = { workspace = true }
tower-sessions-sqlx-store = { workspace = true }
tower-http = { workspace = true, features = ["cors"] }

async-trait = { version = "0.1.77" }
password-auth = { version = "1.0.0" }
oauth2 = { version = "4.4.2" }
reqwest = { version = "0.11.23", features = ["json"] }
thiserror = "1.0.56"
tower-http = { version = "0.5.1", default-features = false, features = ["cors"] }
http = "1.0.0"
http = "1.0.0"
garde = { version = "0.18.0", default-features = false, features = ["derive", "phone-number", "email"] }
axum_garde = { version = "0.18.0", default-features = false, features = ["json", "query"] }
24 changes: 0 additions & 24 deletions rest-server/migrations/20231224161433_init.up.sql

This file was deleted.

16 changes: 16 additions & 0 deletions rest-server/migrations/20240215101212_user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS "users" (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL,
username CHAR(16) NOT NULL UNIQUE,
email VARCHAR(256) NOT NULL UNIQUE,
avatar VARCHAR(256),
password TEXT,
account_type INTEGER DEFAULT 2,
address varchar(256),
bio varchar(512),
permissions INT NOT NULL DEFAULT 0,
access_token TEXT
);

CREATE INDEX user_email ON "users" (email);
CREATE INDEX user_username ON "users" (username);
7 changes: 7 additions & 0 deletions rest-server/migrations/20240215101943_session.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE SCHEMA IF NOT EXISTS "tower_sessions";

CREATE TABLE IF NOT EXISTS "tower_sessions"."sessions" (
id text PRIMARY KEY NOT NULL,
data bytea NOT NULL,
expiry_date timestamptz NOT NULL
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use async_trait::async_trait;
use axum_login::{AuthnBackend, UserId};
use password_auth::verify_password;
use sqlx::{Pool, Postgres};
use sqlx::types::Uuid;

use crate::json::auth::Credentials;
use crate::models::users::ProtectedUser;

Expand Down Expand Up @@ -32,7 +32,15 @@ impl AuthnBackend for Backend {
async fn authenticate(&self, creds: Self::Credentials) -> Result<Option<Self::User>, Self::Error> {
match creds {
Credentials::Password(creds) => {
let user: Option<Self::User> = sqlx::query_as("SELECT * FROM USERS WHERE email = $1")
let user: Option<Self::User> = sqlx::query_as(
r#"
SELECT
id, email, password,
permissions, access_token, account_type
FROM USERS
WHERE email = $1
"#
)
.bind(creds.email)
.fetch_optional(&self.db)
.await
Expand All @@ -51,10 +59,13 @@ impl AuthnBackend for Backend {
Credentials::OAuth(creds) => {
let user = sqlx::query_as(
r#"
INSERT INTO users (email, name, access_token) VALUES ($1, $2, $3)
ON CONFLICT(email) DO UPDATE
SET access_token = excluded.access_token
RETURNING *
INSERT INTO users (email, name, access_token)
VALUES ($1, $2, $3)
ON CONFLICT(email) DO UPDATE
SET access_token = excluded.access_token
RETURNING
id, email, password,
permissions, access_token, account_type
"#,
)
.bind(creds.user.email)
Expand All @@ -70,8 +81,15 @@ impl AuthnBackend for Backend {
}

async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
let user = sqlx::query_as("SELECT * FROM users WHERE id = $1")
.bind(Uuid::parse_str(user_id).unwrap())
let user = sqlx::query_as(
r#"
SELECT
id, email, password,
permissions, access_token, account_type
FROM users WHERE id = $1
"#
)
.bind(user_id)
.fetch_optional(&self.db)
.await
.map_err(Self::Error::Sqlx)?;
Expand Down
3 changes: 3 additions & 0 deletions rest-server/src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod web;
pub mod provider;
pub mod backend;
File renamed without changes.
39 changes: 22 additions & 17 deletions rest-server/src/app.rs → rest-server/src/app/web.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use axum::{Extension, Router};
use axum_login::{login_required, AuthManagerLayer, AuthManagerLayerBuilder};
use http::header::CONTENT_TYPE;
use http::{HeaderValue, Method};
use axum_login::{AuthManagerLayer, AuthManagerLayerBuilder};
use http::{header, Method};
use sqlx::{Pool, Postgres};
use tower_sessions::{Expiry, SessionManagerLayer};
use tower_sessions::cookie::time::Duration;
use tower_sessions_sqlx_store::PostgresStore;
use app_core::database::SqlxManager;
use common::config::Settings;
use crate::provider::AuthProviders;
use crate::app::backend::Backend;
use crate::app::provider::AuthProviders;
use crate::routes;
use crate::routes::auth::backend::Backend;

pub struct WebApp {
settings: Settings,
Expand Down Expand Up @@ -43,20 +42,22 @@ impl WebApp {
settings: self.settings.clone(),
};

let cors = tower_http::cors::CorsLayer::default()
.allow_origin(state.settings.webapp.protocol_url().parse::<HeaderValue>().unwrap())
.allow_methods([Method::GET, Method::POST, Method::PATCH])
.allow_headers([CONTENT_TYPE])
.allow_credentials(true);
let cors = tower_http::cors::CorsLayer::new()
.allow_credentials(true)
.allow_origin([
state.settings.webapp.protocol_url().parse()
.unwrap()
])
.allow_headers([
header::AUTHORIZATION, header::CONTENT_TYPE,
])
.allow_methods([
Method::GET, Method::PUT,
Method::DELETE, Method::PATCH,
]);

Router::new()
.nest("/api",
routes::auth::router()
.merge(
routes::users::router()
.route_layer(login_required!(Backend))
)
)
.nest("/api", routes::router())
.with_state(state)
.layer(self.auth_layer())
.layer(Extension(AuthProviders::new(&self.settings)))
Expand Down Expand Up @@ -92,4 +93,8 @@ impl WebApp {
.await
.expect("Failed to start axum server");
}
}

impl axum::extract::FromRef<AppState> for () {
fn from_ref(_: &AppState) {}
}
22 changes: 17 additions & 5 deletions rest-server/src/json/auth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use garde::Validate;
use oauth2::CsrfToken;
use serde::{Deserialize, Serialize};
use crate::json::validations::validate_name;

#[derive(Debug, Clone, Deserialize)]
pub enum Credentials {
Expand All @@ -13,18 +15,28 @@ pub struct OAuthCreds {
pub token: String,
}

#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Validate)]
pub struct LoginCreds {
#[garde(email)]
pub email: String,
#[garde(length(min = 8))]
pub password: String,
}

#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Validate)]
pub struct SignCreds {
#[garde(email)]
pub email: String,
pub name: String,
pub phone: i32,
#[garde(custom(validate_name), length(min = 8))]
#[garde(required)]
pub name: Option<String>,
//#[garde(phone_number)]
#[garde(skip)]
pub phone: Option<String>,
#[garde(length(min = 8))]
pub password: String,
#[garde(alphanumeric, length(min=3, max=16))]
pub username: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
Expand All @@ -41,5 +53,5 @@ pub struct UserInfo {

#[derive(Serialize)]
pub struct AuthUrlResponse {
pub(crate) auth_url: String,
pub auth_url: String,
}
3 changes: 2 additions & 1 deletion rest-server/src/json/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod auth;
pub mod users;
pub mod users;
mod validations;
27 changes: 23 additions & 4 deletions rest-server/src/json/users.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
use serde::Deserialize;
use garde::Validate;
use serde::{Deserialize, Serialize};
use crate::json::validations::validate_name;

#[derive(Debug, Clone, Deserialize)]
#[derive(Deserialize, Validate)]
pub struct UpdateUser {
#[garde(skip)]
pub avatar: Option<String>,
#[garde(alphanumeric, length(min=3, max=16))]
pub username: Option<String>,
#[garde(custom(validate_name), length(min = 8))]
pub name: Option<String>,
pub phone: Option<i32>,
//#[garde(phone_number)]
#[garde(skip)]
pub phone: Option<String>,
#[garde(skip)]
pub address: Option<String>,
#[garde(email)]
pub email: Option<String>,
pub password: Option<String>,
}

#[derive(Serialize)]
pub struct UserMe {
pub id: i32,
pub email: String,
pub name: String,
pub username: String,
pub avatar: Option<String>,
pub account_type: i32,
pub bio: Option<String>
}
14 changes: 14 additions & 0 deletions rest-server/src/json/validations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use garde::Error;

pub fn validate_name(value: &Option<String>, _: &()) -> garde::Result {
match value {
Some(n) => {
if n.chars().all(char::is_alphabetic) {
Ok(())
} else {
Err(Error::new("Name must contain only letters"))
}
}
None => Ok(()),
}
}
5 changes: 2 additions & 3 deletions rest-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use crate::app::WebApp;
use crate::app::web::WebApp;

mod routes;
mod models;
mod app;
pub mod json;
mod provider;
mod app;

#[tokio::main]
async fn main() {
Expand Down
Loading

0 comments on commit 39aa9d8

Please sign in to comment.