handle sign in

This commit is contained in:
Adrian Woźniak 2023-08-03 16:16:46 +02:00
parent 7b3014b44d
commit 86d4b2c6de
18 changed files with 150 additions and 109 deletions

57
Cargo.lock generated
View File

@ -758,6 +758,33 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "autometrics"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fd5718a452b060adfb2402004a5ebd67ea90e926da637259748de0ca654ea37"
dependencies = [
"autometrics-macros",
"cfg_aliases",
"once_cell",
"spez",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "autometrics-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0beb4214dae229373b9f437afe2c954a1c8cfb8a64ac753a47285a759ae80284"
dependencies = [
"percent-encoding",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "backtrace"
version = "0.3.68"
@ -894,7 +921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [
"borsh-derive",
"hashbrown 0.13.2",
"hashbrown 0.12.3",
]
[[package]]
@ -1027,6 +1054,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "chrono"
version = "0.4.26"
@ -1669,15 +1702,6 @@ dependencies = [
"ahash 0.7.6",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash 0.8.3",
]
[[package]]
name = "hashbrown"
version = "0.14.0"
@ -2296,6 +2320,8 @@ dependencies = [
"actix-web",
"argon2",
"askama",
"askama_actix",
"autometrics",
"futures",
"jsonwebtoken",
"oswilno-contract",
@ -3294,6 +3320,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "spez"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.28",
]
[[package]]
name = "spin"
version = "0.5.2"

View File

@ -10,9 +10,9 @@ path = "src/lib.rs"
[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }
tokio = { version = "1.29.1", features = ["full"] }
oswilno-contract = { path = "../oswilno-contract" }
sea-orm = { version = "0.11", features = ["runtime-actix-rustls", "sqlx-postgres", "postgres-array", "sqlx"] }
tokio = { version = "1.29.1", features = ["full"] }
[dependencies.sea-orm-migration]
version = "0.11.0"

View File

@ -11,6 +11,6 @@ actix-web = "4.3.1"
actix-web-grants = "3.0.2"
askama = "0.12.0"
chrono = "0.4.26"
oswilno-contract = { path = "../oswilno-contract" }
tera = "1.17.1"
uuid = { version = "1.4.1", features = ["v4"] }
oswilno-contract = { path = "../oswilno-contract" }

View File

@ -10,8 +10,8 @@ actix = "0.13.0"
actix-admin = { git = "https://code.ita-prog.pl/Tsumanu/actix-admin.git", features = ['enable-tracing'] }
actix-rt = { version = "2.8.0", features = [] }
chrono = "0.4.26"
oswilno-actix-admin = { path = "../oswilno-actix-admin" }
regex = "1.9.1"
sea-orm = { version = "0.11", features = ["postgres-array", "runtime-actix-rustls", "sqlx-postgres", "macros", "sqlx"] }
serde = { version = "1.0.175", features = ["derive"] }
uuid = { version = "1.4.1", features = ["v4"] }
oswilno-actix-admin = { path = "../oswilno-actix-admin" }

View File

@ -8,9 +8,9 @@ edition = "2021"
actix = "0.13.0"
actix-web = "4.3.1"
askama = { version = "0.12.0", features = ["serde", "with-actix-web", "comrak", "mime"] }
tokio = { version = "1.29.1", features = ["full"] }
askama_actix = "0.14.0"
oswilno-contract = { path = "../oswilno-contract" }
sea-orm = { version = "0.11.1", features = ["runtime-actix-rustls", "sqlx", "sqlx-postgres", "debug-print"] }
serde = { version = "1.0.176", features = ["derive"] }
serde_json = "1.0.104"
askama_actix = "0.14.0"
tokio = { version = "1.29.1", features = ["full"] }

View File

@ -23,6 +23,7 @@ struct AllParkingSpace {
parking_space_by_id: HashMap<i32, parking_spaces::Model>,
account_by_id: HashMap<i32, accounts::Model>,
}
#[derive(Template)]
#[template(path = "../templates/parking-spaces/all-partial.html")]
struct AllPartialParkingSpace {

View File

@ -1,4 +1,5 @@
{% extends "../base.html" %}
{% block body %}
{% include "./all-partial.html" %}
{% endblock %}

View File

@ -10,19 +10,19 @@ actix-web = "4.3.1"
actix-web-grants = "3.0.2"
askama = { version = "0.12.0", features = ["serde", "with-actix-web", "comrak", "mime"] }
futures = { version = "0.3.28", features = ["futures-executor"] }
oswilno-admin = { path = "../oswilno-admin" }
oswilno-config = { path = "../oswilno-config" }
oswilno-parking-space = { path = "../oswilno-parking-space" }
oswilno-view = { path = "../oswilno-view" }
oswilno-admin = { path = "../oswilno-admin" }
oswilno-session = { path = "../oswilno-session" }
web-assets = { path = "../web-assets" }
oswilno-view = { path = "../oswilno-view" }
sea-orm = { version = "0.11", features = ["postgres-array", "runtime-actix-rustls", "sqlx-postgres"] }
serde = { version = "1.0.175", features = ["derive"] }
serde_json = "1.0.103"
tokio = { version = "1.29.1", features = ["full"] }
toml = "0.7.6"
tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] }
web-assets = { path = "../web-assets" }
[dev-dependencies]
insta = { version = "1.31.0", features = ["ron"] }

View File

@ -7,17 +7,19 @@ edition = "2021"
actix-http = "3.3.1"
actix-jwt-authc = { version = "0.2.0", features = ["tracing", "session"] }
actix-web = "4.3.1"
argon2 = "0.5.1"
askama = { version = "0.12.0", features = ["serde", "with-actix-web", "comrak", "mime"] }
askama_actix = { version = "0.14.0" }
autometrics = { version = "0.5.0", features = ["tracing", "tracing-subscriber", "thiserror"] }
futures = { version = "0.3.28", features = ["thread-pool"] }
jsonwebtoken = "8.3.0"
oswilno-contract = { path = "../oswilno-contract" }
oswilno-view = { path = "../oswilno-view" }
rand = "0.8.5"
ring = "0.16.20"
sea-orm = { version = "0.11", features = ["postgres-array", "runtime-actix-rustls", "sqlx-postgres", "macros", "sqlx"] }
serde = { version = "1.0.180", features = ["derive"] }
time = "0.3.24"
tokio = { version = "1.29.1", features = ["full"] }
uuid = { version = "1.4.1", features = ["v4"] }
sea-orm = { version = "0.11", features = ["postgres-array", "runtime-actix-rustls", "sqlx-postgres", "macros", "sqlx"] }
oswilno-contract = { path = "../oswilno-contract" }
tracing = "0.1.37"
argon2 = "0.5.1"
askama = { version = "0.12.0", features = ["serde", "with-actix-web", "comrak", "mime"] }
oswilno-view = { path = "../oswilno-view" }
uuid = { version = "1.4.1", features = ["v4"] }

View File

@ -2,8 +2,9 @@ use std::ops::Add;
use std::sync::Arc;
use actix_jwt_authc::*;
use actix_web::web::{Data, Json, ServiceConfig};
use actix_web::web::{Data, Json, ServiceConfig, Query, Form};
use actix_web::{get, post, HttpResponse};
use autometrics::autometrics;
use futures::channel::{mpsc, mpsc::Sender};
use futures::stream::Stream;
use futures::SinkExt;
@ -15,6 +16,7 @@ use serde::{Deserialize, Serialize};
use time::ext::*;
use time::OffsetDateTime;
use tokio::sync::Mutex;
use askama_actix::Template;
mod hashing;
@ -68,6 +70,7 @@ impl SessionConfigurator {
.app_data(self.encoding_key)
.app_data(self.jwt_ttl)
.service(login)
.service(login_view)
.service(logout)
.service(session_info);
}
@ -77,13 +80,18 @@ impl SessionConfigurator {
}
pub fn translations(&self, l10n: &mut oswilno_view::TranslationStorage) {
l10n.with_lang("en")
l10n
// English
.with_lang("en")
.add("Sign in", "Sign in")
.add("Sign up", "Sign up")
.add("Bad credentials", "Bad credentials")
.done()
// Polish
.with_lang("pl")
.add("Sign in", "Logowanie")
.add("Sign up", "Rejestracja")
.add("Bad credentials", "Złe dane uwierzytelniające")
.done();
}
@ -110,21 +118,61 @@ impl SessionConfigurator {
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
pub struct SignInPayload {
login: String,
password: String,
#[derive(Template)]
#[template(path = "./sign-in/full.html")]
struct SignInTemplate {
form: SignInPayload,
}
#[derive(Template)]
#[template(path = "./sign-in/partial.html")]
struct SignInPartialTemplate {
form: SignInPayload,
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)]
pub struct SignInPayload {
#[serde(skip, default)]
errors: Vec<String>,
login: String,
#[serde(skip, default)]
login_errors: Vec<String>,
password: String,
#[serde(skip, default)]
password_errors: Vec<String>,
}
#[get("/login")]
async fn login_view() -> SignInTemplate {
SignInTemplate { form: SignInPayload::default() }
}
#[autometrics]
#[post("/login")]
async fn login(
jwt_encoding_key: Data<EncodingKey>,
jwt_ttl: Data<JWTTtl>,
db: Data<DatabaseConnection>,
payload: Json<SignInPayload>,
payload: Form<SignInPayload>,
t: Data<oswilno_view::TranslationStorage>,
) -> Result<HttpResponse, Error> {
match login_inner(jwt_encoding_key, jwt_ttl, db, payload, t).await {
Ok(res) => Ok(HttpResponse::Ok().json(res)),
Err(form) => Ok(HttpResponse::Ok().body(SignInPartialTemplate { form }.render().unwrap())),
}
}
async fn login_inner(
jwt_encoding_key: Data<EncodingKey>,
jwt_ttl: Data<JWTTtl>,
db: Data<DatabaseConnection>,
payload: Form<SignInPayload>,
t: Data<oswilno_view::TranslationStorage>,
) -> Result<LoginResponse, SignInPayload> {
let db = db.into_inner();
let payload = payload.into_inner();
let mut payload = payload.into_inner();
let iat = OffsetDateTime::now_utc().unix_timestamp() as usize;
let expires_at = OffsetDateTime::now_utc().add(jwt_ttl.0);
@ -138,15 +186,18 @@ async fn login(
{
Ok(Some(a)) => a,
Ok(None) => {
return Err(Error::InternalError);
payload.errors.push(t.to_lang("pl", "Bad credentials"));
return Err(payload);
}
Err(e) => {
tracing::warn!("Failed to find account: {e}");
return Err(Error::InternalError);
payload.errors.push(t.to_lang("pl", "Bad credentials"));
return Err(payload);
}
};
if hashing::verify(account.pass_hash.as_str(), payload.password.as_str()).is_err() {
return Ok(HttpResponse::Unauthorized().body(""));
payload.errors.push(t.to_lang("pl", "Bad credentials"));
return Err(payload);
}
let jwt_claims = Claims {
@ -161,20 +212,23 @@ async fn login(
&jwt_claims,
&jwt_encoding_key,
)
.map_err(|_| Error::InternalError)?;
let login_response = LoginResponse {
.map_err(|_| {
payload.errors.push(t.to_lang("pl", "Bad credentials"));
payload
})?;
Ok(LoginResponse {
bearer_token: jwt_token,
claims: jwt_claims,
};
Ok(HttpResponse::Ok().json(login_response))
})
}
#[autometrics]
#[get("/session")]
async fn session_info(authenticated: UserSession) -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok().json(authenticated))
}
#[autometrics]
#[get("/logout")]
async fn logout(
invalidated_jwts: Data<InvalidatedJWTStore>,
@ -190,10 +244,11 @@ struct AccountInfo {
password: String,
}
#[autometrics]
#[post("/register")]
async fn register(
db: Data<DatabaseConnection>,
payload: Json<AccountInfo>,
payload: Query<AccountInfo>,
) -> Result<HttpResponse, Error> {
use oswilno_contract::accounts::*;
use sea_orm::ActiveValue::NotSet;

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>OS Wilno</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="/assets/build.js" type="module"></script>
{% block head %}
{% endblock %}
</head>
<body>
<base url="/" />
<main>
{% block body %}
{% endblock %}
</main>
</body>
</html>

View File

@ -1,22 +0,0 @@
{% extends "../base.html" %}
{% block body %}
<section>
<form>
<div>
<label for="login">Login</label>
<input id="login" name="login" />
</div>
<div>
<label for="email">E-Mail</label>
<input id="email" name="email" type="email" />
</div>
<div>
<label for="password">Password</label>
<input id="password" name="password" type="password" />
</div>
<div>
<input type="submit" value="Sign Up" />
</div>
</form>
</section>
{% endblock %}

View File

@ -1,18 +0,0 @@
{% extends "../base.html" %}
{% block body %}
<section>
<form>
<div>
<label for="login">Login</label>
<input id="login" name="login" />
</div>
<div>
<label for="password">Password</label>
<input id="password" name="password" type="password" />
</div>
<div>
<input type="submit" value="Sign In" />
</div>
</form>
</section>
{% endblock %}

View File

@ -7,6 +7,6 @@ edition = "2021"
actix = "0.13.0"
actix-web = "4.3.1"
askama = { version = "0.12.0", features = ["serde", "with-actix-web", "comrak", "mime"] }
tokio = { version = "1.29.1", features = ["full"] }
serde = { version = "1.0.176", features = ["derive"] }
serde_json = "1.0.104"
tokio = { version = "1.29.1", features = ["full"] }

View File

@ -1 +1,2 @@
import './elements/oswilno-price.js';
import("https://unpkg.com/htmx.org@1.9.4/dist/htmx.min.js");

View File

@ -9,5 +9,6 @@ cd ${ROOT}
mkdir assets
cd ${WEB_ASSETS_ROOT}
npm install esbuild
yarn add esbuild
yarn add htmx
${WEB_ASSETS_ROOT}/node_modules/.bin/esbuild --bundle ${WEB_ASSETS_ROOT}/assets/app.js --outfile=${ROOT}/assets/build.js --minify --sourcemap

View File

@ -1,5 +1,7 @@
{
"dependencies": {
"esbuild": "^0.18.17"
"esbuild": "^0.18.17",
"htmlx": "^0.1.0",
"htmx": "^0.0.2"
}
}

View File

@ -1,8 +1,7 @@
[toolchain]
# channel = "nightly-2022-10-29"
channel = "stable"
components = ['rust-analyzer', "rustfmt"]
targets = []
profile = "default"
# rustc 1.66.0-nightly (5e9772042 2022-10-29)
# channel = "nightly-2023-08-02"
channel = "stable"
# channel = "stable-2022-09-22"
components = ['rust-analyzer', "rustfmt"]