Add bff
This commit is contained in:
@@ -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
21
como_bin/src/error.rs
Normal 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()
|
||||
}
|
||||
}
|
@@ -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(
|
||||
|
@@ -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 })))
|
||||
}
|
||||
|
@@ -1 +0,0 @@
|
||||
pub struct CookieService {}
|
@@ -1,2 +1 @@
|
||||
pub mod cookie_service;
|
||||
pub mod users_service;
|
||||
|
@@ -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),
|
||||
|
@@ -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"
|
||||
}
|
Reference in New Issue
Block a user