feat: registration of users
This commit is contained in:
parent
2374f78a07
commit
ea35f5475f
@ -13,7 +13,7 @@ CREATE TABLE location
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
areaId INTEGER NOT NULL REFERENCES area (id)
|
||||
areaId INTEGER NOT NULL REFERENCES area (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE user_
|
||||
@ -25,7 +25,7 @@ CREATE TABLE user_
|
||||
salt TEXT ,
|
||||
role role NOT NULL,
|
||||
function function NOT NULL,
|
||||
areaId INTEGER NOT NULL REFERENCES area (id),
|
||||
areaId INTEGER NOT NULL REFERENCES area (id) ON DELETE CASCADE,
|
||||
locked BOOLEAN NOT NULL DEFAULT false,
|
||||
lastLogin TIMESTAMP WITH TIME ZONE,
|
||||
receiveNotifications BOOLEAN NOT NULL DEFAULT true
|
||||
@ -34,7 +34,7 @@ CREATE TABLE user_
|
||||
CREATE TABLE availabillity
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
userId INTEGER NOT NULL REFERENCES user_ (id),
|
||||
userId INTEGER NOT NULL REFERENCES user_ (id) ON DELETE CASCADE,
|
||||
date DATE NOT NULL,
|
||||
startTime TIME,
|
||||
endTime TIME,
|
||||
@ -48,7 +48,7 @@ CREATE TABLE event
|
||||
startTime TIME NOT NULL,
|
||||
endTime TIME NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
locationId INTEGER NOT NULL REFERENCES location (id),
|
||||
locationId INTEGER NOT NULL REFERENCES location (id) ON DELETE CASCADE,
|
||||
voluntaryWachhabender BOOLEAN NOT NULL,
|
||||
amountOfPosten SMALLINT NOT NULL CHECK (amountOfPosten >= 0),
|
||||
clothing TEXT NOT NULL,
|
||||
@ -57,8 +57,8 @@ CREATE TABLE event
|
||||
|
||||
CREATE TABLE assignment
|
||||
(
|
||||
eventId INTEGER REFERENCES event (id),
|
||||
availabillityId INTEGER REFERENCES availabillity (id),
|
||||
eventId INTEGER REFERENCES event (id) ON DELETE CASCADE,
|
||||
availabillityId INTEGER REFERENCES availabillity (id) ON DELETE CASCADE,
|
||||
function function NOT NULL,
|
||||
startTime TIME NOT NULL,
|
||||
endTime TIME NOT NULL,
|
||||
@ -74,8 +74,8 @@ CREATE TABLE vehicle
|
||||
|
||||
CREATE TABLE vehicleassignement
|
||||
(
|
||||
eventId INTEGER REFERENCES event (id),
|
||||
vehicleId INTEGER REFERENCES vehicle (id),
|
||||
eventId INTEGER REFERENCES event (id) ON DELETE CASCADE,
|
||||
vehicleId INTEGER REFERENCES vehicle (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (eventId, vehicleId)
|
||||
);
|
||||
|
||||
@ -93,7 +93,7 @@ CREATE UNLOGGED TABLE passwordReset
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
token TEXT UNIQUE NOT NULL,
|
||||
userId INTEGER NOT NULL REFERENCES user_ (id),
|
||||
userId INTEGER NOT NULL REFERENCES user_ (id) ON DELETE CASCADE,
|
||||
expires TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
@ -103,7 +103,7 @@ CREATE UNLOGGED TABLE registration
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
token TEXT UNIQUE NOT NULL,
|
||||
userId INTEGER NOT NULL REFERENCES user_ (id),
|
||||
userId INTEGER NOT NULL REFERENCES user_ (id) ON DELETE CASCADE,
|
||||
expires TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
|
@ -41,6 +41,8 @@ pub fn init(cfg: &mut ServiceConfig) {
|
||||
cfg.service(user::post_toggle::post);
|
||||
cfg.service(user::get_changepassword::get);
|
||||
cfg.service(user::post_changepassword::post);
|
||||
cfg.service(user::get_register::get);
|
||||
cfg.service(user::post_register::post);
|
||||
|
||||
cfg.service(availability::delete::delete);
|
||||
cfg.service(availability::get_new::get);
|
||||
|
@ -1,30 +1,32 @@
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{endpoints::IdPath, models::{Role, User}};
|
||||
use crate::{
|
||||
endpoints::IdPath,
|
||||
models::{Role, User}, utils::ApplicationError,
|
||||
};
|
||||
|
||||
#[actix_web::delete("/users/{id}")]
|
||||
pub async fn delete(user: Identity, pool: web::Data<PgPool>, path: web::Path<IdPath>) -> impl Responder {
|
||||
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if current_user.role != Role::AreaManager && current_user.role != Role::Admin {
|
||||
return HttpResponse::Unauthorized().finish();
|
||||
pub async fn delete(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
path: web::Path<IdPath>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
if user.role != Role::AreaManager && user.role != Role::Admin {
|
||||
return Err(ApplicationError::Unauthorized);
|
||||
}
|
||||
|
||||
if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await {
|
||||
if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id {
|
||||
return HttpResponse::Unauthorized().finish();
|
||||
let user_in_db = User::read_by_id(pool.get_ref(), path.id).await?;
|
||||
|
||||
if user.role == Role::AreaManager && user.area_id != user_in_db.area_id {
|
||||
return Err(ApplicationError::Unauthorized);
|
||||
}
|
||||
|
||||
if user_in_db.locked {
|
||||
if let Ok(_) = User::delete(pool.get_ref(), user_in_db.id).await {
|
||||
return HttpResponse::Ok().finish();
|
||||
}
|
||||
}
|
||||
User::delete(pool.get_ref(), user_in_db.id).await?;
|
||||
|
||||
return Ok(HttpResponse::Ok().finish());
|
||||
}
|
||||
|
||||
HttpResponse::BadRequest().finish()
|
||||
Ok(HttpResponse::BadRequest().finish())
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use askama_actix::TemplateToResponse;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::Registration;
|
||||
use crate::{models::Registration, utils::ApplicationError};
|
||||
|
||||
use super::ResetPasswordTemplate;
|
||||
|
||||
@ -18,14 +18,14 @@ pub async fn get(
|
||||
user: Option<Identity>,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<TokenQuery>,
|
||||
) -> impl Responder {
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
if user.is_some() {
|
||||
return HttpResponse::Found()
|
||||
return Ok(HttpResponse::Found()
|
||||
.insert_header((LOCATION, "/"))
|
||||
.finish();
|
||||
.finish());
|
||||
}
|
||||
|
||||
if let Ok(_) = Registration::does_token_exist(pool.get_ref(), &query.token).await {
|
||||
if let Some(_) = Registration::does_token_exist(pool.get_ref(), &query.token).await? {
|
||||
let template = ResetPasswordTemplate {
|
||||
token: &query.token,
|
||||
title: "Brass - Registrierung",
|
||||
@ -35,8 +35,8 @@ pub async fn get(
|
||||
submit_button_label: "Registrieren",
|
||||
};
|
||||
|
||||
return template.to_response();
|
||||
return Ok(template.to_response());
|
||||
}
|
||||
|
||||
HttpResponse::NotFound().finish()
|
||||
Ok(HttpResponse::NotFound().finish())
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ pub async fn post_new(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
form: web::Form<NewUserForm>,
|
||||
mailer: web::ReqData<SmtpTransport>,
|
||||
mailer: web::Data<SmtpTransport>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
if user.role != Role::AreaManager && user.role != Role::Admin {
|
||||
return Err(ApplicationError::Unauthorized);
|
||||
@ -48,9 +48,10 @@ pub async fn post_new(
|
||||
.await?;
|
||||
|
||||
let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?;
|
||||
let message = email::build_registration_message(&user, ®istration.token);
|
||||
let new_user = User::read_by_id(pool.get_ref(), id).await?;
|
||||
let message = email::build_registration_message(&new_user, ®istration.token)?;
|
||||
|
||||
mailer.send(&message).unwrap();
|
||||
mailer.send(&message)?;
|
||||
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header((LOCATION, "/users"))
|
||||
|
@ -23,7 +23,8 @@ async fn post(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
let is_dry = form.dry.unwrap_or(false);
|
||||
let token = Registration::does_token_exist(pool.get_ref(), &form.token).await?;
|
||||
// TODO: flip unauthorized with not found or unwrap result in a other way
|
||||
let token = Registration::does_token_exist(pool.get_ref(), &form.token).await?.ok_or(ApplicationError::Unauthorized)?;
|
||||
|
||||
if form.password.chars().count() > 256 {
|
||||
if is_dry {
|
||||
|
@ -52,6 +52,7 @@ where
|
||||
&& request.path() != "/login"
|
||||
&& request.path() != "/imprint"
|
||||
&& !request.path().starts_with("/reset-password")
|
||||
&& !request.path().starts_with("/register")
|
||||
&& !request.path().starts_with("/static")
|
||||
{
|
||||
let (request, _pl) = request.into_parts();
|
||||
|
@ -1,6 +1,6 @@
|
||||
use sqlx::{query, query_as, PgPool};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Area {
|
||||
pub id: i32,
|
||||
pub name: String
|
||||
|
@ -30,13 +30,13 @@ impl Registration {
|
||||
Ok(inserted)
|
||||
}
|
||||
|
||||
pub async fn does_token_exist(pool: &PgPool, token: &str) -> Result<Registration> {
|
||||
pub async fn does_token_exist(pool: &PgPool, token: &str) -> Result<Option<Registration>> {
|
||||
query_as!(
|
||||
Registration,
|
||||
"SELECT * FROM registration WHERE token = $1 AND expires > NOW();",
|
||||
token
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ use sqlx::PgPool;
|
||||
|
||||
use super::{Area, Function, Result, Role};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
@ -366,7 +366,7 @@ impl User {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(pool: &PgPool, id: i32) -> anyhow::Result<()> {
|
||||
pub async fn delete(pool: &PgPool, id: i32) -> Result<()> {
|
||||
sqlx::query!("DELETE FROM user_ WHERE id = $1;", id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
@ -9,6 +9,14 @@ pub enum ApplicationError {
|
||||
Unauthorized,
|
||||
#[error("database error")]
|
||||
Database(#[from] sqlx::Error),
|
||||
#[error("environment variable not present or not unicode")]
|
||||
EnvVariable(#[from] std::env::VarError),
|
||||
#[error("email address not conform")]
|
||||
EmailAdress(#[from] lettre::address::AddressError),
|
||||
#[error("email content not right")]
|
||||
Email(#[from] lettre::error::Error),
|
||||
#[error("email transport not working")]
|
||||
EmailTransport(#[from] lettre::transport::smtp::Error),
|
||||
}
|
||||
|
||||
impl actix_web::error::ResponseError for ApplicationError {
|
||||
@ -17,10 +25,24 @@ impl actix_web::error::ResponseError for ApplicationError {
|
||||
ApplicationError::UnsupportedEnumValue { .. } => StatusCode::BAD_REQUEST,
|
||||
ApplicationError::Unauthorized { .. } => StatusCode::UNAUTHORIZED,
|
||||
ApplicationError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ApplicationError::EnvVariable(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ApplicationError::EmailAdress(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ApplicationError::Email(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ApplicationError::EmailTransport(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::build(self.status_code()).body(self.to_string())
|
||||
let mut response = HttpResponse::build(self.status_code());
|
||||
|
||||
match self {
|
||||
ApplicationError::UnsupportedEnumValue { .. } => response.body(self.to_string()),
|
||||
ApplicationError::Unauthorized { .. } => response.body(self.to_string()),
|
||||
ApplicationError::Database(e) => response.body(format!("{self} - {e}")),
|
||||
ApplicationError::EnvVariable(_) => response.body(self.to_string()),
|
||||
ApplicationError::EmailAdress(e) => response.body(format!("{self} - {e}")),
|
||||
ApplicationError::Email(e) => response.body(format!("{self} - {e}")),
|
||||
ApplicationError::EmailTransport(e) => response.body(format!("{self} - {e}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
use std::env;
|
||||
|
||||
use lettre::{
|
||||
message::{header::ContentType, MultiPart, SinglePart},
|
||||
message::{header::ContentType, Mailbox, MultiPart, SinglePart},
|
||||
transport::smtp::{authentication::Credentials, extension::ClientId},
|
||||
Message, SmtpTransport,
|
||||
};
|
||||
|
||||
use crate::models::User;
|
||||
|
||||
use super::ApplicationError;
|
||||
|
||||
pub fn get_mailer() -> anyhow::Result<SmtpTransport> {
|
||||
let server = &env::var("SMTP_SERVER")?;
|
||||
let port = &env::var("SMTP_PORT")?.parse()?;
|
||||
@ -79,14 +81,15 @@ Viele Grüße"##, user.name))
|
||||
return message;
|
||||
}
|
||||
|
||||
pub fn build_registration_message(user: &User, token: &str) -> Message {
|
||||
let hostname = env::var("HOSTNAME").unwrap();
|
||||
pub fn build_registration_message(user: &User, token: &str) -> Result<Message, ApplicationError> {
|
||||
let hostname = env::var("HOSTNAME")?;
|
||||
let register_url = format!("https://{hostname}/register?token={token}");
|
||||
println!("{user:?}");
|
||||
|
||||
let message = Message::builder()
|
||||
.from("noreply <noreply@brasiwa-leipzig.de>".parse().unwrap())
|
||||
.reply_to("noreply <noreply@brasiwa-leipzig.de>".parse().unwrap())
|
||||
.to(format!("{} <{}>", user.name, user.email).parse().unwrap())
|
||||
.from("noreply <noreply@brasiwa-leipzig.de>".parse()?)
|
||||
.reply_to("noreply <noreply@brasiwa-leipzig.de>".parse()?)
|
||||
.to(Mailbox::new(Some(user.name.clone()), user.email.parse()?))
|
||||
.subject("Brass: Registrierung deines Accounts")
|
||||
.multipart(
|
||||
MultiPart::alternative()
|
||||
@ -116,7 +119,7 @@ Viele Grüße"##, user.name))
|
||||
|
||||
<p>Viele Grüße</p>"##, user.name))
|
||||
))
|
||||
.unwrap();
|
||||
?;
|
||||
|
||||
return message;
|
||||
Ok(message)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<div class="field">
|
||||
<label class="label" for="password">{{ new_password_label }}</label>
|
||||
<div class="control">
|
||||
<input class="input" hx-post="/reset-password?dry=true" hx-params="*" hx-trigger="keyup changed delay:500ms"
|
||||
<input class="input" hx-post="{{ endpoint }}?dry=true" hx-params="*" hx-trigger="keyup changed delay:500ms"
|
||||
hx-target="#password-strength" hx-target-400="#password-strength" placeholder="**********" name="password"
|
||||
type="password" required hx-swap="outerHTML" maxlength=256
|
||||
hx-on:input="document.getElementById('password-strength').innerHTML = ''">
|
||||
|
Loading…
x
Reference in New Issue
Block a user