add start
This commit is contained in:
2
como_bin/src/gqlx/mod.rs
Normal file
2
como_bin/src/gqlx/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod users;
|
||||
|
0
como_bin/src/gqlx/users.rs
Normal file
0
como_bin/src/gqlx/users.rs
Normal file
@@ -1,13 +1,49 @@
|
||||
use async_graphql::{Context, EmptyMutation, EmptySubscription, Object, Schema, SimpleObject};
|
||||
use async_graphql::{Context, EmptySubscription, Object, Schema, SimpleObject};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub type CibusSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
|
||||
use crate::services::users_service::UserService;
|
||||
|
||||
pub type CibusSchema = Schema<QueryRoot, MutationRoot, EmptySubscription>;
|
||||
|
||||
pub struct MutationRoot;
|
||||
|
||||
#[Object]
|
||||
impl MutationRoot {
|
||||
async fn login(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> anyhow::Result<bool> {
|
||||
let user_service = ctx.data_unchecked::<UserService>();
|
||||
|
||||
let valid = user_service.validate_user(username, password).await?;
|
||||
|
||||
Ok(match valid {
|
||||
Some(..) => true,
|
||||
None => false,
|
||||
})
|
||||
}
|
||||
|
||||
async fn register(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> anyhow::Result<String> {
|
||||
let user_service = ctx.data_unchecked::<UserService>();
|
||||
|
||||
let user_id = user_service.add_user(username, password).await?;
|
||||
|
||||
Ok(user_id.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueryRoot;
|
||||
|
||||
#[Object]
|
||||
impl QueryRoot {
|
||||
async fn get_upcoming(&self, ctx: &Context<'_>) -> Vec<Event> {
|
||||
async fn get_upcoming(&self, _ctx: &Context<'_>) -> Vec<Event> {
|
||||
vec![Event::new(
|
||||
None,
|
||||
"Some-name".into(),
|
||||
|
@@ -1,6 +1,8 @@
|
||||
use std::env::{self, current_dir};
|
||||
|
||||
mod gqlx;
|
||||
mod graphql;
|
||||
mod services;
|
||||
|
||||
use axum::{
|
||||
extract::Extension,
|
||||
@@ -12,14 +14,15 @@ use axum::{
|
||||
|
||||
use async_graphql::{
|
||||
http::{playground_source, GraphQLPlaygroundConfig},
|
||||
EmptyMutation, EmptySubscription, Request, Response, Schema,
|
||||
EmptySubscription, Request, Response, Schema,
|
||||
};
|
||||
use graphql::CibusSchema;
|
||||
use services::users_service;
|
||||
use sqlx::PgPool;
|
||||
use tower_http::{cors::CorsLayer, trace::TraceLayer};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
use crate::graphql::QueryRoot;
|
||||
use crate::graphql::{MutationRoot, QueryRoot};
|
||||
|
||||
async fn graphql_handler(schema: Extension<CibusSchema>, req: Json<Request>) -> Json<Response> {
|
||||
schema.execute(req.0).await.into()
|
||||
@@ -39,7 +42,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::EnvFilter::new(
|
||||
std::env::var("RUST_LOG").unwrap_or_else(|_| {
|
||||
"como_bin=debug,tower_http=debug,axum_extra=debug,hyper=info,mio=info".into()
|
||||
"como_bin=debug,tower_http=debug,axum_extra=debug,hyper=info,mio=info,sqlx=info,async_graphql=debug"
|
||||
.into()
|
||||
}),
|
||||
))
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
@@ -58,7 +62,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
// Schema
|
||||
println!("Building schema");
|
||||
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).finish();
|
||||
let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription)
|
||||
.data(users_service::UserService::new(pool))
|
||||
.finish();
|
||||
|
||||
// CORS
|
||||
let cors = vec!["http://localhost:3000".parse().unwrap()];
|
||||
|
1
como_bin/src/services/cookie_service.rs
Normal file
1
como_bin/src/services/cookie_service.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub struct CookieService {}
|
2
como_bin/src/services/mod.rs
Normal file
2
como_bin/src/services/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod cookie_service;
|
||||
pub mod users_service;
|
80
como_bin/src/services/users_service.rs
Normal file
80
como_bin/src/services/users_service.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
use rand_core::OsRng;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
pub struct UserService {
|
||||
pgx: Pool<Postgres>,
|
||||
}
|
||||
|
||||
impl UserService {
|
||||
pub fn new(pgx: Pool<Postgres>) -> Self {
|
||||
Self { pgx }
|
||||
}
|
||||
|
||||
pub async fn add_user(&self, username: String, password: String) -> anyhow::Result<String> {
|
||||
let hashed_password = self.hash_password(password)?;
|
||||
|
||||
let rec = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO users (username, password_hash)
|
||||
VALUES ( $1, $2 )
|
||||
RETURNING id
|
||||
"#,
|
||||
username,
|
||||
hashed_password
|
||||
)
|
||||
.fetch_one(&self.pgx)
|
||||
.await?;
|
||||
|
||||
Ok(rec.id.to_string())
|
||||
}
|
||||
|
||||
pub async fn validate_user(
|
||||
&self,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> anyhow::Result<Option<()>> {
|
||||
let rec = sqlx::query!(
|
||||
r#"
|
||||
SELECT * from users
|
||||
where username=$1
|
||||
"#,
|
||||
username,
|
||||
)
|
||||
.fetch_optional(&self.pgx)
|
||||
.await?;
|
||||
|
||||
match rec {
|
||||
Some(user) => match self.validate_password(password, user.password_hash)? {
|
||||
true => Ok(Some(())),
|
||||
false => Ok(None),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_password(&self, password: String) -> anyhow::Result<String> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
let password_hash = argon2
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
.map_err(|e| anyhow!(e))?
|
||||
.to_string();
|
||||
|
||||
Ok(password_hash)
|
||||
}
|
||||
|
||||
fn validate_password(&self, password: String, hashed_password: String) -> anyhow::Result<bool> {
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
let parsed_hash = PasswordHash::new(&hashed_password).map_err(|e| anyhow!(e))?;
|
||||
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
|
||||
Ok(..) => Ok(true),
|
||||
Err(..) => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user