Working sign in

This commit is contained in:
Adrian Woźniak 2023-08-15 12:33:53 +02:00
parent 29e6e75e95
commit 2901da51f5
18 changed files with 116 additions and 62 deletions

View File

@ -1,7 +1,7 @@
(()=>{var c=Object.create;var i=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var h=Object.getPrototypeOf,p=Object.prototype.hasOwnProperty;var u=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,s)=>(typeof require<"u"?require:e)[s]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var m=(t,e,s,l)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of a(e))!p.call(t,o)&&o!==s&&i(t,o,{get:()=>e[o],enumerable:!(l=d(e,o))||l.enumerable});return t};var y=(t,e,s)=>(s=t!=null?c(h(t)):{},m(e||!t||!t.__esModule?i(s,"default",{value:t,enumerable:!0}):s,t));customElements.define("oswilno-price",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){let t=this.shadowRoot,e=parseInt(this.getAttribute("price"));isNaN(e)&&(e=0);let s=parseInt(this.getAttribute("multiplier")),l=e,o=0;isNaN(s)||(l=Math.floor(e/s),o=e%s);let r=this.getAttribute("currency")||"PLN";t.innerHTML=`<style>:host{display:block;}</style><div>${l}.${o>=10?o:o+"0"} ${r}</div>`}});var n=t=>{let e="";for(let l=0;l<document.styleSheets.length;l++){let o=document.styleSheets[l];for(let r=0;r<o.rules.length;r++)e+=o.rules[r].cssText}let s=new CSSStyleSheet;s.replaceSync(e),t.adoptedStyleSheets=[s]};customElements.define("oswilno-error",class extends HTMLElement{constructor(){super();let t=this.attachShadow({mode:"open"});t.innerHTML=`
(()=>{var a=Object.create;var n=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var u=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty;var p=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,s)=>(typeof require<"u"?require:t)[s]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var f=(e,t,s,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of h(t))!m.call(e,o)&&o!==s&&n(e,o,{get:()=>t[o],enumerable:!(r=d(t,o))||r.enumerable});return e};var g=(e,t,s)=>(s=e!=null?a(u(e)):{},f(t||!e||!e.__esModule?n(s,"default",{value:e,enumerable:!0}):s,e));customElements.define("oswilno-price",class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){let e=this.shadowRoot,t=parseInt(this.getAttribute("price"));isNaN(t)&&(t=0);let s=parseInt(this.getAttribute("multiplier")),r=t,o=0;isNaN(s)||(r=Math.floor(t/s),o=t%s);let l=this.getAttribute("currency")||"PLN";e.innerHTML=`<style>:host{display:block;}</style><div>${r}.${o>=10?o:o+"0"} ${l}</div>`}});var i=e=>{let t="";for(let r=0;r<document.styleSheets.length;r++){let o=document.styleSheets[r];for(let l=0;l<o.rules.length;l++)t+=o.rules[l].cssText}let s=new CSSStyleSheet;s.replaceSync(t),e.adoptedStyleSheets=[s]};customElements.define("oswilno-error",class extends HTMLElement{constructor(){super();let e=this.attachShadow({mode:"open"});e.innerHTML=`
<style>:host{display:block;}</style>
<div class="flex bg-red-100 rounded-lg p-4 mb-4 text-sm text-red-700">
<slot></slot>
<div>
`,n(t)}});import("https://unpkg.com/htmx.org@1.9.4/dist/htmx.min.js");document.body.addEventListener("htmx:beforeOnLoad",function(t){let e=t.detail.xhr.status;(e===422||e===400)&&(t.detail.shouldSwap=!0,t.detail.isError=!1)});})();
`,i(e)}});import("https://unpkg.com/htmx.org@1.9.4/dist/htmx.min.js");var c=document.body;c.addEventListener("htmx:beforeOnLoad",function(e){console.log(e);let t=e.detail,s=t.xhr,r=s.status,o=t.successful;if(console.log(o,s.getResponseHeader("Authorization")),r===200){let l=s.getResponseHeader("Authorization");l&&localStorage.setItem("jwt",l.replace(/^Bearer /i,""))}else r===401&&localStorage.removeItem("jwt");(r===422||r===400)&&(t.shouldSwap=!0,t.isError=!1)});c.addEventListener("htmx:configRequest",function(e){localStorage.getItem("jwt")&&(e.detail.headers.Authorization="Bearer "+(localStorage.getItem("jwt")||""))});})();
//# sourceMappingURL=build.js.map

