no fucking idea. was on the plane. project runs, tests pass.
This commit is contained in:
1 parent
9cc566c9d5
commit
fed675f4cb
14 files changed
+133
-40
No files matched your search
@@ -43,10 +43,6 @@
|
|||||||
cargo-tarpaulin
|
cargo-tarpaulin
|
||||||
deno];
|
deno];
|
||||||
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
||||||
shellHook = ''
|
|
||||||
export PROJECT_ROOT=${PROJECT_ROOT};
|
|
||||||
export PATH=$PROJECT_ROOT/tools:$PATH;
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% set active_navbar_item = 'accounts' %}
|
||||||
|
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div>bob le chef</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -14,22 +14,7 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div hx-headers='{"myHeader": "My Value"}'>
|
<div hx-headers='{"myHeader": "My Value"}'>
|
||||||
|
{% include "components/navbar.html" %}
|
||||||
<nav class="uk-navbar-container" uk-navbar>
|
|
||||||
<div class="uk-navbar-left">
|
|
||||||
<a href="/" class="uk-navbar-item uk-logo uk-margin-medium-left">
|
|
||||||
<img src="/static/images/33-game-robot.png" class="uk-margin-medium" style="height: 1em;">
|
|
||||||
Ledger
|
|
||||||
</a>
|
|
||||||
<ul class="uk-navbar-nav">
|
|
||||||
{% set nav_bar_items = [{'id': 'overview', 'label': 'Overview'},
|
|
||||||
{'id': 'actions', 'label': 'Actions'}]%}
|
|
||||||
{% for itm in nav_bar_items %}
|
|
||||||
<li class="{% if active_navbar_item == itm.id %}uk-active"{% endif %}"><a href="{{ itm.id }}">{{ itm.label }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="uk-grid">
|
<div class="uk-grid">
|
||||||
<div class="uk-width-1-6@m"></div>
|
<div class="uk-width-1-6@m"></div>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<nav class="uk-navbar-container" uk-navbar>
|
||||||
|
<div class="uk-navbar-left">
|
||||||
|
<a href="/" class="uk-navbar-item uk-logo uk-margin-medium-left">
|
||||||
|
<img src="/static/images/33-game-robot.png" class="uk-margin-medium" style="height: 1em;">
|
||||||
|
Ledger
|
||||||
|
</a>
|
||||||
|
<ul class="uk-navbar-nav">
|
||||||
|
{% set nav_bar_items = [{'id': 'overview', 'label': 'Overview'},
|
||||||
|
{'id': 'accounts', 'label': 'Accounts'},
|
||||||
|
{'id': 'actions', 'label': 'Actions'}]%}
|
||||||
|
{% for itm in nav_bar_items %}
|
||||||
|
<li class="{% if active_navbar_item == itm.id %}uk-active"{% endif %}"><a href="{{ itm.id }}">{{ itm.label }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
@@ -2,7 +2,7 @@ use rusqlite::{Connection, params, Row, Transaction, Result};
|
|||||||
use crate::util::{CustomResult, Error};
|
use crate::util::{CustomResult, Error};
|
||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
|
||||||
struct LedgerInternal {}
|
pub struct LedgerInternal {}
|
||||||
|
|
||||||
impl LedgerInternal {
|
impl LedgerInternal {
|
||||||
pub fn new() -> LedgerInternal {
|
pub fn new() -> LedgerInternal {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ mod _test_utils;
|
|||||||
mod _users;
|
mod _users;
|
||||||
mod _ledger_internal;
|
mod _ledger_internal;
|
||||||
|
|
||||||
|
pub mod ledger_internal{
|
||||||
|
pub use super::_ledger_internal::*;
|
||||||
|
}
|
||||||
|
|
||||||
pub mod external_id {
|
pub mod external_id {
|
||||||
pub use super::_external_id::*;
|
pub use super::_external_id::*;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
use cookie::{CookieJar, Cookie, Key};
|
use cookie::{CookieJar, Cookie, Key};
|
||||||
use cookie::time::OffsetDateTime;
|
use cookie::time::OffsetDateTime;
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::db::ledger_internal::LedgerInternal;
|
||||||
|
use crate::{CustomResult, Error};
|
||||||
|
|
||||||
struct SessionService {
|
pub struct SessionService {
|
||||||
key: Key,
|
key: Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl SessionService {
|
impl SessionService {
|
||||||
fn new() -> SessionService{
|
|
||||||
let super_secret_not_hardcoded_at_all: [u8; 32] = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1];
|
pub fn from_conn(conn: &mut Connection) -> CustomResult<SessionService>{
|
||||||
SessionService{key: Key::derive_from(&super_secret_not_hardcoded_at_all)}
|
let tx = conn.transaction().map_err(Error::SqlError)?;
|
||||||
|
let secret = LedgerInternal::new().get_cookie_secret(& tx)?;
|
||||||
|
let ret = SessionService{key: Key::derive_from(&secret)};
|
||||||
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
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).add(Cookie::new("ledger-user-id", user_id.clone()));
|
jar.private_mut(& self.key).add(Cookie::new("ledger-user-id", user_id.clone()));
|
||||||
jar.get("ledger-user-id").unwrap_or(& Cookie::new("ledger-user-id", "")).clone()
|
jar.get("ledger-user-id").unwrap_or(& Cookie::new("ledger-user-id", "")).clone()
|
||||||
@@ -26,12 +33,12 @@ impl SessionService {
|
|||||||
// todo: this service should not be aware of http requests.
|
// todo: this service should not be aware of http requests.
|
||||||
// basically, it should be easy to migrate of
|
// basically, it should be easy to migrate of
|
||||||
// web framework
|
// web framework
|
||||||
fn get_login(&self, req: &HttpRequest) -> Option<String> {
|
pub fn get_login(&self, req: &HttpRequest) -> Option<String> {
|
||||||
req.cookie("ledger-user-id")
|
req.cookie("ledger-user-id")
|
||||||
.and_then(|c|self._decrypt_cookie(c)).map(|c| c.value().to_string())
|
.and_then(|c|self._decrypt_cookie(c)).map(|c| c.value().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn logout(&self) -> Cookie{
|
pub fn logout(&self) -> Cookie{
|
||||||
let mut cookie = Cookie::new("ledger-user-id", "");
|
let mut cookie = Cookie::new("ledger-user-id", "");
|
||||||
let epoch = OffsetDateTime::from_unix_timestamp_nanos(0).unwrap();
|
let epoch = OffsetDateTime::from_unix_timestamp_nanos(0).unwrap();
|
||||||
cookie.set_expires(epoch);
|
cookie.set_expires(epoch);
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
use rusqlite::{Connection};
|
||||||
|
|
||||||
|
pub struct SecurityDTO{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SecurityService<'a> {
|
||||||
|
conn: &'a Connection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SecurityService<'_> {
|
||||||
|
pub fn new(conn: &Connection) -> SecurityService {
|
||||||
|
SecurityService { conn }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_securities(self) -> Vec<SecurityDTO> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-6
@@ -1,20 +1,30 @@
|
|||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
use crate::{CustomResult, Error};
|
||||||
|
|
||||||
use crate::services::_users::UserService;
|
pub use crate::services::_users::UserService;
|
||||||
|
pub use crate::services::_authentication::SessionService;
|
||||||
|
|
||||||
mod _transactions;
|
mod _transactions;
|
||||||
mod _users;
|
mod _users;
|
||||||
mod _authentication;
|
mod _authentication;
|
||||||
|
mod _securities;
|
||||||
|
|
||||||
pub struct ServiceFactory<'a> {
|
pub use crate::db::ledger_internal::LedgerInternal;
|
||||||
conn: &'a Connection,
|
|
||||||
|
pub struct ReadOnlyServiceFactory<'a> {
|
||||||
|
conn: &'a mut Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<'_> {
|
impl ReadOnlyServiceFactory<'_> {
|
||||||
pub fn new(conn: &Connection) -> ServiceFactory {
|
pub fn new(conn: &mut Connection) -> ReadOnlyServiceFactory {
|
||||||
ServiceFactory { conn }
|
ReadOnlyServiceFactory { conn }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_user_service(&self) -> UserService {
|
pub fn get_user_service(&self) -> UserService {
|
||||||
UserService::new(self.conn)
|
UserService::new(self.conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_authentication_service(&mut self) -> CustomResult<SessionService> {
|
||||||
|
SessionService::from_conn(self.conn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@ use minijinja::context;
|
|||||||
use crate::webui::MiniJinjaRenderer;
|
use crate::webui::MiniJinjaRenderer;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use crate::db::external_id::UserId;
|
use crate::db::external_id::UserId;
|
||||||
|
use crate::services::ReadOnlyServiceFactory;
|
||||||
|
use crate::SQLiteConnectionFactory;
|
||||||
|
|
||||||
struct SessionService {
|
struct SessionService {
|
||||||
key: Key,
|
key: Key,
|
||||||
@@ -53,14 +55,17 @@ struct LoginPayload {
|
|||||||
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>,
|
||||||
|
conn_factory: web::Data<SQLiteConnectionFactory>,
|
||||||
) -> actix_web::Result<impl Responder> {
|
) -> actix_web::Result<impl Responder> {
|
||||||
|
let mut conn = conn_factory.readonly().unwrap(); // todo; to proper 500
|
||||||
|
let mut service_factory = ReadOnlyServiceFactory::new(&mut conn);
|
||||||
|
let authentication_service = service_factory.get_authentication_service().unwrap();
|
||||||
|
let cookie = authentication_service.create_login_cookie(& form.username);
|
||||||
let ctx = context! {
|
let ctx = context! {
|
||||||
name => form.username,
|
name => form.username,
|
||||||
};
|
};
|
||||||
let mut ret = tmpl_env.render("partials/index/success.html", ctx)?.customize();
|
let mut ret = tmpl_env.render("partials/index/success.html", ctx)?.customize();
|
||||||
let s = SessionService::new();
|
Ok(ret.insert_header(("Set-Cookie", cookie.to_string())))
|
||||||
let cookie = s.create_login_cookie(& form.username).to_string();
|
|
||||||
Ok(ret.insert_header(("Set-Cookie", cookie)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::post("/logout")]
|
#[actix_web::post("/logout")]
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use crate::webui::worker::{bob, event_processor_loop};
|
|||||||
mod views;
|
mod views;
|
||||||
mod worker;
|
mod worker;
|
||||||
mod index_views;
|
mod index_views;
|
||||||
|
mod services;
|
||||||
|
|
||||||
|
|
||||||
struct MiniJinjaRenderer {
|
struct MiniJinjaRenderer {
|
||||||
@@ -102,6 +103,7 @@ pub async fn web_ui_main(
|
|||||||
.service(index_views::login)
|
.service(index_views::login)
|
||||||
.service(index_views::logout)
|
.service(index_views::logout)
|
||||||
.service(views::actions)
|
.service(views::actions)
|
||||||
|
.service(views::accounts)
|
||||||
.service(views::overview)
|
.service(views::overview)
|
||||||
.service(views::overview_submit_button)
|
.service(views::overview_submit_button)
|
||||||
.service(views::actions_partials)
|
.service(views::actions_partials)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use crate::util::{CustomResult, Error, SQLiteConnectionFactory};
|
||||||
|
use crate::services::{SessionService, ReadOnlyServiceFactory, UserService};
|
||||||
|
|
||||||
|
struct ServicesCache {
|
||||||
|
session_service: SessionService,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Services {
|
||||||
|
conn_fact: SQLiteConnectionFactory,
|
||||||
|
cache: ServicesCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Services {
|
||||||
|
pub fn new(conn_fact: SQLiteConnectionFactory) -> Services {
|
||||||
|
let mut tmp_conn = conn_fact.connection().unwrap();
|
||||||
|
let session_service = SessionService::from_conn(&mut tmp_conn).unwrap();
|
||||||
|
let cache = ServicesCache{session_service};
|
||||||
|
Services { conn_fact, cache }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_authentication_service(& self) -> &SessionService {
|
||||||
|
& self.cache.session_service
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_user_service(&self) -> i32 {
|
||||||
|
// let mut conn = self.conn_fact.readonly().unwrap(); // todo; to proper 500
|
||||||
|
// let service_factory = ReadOnlyServiceFactory::new(&mut conn);
|
||||||
|
// service_factory.get_user_service()
|
||||||
|
88
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-3
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use actix_web::{error, web, HttpRequest, Responder};
|
use actix_web::{error, web, HttpRequest, Responder};
|
||||||
|
use actix_web_lab::__reexports::futures_util::SinkExt;
|
||||||
use clap::builder::TypedValueParser;
|
use clap::builder::TypedValueParser;
|
||||||
use log::info;
|
use log::info;
|
||||||
use minijinja::context;
|
use minijinja::context;
|
||||||
@@ -8,7 +9,7 @@ use serde::Deserialize;
|
|||||||
|
|
||||||
use crate::db::users::list_users;
|
use crate::db::users::list_users;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::services::ServiceFactory;
|
use crate::services::ReadOnlyServiceFactory;
|
||||||
use crate::util::SQLiteConnectionFactory;
|
use crate::util::SQLiteConnectionFactory;
|
||||||
use crate::webui::MiniJinjaRenderer;
|
use crate::webui::MiniJinjaRenderer;
|
||||||
|
|
||||||
@@ -17,13 +18,18 @@ pub async fn actions(tmpl_env: MiniJinjaRenderer) -> actix_web::Result<impl Resp
|
|||||||
tmpl_env.render("actions.html", ())
|
tmpl_env.render("actions.html", ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_web::get("/accounts")]
|
||||||
|
pub async fn accounts(tmpl_env: MiniJinjaRenderer) -> actix_web::Result<impl Responder> {
|
||||||
|
tmpl_env.render("accounts.html", ())
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::get("/overview")]
|
#[actix_web::get("/overview")]
|
||||||
pub async fn overview(
|
pub async fn overview(
|
||||||
tmpl_env: MiniJinjaRenderer,
|
tmpl_env: MiniJinjaRenderer,
|
||||||
conn_factory: web::Data<SQLiteConnectionFactory>,
|
conn_factory: web::Data<SQLiteConnectionFactory>,
|
||||||
) -> actix_web::Result<impl Responder> {
|
) -> actix_web::Result<impl Responder> {
|
||||||
let conn = conn_factory.readonly().unwrap(); // todo; to proper 500
|
let mut conn = conn_factory.readonly().unwrap(); // todo; to proper 500
|
||||||
let service_factory = ServiceFactory::new(&conn);
|
let service_factory = ReadOnlyServiceFactory::new(&mut conn);
|
||||||
let user_service = service_factory.get_user_service();
|
let user_service = service_factory.get_user_service();
|
||||||
let users = user_service.list_users().unwrap();
|
let users = user_service.list_users().unwrap();
|
||||||
let ctx = context! {
|
let ctx = context! {
|
||||||
|
|||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
exec cargo run -- wui run --port 9999 --enable-template-autoreload
|
||||||
Reference in new issue
Block a user