Actix setup: init
Build nomnom / Build-NomNom (push) Successful in 5m48s Details

This commit is contained in:
Félix Baylac Jacqué 2023-07-31 08:51:35 +02:00
parent 80a2048d10
commit 04679f797c
9 changed files with 505 additions and 400 deletions

11
.gitea/workflows/ci.yaml Normal file
View File

@ -0,0 +1,11 @@
name: Build nomnom
run-name: Build nomnom
on: [push]
jobs:
Build-NomNom:
runs-on: nix
steps:
- name: Check out repository code
uses: actions/checkout@v3
- run: nix build -L

765
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4"
clap = { version = "4.0.29", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
url = "*"
warp = "0.3"
webauthn-rs = "0.4.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -3,6 +3,8 @@
pkgs.rustPlatform.buildRustPackage {
pname = "nom-nom-nix-gc";
version = "0.0";
nativeBuildInputs = [ pkgs.pkg-config ];
buildInputs = [ pkgs.openssl ];
src = pkgs.lib.cleanSource ./.;
cargoHash = "sha256-3qIr0VcstuG/xzUTO5TAguJ9ZTmQsYMSw5VXyuY2HMc=";
cargoHash = "sha256-RLVuWCKDnffILt8XrdiYiPwfXMQpw/f43YOsOrMwX9o=";
}

0
src/app/mod.rs Normal file
View File

View File

@ -1,42 +0,0 @@
use serde::de::DeserializeOwned;
use warp::Filter;
use webauthn_rs::prelude::RegisterPublicKeyCredential;
use crate::handlers;
use crate::models;
pub fn all(state: models::AppState) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone + '_{
landing(state.clone())
.or(start_webauthn_registration(state.clone()))
.or(finish_webauthn_registration(state.clone()))
}
pub fn landing(state: models::AppState) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone + '_ {
warp::path::end()
.and(warp::any().map(move || state.clone()))
.and_then(handlers::landing_page)
}
pub fn start_webauthn_registration<'a>(state: models::AppState<'a>) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone + 'a {
warp::path!("account" / "register-init")
.and(warp::post())
.and(json_body::<models::User>())
.and(warp::any().map(move || state.clone()))
.and_then(handlers::start_webauthn_registration)
}
pub fn finish_webauthn_registration<'a>(state: models::AppState<'a>) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone + 'a {
warp::path!("account" / "register-finish")
.and(warp::post())
.and(json_body::<RegisterPublicKeyCredential>())
.and(warp::any().map(move || state.clone()))
.and(warp::cookie("uuid"))
.and_then(handlers::finish_webauthn_registration)
}
fn json_body<'a, M: Send + DeserializeOwned>() -> impl Filter<Extract = (M,), Error = warp::Rejection> + Clone {
// When accepting a body, we want a JSON body
// (and to reject huge payloads)...
warp::body::content_length_limit(1024 * 16).and(warp::body::json())
}

View File

