From 52049947698ae2b0ab0d798551da368d96646047 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 19 Nov 2023 11:31:30 -0500 Subject: [PATCH] email sending & magic links --- Cargo.lock | 416 +++++++++++++++++- trifid-api/Cargo.toml | 3 +- trifid-api/config.toml | 26 +- .../down.sql | 1 + .../up.sql | 5 + trifid-api/src/config.rs | 21 +- trifid-api/src/email.rs | 25 ++ trifid-api/src/main.rs | 1 + trifid-api/src/models.rs | 15 +- trifid-api/src/routes/v1/signup.rs | 19 +- trifid-api/src/schema.rs | 15 + 11 files changed, 530 insertions(+), 17 deletions(-) create mode 100644 trifid-api/migrations/2023-11-19-161415_create_magic_links/down.sql create mode 100644 trifid-api/migrations/2023-11-19-161415_create_magic_links/up.sql create mode 100644 trifid-api/src/email.rs diff --git a/Cargo.lock b/Cargo.lock index b32c37c..00bb8ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "zstd", + "zstd 0.12.4", ] [[package]] @@ -211,6 +211,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.6" @@ -349,6 +360,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "base64" version = "0.21.5" @@ -476,6 +493,27 @@ dependencies = [ "bytes", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cc" version = "1.0.83" @@ -522,6 +560,16 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -596,6 +644,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "convert_case" version = "0.4.0" @@ -647,6 +701,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossterm" version = "0.25.0" @@ -756,6 +819,12 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + [[package]] name = "der" version = "0.7.8" @@ -937,6 +1006,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "env_logger" version = "0.10.1" @@ -1082,6 +1163,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -1154,6 +1245,57 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand", + "ring 0.16.20", + "rustls", + "rustls-pemfile", + "thiserror", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "rustls", + "smallvec", + "thiserror", + "tokio", + "tokio-rustls", + "tracing", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1172,6 +1314,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[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.9" @@ -1311,6 +1464,27 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.5", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -1389,6 +1563,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.10" @@ -1428,6 +1608,79 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "mail-auth" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab6524549b92c6f1250ddefe10f67bcedb9de0674a026b21e88dc1958437f8a6" +dependencies = [ + "ahash", + "flate2", + "hickory-resolver", + "lru-cache", + "mail-builder", + "mail-parser", + "parking_lot", + "quick-xml", + "ring 0.17.5", + "rustls-pemfile", + "serde", + "serde_json", + "zip", +] + +[[package]] +name = "mail-builder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef70f53409852d2612f2249810cbbe0c9931ca25b739b734bafc7f61d88051d4" +dependencies = [ + "gethostname", +] + +[[package]] +name = "mail-parser" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e51d275d90a41584d12ea25fc4a7d7d00236a4718308b994ed13a7ff90a6985" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "mail-send" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbc58a799366b3b2956a2c5ae7e2892ea34b3016343cbbdc5deb844aa6c0973" +dependencies = [ + "base64 0.20.0", + "gethostname", + "mail-auth", + "mail-builder", + "md5", + "rand", + "rustls", + "smtp-proto", + "tokio", + "tokio-rustls", + "webpki-roots", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "md-5" version = "0.10.6" @@ -1438,6 +1691,12 @@ dependencies = [ "digest", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.6.4" @@ -1599,12 +1858,35 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1757,6 +2039,12 @@ dependencies = [ "checked_int_cast", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-protobuf" version = "0.8.1" @@ -1766,6 +2054,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.33" @@ -1903,6 +2200,31 @@ dependencies = [ "winreg", ] +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + [[package]] name = "ring" version = "0.17.5" @@ -1912,8 +2234,8 @@ dependencies = [ "cc", "getrandom", "libc", - "spin", - "untrusted", + "spin 0.9.8", + "untrusted 0.9.0", "windows-sys 0.48.0", ] @@ -1958,7 +2280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ "log", - "ring", + "ring 0.17.5", "rustls-webpki", "sct", ] @@ -1978,8 +2300,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -2010,8 +2332,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -2211,6 +2533,12 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +[[package]] +name = "smtp-proto" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b756ac662e92a0e5b360349bea5f0b0784d4be4541eff2972049dfdfd7f862" + [[package]] name = "socket2" version = "0.4.10" @@ -2231,6 +2559,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -2582,9 +2916,21 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -2606,6 +2952,7 @@ dependencies = [ "diesel_migrations", "env_logger", "log", + "mail-send", "rand", "serde", "serde_json", @@ -2667,6 +3014,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -2815,6 +3168,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -3049,13 +3408,52 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + [[package]] name = "zstd" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" dependencies = [ - "zstd-safe", + "zstd-safe 6.0.6", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", ] [[package]] diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index 5bc0019..595c9c9 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -23,4 +23,5 @@ diesel = { version = "2" } diesel-async = { version = "0.4", features = ["postgres", "bb8", "async-connection-wrapper"] } diesel_migrations = "2" bb8 = "0.8" -rand = "0.8" \ No newline at end of file +rand = "0.8" +mail-send = "0.4" \ No newline at end of file diff --git a/trifid-api/config.toml b/trifid-api/config.toml index 5a424bf..7546671 100644 --- a/trifid-api/config.toml +++ b/trifid-api/config.toml @@ -15,4 +15,28 @@ bind = { ip = "127.0.0.1", port = 8080 } # [database] contains options for the Postgres database used for all data storage [database] # (Required) The postgres database connection url to use -url = "postgres://postgres:postgres@localhost/trifid2" \ No newline at end of file +url = "postgres://postgres:postgres@localhost/trifid2" + +# [email] contains options for the SMTP connection used to send magic link emails +[email] +# (Required) The mail server to connect to +server = "mail.e3t.cc" +# (Required) The SMTP port to connect to. +port = 587 +# (Required) The username to authenticate with. +username = "username" +# (Required) The password to authenticate with. If set to %PASSWORD%, will be filled from the environment variable TRIFID_EMAIL_PASSWORD. +password = "$PASSWORD$" +# (Required) The "From Name" to send the email from +from_name = "Trifid" +# (Required) The address to send the email from +from_email = "trifid@e3t.cc" +# (Required) The email template to use. %TOKEN% will be replaced with the magic link token. +template = "Click this link to sign in! http://localhost:5173/magic-link?magicLinkToken=%TOKEN%" +# (Required) Should STARTTLS be used? +starttls = false + +# [tokens] contains options for token expiry +[tokens] +# (Required) How long should magic links be valid for, in seconds? +magic_link_expiry_seconds = 3600 # 1 hour \ No newline at end of file diff --git a/trifid-api/migrations/2023-11-19-161415_create_magic_links/down.sql b/trifid-api/migrations/2023-11-19-161415_create_magic_links/down.sql new file mode 100644 index 0000000..13fd6b1 --- /dev/null +++ b/trifid-api/migrations/2023-11-19-161415_create_magic_links/down.sql @@ -0,0 +1 @@ +DROP TABLE magic_links; \ No newline at end of file diff --git a/trifid-api/migrations/2023-11-19-161415_create_magic_links/up.sql b/trifid-api/migrations/2023-11-19-161415_create_magic_links/up.sql new file mode 100644 index 0000000..65705e3 --- /dev/null +++ b/trifid-api/migrations/2023-11-19-161415_create_magic_links/up.sql @@ -0,0 +1,5 @@ +CREATE TABLE magic_links ( + id VARCHAR NOT NULL PRIMARY KEY, + user_id VARCHAR NOT NULL REFERENCES users(id), + expires TIMESTAMP NOT NULL +); \ No newline at end of file diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 0226ce9..ef0c9e6 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -4,7 +4,9 @@ use serde::Deserialize; #[derive(Deserialize, Clone)] pub struct Config { pub server: ConfigServer, - pub database: ConfigDatabase + pub database: ConfigDatabase, + pub email: ConfigEmail, + pub tokens: ConfigTokens } #[derive(Deserialize, Clone)] @@ -22,4 +24,21 @@ pub struct ConfigServerBind { #[derive(Deserialize, Clone)] pub struct ConfigDatabase { pub url: String +} + +#[derive(Deserialize, Clone)] +pub struct ConfigEmail { + pub server: String, + pub port: u16, + pub username: String, + pub password: String, + pub from_name: String, + pub from_email: String, + pub template: String, + pub starttls: bool +} + +#[derive(Deserialize, Clone)] +pub struct ConfigTokens { + pub magic_link_expiry_seconds: u64 } \ No newline at end of file diff --git a/trifid-api/src/email.rs b/trifid-api/src/email.rs new file mode 100644 index 0000000..80952f8 --- /dev/null +++ b/trifid-api/src/email.rs @@ -0,0 +1,25 @@ +use std::error::Error; +use mail_send::mail_builder::MessageBuilder; +use mail_send::SmtpClientBuilder; +use crate::config::Config; + +pub async fn send_email(token: &str, to_address: &str, config: &Config) -> Result<(), Box> { + let message = MessageBuilder::new() + .from((config.email.from_name.as_str(), config.email.from_email.as_str())) + .to(vec![to_address]) + .subject("Trifid - Log In") + .text_body(config.email.template.replace("%TOKEN%", token)); + + let mut password = config.email.password.clone(); + if password == "%PASSWORD%" { + password = std::env::var("TRIFID_EMAIL_PASSWORD")?.clone(); + } + + SmtpClientBuilder::new(config.email.server.as_str(), config.email.port) + .implicit_tls(!config.email.starttls) + .credentials((config.email.username.as_str(), password.as_str())) + .connect().await? + .send(message).await?; + + Ok(()) +} \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 7e9392a..f13e340 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -22,6 +22,7 @@ pub mod schema; pub mod models; #[macro_use] pub mod id; +pub mod email; #[derive(Clone)] diff --git a/trifid-api/src/models.rs b/trifid-api/src/models.rs index f046553..8250c60 100644 --- a/trifid-api/src/models.rs +++ b/trifid-api/src/models.rs @@ -1,9 +1,20 @@ -use diesel::{Insertable, Queryable, Selectable}; +use std::time::SystemTime; +use diesel::{Associations, Identifiable, Insertable, Queryable, Selectable}; -#[derive(Queryable, Selectable, Insertable)] +#[derive(Queryable, Selectable, Insertable, Identifiable, Debug, PartialEq)] #[diesel(table_name = crate::schema::users)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct User { pub id: String, pub email: String +} + +#[derive(Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq)] +#[diesel(belongs_to(User))] +#[diesel(table_name = crate::schema::magic_links)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct MagicLink { + pub id: String, + pub user_id: String, + pub expires: SystemTime } \ No newline at end of file diff --git a/trifid-api/src/routes/v1/signup.rs b/trifid-api/src/routes/v1/signup.rs index 0881d15..5a6d46b 100644 --- a/trifid-api/src/routes/v1/signup.rs +++ b/trifid-api/src/routes/v1/signup.rs @@ -1,11 +1,13 @@ +use std::time::{Duration, SystemTime}; use actix_web::post; use actix_web::web::{Data, Json}; use serde::{Deserialize, Serialize}; use crate::{AppState, randid}; -use crate::models::User; +use crate::models::{MagicLink, User}; use crate::response::JsonAPIResponse; -use crate::schema::users; +use crate::schema::{users, magic_links}; use diesel_async::RunQueryDsl; +use crate::email::send_email; #[derive(Deserialize)] pub struct SignupRequest { @@ -24,12 +26,23 @@ pub struct SignupResponseMetadata {} pub async fn signup_req(req: Json, state: Data) -> JsonAPIResponse { let mut conn = handle_error!(state.pool.get().await); + let user_id = randid!(id "user"); + let new_user = User { - id: randid!(id "user"), + id: user_id.clone(), email: req.email.clone(), }; handle_error!(diesel::insert_into(users::table).values(&new_user).execute(&mut conn).await); + let new_magic_link = MagicLink { + id: randid!(token "ml"), + user_id, + expires: SystemTime::now() + Duration::from_secs(state.config.tokens.magic_link_expiry_seconds) + }; + + handle_error!(diesel::insert_into(magic_links::table).values(&new_magic_link).execute(&mut conn).await); + handle_error!(send_email(&new_magic_link.id, &req.email, &state.config).await); + ok!(SignupResponse { data: None, metadata: SignupResponseMetadata {} }) } \ No newline at end of file diff --git a/trifid-api/src/schema.rs b/trifid-api/src/schema.rs index b9be79a..6e38b6a 100644 --- a/trifid-api/src/schema.rs +++ b/trifid-api/src/schema.rs @@ -1,8 +1,23 @@ // @generated automatically by Diesel CLI. +diesel::table! { + magic_links (id) { + id -> Varchar, + user_id -> Varchar, + expires -> Timestamp, + } +} + diesel::table! { users (id) { id -> Varchar, email -> Varchar, } } + +diesel::joinable!(magic_links -> users (user_id)); + +diesel::allow_tables_to_appear_in_same_query!( + magic_links, + users, +);