Actix setup: init
Build nomnom / Build-NomNom (push) Successful in 5m48s
Details
Build nomnom / Build-NomNom (push) Successful in 5m48s
Details
This commit is contained in:
parent
80a2048d10
commit
04679f797c
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
|
|
|
@ -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=";
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
|
@ -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(®ister, 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| {
|
||||
match registration_result {
|
||||
Ok(passkey) => {
|
||||
user_keys.insert(uuid, passkey);
|
||||
warp::reply::with_status("ok",warp::http::StatusCode::OK)
|
||||
})
|
||||
};
|
||||
Ok(reply)
|
||||
HttpResponse::Ok()
|
||||
.body("ok")
|
||||
},
|
||||
Err(_) =>
|
||||
HttpResponse::from_error(error::ErrorUnauthorized("Webauthn challenge failed"))
|
||||
}
|
||||
}
|
||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue