diff --git a/.gitignore b/.gitignore
index d45de36..838a2f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
target
.idea
-.env
\ No newline at end of file
+.env
+.DS_Store
diff --git a/.sqlx/query-02d51edf65163f311dfa215da26acb2bc8c02735e2f79782c612d5fe0cf01042.json b/.sqlx/query-02d51edf65163f311dfa215da26acb2bc8c02735e2f79782c612d5fe0cf01042.json
new file mode 100644
index 0000000..a32e4bf
--- /dev/null
+++ b/.sqlx/query-02d51edf65163f311dfa215da26acb2bc8c02735e2f79782c612d5fe0cf01042.json
@@ -0,0 +1,27 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n INSERT INTO users (\n pid,\n username,\n password,\n birthdate,\n timezone,\n email,\n country,\n language,\n marketing_allowed,\n off_device_allowed,\n region,\n gender,\n mii_data,\n verification_code\n ) VALUES (\n $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14\n )\n ",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Int4",
+ "Varchar",
+ "Varchar",
+ "Date",
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Bool",
+ "Bool",
+ "Int4",
+ "Bpchar",
+ "Varchar",
+ "Int4"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "02d51edf65163f311dfa215da26acb2bc8c02735e2f79782c612d5fe0cf01042"
+}
diff --git a/.sqlx/query-08c4a5721982ecd267ebc515b866c61aaef9395170430a58dcd7ac78f8cc75b2.json b/.sqlx/query-08c4a5721982ecd267ebc515b866c61aaef9395170430a58dcd7ac78f8cc75b2.json
new file mode 100644
index 0000000..529604f
--- /dev/null
+++ b/.sqlx/query-08c4a5721982ecd267ebc515b866c61aaef9395170430a58dcd7ac78f8cc75b2.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT EXISTS(SELECT 1 FROM users WHERE username = $1 ) as exists",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "exists",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "08c4a5721982ecd267ebc515b866c61aaef9395170430a58dcd7ac78f8cc75b2"
+}
diff --git a/.sqlx/query-248fc3dbfadb793f1f380486d9c1c95230d4d5a7ee3cb66d9382b7f0522c5e82.json b/.sqlx/query-248fc3dbfadb793f1f380486d9c1c95230d4d5a7ee3cb66d9382b7f0522c5e82.json
new file mode 100644
index 0000000..ec4df8e
--- /dev/null
+++ b/.sqlx/query-248fc3dbfadb793f1f380486d9c1c95230d4d5a7ee3cb66d9382b7f0522c5e82.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "UPDATE users SET email_verified_since = $1 WHERE pid = $2",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Timestamp",
+ "Int4"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "248fc3dbfadb793f1f380486d9c1c95230d4d5a7ee3cb66d9382b7f0522c5e82"
+}
diff --git a/.sqlx/query-3c9b1695f8ae49e4308c048de98c1c262351465b6f102e98912f67442a1f54d9.json b/.sqlx/query-3c9b1695f8ae49e4308c048de98c1c262351465b6f102e98912f67442a1f54d9.json
new file mode 100644
index 0000000..99a26f0
--- /dev/null
+++ b/.sqlx/query-3c9b1695f8ae49e4308c048de98c1c262351465b6f102e98912f67442a1f54d9.json
@@ -0,0 +1,20 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT nextval('pid_counter') as pid",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "pid",
+ "type_info": "Int8"
+ }
+ ],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "3c9b1695f8ae49e4308c048de98c1c262351465b6f102e98912f67442a1f54d9"
+}
diff --git a/.sqlx/query-48710e0b87742cc3fef816b3c95604095f71324011e7093ec37af15da8c158f4.json b/.sqlx/query-48710e0b87742cc3fef816b3c95604095f71324011e7093ec37af15da8c158f4.json
new file mode 100644
index 0000000..f831fdc
--- /dev/null
+++ b/.sqlx/query-48710e0b87742cc3fef816b3c95604095f71324011e7093ec37af15da8c158f4.json
@@ -0,0 +1,66 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "select * from tokens where pid = $1 and token_id = $2 and random = $3",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "pid",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 1,
+ "name": "token_id",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 2,
+ "name": "assigned_ip",
+ "type_info": "Inet"
+ },
+ {
+ "ordinal": 3,
+ "name": "random",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 4,
+ "name": "token_type",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 5,
+ "name": "creation_time",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 6,
+ "name": "expires",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 7,
+ "name": "title_id",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int4",
+ "Int8",
+ "Int4"
+ ]
+ },
+ "nullable": [
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ true
+ ]
+ },
+ "hash": "48710e0b87742cc3fef816b3c95604095f71324011e7093ec37af15da8c158f4"
+}
diff --git a/.sqlx/query-566221b869f293e6c721e2e8bbf3087943e5816a98cdce151302055c58bd1183.json b/.sqlx/query-566221b869f293e6c721e2e8bbf3087943e5816a98cdce151302055c58bd1183.json
new file mode 100644
index 0000000..409a9b7
--- /dev/null
+++ b/.sqlx/query-566221b869f293e6c721e2e8bbf3087943e5816a98cdce151302055c58bd1183.json
@@ -0,0 +1,28 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "select pid, username from users where username = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "pid",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 1,
+ "name": "username",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ false
+ ]
+ },
+ "hash": "566221b869f293e6c721e2e8bbf3087943e5816a98cdce151302055c58bd1183"
+}
diff --git a/.sqlx/query-5bd26d4c9e701bde77ce598fea2ce0b98ea2c7de03e71ac704c3ba047162c0b2.json b/.sqlx/query-5bd26d4c9e701bde77ce598fea2ce0b98ea2c7de03e71ac704c3ba047162c0b2.json
new file mode 100644
index 0000000..56b26e0
--- /dev/null
+++ b/.sqlx/query-5bd26d4c9e701bde77ce598fea2ce0b98ea2c7de03e71ac704c3ba047162c0b2.json
@@ -0,0 +1,28 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "select address, port from nex_servers where game_server_id = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "address",
+ "type_info": "Inet"
+ },
+ {
+ "ordinal": 1,
+ "name": "port",
+ "type_info": "Int4"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ false
+ ]
+ },
+ "hash": "5bd26d4c9e701bde77ce598fea2ce0b98ea2c7de03e71ac704c3ba047162c0b2"
+}
diff --git a/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json b/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json
new file mode 100644
index 0000000..3c3dea3
--- /dev/null
+++ b/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json
@@ -0,0 +1,130 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT * FROM users WHERE username = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "pid",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 1,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 2,
+ "name": "password",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 3,
+ "name": "birthdate",
+ "type_info": "Date"
+ },
+ {
+ "ordinal": 4,
+ "name": "timezone",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 5,
+ "name": "email",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 6,
+ "name": "email_verified_since",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 7,
+ "name": "country",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 8,
+ "name": "language",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 9,
+ "name": "gender",
+ "type_info": "Bpchar"
+ },
+ {
+ "ordinal": 10,
+ "name": "marketing_allowed",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 11,
+ "name": "off_device_allowed",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 12,
+ "name": "region",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 13,
+ "name": "mii_data",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 14,
+ "name": "account_level",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 15,
+ "name": "creation_date",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 16,
+ "name": "updated",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 17,
+ "name": "nex_password",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 18,
+ "name": "verification_code",
+ "type_info": "Int4"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true
+ ]
+ },
+ "hash": "606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100"
+}
diff --git a/.sqlx/query-6c1df0b05553305ba847f571a5859bf11353f28c25e4f81268e9379b5b2cb375.json b/.sqlx/query-6c1df0b05553305ba847f571a5859bf11353f28c25e4f81268e9379b5b2cb375.json
new file mode 100644
index 0000000..f449706
--- /dev/null
+++ b/.sqlx/query-6c1df0b05553305ba847f571a5859bf11353f28c25e4f81268e9379b5b2cb375.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "select nex_password from users where pid = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "nex_password",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int4"
+ ]
+ },
+ "nullable": [
+ false
+ ]
+ },
+ "hash": "6c1df0b05553305ba847f571a5859bf11353f28c25e4f81268e9379b5b2cb375"
+}
diff --git a/.sqlx/query-93960465bbf8f670891d49b95fc52257c0ef596eee2d9a0e6a7d0aad03de4421.json b/.sqlx/query-93960465bbf8f670891d49b95fc52257c0ef596eee2d9a0e6a7d0aad03de4421.json
new file mode 100644
index 0000000..12f43d6
--- /dev/null
+++ b/.sqlx/query-93960465bbf8f670891d49b95fc52257c0ef596eee2d9a0e6a7d0aad03de4421.json
@@ -0,0 +1,130 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT * FROM users WHERE pid = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "pid",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 1,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 2,
+ "name": "password",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 3,
+ "name": "birthdate",
+ "type_info": "Date"
+ },
+ {
+ "ordinal": 4,
+ "name": "timezone",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 5,
+ "name": "email",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 6,
+ "name": "email_verified_since",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 7,
+ "name": "country",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 8,
+ "name": "language",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 9,
+ "name": "gender",
+ "type_info": "Bpchar"
+ },
+ {
+ "ordinal": 10,
+ "name": "marketing_allowed",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 11,
+ "name": "off_device_allowed",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 12,
+ "name": "region",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 13,
+ "name": "mii_data",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 14,
+ "name": "account_level",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 15,
+ "name": "creation_date",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 16,
+ "name": "updated",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 17,
+ "name": "nex_password",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 18,
+ "name": "verification_code",
+ "type_info": "Int4"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int4"
+ ]
+ },
+ "nullable": [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true
+ ]
+ },
+ "hash": "93960465bbf8f670891d49b95fc52257c0ef596eee2d9a0e6a7d0aad03de4421"
+}
diff --git a/.sqlx/query-9d3cee43a86cead9a6d078abc1266fc2a97ac6e25a9733d1d20faf555c67abe1.json b/.sqlx/query-9d3cee43a86cead9a6d078abc1266fc2a97ac6e25a9733d1d20faf555c67abe1.json
new file mode 100644
index 0000000..2da8161
--- /dev/null
+++ b/.sqlx/query-9d3cee43a86cead9a6d078abc1266fc2a97ac6e25a9733d1d20faf555c67abe1.json
@@ -0,0 +1,66 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "select * from tokens where pid = $1 and token_id = $2 and random =$3",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "pid",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 1,
+ "name": "token_id",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 2,
+ "name": "assigned_ip",
+ "type_info": "Inet"
+ },
+ {
+ "ordinal": 3,
+ "name": "random",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 4,
+ "name": "token_type",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 5,
+ "name": "creation_time",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 6,
+ "name": "expires",
+ "type_info": "Timestamp"
+ },
+ {
+ "ordinal": 7,
+ "name": "title_id",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int4",
+ "Int8",
+ "Int4"
+ ]
+ },
+ "nullable": [
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ true
+ ]
+ },
+ "hash": "9d3cee43a86cead9a6d078abc1266fc2a97ac6e25a9733d1d20faf555c67abe1"
+}
diff --git a/.sqlx/query-b16ba4b6c1b7d1c207e94515268c3bbd90d4bcf92d20203cba5afb2664f4bb8a.json b/.sqlx/query-b16ba4b6c1b7d1c207e94515268c3bbd90d4bcf92d20203cba5afb2664f4bb8a.json
new file mode 100644
index 0000000..18d1e88
--- /dev/null
+++ b/.sqlx/query-b16ba4b6c1b7d1c207e94515268c3bbd90d4bcf92d20203cba5afb2664f4bb8a.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT verification_code FROM users WHERE pid = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "verification_code",
+ "type_info": "Int4"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int4"
+ ]
+ },
+ "nullable": [
+ true
+ ]
+ },
+ "hash": "b16ba4b6c1b7d1c207e94515268c3bbd90d4bcf92d20203cba5afb2664f4bb8a"
+}
diff --git a/.sqlx/query-c8d50662530cac49c4261fb321cd15f9e4bafdfca12d2130a873d44a88dd435b.json b/.sqlx/query-c8d50662530cac49c4261fb321cd15f9e4bafdfca12d2130a873d44a88dd435b.json
new file mode 100644
index 0000000..da6f233
--- /dev/null
+++ b/.sqlx/query-c8d50662530cac49c4261fb321cd15f9e4bafdfca12d2130a873d44a88dd435b.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT EXISTS(select 1 from users where pid = $1)",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "exists",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int4"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "c8d50662530cac49c4261fb321cd15f9e4bafdfca12d2130a873d44a88dd435b"
+}
diff --git a/.sqlx/query-e5a2f7f28c3d7b9524d3dce48a9e47d6180ff634ebf59f3a1efd92b797170ac2.json b/.sqlx/query-e5a2f7f28c3d7b9524d3dce48a9e47d6180ff634ebf59f3a1efd92b797170ac2.json
new file mode 100644
index 0000000..7c1cae0
--- /dev/null
+++ b/.sqlx/query-e5a2f7f28c3d7b9524d3dce48a9e47d6180ff634ebf59f3a1efd92b797170ac2.json
@@ -0,0 +1,30 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "insert into tokens (token_type, pid, title_id)\n values ($1, $2, $3) returning token_id, random",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "token_id",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 1,
+ "name": "random",
+ "type_info": "Int4"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int4",
+ "Int4",
+ "Varchar"
+ ]
+ },
+ "nullable": [
+ false,
+ false
+ ]
+ },
+ "hash": "e5a2f7f28c3d7b9524d3dce48a9e47d6180ff634ebf59f3a1efd92b797170ac2"
+}
diff --git a/.sqlx/query-eafa97669e8ec04f1dc0a8e05d417f12ffeeb26a5eabca323abfed0e1d7bcbea.json b/.sqlx/query-eafa97669e8ec04f1dc0a8e05d417f12ffeeb26a5eabca323abfed0e1d7bcbea.json
new file mode 100644
index 0000000..fe69d0a
--- /dev/null
+++ b/.sqlx/query-eafa97669e8ec04f1dc0a8e05d417f12ffeeb26a5eabca323abfed0e1d7bcbea.json
@@ -0,0 +1,28 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "select pid, username from users where pid = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "pid",
+ "type_info": "Int4"
+ },
+ {
+ "ordinal": 1,
+ "name": "username",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int4"
+ ]
+ },
+ "nullable": [
+ false,
+ false
+ ]
+ },
+ "hash": "eafa97669e8ec04f1dc0a8e05d417f12ffeeb26a5eabca323abfed0e1d7bcbea"
+}
diff --git a/Cargo.lock b/Cargo.lock
index 9f3a89b..b4679e1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -20,6 +20,7 @@ dependencies = [
"hmac",
"juniper",
"juniper_rocket",
+ "lettre",
"log",
"md-5",
"mii",
@@ -27,6 +28,7 @@ dependencies = [
"once_cell",
"prost",
"quick-xml",
+ "rand",
"rocket",
"serde",
"serde_json",
@@ -64,6 +66,18 @@ dependencies = [
"cpufeatures",
]
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -470,6 +484,16 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "chumsky"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
+dependencies = [
+ "hashbrown 0.14.5",
+ "stacker",
+]
+
[[package]]
name = "cipher"
version = "0.4.4"
@@ -713,6 +737,22 @@ dependencies = [
"serde",
]
+[[package]]
+name = "email-encoding"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6"
+dependencies = [
+ "base64",
+ "memchr",
+]
+
+[[package]]
+name = "email_address"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+
[[package]]
name = "encoding_rs"
version = "0.8.35"
@@ -1072,6 +1112,10 @@ name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+]
[[package]]
name = "hashbrown"
@@ -1144,6 +1188,17 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "hostname"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "windows-link",
+]
+
[[package]]
name = "http"
version = "0.2.12"
@@ -1639,6 +1694,31 @@ dependencies = [
"spin",
]
+[[package]]
+name = "lettre"
+version = "0.11.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "759bc2b8eabb6a30b235d6f716f7f36479f4b38cbe65b8747aefee51f89e8437"
+dependencies = [
+ "base64",
+ "chumsky",
+ "email-encoding",
+ "email_address",
+ "fastrand",
+ "futures-util",
+ "hostname",
+ "httpdate",
+ "idna",
+ "mime",
+ "native-tls",
+ "nom",
+ "percent-encoding",
+ "quoted_printable",
+ "socket2",
+ "tokio",
+ "url",
+]
+
[[package]]
name = "libc"
version = "0.2.170"
@@ -1861,6 +1941,15 @@ dependencies = [
"tempfile",
]
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -2256,6 +2345,15 @@ dependencies = [
"prost",
]
+[[package]]
+name = "psm"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "quick-xml"
version = "0.37.2"
@@ -2275,6 +2373,12 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "quoted_printable"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
+
[[package]]
name = "rand"
version = "0.8.5"
@@ -3029,6 +3133,19 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+[[package]]
+name = "stacker"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "psm",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "state"
version = "0.6.0"
@@ -3781,6 +3898,12 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "windows-link"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
+
[[package]]
name = "windows-registry"
version = "0.2.0"
diff --git a/Cargo.toml b/Cargo.toml
index 59de8b7..7252c2d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,8 +44,10 @@ juniper_rocket = "0.9.0"
tonic = "0.12.3"
prost = "0.13.4"
+lettre = "0.11.15"
+rand = "0.8.5"
[build-dependencies]
-tonic-build = "0.12.3"
\ No newline at end of file
+tonic-build = "0.12.3"
diff --git a/res/email/confirmationTemplate.html b/res/email/confirmationTemplate.html
new file mode 100644
index 0000000..5387c5b
--- /dev/null
+++ b/res/email/confirmationTemplate.html
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+ Hello {{username}}! Your Splatfestival Network ID activation is almost complete. Please click the link in this email to confirm your e-mail address and complete the activation process.
+
+
+
+
+
+
+
+
+ | |
+
+
+
+ | |
+
+
+
+
+
+
+
+
+
+ |
+
+
+ | |
+
+
+
+
+
+ | |
+
+
+
+ | |
+
+
+ |
+ Hello {{username}}!
+ |
+
+
+ | |
+
+
+ |
+ Your Splatfestival Network ID activation is almost complete.
+ |
+
+
+ | |
+
+
+
+
+
+
+
+
+
+ | |
+
+
+ |
+ Enter the following 6-digit code on your console:
+ |
+
+
+ | |
+
+
+ |
+ {{confirmation-code}}
+ |
+
+
+ | |
+
+
+ |
+ We hope you have fun using our services!
+ |
+
+
+ | |
+
+
+ |
+ The SPFN team
+ |
+
+
+ | |
+
+
+ |
+ |
+
+
+ |
+
+
+ | |
+
+
+ |
+ Note: this email message was auto-generated, please do not respond. For further assistance, please join our Discord server or make a post on our Forum.
+ |
+
+
+ | |
+
+
+ |
+
+
+ |
+ |
+
+
+ |
+
+
+ |
+
+
+
+
\ No newline at end of file
diff --git a/src/account/account.rs b/src/account/account.rs
index 6ceead2..e83a1be 100644
--- a/src/account/account.rs
+++ b/src/account/account.rs
@@ -54,7 +54,8 @@ pub struct User {
pub mii_data: String,
pub creation_date: NaiveDateTime,
pub updated: NaiveDateTime,
- pub nex_password: String
+ pub nex_password: String,
+ pub verification_code: Option
}
fn generate_nintendo_hash(pid: i32, text_password: &str) -> String{
diff --git a/src/email.rs b/src/email.rs
new file mode 100644
index 0000000..255fe49
--- /dev/null
+++ b/src/email.rs
@@ -0,0 +1,38 @@
+use lettre::transport::smtp::authentication::Credentials;
+use lettre::{Message, SmtpTransport, Transport};
+use std::env;
+use std::fs;
+
+pub async fn send_verification_email(to: &str, code: i32, username: &str) -> Result<(), String> {
+ let smtp_user = env::var("SMTP_USER").map_err(|_| "SMTP_USER not set".to_string())?;
+ let smtp_pass = env::var("SMTP_PASS").map_err(|_| "SMTP_PASS not set".to_string())?;
+ let smtp_server = env::var("SMTP_SERVER").map_err(|_| "SMTP_SERVER not set".to_string())?;
+
+ // Load template
+ let template = fs::read_to_string("res/email/confirmationTemplate.html")
+ .map_err(|e| format!("Failed to read email template: {}", e))?;
+
+ // Replace placeholders
+ let body = template
+ .replace("{{username}}", username)
+ .replace("{{confirmation-code}}", &format!("{:06}", code));
+
+ let email = Message::builder()
+ .from(smtp_user.parse().unwrap())
+ .to(to.parse().unwrap())
+ .subject("Your Verification Code")
+ .header(lettre::message::header::ContentType::TEXT_HTML)
+ .body(body)
+ .map_err(|e| e.to_string())?;
+
+ let creds = Credentials::new(smtp_user, smtp_pass);
+
+ let mailer = SmtpTransport::relay(&smtp_server)
+ .map_err(|e| e.to_string())?
+ .credentials(creds)
+ .build();
+
+ mailer.send(&email).map_err(|e| e.to_string())?;
+
+ Ok(())
+}
diff --git a/src/main.rs b/src/main.rs
index 26a766a..071c5e1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -26,6 +26,7 @@ mod data_wrapper;
#[deprecated]
mod grpc;
mod graphql;
+mod email;
type Pool = sqlx::Pool;
@@ -109,7 +110,8 @@ async fn launch() -> _ {
nnid::agreements::get_agreement,
nnid::timezones::get_timezone,
nnid::person_exists::person_exists,
- nnid::email::validate,
+ nnid::support::validate,
+ nnid::support::verify_email,
nnid::people::create_account,
nnid::people::get_own_profile,
nnid::oauth::generate_token::generate_token,
diff --git a/src/nnid/email.rs b/src/nnid/email.rs
deleted file mode 100644
index a229bd6..0000000
--- a/src/nnid/email.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use rocket::{post, FromForm};
-use rocket::form::Form;
-
-#[derive(FromForm)]
-struct ValidateEmailInput{
- email: String,
-}
-#[post("/v1/api/support/validate/email", data="")]
-pub fn validate(data: Form){
-
-}
\ No newline at end of file
diff --git a/src/nnid/mod.rs b/src/nnid/mod.rs
index e0c1f2f..a86c6f7 100644
--- a/src/nnid/mod.rs
+++ b/src/nnid/mod.rs
@@ -2,9 +2,9 @@ pub mod devices;
pub mod agreements;
pub mod timezones;
pub mod person_exists;
-pub mod email;
pub mod oauth;
mod pid_distribution;
pub mod people;
pub mod provider;
pub mod mapped_ids;
+pub mod support;
diff --git a/src/nnid/people.rs b/src/nnid/people.rs
index 5450d77..3ae5895 100644
--- a/src/nnid/people.rs
+++ b/src/nnid/people.rs
@@ -18,6 +18,8 @@ use crate::nnid::pid_distribution::next_pid;
use crate::nnid::timezones::{OFFSET_FROM_TIMEZONE, ZONE_TO_TIMEZONES};
use crate::Pool;
use crate::xml::{Xml, YesNoVal};
+use crate::email::send_verification_email;
+use rand::Rng;
static S3_URL_STRING: Lazy> = Lazy::new(||
env::var("S3_URL").expect("S3_URL not specified").into_boxed_str()
@@ -114,6 +116,8 @@ pub async fn create_account(database: &State, data: Xml, data: Xml, data: Xml){
+ if let Err(e) = send_verification_email(&*data.email, 123456, "Andrea Test Username").await {
+ println!("Failed to send verification email: {e}");
+ }
+}
+
+#[get("/v1/api/support/email_confirmation//")]
+pub async fn verify_email(database: &State, pid: i32, code: i32) -> Result<(), Errors<'static>> {
+ let db = database.inner();
+
+ let result = sqlx::query!(
+ "SELECT verification_code FROM users WHERE pid = $1",
+ pid
+ )
+ .fetch_optional(db)
+ .await;
+
+ let Ok(Some(record)) = result else {
+ return Err(BAD_CODE_ERROR);
+ };
+
+ if let Some(stored_code) = record.verification_code {
+ if stored_code == code {
+ // Set email_verified_since to NOW
+ let now = Utc::now().naive_utc();
+ let update_result = sqlx::query!(
+ "UPDATE users SET email_verified_since = $1 WHERE pid = $2",
+ now,
+ pid
+ )
+ .execute(db)
+ .await;
+
+ if update_result.is_err() {
+ return Err(BAD_CODE_ERROR); // fallback in case the update fails
+ }
+
+ return Ok(()); // Success
+ }
+ }
+
+ Err(BAD_CODE_ERROR)
+}