Added favicons and shit like this.

This commit is contained in:
didier amyot committed 2023-04-01 11:05:56 -04:00
1 parent 76e1e8cb53
commit 1b94784c7f
18 files changed
+163 -22

No files matched your search

+74 -6
View File
@@ -1,4 +1,4 @@
use rusqlite::{params, Connection, Result, Row, Transaction};
use rusqlite::{params, Connection, Result, Row, Transaction, OptionalExtension};
use serde::Serialize;
use crate::db::_external_id::{Id, UserId};
@@ -22,12 +22,12 @@ impl User {
}
pub fn create_user(tx: &Transaction, name: &String, public_key: &String) -> CustomResult<User> {
tx.execute(
"INSERT INTO users (name, public_key) VALUES (?1, ?2)",
tx.query_row(
"INSERT INTO users (name, public_key) VALUES (?1, ?2) RETURNING *",
(name, public_key),
_user_from_row
)
.map_err(Error::SqlError)?;
return get_user_by_id(tx, tx.last_insert_rowid() as i32);
.map_err(Error::SqlError)
}
fn _user_from_row(row: &Row) -> Result<User> {
@@ -38,11 +38,21 @@ fn _user_from_row(row: &Row) -> Result<User> {
})
}
fn get_user_by_id(tx: &Transaction, id: i32) -> CustomResult<User> {
fn get_user_by_id(tx: &Transaction, id: & UserId) -> CustomResult<Option<User>> {
let q = "SELECT id, name, public_key
FROM users
WHERE id = ?1";
tx.query_row(q, params![id], _user_from_row)
.optional()
.map_err(Error::SqlError)
}
pub fn get_user_by_name(tx: &Connection, name: &String) -> CustomResult<Option<User>>{
let q = "SELECT id, name, public_key
FROM users
WHERE name = ?1";
tx.query_row(q, params![name], _user_from_row)
.optional()
.map_err(Error::SqlError)
}
@@ -58,3 +68,61 @@ pub fn list_users(conn: &Connection) -> CustomResult<Vec<User>> {
.collect();
return Ok(ret);
}
#[cfg(test)]
mod test_users {
use crate::migration;
use super::*;
fn setup() -> Connection{
let mut conn = Connection::open_in_memory().unwrap();
migration::apply_migrations(&mut conn).unwrap();
conn
}
fn setup_with_one_user() -> (Connection, User){
let mut conn = setup();
let tx = conn.transaction().unwrap();
let u = create_user(& tx, & "bob".to_string(), & "123".to_string()).unwrap();
tx.commit().unwrap();
(conn, u)
}
#[test]
fn test_happy_path() -> CustomResult<()>{
let mut conn = setup();
let tx = conn.transaction().unwrap();
let u = create_user(& tx, & "alice".to_string(), & "123".to_string())?;
tx.commit().unwrap();
let tx = conn.transaction().unwrap();
assert_eq!( list_users(& tx)?.len(), 1);
assert!( get_user_by_name(&tx, & u.name)?.is_some());
Ok(())
}
#[test]
fn test_get_user() -> CustomResult<()>{
let (mut conn, user) = setup_with_one_user();
let tx = conn.transaction().unwrap();
assert!( get_user_by_name(&tx, & user.name)?.is_some());
assert!( get_user_by_name(&tx, & "404-not-found".to_string())?.is_none());
Ok(())
}
#[test]
fn test_get_user_by_id() -> CustomResult<()> {
let (mut conn, user) = setup_with_one_user();
let tx = conn.transaction().unwrap();
assert!( get_user_by_id(&tx, & user.id())?.is_some());
let random_id = UserId::from_id(8734289723902734);
assert!( get_user_by_id(&tx, & random_id)?.is_none());
Ok(())
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ pub mod securities {
}
pub mod users {
pub use super::_users::{create_user, list_users, User};
pub use super::_users::{create_user, list_users, get_user_by_name, User};
}
pub mod accounts {
+11
View File
@@ -11,6 +11,13 @@ pub struct SessionService {
}
impl SessionService {
pub fn get_key(conn: &mut Connection) -> CustomResult<Key> {
let tx = conn.transaction().map_err(Error::SqlError)?;
let secret = LedgerInternal::new().get_cookie_secret(&tx)?;
Ok(Key::derive_from(&secret))
}
pub fn from_conn(conn: &mut Connection) -> CustomResult<SessionService> {
let tx = conn.transaction().map_err(Error::SqlError)?;
let secret = LedgerInternal::new().get_cookie_secret(&tx)?;
@@ -20,6 +27,10 @@ impl SessionService {
Ok(ret)
}
pub fn from_key(key: Key) -> SessionService{
SessionService{key}
}
pub fn create_login_cookie(&self, user_id: &String) -> Cookie {
let mut jar = CookieJar::new();
jar.private_mut(&self.key)
+9 -4
View File
@@ -2,7 +2,7 @@ use rusqlite::Connection;
use serde::Deserialize;
use serde::Serialize;
use crate::db::users::{list_users, User};
use crate::db::users::{get_user_by_name, list_users, User};
use crate::util::CustomResult;
#[derive(Serialize, Debug)]
@@ -28,10 +28,10 @@ where
}
}
#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct UserDTO {
name: String,
id: String, // todo: should be of type UserId
pub name: String,
pub id: String, // todo: should be of type UserId
}
impl UserDTO {
@@ -52,6 +52,11 @@ impl UserService<'_> {
UserService { conn }
}
pub fn get_user_for_authentication(&self, username: &String, _credentials: &String) -> CustomResult<Option<UserDTO>> {
let ret = get_user_by_name(self.conn, username)?;
Ok(ret.as_ref().map(UserDTO::from_entity))
}
pub fn list_users(self) -> CustomResult<Page<UserDTO>> {
let users = list_users(self.conn)?
.iter()
+18 -3
View File
@@ -11,16 +11,27 @@ pub struct LoginPayload {
password: String,
}
fn logout_req(){
}
#[actix_web::post("/login")]
pub async fn login(
tmpl_env: MiniJinjaRenderer,
web::Json(form): web::Json<LoginPayload>,
service_factory: web::Data<ServicesFactory>,
) -> actix_web::Result<impl Responder> {
let conn = service_factory.get_connection()?;
let user_service = service_factory.get_user_service(&conn)?;
let maybeUser = user_service.get_user_for_authentication(&form.username, &form.password).unwrap();
if maybeUser.is_none() {
return Ok(tmpl_env.render("partials/index/failure.html", context!{})?.customize());
}
let user = maybeUser.unwrap();
let authentication_service = service_factory.get_authentication_service()?;
let cookie = authentication_service.create_login_cookie(&form.username);
let cookie = authentication_service.create_login_cookie(&user.id);
let ctx = context! {
name => form.username,
user => & user,
};
let ret = tmpl_env
.render("partials/index/success.html", ctx)?
@@ -46,12 +57,16 @@ pub async fn index(
service_factory: web::Data<ServicesFactory>,
tmpl_env: MiniJinjaRenderer,
) -> actix_web::Result<impl Responder> {
let conn = service_factory.get_connection()?;
let authentication_service = service_factory.get_authentication_service()?;
let user_service = service_factory.get_user_service(&conn)?;
let userid = authentication_service.get_login(&req);
let username = userid.clone().unwrap_or("".to_string());
let ctx = context! {
is_logged_in => userid.is_some(),
name => username,
// user => user_service.get_user_for_authentication(userid.unwrap(),
};
tmpl_env.render("index.html", ctx)
}
+5 -4
View File
@@ -5,6 +5,7 @@ use r2d2::PooledConnection;
use r2d2_sqlite::SqliteConnectionManager;
use std::path::PathBuf;
use cookie::Key;
pub type DbPool = r2d2::Pool<SqliteConnectionManager>;
@@ -15,7 +16,7 @@ pub fn creat_db_pool(path: &PathBuf) -> DbPool {
#[derive(Clone)]
struct ServicesCache {
session_service: SessionService,
session_service_key: Key,
}
#[derive(Clone)]
@@ -27,8 +28,8 @@ pub struct ServicesFactory {
impl ServicesFactory {
pub fn new(pool: DbPool) -> ServicesFactory {
let mut conn = pool.get().unwrap();
let session_service = SessionService::from_conn(&mut conn).unwrap();
let cache = ServicesCache { session_service };
let session_service_key = SessionService::get_key(&mut conn).unwrap();
let cache = ServicesCache { session_service_key };
ServicesFactory { pool, cache }
}
@@ -39,7 +40,7 @@ impl ServicesFactory {
}
pub fn get_authentication_service(&self) -> Result<SessionService> {
Ok(self.cache.session_service.clone())
Ok(SessionService::from_key(self.cache.session_service_key.clone()))
}
pub fn get_user_service<'a>(