View File

@ -1,7 +1,7 @@
{
"version": 3,
"sources": ["../crates/web-assets/assets/elements/oswilno-price.js", "../crates/web-assets/assets/css.js", "../crates/web-assets/assets/elements/oswilno-error.js", "../crates/web-assets/assets/app.js"],
"sourcesContent": ["customElements.define('oswilno-price', class extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.attachShadow({ mode: 'open' });\n\t}\n\tconnectedCallback() {\n\t\tlet shadow = this.shadowRoot;\n\t\tlet price = parseInt(this.getAttribute('price'));\n\t\tif (isNaN(price)) price = 0;\n\t\tconst multiplier = parseInt(this.getAttribute('multiplier'));\n\t\tlet major = price;\n\t\tlet minor = 0;\n\t\tif (!isNaN(multiplier)) {\n\t\t\tmajor = Math.floor(price / multiplier);\n\t\t\tminor = price % multiplier;\n\t\t}\n\t\tconst currency = this.getAttribute('currency') || 'PLN';\n\t\tshadow.innerHTML = `<style>:host{display:block;}</style><div>${major}.${minor >= 10 ? minor : minor + '0'} ${ currency }</div>`;\n\t}\n});\n", "export const copyCss = (shadow) => {\n\tlet css = '';\n\tfor (let i = 0; i < document.styleSheets.length; i++) {\n\t\tconst styleSheet = document.styleSheets[i];\n\t\tfor (let j = 0; j < styleSheet.rules.length; j++) {\n\t\t\tcss += styleSheet.rules[j].cssText;\t\n\t\t}\n\t}\n\tconst sheet = new CSSStyleSheet();\n\tsheet.replaceSync(css);\n\tshadow.adoptedStyleSheets = [sheet];\n};\n", "import { copyCss } from \"../css.js\";\n\ncustomElements.define('oswilno-error', class extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tconst shadow = this.attachShadow({ mode: 'open' });\n\t\tshadow.innerHTML = `\n\t\t\t<style>:host{display:block;}</style>\n\t\t\t<div class=\"flex bg-red-100 rounded-lg p-4 mb-4 text-sm text-red-700\">\n\t\t\t\t<slot></slot>\n\t\t\t<div>\n\t\t`;\n\t\tcopyCss(shadow);\n\t}\n});\n", "import './elements/oswilno-price.js';\nimport './elements/oswilno-error.js';\nimport(\"https://unpkg.com/htmx.org@1.9.4/dist/htmx.min.js\");\n\ndocument.body.addEventListener('htmx:beforeOnLoad', function (evt) {\n\tconst status = evt.detail.xhr.status;\n\tif (status === 422 || status === 400) {\n\t\tevt.detail.shouldSwap = true;\n\t\tevt.detail.isError = false;\n\t}\n});\n\n"],
"mappings": "0sBAAA,eAAe,OAAO,gBAAiB,cAAc,WAAY,CAChE,aAAc,CACb,MAAM,EACN,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,CACnC,CACA,mBAAoB,CACnB,IAAIA,EAAS,KAAK,WACdC,EAAQ,SAAS,KAAK,aAAa,OAAO,CAAC,EAC3C,MAAMA,CAAK,IAAGA,EAAQ,GAC1B,IAAMC,EAAa,SAAS,KAAK,aAAa,YAAY,CAAC,EACvDC,EAAQF,EACRG,EAAQ,EACP,MAAMF,CAAU,IACpBC,EAAQ,KAAK,MAAMF,EAAQC,CAAU,EACrCE,EAAQH,EAAQC,GAEjB,IAAMG,EAAW,KAAK,aAAa,UAAU,GAAK,MAClDL,EAAO,UAAY,4CAA4CG,CAAK,IAAIC,GAAS,GAAKA,EAAQA,EAAQ,GAAG,IAAKC,CAAS,QACxH,CACD,CAAC,ECnBM,IAAMC,EAAWC,GAAW,CAClC,IAAIC,EAAM,GACV,QAASC,EAAI,EAAGA,EAAI,SAAS,YAAY,OAAQA,IAAK,CACrD,IAAMC,EAAa,SAAS,YAAYD,CAAC,EACzC,QAASE,EAAI,EAAGA,EAAID,EAAW,MAAM,OAAQC,IAC5CH,GAAOE,EAAW,MAAMC,CAAC,EAAE,OAE7B,CACA,IAAMC,EAAQ,IAAI,cAClBA,EAAM,YAAYJ,CAAG,EACrBD,EAAO,mBAAqB,CAACK,CAAK,CACnC,ECTA,eAAe,OAAO,gBAAiB,cAAc,WAAY,CAChE,aAAc,CACb,MAAM,EACN,IAAMC,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjDA,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA,IAMnBC,EAAQD,CAAM,CACf,CACD,CAAC,ECZD,OAAO,mDAAmD,EAE1D,SAAS,KAAK,iBAAiB,oBAAqB,SAAUE,EAAK,CAClE,IAAMC,EAASD,EAAI,OAAO,IAAI,QAC1BC,IAAW,KAAOA,IAAW,OAChCD,EAAI,OAAO,WAAa,GACxBA,EAAI,OAAO,QAAU,GAEvB,CAAC",
"names": ["shadow", "price", "multiplier", "major", "minor", "currency", "copyCss", "shadow", "css", "i", "styleSheet", "j", "sheet", "shadow", "copyCss", "evt", "status"]
"sourcesContent": ["customElements.define('oswilno-price', class extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.attachShadow({ mode: 'open' });\n\t}\n\tconnectedCallback() {\n\t\tlet shadow = this.shadowRoot;\n\t\tlet price = parseInt(this.getAttribute('price'));\n\t\tif (isNaN(price)) price = 0;\n\t\tconst multiplier = parseInt(this.getAttribute('multiplier'));\n\t\tlet major = price;\n\t\tlet minor = 0;\n\t\tif (!isNaN(multiplier)) {\n\t\t\tmajor = Math.floor(price / multiplier);\n\t\t\tminor = price % multiplier;\n\t\t}\n\t\tconst currency = this.getAttribute('currency') || 'PLN';\n\t\tshadow.innerHTML = `<style>:host{display:block;}</style><div>${major}.${minor >= 10 ? minor : minor + '0'} ${ currency }</div>`;\n\t}\n});\n", "export const copyCss = (shadow) => {\n\tlet css = '';\n\tfor (let i = 0; i < document.styleSheets.length; i++) {\n\t\tconst styleSheet = document.styleSheets[i];\n\t\tfor (let j = 0; j < styleSheet.rules.length; j++) {\n\t\t\tcss += styleSheet.rules[j].cssText;\t\n\t\t}\n\t}\n\tconst sheet = new CSSStyleSheet();\n\tsheet.replaceSync(css);\n\tshadow.adoptedStyleSheets = [sheet];\n};\n", "import { copyCss } from \"../css.js\";\n\ncustomElements.define('oswilno-error', class extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tconst shadow = this.attachShadow({ mode: 'open' });\n\t\tshadow.innerHTML = `\n\t\t\t<style>:host{display:block;}</style>\n\t\t\t<div class=\"flex bg-red-100 rounded-lg p-4 mb-4 text-sm text-red-700\">\n\t\t\t\t<slot></slot>\n\t\t\t<div>\n\t\t`;\n\t\tcopyCss(shadow);\n\t}\n});\n", "import './elements/oswilno-price.js';\nimport './elements/oswilno-error.js';\nimport(\"https://unpkg.com/htmx.org@1.9.4/dist/htmx.min.js\");\n\nconst body = document.body;\nbody.addEventListener('htmx:beforeOnLoad', function (evt) {\n\tconsole.log(evt);\n\tconst detail = evt.detail;\n\tconst xhr = detail.xhr;\n\tconst status = xhr.status;\n\tconst successful = detail.successful;\n\n\tconsole.log(successful, xhr.getResponseHeader('Authorization'));\n\tif (status === 200) {\n\t\tconst bearer = xhr.getResponseHeader('Authorization');\n\t\tif (bearer) {\n\t\t\tlocalStorage.setItem('jwt', bearer.replace(/^Bearer /i, ''));\n\t\t}\n\t} else if (status === 401) {\n\t\tlocalStorage.removeItem('jwt');\n\t}\n\tif (status === 422 || status === 400) {\n\t\tdetail.shouldSwap = true;\n\t\tdetail.isError = false;\n\t}\n});\nbody.addEventListener('htmx:configRequest', function (evt) {\n\tif (localStorage.getItem('jwt')) {\n\tevt.detail.headers.Authorization = 'Bearer ' + (localStorage.getItem('jwt') || '');\n\t}\n});\n\n"],
"mappings": "0sBAAA,eAAe,OAAO,gBAAiB,cAAc,WAAY,CAChE,aAAc,CACb,MAAM,EACN,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,CACnC,CACA,mBAAoB,CACnB,IAAIA,EAAS,KAAK,WACdC,EAAQ,SAAS,KAAK,aAAa,OAAO,CAAC,EAC3C,MAAMA,CAAK,IAAGA,EAAQ,GAC1B,IAAMC,EAAa,SAAS,KAAK,aAAa,YAAY,CAAC,EACvDC,EAAQF,EACRG,EAAQ,EACP,MAAMF,CAAU,IACpBC,EAAQ,KAAK,MAAMF,EAAQC,CAAU,EACrCE,EAAQH,EAAQC,GAEjB,IAAMG,EAAW,KAAK,aAAa,UAAU,GAAK,MAClDL,EAAO,UAAY,4CAA4CG,CAAK,IAAIC,GAAS,GAAKA,EAAQA,EAAQ,GAAG,IAAKC,CAAS,QACxH,CACD,CAAC,ECnBM,IAAMC,EAAWC,GAAW,CAClC,IAAIC,EAAM,GACV,QAASC,EAAI,EAAGA,EAAI,SAAS,YAAY,OAAQA,IAAK,CACrD,IAAMC,EAAa,SAAS,YAAYD,CAAC,EACzC,QAASE,EAAI,EAAGA,EAAID,EAAW,MAAM,OAAQC,IAC5CH,GAAOE,EAAW,MAAMC,CAAC,EAAE,OAE7B,CACA,IAAMC,EAAQ,IAAI,cAClBA,EAAM,YAAYJ,CAAG,EACrBD,EAAO,mBAAqB,CAACK,CAAK,CACnC,ECTA,eAAe,OAAO,gBAAiB,cAAc,WAAY,CAChE,aAAc,CACb,MAAM,EACN,IAAMC,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjDA,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA,IAMnBC,EAAQD,CAAM,CACf,CACD,CAAC,ECZD,OAAO,mDAAmD,EAE1D,IAAME,EAAO,SAAS,KACtBA,EAAK,iBAAiB,oBAAqB,SAAUC,EAAK,CACzD,QAAQ,IAAIA,CAAG,EACf,IAAMC,EAASD,EAAI,OACbE,EAAMD,EAAO,IACbE,EAASD,EAAI,OACbE,EAAaH,EAAO,WAG1B,GADA,QAAQ,IAAIG,EAAYF,EAAI,kBAAkB,eAAe,CAAC,EAC1DC,IAAW,IAAK,CACnB,IAAME,EAASH,EAAI,kBAAkB,eAAe,EAChDG,GACH,aAAa,QAAQ,MAAOA,EAAO,QAAQ,YAAa,EAAE,CAAC,CAE7D,MAAWF,IAAW,KACrB,aAAa,WAAW,KAAK,GAE1BA,IAAW,KAAOA,IAAW,OAChCF,EAAO,WAAa,GACpBA,EAAO,QAAU,GAEnB,CAAC,EACDF,EAAK,iBAAiB,qBAAsB,SAAUC,EAAK,CACtD,aAAa,QAAQ,KAAK,IAC9BA,EAAI,OAAO,QAAQ,cAAgB,WAAa,aAAa,QAAQ,KAAK,GAAK,IAEhF,CAAC",
"names": ["shadow", "price", "multiplier", "major", "minor", "currency", "copyCss", "shadow", "css", "i", "styleSheet", "j", "sheet", "shadow", "copyCss", "body", "evt", "detail", "xhr", "status", "successful", "bearer"]
}

