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:
parent
38737bc725
commit
620e86e10b
1080
Cargo.lock
generated
1080
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
flake.lock
generated
24
flake.lock
generated
@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1667395993,
|
"lastModified": 1678901627,
|
||||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -33,11 +33,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1672441588,
|
"lastModified": 1679966490,
|
||||||
"narHash": "sha256-jx5kxOyeObnVD44HRebKYL3cjWrcKhhcDmEYm0/naDY=",
|
"narHash": "sha256-k0jV+y1jawE6w4ZvKgXDNg4+O9NNtcaWwzw8gufv0b4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6a0d2701705c3cf6f42c15aa92b7885f1f8a477f",
|
"rev": "5b7cd5c39befee629be284970415b6eb3b0ff000",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -49,11 +49,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1672350804,
|
"lastModified": 1679944645,
|
||||||
"narHash": "sha256-jo6zkiCabUBn3ObuKXHGqqORUMH27gYDIFFfLq5P4wg=",
|
"narHash": "sha256-e5Qyoe11UZjVfgRfwNoSU57ZeKuEmjYb77B9IVW7L/M=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "677ed08a50931e38382dbef01cba08a8f7eac8f6",
|
"rev": "4bb072f0a8b267613c127684e099a70e1f6ff106",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -82,11 +82,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1672626196,
|
"lastModified": 1680056830,
|
||||||
"narHash": "sha256-BfdLrMqxqa4YA1I1wgPBQyu4FPzL0Tp4WI2C5S6BuYo=",
|
"narHash": "sha256-WB4KD8oLSxAAtmXYSzwVwQusC2Gy5vTEln1uTt0GI2k=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "c8bf9c162bb3f734cf357846e995eb70b94e2bcd",
|
"rev": "c8d8d05b8100d451243b614d950fa3f966c1fcc2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@ -71,7 +71,7 @@ impl Adf {
|
|||||||
ac: vec![Term(0); parser.dict_size()],
|
ac: vec![Term(0); parser.dict_size()],
|
||||||
rng: Adf::default_rng(),
|
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));
|
log::trace!("adding variable {}", Var(value));
|
||||||
result.bdd.variable(Var(value));
|
result.bdd.variable(Var(value));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -556,7 +556,7 @@ impl Bdd {
|
|||||||
|
|
||||||
/// Counts how often another roBDD uses a [variable][crate::datatypes::Var], which occurs in this roBDD.
|
/// 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 {
|
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
|
if self
|
||||||
.var_dependencies(termlist[var.value()])
|
.var_dependencies(termlist[var.value()])
|
||||||
.contains(&Var(idx))
|
.contains(&Var(idx))
|
||||||
|
|||||||
@ -20,6 +20,10 @@ derive_more = "0.99.17"
|
|||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = "1"
|
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]
|
[features]
|
||||||
cors_for_local_development = []
|
cors_for_local_development = []
|
||||||
|
|||||||
@ -1,9 +1,23 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use actix_files as fs;
|
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::task::spawn_blocking;
|
||||||
use actix_web::rt::time::timeout;
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use derive_more::{Display, Error};
|
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::adfbiodivine::Adf as BdAdf;
|
||||||
use adf_bdd::parser::AdfParser;
|
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 {
|
enum Parsing {
|
||||||
Naive,
|
Naive,
|
||||||
Hybrid,
|
Hybrid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
enum Strategy {
|
enum Strategy {
|
||||||
ParseOnly,
|
ParseOnly,
|
||||||
Ground,
|
Ground,
|
||||||
@ -32,6 +182,30 @@ enum Strategy {
|
|||||||
StableNogood,
|
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)]
|
#[derive(Deserialize)]
|
||||||
struct SolveReqBody {
|
struct SolveReqBody {
|
||||||
code: String,
|
code: String,
|
||||||
@ -39,11 +213,6 @@ struct SolveReqBody {
|
|||||||
strategy: Strategy,
|
strategy: Strategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct SolveResBody {
|
|
||||||
graph: DoubleLabeledGraph,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn solve(req_body: web::Json<SolveReqBody>) -> impl Responder {
|
fn solve(req_body: web::Json<SolveReqBody>) -> impl Responder {
|
||||||
let input = &req_body.code;
|
let input = &req_body.code;
|
||||||
let parsing = &req_body.parsing;
|
let parsing = &req_body.parsing;
|
||||||
@ -72,7 +241,7 @@ fn solve(req_body: web::Json<SolveReqBody>) -> impl Responder {
|
|||||||
|
|
||||||
log::debug!("{:?}", adf);
|
log::debug!("{:?}", adf);
|
||||||
|
|
||||||
let acs = match strategy {
|
let acs: Vec<Ac> = match strategy {
|
||||||
Strategy::ParseOnly => vec![None],
|
Strategy::ParseOnly => vec![None],
|
||||||
Strategy::Ground => vec![Some(adf.grounded())],
|
Strategy::Ground => vec![Some(adf.grounded())],
|
||||||
Strategy::Complete => adf.complete().map(Some).collect(),
|
Strategy::Complete => adf.complete().map(Some).collect(),
|
||||||
@ -125,30 +294,54 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.filter_level(log::LevelFilter::Debug)
|
.filter_level(log::LevelFilter::Debug)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
#[cfg(feature = "cors_for_local_development")]
|
// setup mongodb
|
||||||
let server = HttpServer::new(|| {
|
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()
|
let cors = Cors::default()
|
||||||
.allow_any_origin()
|
.allowed_origin("http://localhost:1234")
|
||||||
.allow_any_method()
|
.allow_any_method()
|
||||||
.allow_any_header()
|
.allow_any_header()
|
||||||
.max_age(3600);
|
.max_age(3600);
|
||||||
|
|
||||||
App::new()
|
#[cfg(feature = "cors_for_local_development")]
|
||||||
.wrap(cors)
|
let app = app.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(not(feature = "cors_for_local_development"))]
|
#[cfg(feature = "cors_for_local_development")]
|
||||||
let server = HttpServer::new(|| {
|
let cookie_secure = false;
|
||||||
App::new()
|
#[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)
|
.service(solve_with_timeout)
|
||||||
// this mus be last to not override anything
|
// 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))?
|
.bind(("0.0.0.0", 8080))?
|
||||||
.run()
|
.run()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user