Skip to content

Commit

Permalink
feat(public-app): implement registration
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeysova committed Mar 16, 2020
1 parent 167ce72 commit ef6286e
Show file tree
Hide file tree
Showing 16 changed files with 413 additions and 174 deletions.
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions public-api.openapi.yaml
Expand Up @@ -135,6 +135,7 @@ components:
type: string
enum:
- "email_already_registered"
- "invalid_form"

RegisterConfirmationFailed:
description: Please, login or recover password
Expand All @@ -149,6 +150,7 @@ components:
enum:
- "code_invalid_or_expired"
- "email_already_activated"
- "invalid_form"

parameters:
OAuthResponseType:
Expand Down
6 changes: 6 additions & 0 deletions public-api/src/generated.rs
Expand Up @@ -164,6 +164,9 @@ pub mod components {
pub enum RegisterFailedError {
#[serde(rename = "email_already_registered")]
EmailAlreadyRegistered,

#[serde(rename = "invalid_form")]
InvalidForm,
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -178,6 +181,9 @@ pub mod components {

#[serde(rename = "email_already_activated")]
EmailAlreadyActivated,

#[serde(rename = "invalid_form")]
InvalidForm,
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
3 changes: 2 additions & 1 deletion public-api/src/main.rs
Expand Up @@ -67,7 +67,8 @@ async fn main() -> std::io::Result<()> {
.default_service(web::route().to(not_found))
.service(
generated::api::AuthmenowPublicApi::new()
.bind_register_request(routes::register_request::route),
.bind_register_request(routes::register_request::route)
.bind_register_confirmation(routes::register_confirmation::route),
)
})
.bind(bind_address)?
Expand Down
2 changes: 1 addition & 1 deletion public-api/src/routes/mod.rs
@@ -1,3 +1,3 @@
// pub mod oauth;
// pub mod register_confirmation;
pub mod register_confirmation;
pub mod register_request;
96 changes: 25 additions & 71 deletions public-api/src/routes/register_confirmation.rs
@@ -1,86 +1,40 @@
use crate::generated::components::request_bodies;
use crate::generated::components::responses::{
RegisterConfirmationFailed as ConfirmFailed, RegisterConfirmationFailedError as ErrorCode,
RegisterConfirmationFailed, RegisterConfirmationFailedError,
};
use crate::generated::paths::register_confirmation as confirm;
use crate::models::{RegistrationRequest, User};
use crate::DbPool;
use actix_swagger::Answer;
use actix_web::web;
use diesel::result::{DatabaseErrorKind, Error as DieselError};

enum HandleError {
CodeNotFound,
AlreadyActivated,
Unexpected,
}

fn handle(
body: request_bodies::RegisterConfirmation,
pool: web::Data<DbPool>,
) -> Result<(), HandleError> {
let conn = &pool.get().unwrap();

match RegistrationRequest::find_by_code_actual(&conn, &body.confirmation_code) {
Err(DieselError::NotFound) => Err(HandleError::CodeNotFound),
Err(error) => {
log::trace!(
"Failed to find registration request by code {}: {:?}",
body.confirmation_code,
error
);
Err(HandleError::Unexpected)
}
Ok(request) => {
log::warn!(
"Registration confirmation should be validated for probably empty first and last names"
);
let user = User::new()
.email_set(&request.email)
.name_set(&body.first_name, &body.last_name)
.password_set(&body.password);

match user.create(&conn) {
Err(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) => {
Err(HandleError::AlreadyActivated)
}
Err(error) => {
log::trace!("Failed to create user {:?}: {:?}", user, error);
Err(HandleError::Unexpected)
}
Ok(user) => {
log::warn!(
"Email 'register complete' do not sent to {} because not implemented",
user.email
);
if let Err(failed) =
RegistrationRequest::delete_all_for_email(&conn, &request.email)
{
log::warn!(
"Failed to delete all registration requests after registration {:?}",
failed
);
}
Ok(())
}
}
}
}
}

pub async fn route(
body: web::Json<request_bodies::RegisterConfirmation>,
pool: web::Data<DbPool>,
app: web::Data<crate::App>,
) -> Answer<'static, confirm::Response> {
match handle(body.0, pool) {
Ok(()) => confirm::Response::Created,
Err(HandleError::CodeNotFound) => confirm::Response::BadRequest(ConfirmFailed {
error: ErrorCode::CodeInvalidOrExpired,
use authmenow_public_app::registrator::{
RegisterConfirmError::{AlreadyActivated, CodeNotFound, InvalidForm, Unexpected},
RegisterForm, Registrator,
};
use confirm::Response;

let form = RegisterForm {
confirmation_code: body.confirmation_code.clone(),
first_name: body.first_name.clone(),
last_name: body.last_name.clone(),
password: body.password.clone(),
};

match app.confirm_registration(form) {
Err(Unexpected) => Response::Unexpected,
Err(CodeNotFound) => Response::BadRequest(RegisterConfirmationFailed {
error: RegisterConfirmationFailedError::CodeInvalidOrExpired,
}),
Err(AlreadyActivated) => Response::BadRequest(RegisterConfirmationFailed {
error: RegisterConfirmationFailedError::EmailAlreadyActivated,
}),
Err(HandleError::AlreadyActivated) => confirm::Response::BadRequest(ConfirmFailed {
error: ErrorCode::EmailAlreadyActivated,
Err(InvalidForm) => Response::BadRequest(RegisterConfirmationFailed {
error: RegisterConfirmationFailedError::InvalidForm,
}),
Err(HandleError::Unexpected) => confirm::Response::Unexpected,
Ok(()) => Response::Created,
}
.answer()
}
12 changes: 8 additions & 4 deletions public-api/src/routes/register_request.rs
Expand Up @@ -7,17 +7,21 @@ pub async fn route(
body: web::Json<request_bodies::Register>,
app: web::Data<crate::App>,
) -> Answer<'static, register_request::Response> {
use authmenow_public_app::register::{
RegisterRequestError::{EmailAlreadyRegistered, UnexpectedError},
use authmenow_public_app::registrator::{
CreateRegisterRequest,
RegisterRequestError::{EmailAlreadyRegistered, InvalidForm, Unexpected},
Registrator,
};
use register_request::Response;

match app.create_register_request(body.email.clone()) {
match app.create_register_request(CreateRegisterRequest::from_email(&body.email)) {
Err(EmailAlreadyRegistered) => Response::BadRequest(responses::RegisterFailed {
error: responses::RegisterFailedError::EmailAlreadyRegistered,
}),
Err(UnexpectedError) => Response::Unexpected,
Err(InvalidForm) => Response::BadRequest(responses::RegisterFailed {
error: responses::RegisterFailedError::InvalidForm,
}),
Err(Unexpected) => Response::Unexpected,
Ok(request) => Response::Created(responses::RegistrationRequestCreated {
expires_at: request.expires_at.timestamp_millis(),
}),
Expand Down
92 changes: 88 additions & 4 deletions public-api/src/services/database.rs
@@ -1,6 +1,9 @@
use authmenow_db::schema::*;
use authmenow_public_app::{
contracts::{RequestsRepo, SaveRegisterRequestError, UnexpectedDatabaseError, UserRepo},
contracts::{
RegisterUserError, RequestsRepo, SaveRegisterRequestError, UnexpectedDatabaseError,
UserRegisterForm, UserRepo,
},
models,
};
use diesel::pg::PgConnection;
Expand Down Expand Up @@ -38,7 +41,7 @@ impl Clone for Database {
}

impl UserRepo for Database {
fn has_user_with_email(&self, email: String) -> Result<bool, UnexpectedDatabaseError> {
fn user_has_with_email(&self, email: String) -> Result<bool, UnexpectedDatabaseError> {
let conn = self.conn();

Ok(users::table
Expand All @@ -48,10 +51,31 @@ impl UserRepo for Database {
.map_err(|_| UnexpectedDatabaseError)?
> 0)
}

fn register_user(
&self,
form: UserRegisterForm,
) -> Result<models::CreatedUser, RegisterUserError> {
let conn = self.conn();

let user = NewUser {
id: uuid::Uuid::new_v4(),
email: form.email,
first_name: form.first_name,
last_name: form.last_name,
password_hash: form.password_hash,
};

diesel::insert_into(users::table)
.values(user)
.get_result::<NewUser>(&conn)
.map(Into::into)
.map_err(diesel_error_to_register_user_error)
}
}

impl RequestsRepo for Database {
fn save_register_request(
fn register_request_save(
&self,
request: models::RegisterRequest,
) -> Result<models::RegisterRequest, SaveRegisterRequestError> {
Expand All @@ -63,6 +87,33 @@ impl RequestsRepo for Database {
.map(Into::into)
.map_err(diesel_error_to_save_register_error)
}

fn register_request_get_by_code(
&self,
code: String,
) -> Result<Option<models::RegisterRequest>, UnexpectedDatabaseError> {
let conn = self.conn();

registration_requests::table
.filter(registration_requests::confirmation_code.eq(code))
.filter(registration_requests::expires_at.gt(chrono::Utc::now().naive_utc()))
.get_result::<RegistrationRequest>(&conn)
.map(Into::into)
.optional()
.map_err(|_| UnexpectedDatabaseError)
}

fn register_requests_delete_all_for_email(
&self,
email: String,
) -> Result<usize, UnexpectedDatabaseError> {
let conn = self.conn();

diesel::delete(registration_requests::table)
.filter(registration_requests::email.eq(email))
.execute(&conn)
.map_err(|_| UnexpectedDatabaseError)
}
}

#[derive(Identifiable, Insertable, PartialEq, Queryable)]
Expand All @@ -73,6 +124,16 @@ struct RegistrationRequest {
expires_at: chrono::NaiveDateTime,
}

#[derive(Identifiable, Insertable, Queryable)]
#[table_name = "users"]
pub struct NewUser {
pub id: uuid::Uuid,
pub email: String,
pub first_name: String,
pub password_hash: String,
pub last_name: String,
}

impl From<models::RegisterRequest> for RegistrationRequest {
fn from(model: models::RegisterRequest) -> Self {
Self {
Expand All @@ -93,6 +154,29 @@ impl Into<models::RegisterRequest> for RegistrationRequest {
}
}

impl Into<models::CreatedUser> for NewUser {
fn into(self) -> models::CreatedUser {
models::CreatedUser {
id: self.id,
email: self.email,
first_name: self.first_name,
last_name: self.last_name,
password_hash: self.password_hash,
}
}
}

fn diesel_error_to_save_register_error(_: diesel::result::Error) -> SaveRegisterRequestError {
SaveRegisterRequestError::UnexpectedError
SaveRegisterRequestError::Unexpected
}

fn diesel_error_to_register_user_error(err: diesel::result::Error) -> RegisterUserError {
use diesel::result::{DatabaseErrorKind, Error as DieselError};

match err {
DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _) => {
RegisterUserError::EmailAlreadyExists
}
_ => RegisterUserError::Unexpected,
}
}

0 comments on commit ef6286e

Please sign in to comment.