View File

@ -5,6 +5,8 @@ use serde::{de::DeserializeOwned, Serialize};
use std::sync::Arc;
use uuid::Uuid;
pub static HEADER_NAME: &str = "Authorization";
pub trait Claims: PartialEq + DeserializeOwned + Serialize + Clone + Send + Sync + 'static {
fn jti(&self) -> uuid::Uuid;
}
@ -188,7 +190,7 @@ impl Extractor {
) -> Result<(), Error> {
let Some(authorisation_header) = req
.headers()
.get("Authorization")
.get(HEADER_NAME)
else {
return Ok(())
};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
use super::sea_orm_active_enums::Userrole;
use sea_orm::entity::prelude::*;
@ -66,7 +66,6 @@ impl PrimaryKeyTrait for PrimaryKey {
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Images,
ParkingSpaces,
}
@ -91,18 +90,11 @@ impl ColumnTrait for Column {
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Images => Entity::has_many(super::images::Entity).into(),
Self::ParkingSpaces => Entity::has_many(super::parking_spaces::Entity).into(),
}
}
}
impl Related<super::images::Entity> for Entity {
fn to() -> RelationDef {
Relation::Images.def()
}
}
impl Related<super::parking_spaces::Entity> for Entity {
fn to() -> RelationDef {
Relation::ParkingSpaces.def()

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
use super::sea_orm_active_enums::ImageState;
use sea_orm::entity::prelude::*;
@ -59,9 +59,7 @@ impl PrimaryKeyTrait for PrimaryKey {
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Accounts,
}
pub enum Relation {}
impl ColumnTrait for Column {
type EntityName = Entity;
@ -80,18 +78,7 @@ impl ColumnTrait for Column {
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Accounts => Entity::belongs_to(super::accounts::Entity)
.from(Column::AccountId)
.to(super::accounts::Column::Id)
.into(),
}
}
}
impl Related<super::accounts::Entity> for Entity {
fn to() -> RelationDef {
Relation::Accounts.def()
panic!("No RelationDef")
}
}

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
pub mod prelude;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
use sea_orm::entity::prelude::*;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
use super::sea_orm_active_enums::ParkingSpaceState;
use sea_orm::entity::prelude::*;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
pub use super::accounts::Entity as Accounts;
pub use super::images::Entity as Images;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
use sea_orm::entity::prelude::*;

View File

@ -10,7 +10,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use oswilno_session::{Authenticated, Claims};
use oswilno_view::{is_partial, Layout, LayoutOptions, Main};
use oswilno_view::{is_partial, Layout, Main, MainOpts};
pub fn mount(config: &mut ServiceConfig) {
config.service(
@ -45,7 +45,7 @@ async fn all_parking_spaces(
Layout {
main: Main {
body: parking_spaces,
opts: LayoutOptions::default(),
opts: MainOpts::default(),
},
}
.render()

View File

@ -9,14 +9,13 @@ use askama_actix::Template;
use autometrics::autometrics;
use garde::Validate;
use jsonwebtoken::*;
use oswilno_view::{Errors, Lang, Layout, LayoutOptions, Main, TranslationStorage};
use oswilno_view::{Errors, Lang, Layout, Main, MainOpts, TranslationStorage};
use ring::rand::SystemRandom;
use ring::signature::{Ed25519KeyPair, KeyPair};
use sea_orm::DatabaseConnection;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
mod extract_session;
mod hashing;
pub use oswilno_view::filters;
@ -164,7 +163,7 @@ async fn login_view(req: HttpRequest, t: Data<TranslationStorage>) -> HttpRespon
t: t.into_inner(),
errors: Errors::default(),
},
opts: LayoutOptions {
opts: MainOpts {
show: true,
search: None,
session: None,
@ -180,7 +179,7 @@ async fn login_view(req: HttpRequest, t: Data<TranslationStorage>) -> HttpRespon
t: t.into_inner(),
errors: Errors::default(),
},
opts: LayoutOptions {
opts: MainOpts {
show: true,
..Default::default()
},
@ -213,7 +212,7 @@ async fn login(
)
.await
{
Ok(res) => Ok(HttpResponse::Ok().json(res)),
Ok(res) => Ok(res),
Err(form) => Ok(HttpResponse::Ok().body(
(SignInPartialTemplate {
form,
@ -233,7 +232,7 @@ async fn login_inner(
db: Arc<DatabaseConnection>,
redis: Arc<SessionStorage<Claims>>,
errors: &mut Errors,
) -> Result<LoginResponse, SignInPayload> {
) -> Result<HttpResponse, SignInPayload> {
let iat = OffsetDateTime::now_utc().unix_timestamp() as usize;
let expires_at = OffsetDateTime::now_utc().add(jwt_ttl.0);
let exp = expires_at.unix_timestamp() as usize;
@ -255,7 +254,8 @@ async fn login_inner(
return Err(payload);
}
};
if hashing::verify(account.pass_hash.as_str(), payload.password.as_str()).is_err() {
if let Err(e) = hashing::verify(account.pass_hash.as_str(), payload.password.as_str()) {
tracing::warn!("Hashing verification failed: {e}");
errors.push_global("Bad credentials");
return Err(payload);
}
@ -284,10 +284,16 @@ async fn login_inner(
return Err(payload);
}
};
Ok(LoginResponse {
bearer_token,
claims: jwt_claims,
})
Ok(HttpResponse::Ok()
.append_header((
actix_jwt_session::HEADER_NAME,
format!("Bearer {bearer_token}").as_str(),
))
.body(""))
// Ok(LoginResponse {
// bearer_token,
// claims: jwt_claims,
// })
}
#[autometrics]
@ -319,6 +325,8 @@ struct AccountInfo {
email: String,
#[garde(length(min = 8, max = 50), custom(is_strong_password))]
password: String,
#[garde(length(min = 8, max = 50), custom(is_strong_password), custom(check_pass_differ))]
password_confirmation: String,
}
#[get("/register")]
@ -332,7 +340,7 @@ async fn register_view(req: HttpRequest, t: Data<TranslationStorage>) -> HttpRes
lang: Lang::Pl,
errors: oswilno_view::Errors::default(),
},
opts: LayoutOptions {
opts: MainOpts {
show: true,
search: None,
session: None,
@ -348,7 +356,7 @@ async fn register_view(req: HttpRequest, t: Data<TranslationStorage>) -> HttpRes
lang: Lang::Pl,
errors: oswilno_view::Errors::default(),
},
opts: LayoutOptions::default(),
opts: MainOpts::default(),
},
}
.render()
@ -369,6 +377,7 @@ struct RegisterPartialTemplate {
#[autometrics]
#[post("/register")]
async fn register(
req: HttpRequest,
db: Data<DatabaseConnection>,
payload: Form<AccountInfo>,
t: Data<oswilno_view::TranslationStorage>,
@ -377,7 +386,7 @@ async fn register(
let t = t.into_inner();
let mut errors = oswilno_view::Errors::default();
Ok(
match register_internal(db.into_inner(), payload.into_inner(), &mut errors).await {
match register_internal(req, db.into_inner(), payload.into_inner(), &mut errors).await {
Ok(res) => res,
Err(p) => HttpResponse::BadRequest().body(
RegisterPartialTemplate {
@ -396,6 +405,7 @@ async fn register(
struct RegisterContext {
login_taken: bool,
email_taken: bool,
pass_differ: bool,
}
fn is_email_free(_value: &str, context: &RegisterContext) -> garde::Result {
@ -412,6 +422,15 @@ fn is_login_free(_value: &str, context: &RegisterContext) -> garde::Result {
}
static WEAK_PASS: &str = "is not strong enough";
fn check_pass_differ(_v: &str, ctx: &RegisterContext) -> garde::Result {
if ctx.pass_differ {
Err(garde::Error::new(DIFFER_PASS))
} else {
Ok(())
}
}
static DIFFER_PASS: &str = "passwords differ";
fn is_strong_password(value: &str, _context: &RegisterContext) -> garde::Result {
if !(8..50).contains(&value.len()) {
return Err(garde::Error::new(WEAK_PASS));
@ -434,6 +453,7 @@ fn is_strong_password(value: &str, _context: &RegisterContext) -> garde::Result
}
async fn register_internal(
_req: HttpRequest,
db: Arc<DatabaseConnection>,
p: AccountInfo,
errors: &mut oswilno_view::Errors,
@ -467,6 +487,7 @@ async fn register_internal(
if let Err(e) = p.validate(&RegisterContext {
login_taken,
email_taken,
pass_differ: p.password != p.password_confirmation,
}) {
errors.consume_garde(e);
return Err(p);
@ -499,7 +520,8 @@ async fn register_internal(
Ok(HttpResponse::SeeOther()
.append_header(("Location", "/login"))
.json(EmptyResponse {}))
.append_header(("Accept", "text/html-partial"))
.body(""))
}
pub struct JwtSigningKeys {

View File

@ -45,6 +45,19 @@
<oswilno-error class="mb-2 mt-2">{{error|t(lang,t)}}</oswilno-error>
{% endfor %}
</div>
<div class="mb-4">
<label for="password_confirmation" class="block mb-2 text-sm text-gray-600">{{"Password confirmation"|t(lang,t)}}</label>
<input
id="password_confirmation"
name="password_confirmation"
type="password"
required
class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-cyan-500"
/>
{% for error in errors.field("password_confirmation") %}
<oswilno-error class="mb-2 mt-2">{{error|t(lang,t)}}</oswilno-error>
{% endfor %}
</div>
<div class="mb-6">
<input
type="submit"

View File

@ -26,18 +26,28 @@ pub struct SessionOpts {
pub profile_image_url: Option<String>,
}
#[derive(Debug, Default)]
pub struct LayoutOptions {
#[derive(Debug)]
pub struct MainOpts {
pub show: bool,
pub search: Option<SearchOptions>,
pub session: Option<SessionOpts>,
}
impl Default for MainOpts {
fn default() -> Self {
Self {
show: true,
search: None,
session: None,
}
}
}
#[derive(Debug, askama_actix::Template)]
#[template(path = "../templates/main.html")]
pub struct Main<BodyTemplate: askama::Template> {
pub body: BodyTemplate,
pub opts: LayoutOptions,
pub opts: MainOpts,
}
#[derive(Debug, askama_actix::Template)]

View File

@ -1,7 +1,14 @@
<div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user">
<ul class="flex flex-col font-medium p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
<li>
<a href="#" class="block py-2 pl-3 pr-4 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500" aria-current="page">Home</a>
<a
href="#"
hx-replace-url="true"
class="block py-2 pl-3 pr-4 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500"
aria-current="page"
>
Home
</a>
</li>
<li>
<a href="#" class="block py-2 pl-3 pr-4 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700">About</a>

View File

@ -8,6 +8,7 @@
aria-expanded="false"
data-dropdown-toggle="user-dropdown"
data-dropdown-placement="bottom"
hx-replace-url="true"
>
<span class="sr-only">Open user menu</span>
{% match session.profile_image_url %}
@ -48,6 +49,7 @@
hx-headers='{"Accept":"text/html-partial"}'
data-te-dropdown-toggle-ref
aria-expanded="false"
hx-replace-url="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -2,11 +2,31 @@ import './elements/oswilno-price.js';
import './elements/oswilno-error.js';
import("https://unpkg.com/htmx.org@1.9.4/dist/htmx.min.js");
document.body.addEventListener('htmx:beforeOnLoad', function (evt) {
const status = evt.detail.xhr.status;
const body = document.body;
body.addEventListener('htmx:beforeOnLoad', function (evt) {
console.log(evt);
const detail = evt.detail;
const xhr = detail.xhr;
const status = xhr.status;
const successful = detail.successful;
console.log(successful, xhr.getResponseHeader('Authorization'));
if (status === 200) {
const bearer = xhr.getResponseHeader('Authorization');
if (bearer) {
localStorage.setItem('jwt', bearer.replace(/^Bearer /i, ''));
}
} else if (status === 401) {
localStorage.removeItem('jwt');
}
if (status === 422 || status === 400) {
evt.detail.shouldSwap = true;
evt.detail.isError = false;
detail.shouldSwap = true;
detail.isError = false;
}
});
body.addEventListener('htmx:configRequest', function (evt) {
if (localStorage.getItem('jwt')) {
evt.detail.headers.Authorization = 'Bearer ' + (localStorage.getItem('jwt') || '');
}
});