this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implementing a wider server-side checker

+313 -260
+1
Cargo.lock
··· 940 940 "dotenv", 941 941 "r2d2", 942 942 "r2d2_sqlite", 943 + "regex", 943 944 "rocket", 944 945 "rocket_ws", 945 946 "serde",
+137 -132
client/src/lumina_client.gleam
··· 63 63 ToRegisterPage -> #( 64 64 Model( 65 65 ..model_, 66 - page: model.Register(fields: model.RegisterPageFields("", "", "", "")), 66 + page: model.Register( 67 + fields: model.RegisterPageFields("", "", "", ""), 68 + ready: None, 69 + ), 67 70 ), 68 71 effect.none(), 69 72 ) 70 73 ToLandingPage -> #(Model(model.Landing, None, None), effect.none()) 71 74 UpdateEmailField(new_email) -> { 72 75 case model_.page { 73 - model.Register(fields) -> #( 76 + model.Register(fields, ready) -> #( 74 77 Model( 75 78 ..model_, 76 79 page: model.Register( 77 80 fields: model.RegisterPageFields(..fields, emailfield: new_email), 81 + ready:, 78 82 ), 79 83 ), 80 - effect.none(), 84 + { 85 + // This block emits an effect to send RegisterPrecheck message to the server 86 + let assert Some(socket) = model_.ws as "Socket not connected" 87 + encode_ws_msg(RegisterPrecheck( 88 + fields.emailfield, 89 + fields.usernamefield, 90 + fields.passwordfield, 91 + )) 92 + |> json.to_string() 93 + |> lustre_websocket.send(socket, _) 94 + }, 81 95 ) 82 96 model.Login(fields) -> #( 83 97 Model( ··· 93 107 } 94 108 UpdatePasswordField(new_password) -> { 95 109 case model_.page { 96 - model.Register(fields) -> #( 110 + model.Register(fields, ready) -> #( 97 111 Model( 98 112 ..model_, 99 113 page: model.Register( 100 114 model.RegisterPageFields(..fields, passwordfield: new_password), 115 + ready:, 101 116 ), 102 117 ), 103 - effect.none(), 118 + { 119 + // This block emits an effect to send RegisterPrecheck message to the server 120 + let assert Some(socket) = model_.ws as "Socket not connected" 121 + encode_ws_msg(RegisterPrecheck( 122 + fields.emailfield, 123 + fields.usernamefield, 124 + fields.passwordfield, 125 + )) 126 + |> json.to_string() 127 + |> lustre_websocket.send(socket, _) 128 + }, 104 129 ) 105 130 model.Login(fields) -> #( 106 131 Model( ··· 116 141 } 117 142 UpdatePasswordConfirmField(new_password_confirmation) -> { 118 143 case model_.page { 119 - model.Register(fields) -> #( 144 + model.Register(fields, ready) -> #( 120 145 Model( 121 146 ..model_, 122 147 page: model.Register( ··· 124 149 ..fields, 125 150 passwordconfirmfield: new_password_confirmation, 126 151 ), 152 + ready:, 127 153 ), 128 154 ), 129 - effect.none(), 155 + { 156 + // This block emits an effect to send RegisterPrecheck message to the server 157 + let assert Some(socket) = model_.ws as "Socket not connected" 158 + encode_ws_msg(RegisterPrecheck( 159 + fields.emailfield, 160 + fields.usernamefield, 161 + fields.passwordfield, 162 + )) 163 + |> json.to_string() 164 + |> lustre_websocket.send(socket, _) 165 + }, 130 166 ) 131 167 _ -> #(model_, effect.none()) 132 168 } 133 169 } 134 170 UpdateUsernameField(new_username) -> { 135 171 case model_.page { 136 - model.Register(fields) -> #( 172 + model.Register(fields, ready) -> #( 137 173 Model( 138 174 ..model_, 139 175 page: model.Register( ··· 145 181 |> string.replace("@", "") 146 182 |> string.replace(".", "") 147 183 }), 184 + ready:, 148 185 ), 149 186 ), 150 - effect.none(), 187 + { 188 + let assert Some(socket) = model_.ws as "Socket not connected" 189 + encode_ws_msg(RegisterPrecheck( 190 + fields.emailfield, 191 + fields.usernamefield, 192 + fields.passwordfield, 193 + )) 194 + |> json.to_string() 195 + |> lustre_websocket.send(socket, _) 196 + }, 151 197 ) 152 198 _ -> #(model_, effect.none()) 153 199 } ··· 177 223 } 178 224 } 179 225 SubmitSignup -> { 180 - let assert model.Register(fields) = model_.page 226 + let assert model.Register(fields, ready) = model_.page 181 227 182 - let values_ok = register_view_checker(fields) 183 - 184 - case values_ok.0 { 228 + case 229 + { 230 + { ready |> option.is_some() } 231 + && { ready |> option.unwrap(Error("")) |> result.is_ok() } 232 + && { fields.passwordfield == fields.passwordconfirmfield } 233 + } 234 + { 185 235 True -> { 186 236 console.log("Submitting signup form") 187 237 let json = ··· 221 271 Ok(Greeting(m)) -> { 222 272 console.log("The server says hi! '" <> m <> "'") 223 273 #(model_, effect.none()) 274 + } 275 + Ok(RegisterPrecheckResponse(ok, why)) -> { 276 + console.log("Register precheck response: " <> string.inspect(ok)) 277 + let ready = 278 + case ok { 279 + True -> Ok(Nil) 280 + False -> Error(why) 281 + } 282 + |> Some 283 + 284 + case model_.page { 285 + model.Register(fields, _) -> #( 286 + Model(..model_, page: model.Register(fields:, ready:)), 287 + effect.none(), 288 + ) 289 + _ -> #(model_, effect.none()) 290 + } 224 291 } 225 292 Ok(f) -> { 226 293 console.error("Unhandled message: " <> string.inspect(f)) ··· 431 498 432 499 fn view_register(model_: Model) -> List(Element(Msg)) { 433 500 // We know that the model is a Login page, so we can safely unwrap it 434 - let assert model.Register(fieldvalues): model.Page = model_.page 435 - let values_ok = register_view_checker(fieldvalues) 501 + let assert model.Register(fieldvalues, ready): model.Page = model_.page 502 + 436 503 // Check if the password and password confirmation fields match and if the email and username fields are not empty 437 504 [ 438 505 html.div([attribute.class("navbar bg-base-100 shadow-sm")], [ ··· 527 594 html.br([]), 528 595 html.div( 529 596 [ 530 - attribute.class( 531 - "bg-base-200 card shadow-md p-4 w-full", 532 - ), 597 + attribute.class(case ready |> option.is_some() { 598 + True -> "bg-base-200 card shadow-md p-4 w-full" 599 + False -> "hidden" 600 + }), 533 601 ], 534 602 [ 535 - case values_ok.0 { 536 - False -> 603 + case 604 + ready |> option.unwrap(Ok(Nil)), 605 + fieldvalues.passwordfield 606 + == fieldvalues.passwordconfirmfield 607 + { 608 + Error(why), _ -> 537 609 html.div([attribute.class("w-full")], [ 538 610 html.div( 539 611 [ ··· 556 628 ), 557 629 ], 558 630 ), 559 - html.text(" " <> values_ok.1), 631 + html.text(" " <> why), 560 632 ]) 561 - True -> 633 + Ok(_), True -> 562 634 html.div([attribute.class("w-full")], [ 563 635 html.div( 564 636 [ ··· 583 655 ), 584 656 html.text(" Ready to go!"), 585 657 ]) 658 + Ok(_), False -> 659 + html.div([attribute.class("w-full")], [ 660 + html.div( 661 + [ 662 + attribute.class( 663 + "inline-grid *:[grid-area:1/1]", 664 + ), 665 + ], 666 + [ 667 + html.div( 668 + [ 669 + attribute.class( 670 + "status status-error animate-ping", 671 + ), 672 + ], 673 + [], 674 + ), 675 + html.div( 676 + [attribute.class("status status-error")], 677 + [], 678 + ), 679 + ], 680 + ), 681 + html.text(" Passwords don't match!"), 682 + ]) 586 683 }, 587 684 ], 588 685 ), 589 686 html.button( 590 - case values_ok.0 { 687 + case 688 + ready |> option.is_some() 689 + && ready |> option.unwrap(Error("")) |> result.is_ok() 690 + && fieldvalues.passwordfield 691 + == fieldvalues.passwordconfirmfield 692 + { 591 693 True -> [ 592 694 attribute.class("btn btn-base-400 mt-4"), 593 695 attribute.type_("submit"), ··· 637 739 |> list.all(fn(x) { x }) 638 740 } 639 741 640 - fn register_view_checker( 641 - fieldvalues: model.RegisterPageFields, 642 - ) -> #(Bool, String) { 643 - [ 644 - #({ fieldvalues.emailfield != "" }, "Email field cannot be empty"), 645 - #( 646 - { 647 - [ 648 - fieldvalues.emailfield |> string.contains("@"), 649 - { 650 - let f = fieldvalues.emailfield |> string.split("@") 651 - case f { 652 - [a, b] -> { string.length(a) > 3 } && { string.length(b) > 5 } 653 - _ -> False 654 - } 655 - }, 656 - fieldvalues.emailfield |> string.contains("."), 657 - { 658 - case fieldvalues.emailfield |> string.split("@") { 659 - [_, c] -> 660 - case string.split(c, ".") { 661 - [a, b] -> { string.length(a) > 1 } && { string.length(b) > 1 } 662 - [a, "co", "uk"] -> { 663 - string.length(a) > 1 664 - } 665 - _ -> False 666 - } 667 - _ -> False 668 - } 669 - }, 670 - ] 671 - |> list.all(fn(x) { x }) 672 - }, 673 - "Must be a valid email address", 674 - ), 675 - #({ fieldvalues.usernamefield != "" }, "Username field cannot be empty"), 676 - #( 677 - { fieldvalues.passwordfield |> string.length() > 7 }, 678 - "Password must be at least 8 characters, are " 679 - <> fieldvalues.passwordfield |> string.length() |> int.to_string(), 680 - ), 681 - #( 682 - { 683 - [ 684 - { fieldvalues.passwordfield |> string.contains("0") }, 685 - { fieldvalues.passwordfield |> string.contains("1") }, 686 - { fieldvalues.passwordfield |> string.contains("2") }, 687 - { fieldvalues.passwordfield |> string.contains("3") }, 688 - { fieldvalues.passwordfield |> string.contains("4") }, 689 - { fieldvalues.passwordfield |> string.contains("5") }, 690 - { fieldvalues.passwordfield |> string.contains("6") }, 691 - { fieldvalues.passwordfield |> string.contains("7") }, 692 - { fieldvalues.passwordfield |> string.contains("8") }, 693 - { fieldvalues.passwordfield |> string.contains("9") }, 694 - ] 695 - |> list.any(fn(x) { x }) 696 - }, 697 - "Password must contain at least one number", 698 - ), 699 - #( 700 - { 701 - [ 702 - { fieldvalues.passwordfield |> string.contains("!") }, 703 - { fieldvalues.passwordfield |> string.contains("@") }, 704 - { fieldvalues.passwordfield |> string.contains("#") }, 705 - { fieldvalues.passwordfield |> string.contains("$") }, 706 - { fieldvalues.passwordfield |> string.contains("%") }, 707 - { fieldvalues.passwordfield |> string.contains("^") }, 708 - { fieldvalues.passwordfield |> string.contains("&") }, 709 - { fieldvalues.passwordfield |> string.contains("*") }, 710 - { fieldvalues.passwordfield |> string.contains("(") }, 711 - { fieldvalues.passwordfield |> string.contains(")") }, 712 - { fieldvalues.passwordfield |> string.contains("-") }, 713 - { fieldvalues.passwordfield |> string.contains("_") }, 714 - { fieldvalues.passwordfield |> string.contains("=") }, 715 - { fieldvalues.passwordfield |> string.contains("+") }, 716 - { fieldvalues.passwordfield |> string.contains("[") }, 717 - { fieldvalues.passwordfield |> string.contains("]") }, 718 - { fieldvalues.passwordfield |> string.contains("{") }, 719 - { fieldvalues.passwordfield |> string.contains("}") }, 720 - { fieldvalues.passwordfield |> string.contains(":") }, 721 - { fieldvalues.passwordfield |> string.contains(";") }, 722 - { fieldvalues.passwordfield |> string.contains("<") }, 723 - { fieldvalues.passwordfield |> string.contains(">") }, 724 - { fieldvalues.passwordfield |> string.contains(",") }, 725 - { fieldvalues.passwordfield |> string.contains(".") }, 726 - { fieldvalues.passwordfield |> string.contains("?") }, 727 - { fieldvalues.passwordfield |> string.contains("/") }, 728 - { fieldvalues.passwordfield |> string.contains("|") }, 729 - { fieldvalues.passwordfield |> string.contains("`") }, 730 - { fieldvalues.passwordfield |> string.contains("~") }, 731 - { fieldvalues.passwordfield |> string.contains("\"") }, 732 - { fieldvalues.passwordfield |> string.contains("'") }, 733 - { fieldvalues.passwordfield |> string.contains("\\") }, 734 - { fieldvalues.passwordfield |> string.contains(" ") }, 735 - ] 736 - |> list.any(fn(x) { x }) 737 - }, 738 - "Password must contain at least one special character", 739 - ), 740 - #( 741 - { fieldvalues.passwordfield == fieldvalues.passwordconfirmfield }, 742 - "Passwords do not match", 743 - ), 744 - ] 745 - |> list.find(fn(x) { x.0 == False }) 746 - |> result.unwrap(#(True, "")) 747 - } 748 - 749 742 // WS Message decoding --------------------------------------------------------- 750 743 751 744 type WsMsg { ··· 756 749 // Password only once? Yes, the equal password check is done in the view. 757 750 password: String, 758 751 ) 752 + RegisterPrecheckResponse(ok: Bool, why: String) 759 753 RegisterRequest(email: String, username: String, password: String) 760 754 LoginAuthenticationRequest(email_username: String, password: String) 761 755 Undecodable ··· 783 777 #("username", json.string(username)), 784 778 #("password", json.string(password)), 785 779 ]) 780 + RegisterPrecheckResponse(ok, why) -> 781 + json.object([ 782 + #("type", json.string("register_precheck_response")), 783 + #("ok", json.bool(ok)), 784 + #("why", json.string(why)), 785 + ]) 786 786 Greeting(_) | Undecodable -> 787 787 json.object([#("type", json.string("unknown"))]) 788 788 } ··· 807 807 use username <- decode.field("username", decode.string) 808 808 use password <- decode.field("password", decode.string) 809 809 decode.success(RegisterPrecheck(email, username, password)) 810 + } 811 + "register_precheck_response" -> { 812 + use ok <- decode.field("ok", decode.bool) 813 + use why <- decode.field("why", decode.string) 814 + decode.success(RegisterPrecheckResponse(ok, why)) 810 815 } 811 816 "greeting" -> { 812 817 use greeting <- decode.field("greeting", decode.string)
+1 -1
client/src/lumina_client/model.gleam
··· 17 17 /// The Page type is, pretty explanatory, an enum of all the pages in the app. Nested if needed, to track fields like the current tab in the Dashboard or the username form field in the login page. 18 18 pub type Page { 19 19 Landing 20 - Register(fields: RegisterPageFields) 20 + Register(fields: RegisterPageFields, ready: Option(Result(Nil, String))) 21 21 Login(fields: LoginFields) 22 22 HomeTimeline(timeline_id: String) 23 23 }
+2 -1
server/Cargo.toml
··· 21 21 tokio-postgres = { version = "0.7.13", features = ["with-uuid-1"] } 22 22 bcrypt = "0.17.0" 23 23 r2d2 = "0.8.10" 24 - r2d2_sqlite = {version = "0.27.0", features = ["bundled"]} 24 + r2d2_sqlite = { version = "0.27.0", features = ["bundled"] } 25 25 tabled = "0.18.0" 26 + regex = "1.10.5"
+55 -15
server/src/client_communication.rs
··· 71 71 } 72 72 Err(e) => { 73 73 match e { 74 - LuminaError::Postgres(e) => 75 - error!("Error creating session token: {:?}", e), 76 - 77 - LuminaError::SqlitePool(e) => 78 - warn!("Error creating session token: {:?}", e) 79 - , 74 + LuminaError::Postgres(e) => 75 + error!("Error creating session token: {:?}", e), 76 + LuminaError::SqlitePool(e) => 77 + warn!("Error creating session token: {:?}", e), 80 78 _ => {} 81 79 } 82 - 83 80 // I would return a more specific error message 84 81 // to the client here, but if the server knows the 85 82 // error, the client should know the error twice as ··· 105 102 LuminaError::RegisterEmailNotValid => { 106 103 println!( 107 104 "{registrationerror} Email {} is not valid", 108 - 109 105 email.clone().color_bright_cyan() 110 106 ); 111 107 } ··· 140 136 } 141 137 let _ = stream.send(ws::Message::from("unknown")).await; 142 138 } 139 + Ok(Message::RegisterPrecheck { email, username, password }) => { 140 + let appstate = state.0.clone(); 141 + let db = &appstate.1.lock().await; 142 + match crate::user::register_validitycheck(email, username, password, db).await { 143 + Err(LuminaError::RegisterEmailInUse) => { 144 + let _ = stream.send(ws::Message::from(msgtojson(Message::RegisterPrecheckResponse { 145 + ok: false, 146 + why: "Email already in use".to_string(), 147 + }))).await; 148 + } 149 + Err(LuminaError::RegisterUsernameInUse) => { 150 + let _ = stream.send(ws::Message::from(msgtojson(Message::RegisterPrecheckResponse { 151 + ok: false, 152 + why: "Username already in use".to_string(), 153 + }))).await; 154 + } 155 + Err(LuminaError::RegisterEmailNotValid) => { 156 + let _ = stream.send(ws::Message::from(msgtojson(Message::RegisterPrecheckResponse { 157 + ok: false, 158 + why: "Email not valid".to_string(), 159 + }))).await; 160 + } 161 + Err(LuminaError::RegisterUsernameInvalid(why)) => { 162 + let _ = stream.send(ws::Message::from(msgtojson(Message::RegisterPrecheckResponse { 163 + ok: false, 164 + why: format!("Username invalid: {}", why), 165 + }))).await; 166 + } 167 + Err(LuminaError::RegisterPasswordNotValid(why)) => { 168 + let _ = stream.send(ws::Message::from(msgtojson(Message::RegisterPrecheckResponse { 169 + ok: false, 170 + why: format!("Password invalid: {}", why), 171 + }))).await; 172 + } 173 + Ok(_) => { 174 + let _ = stream.send(ws::Message::from(msgtojson(Message::RegisterPrecheckResponse { 175 + ok: true, 176 + why: "".to_string(), 177 + }))).await; 178 + } 179 + _ => {} 180 + } 181 + } 143 182 Ok(jsonmsg) => { 144 - let _ = stream.send(ws::Message::from("unknown")).await; 145 183 panic!("Unhandled message: {:?}", jsonmsg); 146 184 } 147 185 Err(e) => { ··· 188 226 username: String, 189 227 password: String, 190 228 }, 191 - #[serde(rename = "register_precheck")] 192 - RegisterPrecheck { 193 - email: String, 194 - username: String, 195 - password: String, 196 - }, 229 + #[serde(rename = "register_precheck")] 230 + RegisterPrecheck { 231 + email: String, 232 + username: String, 233 + password: String, 234 + }, 235 + #[serde(rename = "register_precheck_response")] 236 + RegisterPrecheckResponse { ok: bool, why: String }, 197 237 #[serde(rename = "auth_success")] 198 238 AuthSuccess { token: String, username: String }, 199 239 #[serde(rename = "auth_failure")]
+1
server/src/errors.rs
··· 17 17 RegisterPasswordNotValid(String), 18 18 LoginInvalid, 19 19 UUidError(uuid::Error), 20 + RegexError(regex::Error), 20 21 }
+116 -111
server/src/user.rs
··· 16 16 password: String, 17 17 db: &DbConn, 18 18 ) -> Result<User, LuminaError> { 19 - match { 20 - let mut check_results = vec![]; 21 - { 22 - check_results.push(( 23 - username.len() > 4, 24 - "Username must be at least 5 characters long", 25 - )); 26 - check_results.push(( 27 - username.len() < 20, 28 - "Username must be less than 20 characters long", 29 - )); 30 - // Make sure the username does not contain any special characters, but underscores or dashes are allowed 31 - check_results.push((!username.contains('@'), "Username cannot contain '@'")); 32 - check_results.push((!username.contains('!'), "Username cannot contain '!'")); 33 - check_results.push((!username.contains('#'), "Username cannot contain '#'")); 34 - check_results.push((!username.contains('$'), "Username cannot contain '$'")); 35 - check_results.push((!username.contains('%'), "Username cannot contain '%'")); 36 - check_results.push((!username.contains('^'), "Username cannot contain '^'")); 37 - check_results.push((!username.contains('&'), "Username cannot contain '&'")); 38 - check_results.push((!username.contains('*'), "Username cannot contain '*'")); 39 - check_results.push((!username.contains('('), "Username cannot contain '('")); 40 - check_results.push((!username.contains(')'), "Username cannot contain ')'")); 41 - // check_results.push(( 42 - // username.chars().all(char::is_lowercase), 43 - // "Username must be all lowercase", 44 - // )); 45 - // This false-positive's on the last check, so it's commented out for now, replacing it with a replacement check 46 - check_results.push(( 47 - username.chars().all(|x| { 48 - // No case check on special 49 - if !x.is_alphabetic() { 50 - return true; 51 - } else { 52 - return x.is_lowercase(); 53 - } 54 - }), 55 - "Username must be alphanumeric, with underscores and dashes allowed", 56 - )); 57 - } 58 - check_results.iter().find(|x| x.0 == false).map(|x| x.1) 59 - } { 60 - Some(v) => { 61 - return Err(LuminaError::RegisterUsernameInvalid(v.to_string())); 62 - } 63 - None => {} 64 - } 65 - // Check if the email is valid 66 - if { 67 - let mut check_results = vec![]; 68 - { 69 - check_results.push(email.contains('@')); 70 - check_results.push(email.contains('.')); 71 - let mut splemail = email.split("@"); 72 - check_results.push(match splemail.nth(0) { 73 - Some(v) => v.len() > 4, 74 - None => false, 75 - }); 76 - check_results.push(match splemail.nth(1) { 77 - Some(v) => { 78 - v.len() > 4 79 - && v.contains('.') 80 - && v.split('.').last().unwrap().len() > 1 81 - && v.split('.').last().unwrap().len() < 5 82 - && v == splemail.last().unwrap() 83 - } 84 - None => false, 85 - }); 86 - 87 - check_results.push(email.len() > 8); 88 - } 89 - check_results.contains(&false) 90 - } { 91 - return Err(LuminaError::RegisterEmailNotValid); 92 - } 93 - // Now do that again but with reasons, like for username: 94 - match { 95 - let mut check_results = vec![]; 96 - { 97 - check_results.push(( 98 - password.len() > 7, 99 - "Password must be at least 8 characters long", 100 - )); 101 - check_results.push(( 102 - password.len() < 100, 103 - "Password must be less than 100 characters long", 104 - )); 105 - check_results.push(( 106 - password.chars().any(char::is_uppercase), 107 - "Password must contain at least one uppercase letter", 108 - )); 109 - check_results.push(( 110 - password.chars().any(char::is_lowercase), 111 - "Password must contain at least one lowercase letter", 112 - )); 113 - check_results.push(( 114 - password.chars().any(char::is_numeric), 115 - "Password must contain at least one number", 116 - )); 117 - check_results.push(( 118 - !password.chars().all(char::is_alphanumeric), 119 - "Password must contain at least one special character", 120 - )); 121 - } 122 - check_results.iter().find(|x| x.0 == false).map(|x| x.1) 123 - } { 124 - Some(v) => { 125 - return Err(LuminaError::RegisterPasswordNotValid(v.to_string())); 126 - } 127 - None => {} 128 - } 129 - 19 + let _ = 20 + register_validitycheck(email.clone(), username.clone(), password.clone(), db).await?; 130 21 // hash the password 131 22 let password = 132 23 bcrypt::hash(password, bcrypt::DEFAULT_COST).map_err(LuminaError::BcryptError)?; ··· 308 199 } 309 200 } 310 201 } 202 + 203 + pub(crate) async fn register_validitycheck( 204 + email: String, 205 + username: String, 206 + password: String, 207 + db: &DbConn, 208 + ) -> Result<(), LuminaError> { 209 + // 210 + // 211 + // Email checks 212 + // 213 + { 214 + let email_regex = regex::Regex::new( 215 + r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})", 216 + ) 217 + .map_err(LuminaError::RegexError)?; 218 + if !email_regex.is_match(&email) { 219 + return Err(LuminaError::RegisterEmailNotValid); 220 + }; 221 + } 222 + 223 + // 224 + // 225 + // Username checks 226 + // 227 + { 228 + // Check if username is valid 229 + if username.chars().any(|c| { 230 + match c { 231 + ' ' | '\\' | '/' | '@' | '\n' | '\r' | '\t' | '\x0b' | '\'' | '"' | '(' | ')' 232 + | '`' | '%' | '?' | '!' => true, 233 + '#' => ( 234 + // Make sure, if a # is in the username, only 4 numbers may follow it. 235 + || { 236 + let split_username = username.split('#'); 237 + let array_split_username: Vec<&str> = split_username.collect(); 238 + let lastbit = username.replacen(array_split_username[0], "", 1); 239 + let firstbit = username.replacen(&*lastbit, "", 1); 240 + let vec_split_username: Vec<&str> = vec![&*firstbit, &*lastbit]; 241 + // println!("array: {:?}", array_split_username); 242 + // println!("vec: {:?}", vec_split_username); 243 + if vec_split_username.is_empty() || array_split_username[1].is_empty() { 244 + return true; 245 + }; 246 + (!array_split_username[1].chars().all(char::is_numeric)) 247 + || !(vec_split_username[1].len() == 5 248 + || vec_split_username[1].len() == 7) 249 + } 250 + )(), 251 + _ => false, 252 + } 253 + }) || !username 254 + .replace(['_', '-', '.'], "") 255 + .replacen('#', "", 1) 256 + .chars() 257 + .all(char::is_alphanumeric) 258 + { 259 + return Err(LuminaError::RegisterUsernameInvalid( 260 + "Invalid characters in username".to_string(), 261 + )); 262 + } 263 + // Check if the username is too long 264 + if username.len() > 20 { 265 + return Err(LuminaError::RegisterUsernameInvalid( 266 + "Username too long".to_string(), 267 + )); 268 + } 269 + // Check if the username is too short 270 + if username.len() < 4 { 271 + return Err(LuminaError::RegisterUsernameInvalid( 272 + "Username too short".to_string(), 273 + )); 274 + } 275 + 276 + // Check if the username is already in use 277 + if let Ok(_) = User::get_user_by_identifier(username.clone(), db).await { 278 + return Err(LuminaError::RegisterUsernameInUse); 279 + }; 280 + } 281 + 282 + // 283 + // 284 + // Password checks 285 + // 286 + { 287 + if password.len() < 8 { 288 + return Err(LuminaError::RegisterPasswordNotValid( 289 + "Password too short".to_string(), 290 + )); 291 + } 292 + if password.len() > 100 { 293 + return Err(LuminaError::RegisterPasswordNotValid( 294 + "Password too long".to_string(), 295 + )); 296 + } 297 + if !password.chars().any(char::is_uppercase) { 298 + return Err(LuminaError::RegisterPasswordNotValid( 299 + "Password must contain at least one uppercase letter".to_string(), 300 + )); 301 + } 302 + if !password.chars().any(char::is_lowercase) { 303 + return Err(LuminaError::RegisterPasswordNotValid( 304 + "Password must contain at least one lowercase letter".to_string(), 305 + )); 306 + } 307 + if !password.chars().any(char::is_numeric) { 308 + return Err(LuminaError::RegisterPasswordNotValid( 309 + "Password must contain at least one number".to_string(), 310 + )); 311 + } 312 + } 313 + 314 + Ok(()) 315 + }