Added favicons and shit like this.
This commit is contained in:
1 parent
76e1e8cb53
commit
1b94784c7f
18 files changed
+163
-22
No files matched your search
@@ -14,7 +14,7 @@ VALUES ('aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkk');
|
|||||||
CREATE TABLE IF NOT EXISTS users
|
CREATE TABLE IF NOT EXISTS users
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL UNIQUE,
|
||||||
public_key TEXT NOT NULL,
|
public_key TEXT NOT NULL,
|
||||||
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 748 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"short_name": "",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/favicons/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/favicons/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
@@ -3,6 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicons/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="/static/favicons/site.webmanifest">
|
||||||
|
|
||||||
<title>ledger</title>
|
<title>ledger</title>
|
||||||
<link rel="stylesheet" href="/static/vendor/uikit.min.css" />
|
<link rel="stylesheet" href="/static/vendor/uikit.min.css" />
|
||||||
<script src="/static/vendor/uikit.min.js"></script>
|
<script src="/static/vendor/uikit.min.js"></script>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Sorry mate, that was plainly wrong.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<form hx-post="/logout"
|
||||||
|
hx-ext="json-enc"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
hx-target="#home-page-main-block">
|
||||||
|
|
||||||
|
<div class="uk-margin">
|
||||||
|
<button class="uk-button uk-button-default" type="click">Retry</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
hx-target="#home-page-main-block">
|
hx-target="#home-page-main-block">
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<label>
|
<label>
|
||||||
Username
|
Email
|
||||||
<input class="uk-input" type="text" name="username" required>
|
<input class="uk-input" type="email" name="username" required>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div>
|
<div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
You are logged in as {{ name }}.
|
Congrat! You are now logged in as {{ user.name }}.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+74
-6
@@ -1,4 +1,4 @@
|
|||||||
use rusqlite::{params, Connection, Result, Row, Transaction};
|
use rusqlite::{params, Connection, Result, Row, Transaction, OptionalExtension};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::db::_external_id::{Id, UserId};
|
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> {
|
pub fn create_user(tx: &Transaction, name: &String, public_key: &String) -> CustomResult<User> {
|
||||||
tx.execute(
|
tx.query_row(
|
||||||
"INSERT INTO users (name, public_key) VALUES (?1, ?2)",
|
"INSERT INTO users (name, public_key) VALUES (?1, ?2) RETURNING *",
|
||||||
(name, public_key),
|
(name, public_key),
|
||||||
|
_user_from_row
|
||||||
)
|
)
|
||||||
.map_err(Error::SqlError)?;
|
.map_err(Error::SqlError)
|
||||||
return get_user_by_id(tx, tx.last_insert_rowid() as i32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _user_from_row(row: &Row) -> Result<User> {
|
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
|
let q = "SELECT id, name, public_key
|
||||||
FROM users
|
FROM users
|
||||||
WHERE id = ?1";
|
WHERE id = ?1";
|
||||||
tx.query_row(q, params![id], _user_from_row)
|
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)
|
.map_err(Error::SqlError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,3 +68,61 @@ pub fn list_users(conn: &Connection) -> CustomResult<Vec<User>> {
|
|||||||
.collect();
|
.collect();
|
||||||
return Ok(ret);
|
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
@@ -22,7 +22,7 @@ pub mod securities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod users {
|
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 {
|
pub mod accounts {
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ pub struct SessionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub fn from_conn(conn: &mut Connection) -> CustomResult<SessionService> {
|
||||||
let tx = conn.transaction().map_err(Error::SqlError)?;
|
let tx = conn.transaction().map_err(Error::SqlError)?;
|
||||||
let secret = LedgerInternal::new().get_cookie_secret(&tx)?;
|
let secret = LedgerInternal::new().get_cookie_secret(&tx)?;
|
||||||
@@ -20,6 +27,10 @@ impl SessionService {
|
|||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_key(key: Key) -> SessionService{
|
||||||
|
SessionService{key}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_login_cookie(&self, user_id: &String) -> Cookie {
|
pub fn create_login_cookie(&self, user_id: &String) -> Cookie {
|
||||||
let mut jar = CookieJar::new();
|
let mut jar = CookieJar::new();
|
||||||
jar.private_mut(&self.key)
|
jar.private_mut(&self.key)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use rusqlite::Connection;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
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;
|
use crate::util::CustomResult;
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
@@ -28,10 +28,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
pub struct UserDTO {
|
pub struct UserDTO {
|
||||||
name: String,
|
pub name: String,
|
||||||
id: String, // todo: should be of type UserId
|
pub id: String, // todo: should be of type UserId
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserDTO {
|
impl UserDTO {
|
||||||
@@ -52,6 +52,11 @@ impl UserService<'_> {
|
|||||||
UserService { conn }
|
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>> {
|
pub fn list_users(self) -> CustomResult<Page<UserDTO>> {
|
||||||
let users = list_users(self.conn)?
|
let users = list_users(self.conn)?
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -11,16 +11,27 @@ pub struct LoginPayload {
|
|||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn logout_req(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::post("/login")]
|
#[actix_web::post("/login")]
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
tmpl_env: MiniJinjaRenderer,
|
tmpl_env: MiniJinjaRenderer,
|
||||||
web::Json(form): web::Json<LoginPayload>,
|
web::Json(form): web::Json<LoginPayload>,
|
||||||
service_factory: web::Data<ServicesFactory>,
|
service_factory: web::Data<ServicesFactory>,
|
||||||
) -> actix_web::Result<impl Responder> {
|
) -> 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 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! {
|
let ctx = context! {
|
||||||
name => form.username,
|
user => & user,
|
||||||
};
|
};
|
||||||
let ret = tmpl_env
|
let ret = tmpl_env
|
||||||
.render("partials/index/success.html", ctx)?
|
.render("partials/index/success.html", ctx)?
|
||||||
@@ -46,12 +57,16 @@ pub async fn index(
|
|||||||
service_factory: web::Data<ServicesFactory>,
|
service_factory: web::Data<ServicesFactory>,
|
||||||
tmpl_env: MiniJinjaRenderer,
|
tmpl_env: MiniJinjaRenderer,
|
||||||
) -> actix_web::Result<impl Responder> {
|
) -> actix_web::Result<impl Responder> {
|
||||||
|
let conn = service_factory.get_connection()?;
|
||||||
let authentication_service = service_factory.get_authentication_service()?;
|
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 userid = authentication_service.get_login(&req);
|
||||||
|
|
||||||
let username = userid.clone().unwrap_or("".to_string());
|
let username = userid.clone().unwrap_or("".to_string());
|
||||||
let ctx = context! {
|
let ctx = context! {
|
||||||
is_logged_in => userid.is_some(),
|
is_logged_in => userid.is_some(),
|
||||||
name => username,
|
// user => user_service.get_user_for_authentication(userid.unwrap(),
|
||||||
};
|
};
|
||||||
tmpl_env.render("index.html", ctx)
|
tmpl_env.render("index.html", ctx)
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ use r2d2::PooledConnection;
|
|||||||
use r2d2_sqlite::SqliteConnectionManager;
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use cookie::Key;
|
||||||
|
|
||||||
pub type DbPool = r2d2::Pool<SqliteConnectionManager>;
|
pub type DbPool = r2d2::Pool<SqliteConnectionManager>;
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ pub fn creat_db_pool(path: &PathBuf) -> DbPool {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ServicesCache {
|
struct ServicesCache {
|
||||||
session_service: SessionService,
|
session_service_key: Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -27,8 +28,8 @@ pub struct ServicesFactory {
|
|||||||
impl ServicesFactory {
|
impl ServicesFactory {
|
||||||
pub fn new(pool: DbPool) -> ServicesFactory {
|
pub fn new(pool: DbPool) -> ServicesFactory {
|
||||||
let mut conn = pool.get().unwrap();
|
let mut conn = pool.get().unwrap();
|
||||||
let session_service = SessionService::from_conn(&mut conn).unwrap();
|
let session_service_key = SessionService::get_key(&mut conn).unwrap();
|
||||||
let cache = ServicesCache { session_service };
|
let cache = ServicesCache { session_service_key };
|
||||||
ServicesFactory { pool, cache }
|
ServicesFactory { pool, cache }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ impl ServicesFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_authentication_service(&self) -> Result<SessionService> {
|
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>(
|
pub fn get_user_service<'a>(
|
||||||
|
|||||||
Reference in new issue
Block a user