From 45d4867cb37ce8d7007c4d98de70d81d0b705b92 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sat, 26 Nov 2022 12:26:05 +0100 Subject: [PATCH] Sign up form and other stuff --- .cargo/config.toml | 2 + Cargo.lock | 413 +++++++++++++++++- README.md | 11 + backend/Cargo.toml | 6 +- backend/{launch_debug.ps1 => launch_debug.nu} | 2 +- backend/sql/version_1.sql | 24 +- backend/src/config.rs | 32 ++ backend/src/consts.rs | 6 +- backend/src/db.rs | 46 +- backend/src/email.rs | 22 + backend/src/main.rs | 277 ++++++++++-- backend/templates/base.html | 2 +- backend/templates/message.html | 7 + backend/templates/sign_in_form.html | 15 + backend/templates/sign_up_form.html | 22 + common/Cargo.toml | 4 +- common/src/lib.rs | 7 +- common/src/utils.rs | 24 + 18 files changed, 816 insertions(+), 106 deletions(-) create mode 100644 .cargo/config.toml rename backend/{launch_debug.ps1 => launch_debug.nu} (79%) create mode 100644 backend/src/config.rs create mode 100644 backend/src/email.rs create mode 100644 backend/templates/message.html create mode 100644 backend/templates/sign_in_form.html create mode 100644 backend/templates/sign_up_form.html create mode 100644 common/src/utils.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..73227e6 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc.exe" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 410f181..51e1d16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -330,7 +330,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -451,14 +451,14 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.26" +version = "4.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +checksum = "0acbd8d28a0a60d7108d7ae850af6ba34cf2d1257fc646980e5f97ce14275966" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", + "is-terminal", "once_cell", "strsim", "termcolor", @@ -499,6 +499,10 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" +dependencies = [ + "lazy_static", + "regex", +] [[package]] name = "convert_case" @@ -517,6 +521,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -625,6 +639,22 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "email-encoding" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34dd14c63662e0206599796cd5e1ad0268ab2b9d19b868d6050d688eba2bbf98" +dependencies = [ + "base64", + "memchr", +] + +[[package]] +name = "email_address" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -647,6 +677,27 @@ dependencies = [ "termcolor", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -659,11 +710,20 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -675,6 +735,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -846,6 +921,26 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.8" @@ -911,6 +1006,17 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.3.0" @@ -931,6 +1037,37 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e394faa0efb47f9f227f1cd89978f854542b318a6f64fa695489c9c993056656" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae5bc6e2eb41c9def29a3e0f1306382807764b9b53112030eff57435667352d" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -970,6 +1107,34 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lettre" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eabca5e0b4d0e98e7f2243fb5b7520b6af2b65d8f87bcc86f2c75185a6ff243" +dependencies = [ + "base64", + "email-encoding", + "email_address", + "fastrand", + "futures-util", + "hostname", + "httpdate", + "idna 0.2.3", + "mime", + "native-tls", + "nom", + "once_cell", + "quoted_printable", + "socket2", +] + [[package]] name = "libc" version = "0.2.137" @@ -996,6 +1161,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" + [[package]] name = "local-channel" version = "0.1.3" @@ -1033,6 +1204,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" @@ -1063,9 +1246,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] @@ -1079,7 +1262,25 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1117,7 +1318,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -1127,6 +1328,51 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "openssl" +version = "0.10.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -1153,7 +1399,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1245,6 +1491,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quoted_printable" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fee2dce59f7a43418e3382c766554c614e06a552d53a8f07ef499ea4b332c0f" + [[package]] name = "r2d2" version = "0.8.10" @@ -1307,9 +1559,11 @@ dependencies = [ "askama_actix", "chrono", "clap", + "common", "env_logger", "futures", "itertools", + "lettre", "r2d2", "r2d2_sqlite", "rand", @@ -1345,6 +1599,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ron" version = "0.8.0" @@ -1380,12 +1643,36 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.36.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1fbb4dfc4eb1d390c02df47760bb19a84bb80b301ecc947ab5406394d8223e" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + [[package]] name = "scheduled-thread-pool" version = "0.2.6" @@ -1407,6 +1694,29 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.14" @@ -1435,9 +1745,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -1524,6 +1834,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1697,7 +2021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna", + "idna 0.3.0", "percent-encoding", ] @@ -1810,6 +2134,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -1817,12 +2154,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_x86_64_msvc 0.42.0", ] [[package]] @@ -1831,24 +2168,48 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_gnu" version = "0.42.0" @@ -1861,6 +2222,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "windows_x86_64_msvc" version = "0.42.0" @@ -1888,9 +2255,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" +version = "2.0.3+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +checksum = "44ccf97612ac95f3ccb89b2d7346b345e52f1c3019be4984f0455fb4ba991f8a" dependencies = [ "cc", "libc", diff --git a/README.md b/README.md index 5de0935..d78b1f1 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,17 @@ # Technical +## Cross-compilation on Windows 11 + +The toolchain for Raspberry Pi 64 bits is available here: https://gnutoolchains.com/raspberry64/ + + +## How to install service on RPI Zero + +1. Copy doc/recipes.service to /lib/systemd/system +2. Enabled it: #> systemctl enable recipes + + ## Useful URLs * Rust patterns : https://github.com/rust-unofficial/patterns/tree/master/patterns diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 371b0ed..c443e9b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Grégory Burri "] edition = "2021" [dependencies] -#common = {path = "../common"} +common = {path = "../common"} actix-web = "4" actix-files = "0.6" @@ -31,4 +31,6 @@ askama_actix = "0.13" argon2 = {version = "0.4", features = ["default", "std"]} rand_core = {version = "0.6", features = ["std"]} -rand = "0.8" \ No newline at end of file +rand = "0.8" + +lettre = "0.10" \ No newline at end of file diff --git a/backend/launch_debug.ps1 b/backend/launch_debug.nu similarity index 79% rename from backend/launch_debug.ps1 rename to backend/launch_debug.nu index 5e20487..d095b12 100644 --- a/backend/launch_debug.ps1 +++ b/backend/launch_debug.nu @@ -1,2 +1,2 @@ # To launch RUP and watching source. See https://actix.rs/docs/autoreload/. -cargo watch -x run \ No newline at end of file +cargo [watch -x run] \ No newline at end of file diff --git a/backend/sql/version_1.sql b/backend/sql/version_1.sql index aaa8a9f..1685cd1 100644 --- a/backend/sql/version_1.sql +++ b/backend/sql/version_1.sql @@ -14,7 +14,9 @@ CREATE TABLE [User] ( [password] TEXT NOT NULL, -- argon2(password_plain, salt). [creation_datetime] DATETIME NOT NULL, -- Updated when the validation email is sent. - [validation_token] TEXT -- If not null then the user has not validated his account yet. + [validation_token] TEXT, -- If not null then the user has not validated his account yet. + + [is_admin] INTEGER NOT NULL DEFAULT FALSE ); CREATE UNIQUE INDEX [User_email_index] ON [User] ([email]); @@ -28,20 +30,20 @@ CREATE TABLE [UserLoginToken] ( [ip] TEXT, -- Can be ipv4 or ipv6 [user_agent] TEXT, - FOREIGN KEY([user_id]) REFERENCES [User]([id]) + FOREIGN KEY([user_id]) REFERENCES [User]([id]) ON DELETE CASCADE ); CREATE INDEX [UserLoginToken_token_index] ON [UserLoginToken] ([token]); CREATE TABLE [Recipe] ( [id] INTEGER PRIMARY KEY, - [user_id] INTEGER NOT NULL, + [user_id] INTEGER, -- Can be null if a user is deleted. [title] TEXT NOT NULL, [estimate_time] INTEGER, [description] TEXT, [servings] INTEGER DEFAULT 4, - FOREIGN KEY([user_id]) REFERENCES [User]([id]) + FOREIGN KEY([user_id]) REFERENCES [User]([id]) ON DELETE SET NULL ); CREATE TABLE [Quantity] ( @@ -56,8 +58,8 @@ CREATE TABLE [Ingredient] ( [quantity_id] INTEGER, [input_step_id] INTEGER NOT NULL, - FOREIGN KEY([quantity_id]) REFERENCES Quantity([id]), - FOREIGN KEY([input_step_id]) REFERENCES Step([id]) + FOREIGN KEY([quantity_id]) REFERENCES Quantity([id]) ON DELETE CASCADE, + FOREIGN KEY([input_step_id]) REFERENCES Step([id]) ON DELETE CASCADE ); CREATE TABLE [Group] ( @@ -66,7 +68,7 @@ CREATE TABLE [Group] ( [recipe_id] INTEGER, name TEXT, - FOREIGN KEY([recipe_id]) REFERENCES [Recipe]([id]) + FOREIGN KEY([recipe_id]) REFERENCES [Recipe]([id]) ON DELETE CASCADE ); CREATE INDEX [Group_order_index] ON [Group] ([order]); @@ -77,7 +79,7 @@ CREATE TABLE [Step] ( [action] TEXT NOT NULL, [group_id] INTEGER NOT NULL, - FOREIGN KEY(group_id) REFERENCES [Group](id) + FOREIGN KEY(group_id) REFERENCES [Group](id) ON DELETE CASCADE ); CREATE INDEX [Step_order_index] ON [Group] ([order]); @@ -89,7 +91,7 @@ CREATE TABLE [IntermediateSubstance] ( [output_step_id] INTEGER NOT NULL, [input_step_id] INTEGER NOT NULL, - FOREIGN KEY([quantity_id]) REFERENCES [Quantity]([id]), - FOREIGN KEY([output_step_id]) REFERENCES [Step]([id]), - FOREIGN KEY([input_step_id]) REFERENCES [Step]([id]) + FOREIGN KEY([quantity_id]) REFERENCES [Quantity]([id]) ON DELETE CASCADE, + FOREIGN KEY([output_step_id]) REFERENCES [Step]([id]) ON DELETE CASCADE, + FOREIGN KEY([input_step_id]) REFERENCES [Step]([id]) ON DELETE CASCADE ); diff --git a/backend/src/config.rs b/backend/src/config.rs new file mode 100644 index 0000000..393a03b --- /dev/null +++ b/backend/src/config.rs @@ -0,0 +1,32 @@ +use std::{fmt, fs::File}; + +use ron::de::from_reader; +use serde::Deserialize; + +use crate::consts; + +#[derive(Deserialize, Clone)] +pub struct Config { + pub port: u16, + pub smtp_login: String, + pub smtp_password: String, +} + +// Avoid to print passwords. +impl fmt::Debug for Config { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Config") + .field("port", &self.port) + .field("smtp_login", &self.smtp_login) + .field("smtp_password", &"*****") + .finish() + } +} + +pub fn load() -> Config { + let f = File::open(consts::FILE_CONF).unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF)); + match from_reader(f) { + Ok(c) => c, + Err(e) => panic!("Failed to load config: {}", e) + } +} \ No newline at end of file diff --git a/backend/src/consts.rs b/backend/src/consts.rs index d84c21d..896fbdd 100644 --- a/backend/src/consts.rs +++ b/backend/src/consts.rs @@ -1,4 +1,6 @@ + pub const FILE_CONF: &str = "conf.ron"; pub const DB_DIRECTORY: &str = "data"; -pub const DB_FILENAME: &str = "data/recipes.sqlite"; -pub const SQL_FILENAME: &str = "sql/version_{VERSION}.sql"; \ No newline at end of file +pub const DB_FILENAME: &str = "recipes.sqlite"; +pub const SQL_FILENAME: &str = "sql/version_{VERSION}.sql"; +pub const VALIDATION_TOKEN_DURATION: i64 = 1 * 60 * 60; // 1 hour. [s]. \ No newline at end of file diff --git a/backend/src/db.rs b/backend/src/db.rs index 28bf62c..bae57d0 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, fs::{self, File}, path::Path, io::Read}; +use std::{fmt, fs::{self, File}, path::Path, io::Read}; use itertools::Itertools; use chrono::{prelude::*, Duration}; @@ -22,6 +22,14 @@ pub enum DBError { Other(String), } +impl fmt::Display for DBError { + fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for DBError { } + impl From for DBError { fn from(error: rusqlite::Error) -> Self { DBError::SqliteError(error) @@ -95,7 +103,7 @@ impl Connection { Self::create_connection(SqliteConnectionManager::file(file)) } - fn create_connection(manager: SqliteConnectionManager) -> Result {; + fn create_connection(manager: SqliteConnectionManager) -> Result { let pool = r2d2::Pool::new(manager).unwrap(); let connection = Connection { pool }; connection.create_or_update()?; @@ -206,11 +214,11 @@ impl Connection { } /// - pub fn sign_up(&self, password: &str, email: &str) -> Result { - self.sign_up_with_given_time(password, email, Utc::now()) + pub fn sign_up(&self, email: &str, password: &str) -> Result { + self.sign_up_with_given_time(email, password, Utc::now()) } - fn sign_up_with_given_time(&self, password: &str, email: &str, datetime: DateTime) -> Result { + fn sign_up_with_given_time(&self, email: &str, password: &str, datetime: DateTime) -> Result { let mut con = self.pool.get()?; let tx = con.transaction()?; let token = @@ -313,7 +321,7 @@ impl Connection { } /// Execute a given SQL file. - pub fn execute_file + Display>(&self, file: P) -> Result<()> { + pub fn execute_file + fmt::Display>(&self, file: P) -> Result<()> { let con = self.pool.get()?; let sql = load_sql_file(file)?; con.execute_batch(&sql).map_err(DBError::from) @@ -334,7 +342,7 @@ impl Connection { } } -fn load_sql_file + Display>(sql_file: P) -> Result { +fn load_sql_file + fmt::Display>(sql_file: P) -> Result { let mut file = File::open(&sql_file).map_err(|err| DBError::Other(format!("Cannot open SQL file ({}): {}", &sql_file, err.to_string())))?; let mut sql = String::new(); file.read_to_string(&mut sql).map_err(|err| DBError::Other(format!("Cannot read SQL file ({}) : {}", &sql_file, err.to_string())))?; @@ -352,7 +360,7 @@ mod tests { #[test] fn sign_up() -> Result<()> { let connection = Connection::new_in_memory()?; - match connection.sign_up("12345", "paul@test.org")? { + match connection.sign_up("paul@test.org", "12345")? { SignUpResult::UserCreatedWaitingForValidation(_) => (), // Nominal case. other => panic!("{:?}", other), } @@ -372,7 +380,7 @@ mod tests { 0, NULL );", [])?; - match connection.sign_up("12345", "paul@test.org")? { + match connection.sign_up("paul@test.org", "12345")? { SignUpResult::UserAlreadyExists => (), // Nominal case. other => panic!("{:?}", other), } @@ -393,7 +401,7 @@ mod tests { 0, :token );", named_params! { ":token": token })?; - match connection.sign_up("12345", "paul@test.org")? { + match connection.sign_up("paul@test.org", "12345")? { SignUpResult::UserCreatedWaitingForValidation(_) => (), // Nominal case. other => panic!("{:?}", other), } @@ -404,7 +412,7 @@ mod tests { fn sign_up_then_send_validation_at_time() -> Result<()> { let connection = Connection::new_in_memory()?; let validation_token = - match connection.sign_up("12345", "paul@test.org")? { + match connection.sign_up("paul@test.org", "12345")? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; @@ -419,7 +427,7 @@ mod tests { fn sign_up_then_send_validation_too_late() -> Result<()> { let connection = Connection::new_in_memory()?; let validation_token = - match connection.sign_up_with_given_time("12345", "paul@test.org", Utc::now() - Duration::days(1))? { + match connection.sign_up_with_given_time("paul@test.org", "12345", Utc::now() - Duration::days(1))? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; @@ -434,7 +442,7 @@ mod tests { fn sign_up_then_send_validation_with_bad_token() -> Result<()> { let connection = Connection::new_in_memory()?; let _validation_token = - match connection.sign_up("12345", "paul@test.org")? { + match connection.sign_up("paul@test.org", "12345")? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; @@ -450,12 +458,12 @@ mod tests { fn sign_up_then_send_validation_then_sign_in() -> Result<()> { let connection = Connection::new_in_memory()?; - let password = "12345"; let email = "paul@test.org"; + let password = "12345"; // Sign up. let validation_token = - match connection.sign_up(password, email)? { + match connection.sign_up(email, password)? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; @@ -479,12 +487,12 @@ mod tests { fn sign_up_then_send_validation_then_authentication() -> Result<()> { let connection = Connection::new_in_memory()?; - let password = "12345"; let email = "paul@test.org"; + let password = "12345"; // Sign up. let validation_token = - match connection.sign_up(password, email)? { + match connection.sign_up(email, password)? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; @@ -519,12 +527,12 @@ mod tests { fn sign_up_then_send_validation_then_sign_out_then_sign_in() -> Result<()> { let connection = Connection::new_in_memory()?; - let password = "12345"; let email = "paul@test.org"; + let password = "12345"; // Sign up. let validation_token = - match connection.sign_up(password, email)? { + match connection.sign_up(email, password)? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; diff --git a/backend/src/email.rs b/backend/src/email.rs new file mode 100644 index 0000000..a0043f7 --- /dev/null +++ b/backend/src/email.rs @@ -0,0 +1,22 @@ +use lettre::{transport::smtp::{authentication::Credentials}, Message, SmtpTransport, Transport}; + +/// +pub fn send_validation(site_url: &str, email: &str, token: &str, smtp_login: &str, smtp_password: &str) -> Result<(), Box> { + + let email = Message::builder() + .message_id(None) + .from("recipes@gburri.org".parse()?) + .to(email.parse()?) + .subject("Recipes.gburri.org account validation") + .body(format!("Follow this link to confirm your inscription: {}/validation?token={}", site_url, token))?; + + let credentials = Credentials::new(smtp_login.to_string(), smtp_password.to_string()); + + let mailer = SmtpTransport::relay("mail.gandi.net")?.credentials(credentials).build(); + + if let Err(error) = mailer.send(&email) { + println!("Error when sending E-mail:\n{:?}", &error); + } + + Ok(()) +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 98bebc9..141c87f 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,19 +1,21 @@ -use std::fs::File; -use std::sync::Mutex; +use std::{collections::HashMap, net::ToSocketAddrs}; use actix_files as fs; -use actix_web::{get, web, Responder, middleware, App, HttpServer, HttpRequest}; -use askama_actix::Template; -use chrono::prelude::*; +use actix_web::{http::header, get, post, web, Responder, middleware, App, HttpServer, HttpRequest, HttpResponse}; +use askama_actix::{Template, TemplateToResponse}; +use chrono::{prelude::*, Duration}; use clap::Parser; -use ron::de::from_reader; use serde::Deserialize; +use config::Config; + mod consts; mod db; mod hash; mod model; mod user; +mod email; +mod config; #[derive(Template)] #[template(path = "home.html")] @@ -21,6 +23,11 @@ struct HomeTemplate { recipes: Vec<(i32, String)>, } +#[derive(Template)] +#[template(path = "sign_in_form.html")] +struct SignInFormTemplate { +} + #[derive(Template)] #[template(path = "view_recipe.html")] struct ViewRecipeTemplate { @@ -28,27 +35,219 @@ struct ViewRecipeTemplate { current_recipe: model::Recipe, } -#[derive(Deserialize)] -pub struct Request { - m: Option +#[derive(Template)] +#[template(path = "message.html")] +struct MessageTemplate { + recipes: Vec<(i32, String)>, + message: String, } #[get("/")] async fn home_page(req: HttpRequest, connection: web::Data) -> impl Responder { - HomeTemplate { recipes: connection.get_all_recipe_titles().unwrap() } // TODO: unwrap. + HomeTemplate { recipes: connection.get_all_recipe_titles().unwrap_or_default() } +} + +//// SIGN UP ///// + +#[derive(Template)] +#[template(path = "sign_up_form.html")] +struct SignUpFormTemplate { + email: String, + message: String, + message_email: String, + message_password: String, +} + +impl SignUpFormTemplate { + fn new() -> Self { + SignUpFormTemplate { email: String::new(), message: String::new(), message_email: String::new(), message_password: String::new() } + } +} + +enum SignUpError { + InvalidEmail, + PasswordsNotEqual, + InvalidPassword, + UserAlreadyExists, + DatabaseError, + UnableSendEmail, +} + +#[get("/signup")] +async fn sign_up_get(req: HttpRequest, query: web::Query>, connection: web::Data) -> impl Responder { + SignUpFormTemplate::new() +} + +#[derive(Deserialize)] +struct SignUpFormData { + email: String, + password_1: String, + password_2: String, +} + +#[post("/signup")] +async fn sign_up_post(req: HttpRequest, form: web::Form, connection: web::Data, config: web::Data) -> impl Responder { + println!("Sign Up, email: {}, passwords: {}/{}", form.email, form.password_1, form.password_2); + + fn error_response(error: SignUpError, form: &web::Form) -> HttpResponse { + SignUpFormTemplate { + email: form.email.clone(), + message_email: + match error { + SignUpError::InvalidEmail => "Invalid email", + _ => "", + }.to_string(), + message_password: + match error { + SignUpError::PasswordsNotEqual => "Passwords don't match", + SignUpError::InvalidPassword => "Password must have at least eight characters", + _ => "", + }.to_string(), + message: + match error { + SignUpError::UserAlreadyExists => "This email is already taken", + SignUpError::DatabaseError => "Database error", + SignUpError::UnableSendEmail => "Unable to send the validation email", + _ => "", + }.to_string(), + }.to_response() + } + + // Validation of email and password. + if let common::utils::EmailValidation::NotValid = common::utils::validate_email(&form.email) { + return error_response(SignUpError::InvalidEmail, &form); + } + + if form.password_1 != form.password_2 { + return error_response(SignUpError::PasswordsNotEqual, &form); + } + + if let common::utils::PasswordValidation::TooShort = common::utils::validate_password(&form.password_1) { + return error_response(SignUpError::InvalidPassword, &form); + } + + match connection.sign_up(&form.email, &form.password_1) { + Ok(db::SignUpResult::UserAlreadyExists) => { + error_response(SignUpError::UserAlreadyExists, &form) + }, + Ok(db::SignUpResult::UserCreatedWaitingForValidation(token)) => { + let url = { + let host = req.headers().get(header::HOST).map(|v| v.to_str().unwrap_or_default()).unwrap_or_default(); + let port: Option = 'p: { + let split_port: Vec<&str> = host.split(':').collect(); + if split_port.len() == 2 { + if let Ok(p) = split_port[1].parse::() { + break 'p Some(p) + } + } + None + }; + format!("http{}://{}", if port.is_some() && port.unwrap() != 443 { "" } else { "s" }, host) + }; + match email::send_validation(&url, &form.email, &token, &config.smtp_login, &config.smtp_password) { + Ok(()) => + HttpResponse::Found() + .insert_header((header::LOCATION, "/signup_check_email")) + .finish(), + Err(error) => { + eprintln!("Email validation error: {:?}", error); + error_response(SignUpError::UnableSendEmail, &form) + }, + } + }, + Err(error) => { + eprintln!("Signup database error: {:?}", error); + error_response(SignUpError::DatabaseError, &form) + }, + } +} + +#[get("/signup_check_email")] +async fn sign_up_check_email(connection: web::Data) -> impl Responder { + let recipes = connection.get_all_recipe_titles().unwrap_or_default(); + MessageTemplate { + recipes, + message: "An email has been sent, follow the link to validate your account.".to_string(), + } +} + +#[get("/validation")] +async fn sign_up_validation(req: HttpRequest, query: web::Query>, connection: web::Data) -> impl Responder { + + println!("req:\n{:#?}", req); + + let client_user_agent = req.headers().get(header::USER_AGENT).map(|v| v.to_str().unwrap_or_default()).unwrap_or_default(); + let client_ip = req.peer_addr().map(|addr| addr.ip().to_string()).unwrap_or_default(); + + let recipes = connection.get_all_recipe_titles().unwrap_or_default(); + match query.get("token") { + Some(token) => { + match connection.validation(token, Duration::seconds(consts::VALIDATION_TOKEN_DURATION), &client_ip, client_user_agent).unwrap() { + db::ValidationResult::Ok(token, user_id) => + // TODO: set token to cookie. + MessageTemplate { + recipes, + message: "Email validation successful, your account has been created".to_string(), + }, + db::ValidationResult::ValidationExpired => + MessageTemplate { + recipes, + message: "The validation has expired. Try to sign up again.".to_string(), + }, + db::ValidationResult::UnknownUser => + MessageTemplate { + recipes, + message: "Validation error.".to_string(), + }, + } + }, + None => { + MessageTemplate { + recipes, + message: format!("No token provided"), + } + }, + } +} + +///// SIGN IN ///// + +#[get("/signinform")] +async fn sign_in_form(req: HttpRequest, connection: web::Data) -> impl Responder { + SignInFormTemplate { + } +} + +#[post("/signin")] +async fn sign_in(req: HttpRequest) -> impl Responder { + "todo" } #[get("/recipe/view/{id}")] async fn view_recipe(req: HttpRequest, path: web::Path<(i32,)>, connection: web::Data) -> impl Responder { - ViewRecipeTemplate { - recipes: connection.get_all_recipe_titles().unwrap(), - current_recipe: connection.get_recipe(path.0).unwrap(), + let (id,)= path.into_inner(); + let recipes = connection.get_all_recipe_titles().unwrap_or_default(); + println!("{:?}", recipes); + match connection.get_recipe(id) { + Ok(recipe) => + ViewRecipeTemplate { + recipes, + current_recipe: recipe, + }.to_response(), + Err(_error) => + MessageTemplate { + recipes, + message: format!("Unable to get recipe #{}", id), + }.to_response(), } } -#[derive(Debug, Deserialize)] -struct Config { - port: u16 +async fn not_found(req: HttpRequest, connection: web::Data) -> impl Responder { + let recipes = connection.get_all_recipe_titles().unwrap_or_default(); + MessageTemplate { + recipes, + message: "404: Not found".to_string(), + } } fn get_exe_name() -> String { @@ -66,36 +265,36 @@ async fn main() -> std::io::Result<()> { println!("Starting Recipes as web server..."); - let config: Config = { - let f = File::open(consts::FILE_CONF).unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF)); - match from_reader(f) { - Ok(c) => c, - Err(e) => panic!("Failed to load config: {}", e) - } - }; + let config = web::Data::new(config::load()); + let port = config.as_ref().port; println!("Configuration: {:?}", config); - let db_connection = web::Data::new(db::Connection::new().unwrap()); // TODO: remove unwrap. + let db_connection = web::Data::new(db::Connection::new().unwrap()); std::env::set_var("RUST_LOG", "actix_web=info"); - let mut server = - HttpServer::new( - move || { - App::new() - .wrap(middleware::Logger::default()) - .wrap(middleware::Compress::default()) - .app_data(db_connection.clone()) - .service(home_page) - .service(view_recipe) - .service(fs::Files::new("/static", "static").show_files_listing()) - } - ); - - server = server.bind(&format!("0.0.0.0:{}", config.port)).unwrap(); + let server = + HttpServer::new(move || { + App::new() + .wrap(middleware::Logger::default()) + .wrap(middleware::Compress::default()) + .app_data(db_connection.clone()) + .app_data(config.clone()) + .service(home_page) + .service(sign_up_get) + .service(sign_up_post) + .service(sign_up_check_email) + .service(sign_up_validation) + .service(sign_in_form) + .service(sign_in) + .service(view_recipe) + .service(fs::Files::new("/static", "static")) + .default_service(web::to(not_found)) + //.default_service(not_found) + }); - server.run().await + server.bind(&format!("0.0.0.0:{}", port))?.run().await } #[derive(Parser, Debug)] diff --git a/backend/templates/base.html b/backend/templates/base.html index e71a492..2ca77df 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -8,7 +8,7 @@ - +
{% block main_container %}{% endblock %}
diff --git a/backend/templates/message.html b/backend/templates/message.html new file mode 100644 index 0000000..a274a64 --- /dev/null +++ b/backend/templates/message.html @@ -0,0 +1,7 @@ +{% extends "base_with_list.html" %} + +{% block content %} + +{{ message }} + +{% endblock %} \ No newline at end of file diff --git a/backend/templates/sign_in_form.html b/backend/templates/sign_in_form.html new file mode 100644 index 0000000..d41b3cd --- /dev/null +++ b/backend/templates/sign_in_form.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block main_container %} +
+
+ + + + + + + +
+
+{% endblock %} diff --git a/backend/templates/sign_up_form.html b/backend/templates/sign_up_form.html new file mode 100644 index 0000000..ed329a1 --- /dev/null +++ b/backend/templates/sign_up_form.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block main_container %} +
+
+ + + {{ message_email }} + + + + + + + + {{ message_password }} + + +
+ {{ message }} +
+{% endblock %} diff --git a/common/Cargo.toml b/common/Cargo.toml index 8862968..059cf02 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" authors = ["Grégory Burri "] edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +regex = "1" +lazy_static = "1" \ No newline at end of file diff --git a/common/src/lib.rs b/common/src/lib.rs index 19f35ef..fab870e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1 @@ -mod recipes { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} \ No newline at end of file +pub mod utils; \ No newline at end of file diff --git a/common/src/utils.rs b/common/src/utils.rs new file mode 100644 index 0000000..94b8c38 --- /dev/null +++ b/common/src/utils.rs @@ -0,0 +1,24 @@ +use regex::Regex; +use lazy_static::lazy_static; + +pub enum EmailValidation { + Ok, + NotValid, +} + +lazy_static! { + static ref EMAIL_REGEX: Regex = Regex::new(r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})").expect("Error parsing email regex"); +} + +pub fn validate_email(email: &str) -> EmailValidation { + if EMAIL_REGEX.is_match(email) { EmailValidation::Ok } else { EmailValidation::NotValid } +} + +pub enum PasswordValidation { + Ok, + TooShort, +} + +pub fn validate_password(password: &str) -> PasswordValidation { + if password.len() < 8 { PasswordValidation::TooShort } else { PasswordValidation::Ok } +} \ No newline at end of file -- 2.43.0