1
0
mirror of https://github.com/ellmau/adf-obdd.git synced 2025-12-20 09:39:38 +01:00

Add API for adding/deleting users; login; logout

This commit is contained in:
monsterkrampe 2023-03-30 10:26:48 +02:00
parent 38737bc725
commit 620e86e10b
No known key found for this signature in database
GPG Key ID: B8ADC1F5A5CE5057
6 changed files with 1312 additions and 43 deletions

1080
Cargo.lock generated

File diff suppressed because it is too large Load Diff

24
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"lastModified": 1678901627,
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
"type": "github"
},
"original": {
@ -33,11 +33,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1672441588,
"narHash": "sha256-jx5kxOyeObnVD44HRebKYL3cjWrcKhhcDmEYm0/naDY=",
"lastModified": 1679966490,
"narHash": "sha256-k0jV+y1jawE6w4ZvKgXDNg4+O9NNtcaWwzw8gufv0b4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6a0d2701705c3cf6f42c15aa92b7885f1f8a477f",
"rev": "5b7cd5c39befee629be284970415b6eb3b0ff000",
"type": "github"
},
"original": {
@ -49,11 +49,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1672350804,
"narHash": "sha256-jo6zkiCabUBn3ObuKXHGqqORUMH27gYDIFFfLq5P4wg=",
"lastModified": 1679944645,
"narHash": "sha256-e5Qyoe11UZjVfgRfwNoSU57ZeKuEmjYb77B9IVW7L/M=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "677ed08a50931e38382dbef01cba08a8f7eac8f6",
"rev": "4bb072f0a8b267613c127684e099a70e1f6ff106",
"type": "github"
},
"original": {
@ -82,11 +82,11 @@
]
},
"locked": {
"lastModified": 1672626196,
"narHash": "sha256-BfdLrMqxqa4YA1I1wgPBQyu4FPzL0Tp4WI2C5S6BuYo=",
"lastModified": 1680056830,
"narHash": "sha256-WB4KD8oLSxAAtmXYSzwVwQusC2Gy5vTEln1uTt0GI2k=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "c8bf9c162bb3f734cf357846e995eb70b94e2bcd",
"rev": "c8d8d05b8100d451243b614d950fa3f966c1fcc2",
"type": "github"
},
"original": {

View File

@ -71,7 +71,7 @@ impl Adf {
ac: vec![Term(0); parser.dict_size()],
rng: Adf::default_rng(),
};
(0..parser.dict_size()).into_iter().for_each(|value| {
(0..parser.dict_size()).for_each(|value| {
log::trace!("adding variable {}", Var(value));
result.bdd.variable(Var(value));
});

View File

@ -556,7 +556,7 @@ impl Bdd {
/// Counts how often another roBDD uses a [variable][crate::datatypes::Var], which occurs in this roBDD.
pub fn active_var_impact(&self, var: Var, termlist: &[Term]) -> usize {
(0..termlist.len()).into_iter().fold(0usize, |acc, idx| {
(0..termlist.len()).fold(0usize, |acc, idx| {
if self
.var_dependencies(termlist[var.value()])
.contains(&Var(idx))

View File

@ -20,6 +20,10 @@ derive_more = "0.99.17"
env_logger = "0.9"
log = "0.4"
serde = "1"
mongodb = "2.4.0"
actix-identity = "0.5.2"
argon2 = "0.5.0"
actix-session = { version="0.7.2", features = ["cookie-session"] }
[features]
cors_for_local_development = []

View File

@ -1,9 +1,23 @@
use std::time::Duration;
use actix_files as fs;
use actix_identity::{Identity, IdentityMiddleware};
use actix_session::config::PersistentSession;
use actix_session::storage::CookieSessionStore;
use actix_session::SessionMiddleware;
use actix_web::cookie::Key;
use actix_web::rt::task::spawn_blocking;
use actix_web::rt::time::timeout;
use actix_web::{post, web, App, HttpServer, Responder, ResponseError};
use actix_web::{
delete, post, web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer, Responder,
ResponseError,
};
use adf_bdd::datatypes::Term;
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use mongodb::results::DeleteResult;
use mongodb::{bson::doc, options::IndexOptions, Client, IndexModel};
use serde::{Deserialize, Serialize};
use derive_more::{Display, Error};
@ -15,13 +29,149 @@ use adf_bdd::adf::{Adf, DoubleLabeledGraph};
use adf_bdd::adfbiodivine::Adf as BdAdf;
use adf_bdd::parser::AdfParser;
#[derive(Deserialize)]
const THIRTY_MINUTES: actix_web::cookie::time::Duration =
actix_web::cookie::time::Duration::minutes(30);
const ASSET_DIRECTORY: &str = "./assets";
const DB_NAME: &str = "adf-obdd";
const USER_COLL: &str = "users";
const ADF_COLL: &str = "adf-problems";
#[derive(Deserialize, Serialize)]
struct User {
username: String,
password: String,
}
// Creates an index on the "username" field to force the values to be unique.
async fn create_username_index(client: &Client) {
let options = IndexOptions::builder().unique(true).build();
let model = IndexModel::builder()
.keys(doc! { "username": 1 })
.options(options)
.build();
client
.database(DB_NAME)
.collection::<User>(USER_COLL)
.create_index(model, None)
.await
.expect("creating an index should succeed");
}
// Add new user
#[post("/register")]
async fn register(client: web::Data<Client>, user: web::Json<User>) -> impl Responder {
let mut user: User = user.into_inner();
let user_coll = client.database(DB_NAME).collection(USER_COLL);
let user_exists: bool = user_coll
.find_one(doc! { "username": &user.username }, None)
.await
.ok()
.flatten()
.is_some();
if user_exists {
return HttpResponse::Conflict()
.body("Username is already taken. Please pick another one!");
}
let pw = &user.password;
let salt = SaltString::generate(&mut OsRng);
let hashed_pw = Argon2::default()
.hash_password(pw.as_bytes(), &salt)
.expect("Error while hashing password!")
.to_string();
user.password = hashed_pw;
let result = user_coll.insert_one(user, None).await;
match result {
Ok(_) => HttpResponse::Ok().body("Registration successful!"),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}
// Remove user
#[delete("/delete")]
async fn delete_account(client: web::Data<Client>, identity: Option<Identity>) -> impl Responder {
let user_coll: mongodb::Collection<User> = client.database(DB_NAME).collection(USER_COLL);
match identity.map(|id| id.id()) {
None => HttpResponse::Unauthorized().body("You need to login to delete your account."),
Some(Err(err)) => HttpResponse::InternalServerError().body(err.to_string()),
Some(Ok(username)) => {
match user_coll
.delete_one(doc! { "username": username }, None)
.await
{
Ok(DeleteResult {
deleted_count: 0, ..
}) => HttpResponse::InternalServerError().body("Account could not be deleted."),
Ok(DeleteResult {
deleted_count: 1, ..
}) => HttpResponse::Ok().body("Account deleted."),
Ok(_) => unreachable!(
"delete_one removes at most one entry so all cases are covered already"
),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}
}
}
// Login
#[post("/login")]
async fn login(
req: HttpRequest,
client: web::Data<Client>,
user_data: web::Json<User>,
) -> impl Responder {
let username = &user_data.username;
let pw = &user_data.password;
let user_coll: mongodb::Collection<User> = client.database(DB_NAME).collection(USER_COLL);
match user_coll
.find_one(doc! { "username": username }, None)
.await
{
Ok(Some(user)) => {
let stored_hash = PasswordHash::new(&user.password).unwrap();
let pw_valid = Argon2::default()
.verify_password(pw.as_bytes(), &stored_hash)
.is_ok();
if pw_valid {
Identity::login(&req.extensions(), username.to_string()).unwrap();
HttpResponse::Ok().body("Login successful!")
} else {
HttpResponse::BadRequest().body("Invalid email or password")
}
}
Ok(None) => HttpResponse::NotFound().body(format!(
"No user found with username {}",
&user_data.username
)),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}
#[delete("/logout")]
async fn logout(id: Identity) -> impl Responder {
id.logout();
HttpResponse::Ok().body("Logout successful!")
}
type Ac = Option<Vec<Term>>;
#[derive(Deserialize, Serialize)]
enum Parsing {
Naive,
Hybrid,
}
#[derive(Deserialize)]
#[derive(Deserialize, Serialize)]
enum Strategy {
ParseOnly,
Ground,
@ -32,6 +182,30 @@ enum Strategy {
StableNogood,
}
#[derive(Serialize)]
struct AcsPerStrategy {
parse_only: Ac,
ground: Ac,
complete: Ac,
stable: Ac,
stable_counting_a: Ac,
stable_counting_b: Ac,
stable_nogood: Ac,
}
#[derive(Serialize)]
struct AdfProblem {
code: String,
parsing_used: Parsing,
adf: Adf,
acs_per_strategy: AcsPerStrategy,
}
// #[get("/")]
// fn index() -> impl Responder {
// }
#[derive(Deserialize)]
struct SolveReqBody {
code: String,
@ -39,11 +213,6 @@ struct SolveReqBody {
strategy: Strategy,
}
#[derive(Serialize)]
struct SolveResBody {
graph: DoubleLabeledGraph,
}
fn solve(req_body: web::Json<SolveReqBody>) -> impl Responder {
let input = &req_body.code;
let parsing = &req_body.parsing;
@ -72,7 +241,7 @@ fn solve(req_body: web::Json<SolveReqBody>) -> impl Responder {
log::debug!("{:?}", adf);
let acs = match strategy {
let acs: Vec<Ac> = match strategy {
Strategy::ParseOnly => vec![None],
Strategy::Ground => vec![Some(adf.grounded())],
Strategy::Complete => adf.complete().map(Some).collect(),
@ -125,30 +294,54 @@ async fn main() -> std::io::Result<()> {
.filter_level(log::LevelFilter::Debug)
.init();
#[cfg(feature = "cors_for_local_development")]
let server = HttpServer::new(|| {
// setup mongodb
let mongodb_uri =
std::env::var("MONGODB_URI").unwrap_or_else(|_| "mongodb://localhost:27017".into());
let client = Client::with_uri_str(mongodb_uri)
.await
.expect("failed to connect to mongodb");
create_username_index(&client).await;
// cookie secret ket
let secret_key = Key::generate();
let server = HttpServer::new(move || {
let app = App::new();
#[cfg(feature = "cors_for_local_development")]
let cors = Cors::default()
.allow_any_origin()
.allowed_origin("http://localhost:1234")
.allow_any_method()
.allow_any_header()
.max_age(3600);
App::new()
.wrap(cors)
.service(solve_with_timeout)
// this mus be last to not override anything
.service(fs::Files::new("/", "./assets").index_file("index.html"))
})
.bind(("0.0.0.0", 8080))?
.run()
.await;
#[cfg(feature = "cors_for_local_development")]
let app = app.wrap(cors);
#[cfg(not(feature = "cors_for_local_development"))]
let server = HttpServer::new(|| {
App::new()
#[cfg(feature = "cors_for_local_development")]
let cookie_secure = false;
#[cfg(not(feature = "cors_for_local_development"))]
let cookie_secure = true;
app.app_data(web::Data::new(client.clone()))
.wrap(IdentityMiddleware::default())
.wrap(
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
.cookie_name("adf-obdd-service-auth".to_owned())
.cookie_secure(cookie_secure)
.session_lifecycle(PersistentSession::default().session_ttl(THIRTY_MINUTES))
.build(),
)
.service(
web::scope("/users")
.service(register)
.service(delete_account)
.service(login)
.service(logout),
)
.service(solve_with_timeout)
// this mus be last to not override anything
.service(fs::Files::new("/", "./assets").index_file("index.html"))
.service(fs::Files::new("/", ASSET_DIRECTORY).index_file("index.html"))
})
.bind(("0.0.0.0", 8080))?
.run()