diff --git a/.gitignore b/.gitignore
index 32148b5..7f0cc7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,8 +19,8 @@ client/.gradle
client/build
server/.idea
-#server/.env
-#auth/.env
+server/.env
+auth/.env
*.db
*.db-shm
diff --git a/auth/.env b/auth/.env
deleted file mode 100644
index 2e80e23..0000000
--- a/auth/.env
+++ /dev/null
@@ -1,12 +0,0 @@
-DATABASE_URL=sqlite:auth.db
-PORT=5052
-GOOGLE_CLIENT_ID=972646764847-j9o7v3th4bf1ln18hitgolqp03s6doo4.apps.googleusercontent.com
-GOOGLE_PRIVATE_KEY=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
-DOMAIN=realmchat.com
-
-SERVER_MAIL_PORT=587
-SERVER_MAIL_ADDRESS=mail.abunchofknowitalls.com
-SERVER_MAIL_NAME="Realm Chat Support"
-SERVER_MAIL_FROM_ADDRESS=realmchat@abunchofknowitalls.com
-SERVER_MAIL_USERNAME=realmchat@abunchofknowitalls.com
-SERVER_MAIL_PASSWORD=9BB*bD9RnSKMAf
\ No newline at end of file
diff --git a/auth/Cargo.toml b/auth/Cargo.toml
index bde448f..cdf0328 100644
--- a/auth/Cargo.toml
+++ b/auth/Cargo.toml
@@ -17,6 +17,6 @@ sqlx = { version = "0.8.2", features = [ "runtime-tokio", "tls-rustls", "sqlite"
sha3 = "0.10.8"
hex = "0.4.3"
rand = "0.8.5"
-mail-send = "0.4.9"
+lettre = "0.11.9"
regex = "1.10.5"
realm_shared = { path = "../shared" }
diff --git a/auth/example.env b/auth/example.env
new file mode 100644
index 0000000..1945256
--- /dev/null
+++ b/auth/example.env
@@ -0,0 +1,12 @@
+DATABASE_URL=sqlite:auth.db
+PORT=5052
+GOOGLE_CLIENT_ID=
+GOOGLE_PRIVATE_KEY=
+DOMAIN=
+
+SERVER_MAIL_PORT=
+SERVER_MAIL_ADDRESS=
+SERVER_MAIL_NAME=
+SERVER_MAIL_FROM_ADDRESS=
+SERVER_MAIL_USERNAME=
+SERVER_MAIL_PASSWORD=
\ No newline at end of file
diff --git a/login_email.html b/auth/login_email.html
similarity index 73%
rename from login_email.html
rename to auth/login_email.html
index d533898..766d755 100644
--- a/login_email.html
+++ b/auth/login_email.html
@@ -2,11 +2,11 @@
Confirm your email address
Your 6 digit code is below – enter it into Realm and you will be signed in
-{}
+{$LOGIN_CODE}
If you didn't request this email, there's nothing to worry about it– you can safely ignore it.
Realm
- Need help? Email realm-support@abunchofknowtiwalls.com
+ Need help? Email realm@abunchofknowtiwalls.com
© 2024 Realm, Inc.
\ No newline at end of file
diff --git a/auth/migrations/20240725215330_create_everything.sql b/auth/migrations/20240725215330_create_everything.sql
index 0164f6b..e16c2db 100644
--- a/auth/migrations/20240725215330_create_everything.sql
+++ b/auth/migrations/20240725215330_create_everything.sql
@@ -7,9 +7,5 @@ CREATE TABLE IF NOT EXISTS user (
avatar TEXT NOT NULL,
servers TEXT NOT NULL,
login_code INT(6),
- tokens TEXT,
- google_oauth VARCHAR(255),
- apple_oauth VARCHAR(255),
- github_oauth VARCHAR(255),
- discord_oauth VARCHAR(255)
+ tokens TEXT
);
\ No newline at end of file
diff --git a/auth/src/server.rs b/auth/src/server.rs
index c0bb4ce..e53a70d 100644
--- a/auth/src/server.rs
+++ b/auth/src/server.rs
@@ -2,17 +2,17 @@ use std::env;
use std::net::SocketAddr;
use chrono::Utc;
-use mail_send::{Credentials, SmtpClientBuilder};
-use mail_send::mail_builder::MessageBuilder;
+use lettre::{Message, SmtpTransport, Transport};
+use lettre::message::header::ContentType;
+use lettre::message::Mailbox;
+use lettre::transport::smtp::authentication::Credentials;
use rand::Rng;
use regex::Regex;
use sha3::{Digest, Sha3_256};
use sha3::digest::Update;
use sqlx::{Pool, query, Sqlite};
-use sqlx::sqlite::{SqliteQueryResult, SqliteRow};
use tarpc::context::Context;
use tracing::*;
-use tracing::log::__private_api::log;
use crate::types::{AuthEmail, AuthUser, RealmAuth};
use realm_shared::types::ErrorCode;
use realm_shared::types::ErrorCode::*;
@@ -23,7 +23,6 @@ pub struct RealmAuthServer {
pub db_pool: Pool,
pub auth_email: AuthEmail,
pub template_html: String,
- pub template_txt: String,
pub domain: String,
}
@@ -34,27 +33,21 @@ impl RealmAuthServer {
db_pool,
auth_email,
template_html: std::fs::read_to_string("./login_email.html").expect("A login_email.html file is needed"),
- template_txt: std::fs::read_to_string("./login_email.txt").expect("A login_email.txt file is needed"),
domain: env::var("DOMAIN").expect("DOMAIN must be set"),
}
}
- fn gen_login_code(&self) -> u16 {
+ fn gen_login_code(&self) -> u32 {
let mut rng = rand::thread_rng();
- let mut login_code: u16 = 0;
- for n in 1..=6 {
- if n == 1 {
- login_code += rng.gen_range(1..=9);
- }
- login_code += rng.gen_range(0..=9) * (10^n);
- }
+ let first_digit: u32 = rng.gen_range(1..10);
+ let remaining_digits: u32 = rng.gen_range(0..100_000);
- login_code
+ first_digit * 100_000 + remaining_digits
}
async fn is_username_taken(&self, username: &str) -> Result {
- let result = query!("SELECT NOT EXISTS (SELECT 1 FROM user WHERE username = ?) AS does_exist", username).fetch_one(&self.db_pool).await;
+ let result = query!("SELECT EXISTS (SELECT 1 FROM user WHERE username = ?) AS does_exist", username).fetch_one(&self.db_pool).await;
match result {
Ok(row) => Ok(row.does_exist != 0),
@@ -63,7 +56,7 @@ impl RealmAuthServer {
}
async fn is_email_taken(&self, email: &str) -> Result {
- let result = query!("SELECT NOT EXISTS (SELECT 1 FROM user WHERE email = ?) AS does_exist", email).fetch_one(&self.db_pool).await;
+ let result = query!("SELECT EXISTS (SELECT 1 FROM user WHERE email = ?) AS does_exist", email).fetch_one(&self.db_pool).await;
match result {
Ok(row) => Ok(row.does_exist != 0),
@@ -81,57 +74,54 @@ impl RealmAuthServer {
for i in 0..tokens.len() {
if tokens.get(i).unwrap() == &token {
- return Ok(true)
+ return Ok(true);
}
}
Ok(false)
- },
+ }
Err(_) => Err(InvalidUsername),
}
}
- async fn send_login_message(&self, username: &str, email: &str, login_code: u16) -> Result<(), ErrorCode> {
- let message = MessageBuilder::new()
- .from((self.auth_email.auth_name.clone(), self.auth_email.auth_username.clone()))
- .to(vec![
- (username, email),
- ])
- .subject(format!("Realm confirmation code: {}", &login_code))
- .html_body(self.template_html.replace("{}", &login_code.to_string()))
- .text_body(self.template_txt.replace("{}", &login_code.to_string()));
+ fn send_login_message(&self, username: &str, email: &str, login_code: u32) { // -> Result<(), ErrorCode>
+ let auth_email = self.auth_email.clone();
+ let template_html = self.template_html.clone();
+ let username = username.to_string();
+ let email = email.to_string();
- let result = SmtpClientBuilder::new(&self.auth_email.server_address, self.auth_email.server_port)
- .implicit_tls(false)
- .credentials(Credentials::new(&self.auth_email.auth_username, &self.auth_email.auth_password))
- .connect()
- .await;
+ tokio::spawn(async move {
+ let email = Message::builder()
+ .from(Mailbox::new(Some(auth_email.auth_name.clone()), auth_email.auth_username.clone().parse().unwrap()))
+ .to(Mailbox::new(Some(username.clone()), email.clone().parse().unwrap()))
+ .subject(format!("Realm confirmation code: {}", &login_code))
+ .header(ContentType::TEXT_HTML)
+ .body(template_html.replace("{$LOGIN_CODE}", &login_code.to_string()))
+ .unwrap();
- match result {
- Ok(mut client) => {
- let result = client.send(message).await;
- match result {
- Ok(_) => {
- Ok(())
- }
- Err(_) => {
- Err(UnableToSendMail)
- }
- }
+ let creds = Credentials::new(auth_email.auth_username, auth_email.auth_password);
+
+ // Open a remote connection to gmail
+ let mailer = SmtpTransport::relay(&auth_email.server_address)
+ .unwrap()
+ .credentials(creds)
+ .build();
+
+ // Send the email
+ match mailer.send(&email) {
+ Ok(_) => info!("Email sent successfully!"),
+ Err(e) => error!("Could not send email: {e:?}"),
}
- Err(_) => {
- Err(UnableToConnectToMail)
- }
- }
+ });
}
- async fn is_login_code_valid(&self, username: &str, login_code: u16) -> Result {
+ async fn is_login_code_valid(&self, username: &str, login_code: u32) -> Result {
let result = query!("SELECT login_code FROM user WHERE username = ?;", username).fetch_one(&self.db_pool).await;
match result {
Ok(row) => {
- if row.login_code.unwrap() as u16 != login_code {
- return Ok(false)
+ if row.login_code.unwrap() as u32 != login_code {
+ return Ok(false);
}
Ok(true)
}
@@ -141,19 +131,19 @@ impl RealmAuthServer {
fn is_username_valid(&self, username: &str) -> bool {
if !username.starts_with('@') || !username.contains(':') {
- return false
+ return false;
}
let name = &username[1..username.find(':').unwrap()];
- let domain = &username[username.find(':').unwrap()+1..];
+ let domain = &username[username.find(':').unwrap() + 1..];
let re = Regex::new(r"^[a-zA-Z0-9]+$").unwrap();
if !re.is_match(name) {
- return false
+ return false;
}
if !domain.eq(&self.domain) {
- return false
+ return false;
}
true
@@ -188,12 +178,12 @@ impl RealmAuth for RealmAuthServer {
for token in tokens {
let hash = Sha3_256::new().chain(format!("{}{}{}{}", token, server_id, domain, tarpc_port)).finalize();
if hex::encode(hash) == server_token {
- return true
+ return true;
}
}
false
- },
+ }
Err(_) => false,
}
}
@@ -202,26 +192,29 @@ impl RealmAuth for RealmAuthServer {
info!("API Request: create_account_flow( username -> {}, email -> {} )", username, email);
if !self.is_username_valid(&username) {
- return Err(InvalidUsername)
+ return Err(InvalidUsername);
}
if self.is_username_taken(&username).await? {
- return Err(UsernameTaken)
+ return Err(UsernameTaken);
}
if self.is_email_taken(&email).await? {
- return Err(EmailTaken)
+ return Err(EmailTaken);
}
let code = self.gen_login_code();
- self.send_login_message(&username, &email, code).await?;
+ self.send_login_message(&username, &email, code);
- let result = query!("INSERT INTO user (username, email, avatar, login_code, tokens) VALUES (?, ?, '', ?, '')", username, email, code)
+ let result = query!("INSERT INTO user (username, email, new_email, avatar, servers, login_code, tokens) VALUES (?, ?, '', '', '', ?, '')", username, email, code)
.execute(&self.db_pool).await;
match result {
Ok(_) => Ok(()),
- Err(_) => Err(Error)
+ Err(e) => {
+ error!("Error creating account: {e:?}");
+ Err(Error)
+ }
}
}
@@ -229,7 +222,7 @@ impl RealmAuth for RealmAuthServer {
info!("API Request: create_login_flow( username -> {}, email -> {} )", username.clone().unwrap_or("None".to_string()), email.clone().unwrap_or("None".to_string()));
if username.is_none() && email.is_none() {
- return Err(Error)
+ return Err(Error);
}
if username.is_none() {
@@ -264,17 +257,20 @@ impl RealmAuth for RealmAuthServer {
.execute(&self.db_pool).await;
match result {
- Ok(_) => self.send_login_message(&username.unwrap(), &email.unwrap(), code).await,
+ Ok(_) => {
+ self.send_login_message(&username.unwrap(), &email.unwrap(), code);
+ Ok(())
+ }
Err(_) => Err(InvalidUsername)
}
}
- async fn finish_login_flow(self, _: Context, username: String, login_code: u16) -> Result {
+ async fn finish_login_flow(self, _: Context, username: String, login_code: u32) -> Result {
info!("API Request: finish_login_flow( username -> {}, login_code -> {} )", username, login_code);
if !self.is_login_code_valid(&username, login_code).await? {
error!("Unauthorized request made for finish_login_flow() (bad login code)! username -> {}, login_code -> {}", username, login_code);
- return Err(InvalidLoginCode)
+ return Err(InvalidLoginCode);
}
self.reset_login_code(&username).await?;
@@ -307,11 +303,11 @@ impl RealmAuth for RealmAuthServer {
info!("API Request: change_email_flow( username -> {}, new_email -> {}, token -> {} )", username, new_email, token);
if !self.is_authorized(&username, &token).await? {
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
if self.is_email_taken(&new_email).await? {
- return Err(EmailTaken)
+ return Err(EmailTaken);
}
let _ = query!("UPDATE user SET new_email = ? WHERE username = ?", new_email, username).execute(&self.db_pool).await.unwrap();
@@ -322,27 +318,30 @@ impl RealmAuth for RealmAuthServer {
.execute(&self.db_pool).await;
match result {
- Ok(_) => self.send_login_message(&username, &new_email, code).await,
+ Ok(_) => {
+ self.send_login_message(&username, &new_email, code);
+ Ok(())
+ }
Err(_) => Err(InvalidUsername)
}
}
- async fn finish_change_email_flow(self, _: Context, username: String, new_email: String, token: String, login_code: u16) -> Result<(), ErrorCode> {
+ async fn finish_change_email_flow(self, _: Context, username: String, new_email: String, token: String, login_code: u32) -> Result<(), ErrorCode> {
info!("API Request: finish_change_email_flow( username -> {}, new_email -> {}, token -> {}, login_code -> {} )", username, new_email, token, login_code);
if !self.is_authorized(&username, &token).await? {
error!("Unauthorized request made for finish_change_email_flow() (bad token)! username -> {}, token -> {}", username, token);
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
if self.is_email_taken(&new_email).await? {
error!("Email already taken for email change (but its the end of the flow?!) username -> {}, new_email -> {}", username, new_email);
- return Err(EmailTaken)
+ return Err(EmailTaken);
}
if !self.is_login_code_valid(&username, login_code).await? {
error!("Unauthorized request made for finish_change_email_flow() (bad login code)! username -> {}, login_code -> {}", username, login_code);
- return Err(InvalidLoginCode)
+ return Err(InvalidLoginCode);
}
let _ = query!("UPDATE user SET new_email = NULL WHERE username = ?", username).execute(&self.db_pool).await;
@@ -354,37 +353,12 @@ impl RealmAuth for RealmAuthServer {
Ok(())
}
- // async fn change_username(self, _: Context, username: String, token: String, new_username: String) -> Result<(), ErrorCode> {
- // info!("API Request: change_username( username -> {}, token -> {}, new_username -> {} )", username, token, new_username);
- //
- // if !self.is_authorized(&username, &token).await? {
- // error!("Unauthorized request made for change_username()! username -> {}, token -> {}", username, token);
- // return Err(Unauthorized)
- // }
- //
- // if !self.is_username_valid(&new_username) {
- // error!("Malformed username in request for change_username()! new_username -> {}", new_username);
- // return Err(InvalidUsername)
- // }
- //
- // if self.is_username_taken(&new_username).await? {
- // error!("Username is taken for change_username()! new_username -> {}", new_username);
- // return Err(UsernameTaken)
- // }
- //
- // let result = query!("UPDATE user SET username = ? WHERE username = ?", new_username, username).execute(&self.db_pool).await;
- // match result {
- // Ok(_) => Ok(()),
- // Err(_) => Err(Error)
- // }
- // }
-
async fn change_avatar(self, _: Context, username: String, token: String, new_avatar: String) -> Result<(), ErrorCode> {
info!("API Request: change_avatar( username -> {}, token -> {}, new_avatar -> {} )", username, token, new_avatar);
if !self.is_authorized(&username, &token).await? {
error!("Unauthorized request made for change_avatar()! username -> {}, token -> {}", username, token);
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
let result = query!("UPDATE user SET avatar = ? WHERE username = ?", new_avatar, username).execute(&self.db_pool).await;
@@ -399,7 +373,7 @@ impl RealmAuth for RealmAuthServer {
if !self.is_authorized(&username, &token).await? {
error!("Unauthorized request made for get_all_data()! username -> {}, token -> {}", username, token);
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
let result = query!(r"SELECT * FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await;
@@ -413,10 +387,6 @@ impl RealmAuth for RealmAuthServer {
servers: row.servers,
login_code: None,
bigtoken: row.tokens,
- google_oauth: row.google_oauth,
- apple_oauth: row.apple_oauth,
- github_oauth: row.github_oauth,
- discord_oauth: row.discord_oauth,
})
}
Err(_) => {
@@ -458,11 +428,11 @@ impl RealmAuth for RealmAuthServer {
error!("Unauthorized request made for sign_out()! username -> {}, token -> {}", username, token);
Err(Unauthorized)
- },
+ }
Err(_) => {
error!("Invalid username in request for get_avatar_for_user()! username -> {}", username);
Err(InvalidUsername)
- },
+ }
}
}
@@ -470,7 +440,7 @@ impl RealmAuth for RealmAuthServer {
info!("API Request: delete_account_flow( username -> {}, token -> {} )", username, token);
if !self.is_authorized(&username, &token).await? {
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
let email = match query!("SELECT email FROM user WHERE username = ?;", username).fetch_one(&self.db_pool).await {
@@ -484,20 +454,23 @@ impl RealmAuth for RealmAuthServer {
.execute(&self.db_pool).await;
match result {
- Ok(_) => self.send_login_message(&username, &email, code).await,
+ Ok(_) => {
+ self.send_login_message(&username, &email, code);
+ Ok(())
+ }
Err(_) => Err(InvalidUsername)
}
}
- async fn finish_delete_account_flow(self, _: Context, username: String, token: String, login_code: u16) -> Result<(), ErrorCode> {
+ async fn finish_delete_account_flow(self, _: Context, username: String, token: String, login_code: u32) -> Result<(), ErrorCode> {
info!("API Request: finish_delete_account_flow( username -> {}, token -> {}, login_code -> {} )", username, token, login_code);
if !self.is_authorized(&username, &token).await? {
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
if !self.is_login_code_valid(&username, login_code).await? {
- return Err(InvalidLoginCode)
+ return Err(InvalidLoginCode);
}
self.reset_login_code(&username).await?;
@@ -511,7 +484,7 @@ impl RealmAuth for RealmAuthServer {
async fn add_server(self, _: Context, username: String, token: String, domain: String) -> Result<(), ErrorCode> {
if !self.is_authorized(&username, &token).await? {
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
let result = query!("SELECT servers FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await;
@@ -520,7 +493,7 @@ impl RealmAuth for RealmAuthServer {
let mut vec_servers = row.servers.split('|').collect::>();
for server in &vec_servers {
if server.eq(&domain) {
- return Err(AlreadyJoinedServer)
+ return Err(AlreadyJoinedServer);
}
}
@@ -539,7 +512,7 @@ impl RealmAuth for RealmAuthServer {
async fn remove_server(self, _: Context, username: String, token: String, domain: String) -> Result<(), ErrorCode> {
if !self.is_authorized(&username, &token).await? {
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
let result = query!("SELECT servers FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await;
@@ -555,10 +528,10 @@ impl RealmAuth for RealmAuthServer {
return match result {
Ok(_) => Ok(()),
Err(_) => Err(Error)
- }
+ };
}
}
-
+
Err(NotInServer)
}
Err(_) => Err(Error)
@@ -567,7 +540,7 @@ impl RealmAuth for RealmAuthServer {
async fn get_joined_servers(self, _: Context, username: String, token: String) -> Result, ErrorCode> {
if !self.is_authorized(&username, &token).await? {
- return Err(Unauthorized)
+ return Err(Unauthorized);
}
let result = query!("SELECT servers FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await;
@@ -578,7 +551,7 @@ impl RealmAuth for RealmAuthServer {
for server in vec_servers {
servers.push(server.to_string())
}
-
+
Ok(servers)
}
Err(_) => Err(Error)
diff --git a/auth/src/types.rs b/auth/src/types.rs
index ea53da6..0af42fa 100644
--- a/auth/src/types.rs
+++ b/auth/src/types.rs
@@ -7,17 +7,17 @@ pub trait RealmAuth {
async fn server_token_validation(server_token: String, username: String, server_id: String, domain: String, tarpc_port: u16) -> bool;
async fn create_account_flow(username: String, email: String) -> Result<(), ErrorCode>; //NOTE: Still require sign in flow
async fn create_login_flow(username: Option, email: Option) -> Result<(), ErrorCode>;
- async fn finish_login_flow(username: String, login_code: u16) -> Result;
+ async fn finish_login_flow(username: String, login_code: u32) -> Result;
//NOTE: Need to be the user
async fn change_email_flow(username: String, new_email: String, token: String) -> Result<(), ErrorCode>;
- async fn finish_change_email_flow(username: String, new_email: String, token: String, login_code: u16) -> Result<(), ErrorCode>;
+ async fn finish_change_email_flow(username: String, new_email: String, token: String, login_code: u32) -> Result<(), ErrorCode>;
// async fn change_username(username: String, token: String, new_username: String) -> Result<(), ErrorCode>;
async fn change_avatar(username: String, token: String, new_avatar: String) -> Result<(), ErrorCode>;
async fn get_all_data(username: String, token: String) -> Result;
async fn sign_out(username: String, token: String) -> Result<(), ErrorCode>;
async fn delete_account_flow(username: String, token: String) -> Result<(), ErrorCode>;
- async fn finish_delete_account_flow(username: String, token: String, login_code: u16) -> Result<(), ErrorCode>;
+ async fn finish_delete_account_flow(username: String, token: String, login_code: u32) -> Result<(), ErrorCode>;
async fn add_server(username: String, token: String, domain: String) -> Result<(), ErrorCode>;
async fn remove_server(username: String, token: String, domain: String) -> Result<(), ErrorCode>;
async fn get_joined_servers(username: String, token: String) -> Result, ErrorCode>;
@@ -34,12 +34,8 @@ pub struct AuthUser {
pub email: String,
pub avatar: String,
pub servers: String,
- pub login_code: Option,
+ pub login_code: Option,
pub bigtoken: Option,
- pub google_oauth: Option,
- pub apple_oauth: Option,
- pub github_oauth: Option,
- pub discord_oauth: Option,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
diff --git a/client/Cargo.toml b/client/Cargo.toml
index 8744f89..70d8bc8 100644
--- a/client/Cargo.toml
+++ b/client/Cargo.toml
@@ -14,4 +14,10 @@ eframe = { version = "0.29", default-features = false, features = [
"persistence", # Enable restoring app state when restarting the app.
] }
serde = { version = "1", features = ["derive"] }
-env_logger = "0.11.5"
\ No newline at end of file
+env_logger = "0.11.5"
+tokio = "1.40.0"
+tarpc = { version = "0.34.0", features = ["full"] }
+tracing = "0.1.40"
+tracing-subscriber = "0.3.18"
+regex = "1.10.6"
+native-dialog = "0.7.0"
\ No newline at end of file
diff --git a/client/src/app.rs b/client/src/app.rs
index 00e88eb..79281d9 100644
--- a/client/src/app.rs
+++ b/client/src/app.rs
@@ -1,3 +1,8 @@
+use tokio::sync::broadcast;
+use tokio::sync::broadcast::{Receiver, Sender};
+use tracing::log::info;
+use realm_shared::types::ErrorCode;
+use crate::types::ClientUser;
use crate::ui::panels;
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
@@ -9,9 +14,32 @@ pub struct TemplateApp {
pub selected: bool,
pub selected_serverid: Option,
pub selected_roomid: Option,
+
+ pub current_user: Option,
- #[serde(skip)] // This how you opt-out of serialization of a field
+ #[serde(skip)]
pub value: f32,
+ #[serde(skip)]
+ pub login_window_open: bool,
+ #[serde(skip)]
+ pub login_window_username: String,
+ #[serde(skip)]
+ pub login_window_code: String,
+ #[serde(skip)]
+ pub login_window_server_address: String,
+ #[serde(skip)]
+ pub login_window_email: String,
+
+ #[serde(skip)]
+ pub login_ready_for_code_input: bool,
+
+ #[serde(skip)]
+ pub signup_window_open: bool,
+
+ #[serde(skip)]
+ pub login_start_channel: (Sender>, Receiver>),
+ #[serde(skip)]
+ pub login_ending_channel: (Sender>, Receiver>),
}
impl Default for TemplateApp {
@@ -22,7 +50,19 @@ impl Default for TemplateApp {
selected: false,
selected_serverid: None,
selected_roomid: None,
+ current_user: None,
value: 2.7,
+
+ login_window_open: false,
+ login_window_username: String::new(),
+ login_window_code: String::new(),
+ login_window_server_address: String::new(),
+ login_start_channel: broadcast::channel(10),
+ login_ending_channel: broadcast::channel(10),
+ login_ready_for_code_input: false,
+ login_window_email: String::new(),
+
+ signup_window_open: false,
}
}
}
@@ -49,8 +89,26 @@ impl eframe::App for TemplateApp {
// Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`.
// For inspiration and more examples, go to https://emilk.github.io/egui
+ while let Ok(result) = self.login_start_channel.1.try_recv() {
+ match result {
+ Ok(_) => self.login_ready_for_code_input = true,
+ Err(e) => tracing::error!("Error in login/account creation flow: {:?}", e),
+ }
+ }
+
+ while let Ok(result) = self.login_ending_channel.1.try_recv() {
+ match result {
+ Ok(token) => {
+ info!("Login successful! Token: {token}");
+ self.login_ready_for_code_input = false;
+
+ },
+ Err(e) => tracing::error!("Error in login flow: {:?}", e),
+ }
+ }
+
// File -> Quit
- panels::top_panel(ctx);
+ panels::top_panel(self, ctx);
panels::servers(self, ctx);
diff --git a/client/src/main.rs b/client/src/main.rs
index 464d2a9..28c3757 100644
--- a/client/src/main.rs
+++ b/client/src/main.rs
@@ -1,4 +1,5 @@
-fn main() -> eframe::Result {
+#[tokio::main]
+async fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let native_options = eframe::NativeOptions {
diff --git a/client/src/types.rs b/client/src/types.rs
index e69de29..f71a9c1 100644
--- a/client/src/types.rs
+++ b/client/src/types.rs
@@ -0,0 +1,9 @@
+#[derive(serde::Deserialize, serde::Serialize)]
+pub struct ClientUser {
+ pub id: i64,
+ pub username: String,
+ pub email: String,
+ pub avatar: String,
+ pub servers: Vec,
+ pub token: String,
+}
\ No newline at end of file
diff --git a/client/src/ui/panels.rs b/client/src/ui/panels.rs
index d0af77b..af9d346 100644
--- a/client/src/ui/panels.rs
+++ b/client/src/ui/panels.rs
@@ -1,25 +1,180 @@
use egui::{Context, SelectableLabel};
+use tarpc::context;
+use tarpc::tokio_serde::formats::Json;
+use realm_auth::types::RealmAuthClient;
+use realm_shared::types::ErrorCode::RPCError;
+use regex::Regex;
use crate::app::TemplateApp;
-pub fn top_panel(ctx: &Context) {
+pub fn top_panel(app: &mut TemplateApp, ctx: &Context) {
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
- // The top panel is often a good place for a menu bar:
-
egui::menu::bar(ui, |ui| {
- // NOTE: no File->Quit on web pages!
- let is_web = cfg!(target_arch = "wasm32");
- if !is_web {
- ui.menu_button("File", |ui| {
- if ui.button("Quit").clicked() {
- ctx.send_viewport_cmd(egui::ViewportCommand::Close);
- }
- });
- ui.add_space(16.0);
- }
+ ui.menu_button("File", |ui| {
+ if ui.button("Sign Up").clicked() {
+ app.signup_window_open = true;
+ }
+
+ if ui.button("Login").clicked() {
+ app.login_window_open = true;
+ }
+
+ if ui.button("Logout").clicked() {
+ // TODO: Logout
+ }
+
+ if ui.button("Quit").clicked() {
+ ctx.send_viewport_cmd(egui::ViewportCommand::Close);
+ }
+ });
+ ui.add_space(16.0);
egui::widgets::global_dark_light_mode_buttons(ui);
});
});
+
+ egui::Window::new("Signup")
+ .open(&mut app.signup_window_open)
+ .min_size((500.0, 200.0))
+ .show(ctx, |ui| {
+ ui.horizontal(|ui| {
+ ui.label("Server Address: ");
+ ui.text_edit_singleline(&mut app.login_window_server_address);
+ });
+
+ ui.horizontal(|ui| {
+ ui.label("Username: ");
+ ui.text_edit_singleline(&mut app.login_window_username);
+ });
+
+ ui.horizontal(|ui| {
+ ui.label("Email: ");
+ ui.text_edit_singleline(&mut app.login_window_email);
+ });
+
+ if ui.button("Create Account").clicked() {
+ let login_window_server_address = app.login_window_server_address.clone();
+ let login_window_username = app.login_window_username.clone();
+ let login_window_email = app.login_window_email.clone();
+ let send_channel = app.login_start_channel.0.clone();
+
+ let _handle = tokio::spawn(async move {
+ let mut transport = tarpc::serde_transport::tcp::connect(login_window_server_address, Json::default);
+ transport.config_mut().max_frame_length(usize::MAX);
+
+ let result = transport.await;
+ let connection = match result {
+ Ok(connection) => connection,
+ Err(e) => {
+ tracing::error!("Failed to connect to server: {}", e);
+ return;
+ }
+ };
+
+ let client = RealmAuthClient::new(tarpc::client::Config::default(), connection).spawn();
+ let result = client.create_account_flow(context::current(), login_window_username, login_window_email).await;
+
+ match result {
+ Ok(r) => send_channel.send(r).unwrap(),
+ Err(_) => send_channel.send(Err(RPCError)).unwrap(),
+ };
+ });
+
+ ui.close_menu()
+ }
+ });
+
+ egui::Window::new("Login")
+ .open(&mut app.login_window_open)
+ .min_size((500.0, 200.0))
+ .show(ctx, |ui| {
+ ui.horizontal(|ui| {
+ ui.label("Server Address: ");
+ ui.text_edit_singleline(&mut app.login_window_server_address);
+ });
+
+ ui.horizontal(|ui| {
+ ui.label("Username: ");
+ ui.text_edit_singleline(&mut app.login_window_username);
+ });
+
+ if ui.button("Send Login Code").clicked() {
+ let login_window_server_address = app.login_window_server_address.clone();
+ let login_window_username = app.login_window_username.clone();
+ let send_channel = app.login_start_channel.0.clone();
+
+ let _handle = tokio::spawn(async move {
+ let mut transport = tarpc::serde_transport::tcp::connect(login_window_server_address, Json::default);
+ transport.config_mut().max_frame_length(usize::MAX);
+
+ let result = transport.await;
+ let connection = match result {
+ Ok(connection) => connection,
+ Err(e) => {
+ tracing::error!("Failed to connect to server: {}", e);
+ return;
+ }
+ };
+
+ let client = RealmAuthClient::new(tarpc::client::Config::default(), connection).spawn();
+ let result = client.create_login_flow(context::current(), Some(login_window_username), None).await;
+
+ match result {
+ Ok(r) => send_channel.send(r).unwrap(),
+ Err(_) => send_channel.send(Err(RPCError)).unwrap(),
+ };
+ });
+
+ ui.close_menu()
+ }
+ });
+
+ egui::Window::new("Auth Code")
+ .open(&mut app.login_ready_for_code_input)
+ .min_size((500.0, 200.0))
+ .show(ctx, |ui| {
+ ui.horizontal(|ui| {
+ ui.label("Code: ");
+ if ui.text_edit_singleline(&mut app.login_window_code).changed() {
+ let re = Regex::new(r"[^0-9]+").unwrap();
+ app.login_window_code = re.replace_all(&app.login_window_code, "").to_string();
+ }
+ });
+
+ if ui.button("Login").clicked() {
+ let login_window_server_address = app.login_window_server_address.clone();
+ let login_window_code = app.login_window_code.clone();
+ let login_window_username = app.login_window_username.clone();
+ let send_channel = app.login_ending_channel.0.clone();
+
+ let _handle = tokio::spawn(async move {
+ let mut transport = tarpc::serde_transport::tcp::connect(login_window_server_address, Json::default);
+ transport.config_mut().max_frame_length(usize::MAX);
+
+ let result = transport.await;
+ let connection = match result {
+ Ok(connection) => connection,
+ Err(e) => {
+ tracing::error!("Failed to connect to server: {}", e);
+ return;
+ }
+ };
+
+ let client = RealmAuthClient::new(tarpc::client::Config::default(), connection).spawn();
+ let result = client.finish_login_flow(context::current(), login_window_username, login_window_code.parse::().unwrap()).await;
+
+ match result {
+ Ok(r) => {
+ send_channel.send(r).unwrap();
+ }
+ Err(e) => {
+ send_channel.send(Err(RPCError)).unwrap();
+ }
+ }
+ });
+
+ ui.close_menu()
+ }
+ });
}
pub fn servers(app: &mut TemplateApp, ctx: &Context) {
diff --git a/login_email.txt b/login_email.txt
deleted file mode 100644
index f24a21a..0000000
--- a/login_email.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Confirm your email address
-Your 6 digit code is below-- enter it into Realm and you will be signed in
-
-{}
-
-If you didn't request this email, there's nothing to worry about it-- you can safely ignore it.
-
- Realm
- Need help? Email realm-support@abunchofknowtiwalls.com
- © 2024 Realm, Inc.
diff --git a/server/.env b/server/.env
deleted file mode 100644
index 425d538..0000000
--- a/server/.env
+++ /dev/null
@@ -1,4 +0,0 @@
-DATABASE_URL=sqlite:server.db
-DOMAIN=server.realmchat.com
-SERVER_ID=tester
-PORT=5051
\ No newline at end of file
diff --git a/server/example.env b/server/example.env
new file mode 100644
index 0000000..6cb6ed1
--- /dev/null
+++ b/server/example.env
@@ -0,0 +1,4 @@
+DATABASE_URL=sqlite:server.db
+DOMAIN=
+SERVER_ID=
+PORT=5051
\ No newline at end of file
diff --git a/server/src/server.rs b/server/src/server.rs
index 7412318..a53aae9 100644
--- a/server/src/server.rs
+++ b/server/src/server.rs
@@ -127,7 +127,7 @@ impl RealmChatServer {
}
async fn is_user_in_server(&self, userid: &str) -> bool {
- let result = query!("SELECT NOT EXISTS (SELECT 1 FROM user WHERE userid = ?) AS does_exist", userid).fetch_one(&self.db_pool).await;
+ let result = query!("SELECT EXISTS (SELECT 1 FROM user WHERE userid = ?) AS does_exist", userid).fetch_one(&self.db_pool).await;
match result {
Ok(record) => record.does_exist != 0,
diff --git a/shared/src/types.rs b/shared/src/types.rs
index 26c2fc9..d6c2d89 100644
--- a/shared/src/types.rs
+++ b/shared/src/types.rs
@@ -21,4 +21,6 @@ pub enum ErrorCode {
UserNotFound,
DepthTooLarge,
MalformedDBResponse,
+
+ RPCError,
}
\ No newline at end of file