@ -1,15 +1,16 @@
use std::convert::Infallible;
use warp::Reply;
use webauthn_rs::prelude::{RegisterPublicKeyCredential, Uuid};
use actix_web::{error, HttpResponse, http::header::{ContentType, self}, web, cookie::{Cookie, SameSite}, HttpRequest};
use crate::{models::{AppState, User}, templates};
pub async fn landing_page (app_state: AppState<'_>) -> Result<impl Reply, Infallible> {
let content: String = templates::landing_page(app_state).unwrap();
Ok(warp::reply::html(content))
pub async fn landing_page (app_state: web::Data<AppState<'_>>) -> HttpResponse {
let content: String = templates::landing_page(app_state.hbs.clone()).unwrap();
HttpResponse::Ok()
.content_type(ContentType::html())
.body(content)
}
pub async fn start_webauthn_registration(user: User, app_state: AppState<'_>) -> Result<impl warp::Reply, Infallible> {
pub async fn start_webauthn_registration(app_state: web::Data<AppState<'_>>, user: web::Json<User>) -> HttpResponse {
let (creation_challenge_response, passkey_registration) = app_state.webauthn.start_passkey_registration(user.uuid, &user.user_name, &user.display_name, None).unwrap();
let uuid_str = user.uuid.to_string();
{
@ -18,13 +19,22 @@ pub async fn start_webauthn_registration(user: User, app_state: AppState<'_>) ->
}
{
let mut uuid_db = app_state.db.user_uuid_object.write().await;
uuid_db.insert(user.uuid, user);
uuid_db.insert(user.uuid, user.into_inner());
}
let json_reply = warp::reply::json(&creation_challenge_response);
Ok(warp::reply::with_header(json_reply, "Set-Cookie", format!("uuid={};SameSite=Strict", &uuid_str)))
let res = serde_json::to_string(&creation_challenge_response).unwrap();
let cookie = Cookie::build("uuid", &uuid_str)
.secure(true)
.same_site(SameSite::Strict)
.finish();
HttpResponse::Ok()
.content_type(ContentType::json())
.insert_header((header::SET_COOKIE, cookie.encoded().to_string()))
.body(res)
}
pub async fn finish_webauthn_registration(register: RegisterPublicKeyCredential, app_state: AppState<'_>, uuid: Uuid) -> Result<impl warp::Reply, Infallible> {
pub async fn finish_webauthn_registration(req: HttpRequest, app_state: web::Data<AppState<'_>>, register: web::Json<RegisterPublicKeyCredential>) -> impl actix_web::Responder {
let cook = req.cookie("uuid");
let uuid = Uuid::parse_str(cook.unwrap().value()).unwrap();
let registration_result = {
let users = app_state.db.user_uuid_object.read().await;
let user = users.get(&uuid).unwrap();
@ -32,14 +42,14 @@ pub async fn finish_webauthn_registration(register: RegisterPublicKeyCredential,
let passkey_registration = session.get(&user).unwrap();
app_state.webauthn.finish_passkey_registration(&register, passkey_registration)
};
let reply = {
let mut user_keys = app_state.db.user_keys.write().await;
registration_result.map_or(
warp::reply::with_status("Challenge failed, cannot register key", warp::http::StatusCode::UNAUTHORIZED),
|passkey| {
user_keys.insert(uuid, passkey);
warp::reply::with_status("ok",warp::http::StatusCode::OK)
})
};
Ok(reply)
let mut user_keys = app_state.db.user_keys.write().await;
match registration_result {
Ok(passkey) => {
user_keys.insert(uuid, passkey);
HttpResponse::Ok()
.body("ok")
},
Err(_) =>
HttpResponse::from_error(error::ErrorUnauthorized("Webauthn challenge failed"))
}
}

View File

@ -1,7 +1,8 @@
use std::net::SocketAddr;
use actix_web::{App, web, HttpServer};
use clap::Parser;
mod filters;
mod app;
mod handlers;
mod models;
mod templates;
@ -11,19 +12,26 @@ mod templates;
struct CLIArgs {
#[arg(short, long)]
bind: String
}
#[tokio::main]
async fn main() {
async fn main() -> std::io::Result<()> {
let args = CLIArgs::parse();
let addr: SocketAddr = args.bind.parse().expect(&format!("Cannot bind to {}. Please provide a host and port like [::1]:8000", &args.bind));
println!("Server listening to {}", &args.bind);
let routes = filters::all(models::AppState::new());
warp::serve(routes).run(addr).await;
println!("Turning out server");
println!("Adieu, goodbye, auf wiedersehen");
HttpServer::new(
|| {
let state = models::AppState::new();
App::new().app_data(web::Data::new(state))
.route("/", web::get().to(handlers::landing_page))
.route("/account/register-init", web::post().to(handlers::start_webauthn_registration))
.route("/account/register-finish", web::post().to(handlers::finish_webauthn_registration))
})
.bind(addr)
.unwrap()
.run()
.await
}

View File

@ -2,9 +2,7 @@ use handlebars::RenderError;
use serde_json::json;
use handlebars::Handlebars;
use std::path::PathBuf;
use crate::models::AppState;
use std::{path::PathBuf, sync::Arc};
pub fn new<'a>() -> Result<Handlebars<'a>, RenderError> {
let rootpath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@ -19,8 +17,7 @@ pub fn new<'a>() -> Result<Handlebars<'a>, RenderError> {
return Ok(hbs)
}
pub fn landing_page(app_state: AppState<'_>) -> Result<String, RenderError> {
let hb = app_state.hbs;
pub fn landing_page<'a>(hb: Arc<Handlebars<'a>>) -> Result<String, RenderError> {
let data = json!({
});
hb.render("landing", &data)