This commit is contained in:
2022-10-02 20:51:06 +02:00
parent 71bdea4001
commit b20d3c418c
13 changed files with 821 additions and 61 deletions

View File

@@ -7,7 +7,12 @@ edition = "2021"
[dependencies]
async-graphql = "4.0.6"
async-graphql-axum = "*"
axum = "0.5.13"
axum-extra = { version = "*", features = ["cookie", "cookie-private"] }
axum-sessions = { version = "*" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68"
tokio = { version = "1.20.1", features = ["full"] }
uuid = { version = "1.1.2", features = ["v4", "fast-rng"] }
sqlx = { version = "0.6", features = [
@@ -24,4 +29,4 @@ tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
tower-http = { version = "0.3.4", features = ["full"] }
argon2 = "0.4"
rand_core = { version = "0.6", features = ["std"] }
cookie = "0.16"
cookie = { version = "0.16", features = ["secure", "percent-encode"] }

21
como_bin/src/error.rs Normal file
View File

@@ -0,0 +1,21 @@
use axum::{http::StatusCode, response::IntoResponse, Json};
use serde_json::json;
#[derive(Debug)]
pub enum AppError {
WrongCredentials,
InternalServerError,
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, err_msg) = match self {
Self::WrongCredentials => (StatusCode::BAD_REQUEST, "invalid credentials"),
Self::InternalServerError => (
StatusCode::INTERNAL_SERVER_ERROR,
"something went wrong with your request",
),
};
(status, Json(json!({ "error": err_msg }))).into_response()
}
}

View File

@@ -1,4 +1,6 @@
use async_graphql::{Context, EmptySubscription, Object, Schema, SimpleObject};
use axum_extra::extract::PrivateCookieJar;
use cookie::CookieJar;
use uuid::Uuid;
use crate::services::users_service::UserService;
@@ -18,11 +20,12 @@ impl MutationRoot {
let user_service = ctx.data_unchecked::<UserService>();
let valid = user_service.validate_user(username, password).await?;
Ok(match valid {
let returnvalid = match valid {
Some(..) => true,
None => false,
})
};
Ok(returnvalid)
}
async fn register(

View File

@@ -1,22 +1,33 @@
use std::env::{self, current_dir};
mod error;
mod gqlx;
mod graphql;
mod services;
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
use axum::{
extract::Extension,
http::Method,
http::{Method, StatusCode},
response::{Html, IntoResponse},
routing::get,
routing::{get, post},
Json, Router,
};
use axum_extra::extract::{cookie::Key, PrivateCookieJar};
use async_graphql::{
http::{playground_source, GraphQLPlaygroundConfig},
EmptySubscription, Request, Response, Schema,
};
use axum_sessions::{
async_session::MemoryStore,
extractors::{ReadableSession, WritableSession},
SessionLayer,
};
use error::AppError;
use graphql::CibusSchema;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use services::users_service;
use sqlx::PgPool;
use tower_http::{cors::CorsLayer, trace::TraceLayer};
@@ -24,8 +35,22 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use crate::graphql::{MutationRoot, QueryRoot};
async fn graphql_handler(schema: Extension<CibusSchema>, req: Json<Request>) -> Json<Response> {
schema.execute(req.0).await.into()
async fn graphql_handler(
schema: Extension<CibusSchema>,
session: ReadableSession,
req: GraphQLRequest,
) -> Result<GraphQLResponse, StatusCode> {
let mut req = req.into_inner();
if let Some(user_id) = session.get::<String>("userId") {
req = req.data(user_id);
return Ok(schema.execute(req).await.into());
} else if let Some(on) = &req.operation_name {
if on == "IntrospectionQuery" {
return Ok(schema.execute(req).await.into());
}
}
Err(StatusCode::FORBIDDEN)
}
async fn graphql_playground() -> impl IntoResponse {
@@ -63,18 +88,29 @@ async fn main() -> anyhow::Result<()> {
// Schema
println!("Building schema");
let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription)
.data(users_service::UserService::new(pool))
.data(users_service::UserService::new(pool.clone()))
.finish();
// CORS
let cors = vec!["http://localhost:3000".parse().unwrap()];
// Key
let key = Key::generate();
let store = MemoryStore::new();
let session_layer = SessionLayer::new(store, key.master());
// Webserver
tracing::info!("Building router");
let app = Router::new()
.route("/", get(graphql_playground).post(graphql_handler))
.layer(Extension(schema))
.route("/auth/login", post(login))
.route("/auth/register", post(register))
.layer(TraceLayer::new_for_http())
.layer(Extension(schema))
.layer(Extension(key))
.layer(Extension(pool))
.layer(session_layer)
.layer(
CorsLayer::new()
.allow_origin(cors)
@@ -90,3 +126,50 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}
#[derive(Serialize, Deserialize)]
pub struct Credentials {
pub username: String,
pub password: String,
}
async fn login(
Json(credentials): Json<Credentials>,
Extension(pool): Extension<PgPool>,
mut session: WritableSession,
) -> Result<Json<Value>, error::AppError> {
let us = users_service::UserService::new(pool);
match us
.validate_user(credentials.username, credentials.password)
.await
.map_err(|e| {
tracing::error!("could not validate user: {}", e);
AppError::InternalServerError
})? {
Some(user_id) => {
if let Err(e) = session.insert("userId", user_id.clone()) {
tracing::error!("could not insert session: {}", e);
return Err(AppError::InternalServerError);
}
Ok(Json(json!({ "userId": user_id })))
}
None => Err(AppError::WrongCredentials),
}
}
async fn register(
Json(credentials): Json<Credentials>,
Extension(pool): Extension<PgPool>,
) -> Result<Json<Value>, error::AppError> {
let us = users_service::UserService::new(pool)
.add_user(credentials.username, credentials.password)
.await
.map_err(|e| {
tracing::error!("could not add user: {}", e);
AppError::InternalServerError
})?;
Ok(Json(json!({ "userId": us })))
}

View File

@@ -1 +0,0 @@
pub struct CookieService {}

View File

@@ -1,2 +1 @@
pub mod cookie_service;
pub mod users_service;

View File

@@ -1,5 +1,3 @@
use anyhow::anyhow;
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use rand_core::OsRng;
@@ -36,7 +34,7 @@ impl UserService {
&self,
username: String,
password: String,
) -> anyhow::Result<Option<()>> {
) -> anyhow::Result<Option<String>> {
let rec = sqlx::query!(
r#"
SELECT * from users
@@ -49,7 +47,7 @@ impl UserService {
match rec {
Some(user) => match self.validate_password(password, user.password_hash)? {
true => Ok(Some(())),
true => Ok(Some(user.id.to_string())),
false => Ok(None),
},
None => Ok(None),

View File

@@ -0,0 +1,33 @@
{
"query": "\n SELECT * from users\n where username=$1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "username",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "password_hash",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "d3f222cf6c3d9816705426fdbed3b13cb575bb432eb1f33676c0b414e67aecaf"
}