Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions api-server/src/handlers/auth_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ use crate::errors::Error;
use crate::models::User;
use crate::payloads::{AuthResponse, OAuthCallback};

use serde::Deserialize;

#[routes]
#[get("/api/auth/callback")]
pub async fn callback(
query: web::Query<OAuthCallback>,
data: web::Data<AppState>,
) -> Result<impl Responder, Error> {
let callback_data = query.into_inner();
let state = callback_data.state;
let state = callback_data.state.clone();
let code = callback_data.code;
println!("State: {}, code: {}", state, code);
let (user_info, token) = data
Expand Down Expand Up @@ -45,18 +47,51 @@ pub async fn callback(
};

let response = AuthResponse { user_info, token };
let response_type = if callback_data.state.contains(':') {
callback_data.state.split(':').nth(1).unwrap_or("json")
} else {
"json"
};

// We only take care of html or always return json payload
if response_type == "html" {
let html_body = format!(
r#"
<!DOCTYPE html>
<html>
<head><title>Login Successful</title></head>
<body>
<h1>Login Successful!</h1>
<textarea id="json">{}</textarea>
<button onclick="copy()">Copy Token</button>
<script>function copy() {{ document.getElementById('json').select(); document.execCommand('copy'); }}</script>
</body>
</html>
"#,
serde_json::to_string(&response).unwrap()
);
Ok(HttpResponse::Ok().content_type("text/html").body(html_body))
} else {
Ok(HttpResponse::Ok().json(response))
}
}

Ok(HttpResponse::Ok().json(response))
#[derive(Deserialize)]
pub struct AuthUrlQuery {
pub response_type: Option<String>,
}

#[routes]
#[get("/api/auth/url")]
pub async fn auth_url(data: web::Data<AppState>) -> Result<impl Responder, Error> {
let auth_url = data
pub async fn auth_url(
query: web::Query<AuthUrlQuery>,
data: web::Data<AppState>,
) -> Result<impl Responder, Error> {
let (url, state) = data
.goolge_auth_service
.get_authorisation_url()
.get_authorisation_url(query.response_type.clone())
.await
.map_err(|e| Error::Internal(e.to_string()))?;

Ok(HttpResponse::Ok().json(auth_url))
Ok(HttpResponse::Ok().json((url, state)))
}
4 changes: 2 additions & 2 deletions api-server/src/handlers/picture_hander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub async fn post_picture(
.await
.map_err(|_| Error::Internal("Failed to register or upload picture".to_string()))?;

// This is for testing, this request should be used when someone review if
// the person on the picture is recognised to then authorised and sent it
// NOTE: This is for testing, this request should be used when someone review
// if the person on the picture is recognised to then authorised and sent it
let _ok = data
.status_service
.send_status(status_response.id)
Expand Down
4 changes: 3 additions & 1 deletion api-server/src/models/picture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ use serde::{Deserialize, Serialize};
pub struct Picture {
#[serde(rename = "_id")]
pub id: Uuid,
pub user_id: Uuid,
pub name: String,
pub url: String,
pub created_at: DateTime<Local>,
pub updated_at: Option<DateTime<Local>>,
}

impl Picture {
pub fn new(name: String, url: String) -> Self {
pub fn new(user_id: Uuid, name: String, url: String) -> Self {
Self {
id: Uuid::new(),
user_id,
name,
url,
created_at: Local::now(),
Expand Down
1 change: 1 addition & 0 deletions api-server/src/payloads/google.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::models::Token;

#[derive(Deserialize, Serialize)]
pub struct UserInfo {
#[serde(alias = "sub")]
pub id: String,
pub email: String,
pub name: String,
Expand Down
16 changes: 13 additions & 3 deletions api-server/src/services/google_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@ impl GoogleAuthServiceImpl {
auth_url: "https://accounts.google.com/o/oauth2/v2/auth".to_string(),
token_url: "https://www.googleapis.com/oauth2/v3/token".to_string(),
revocation_url: "https://oauth2.googleapis.com/revoke".to_string(),
user_info_url: "https://www.googleapis.com/oauth2/v2/userinfo".to_string(),
user_info_url: "https://www.googleapis.com/oauth2/v3/userinfo".to_string(),
}
}
}

#[async_trait]
impl GoogleAuthService for GoogleAuthServiceImpl {
async fn get_authorisation_url(&self) -> Result<(String, String), Error> {
async fn get_authorisation_url(
&self,
response_type: Option<String>,
) -> Result<(String, String), Error> {
let auth_url = AuthUrl::new(self.auth_url.clone()).map_err(|e| Error::Parse(e.to_string()));

let token_url =
Expand All @@ -62,8 +65,15 @@ impl GoogleAuthService for GoogleAuthServiceImpl {
.expect("Invalid revocation endpoints URL"),
);

let random_token = CsrfToken::new_random();
let final_token = if let Some(value) = response_type {
CsrfToken::new(format!("{}:{}", random_token.secret(), value))
} else {
random_token
};

let (authorize_url, crsf_state) = client
.authorize_url(CsrfToken::new_random)
.authorize_url(|| final_token)
.add_scopes(
self.scope
.split_whitespace()
Expand Down
8 changes: 6 additions & 2 deletions api-server/src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use async_trait::async_trait;
use bson::Uuid;

use crate::errors::Error;
use crate::models::{Token, User};
use crate::models::{Picture, Token, User};
use crate::payloads::{StatusResponse, UserInfo};

// NOTE: Service should return a model then the API layer convert to payload..
Expand All @@ -36,11 +36,15 @@ pub trait PictureService: Send + Sync {
&self,
image_data: Vec<u8>,
) -> Result<StatusResponse, Error>;
async fn get_all(user_id: Uuid) -> Result<Vec<Picture>, Error>;
}

#[async_trait]
pub trait GoogleAuthService: Send + Sync {
async fn get_authorisation_url(&self) -> Result<(String, String), Error>;
async fn get_authorisation_url(
&self,
response_type: Option<String>,
) -> Result<(String, String), Error>;
async fn exchange_code_for_token(
&self,
code: String,
Expand Down
4 changes: 3 additions & 1 deletion api-server/src/services/picture.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use async_trait::async_trait;
use bson::Uuid;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};

Expand Down Expand Up @@ -30,7 +31,6 @@ impl PictureServiceImpl {
}

#[async_trait]
// TODO: Better error handle, got lazy :)
impl PictureService for PictureServiceImpl {
async fn upload_and_register_picture(
&self,
Expand Down Expand Up @@ -62,4 +62,6 @@ impl PictureService for PictureServiceImpl {
Err(Error::Empty("Image data is empty".to_string()))
}
}

async fn get_all(user_id: Uuid) -> Result<Vec<Picture>, Error> {}
}
12 changes: 11 additions & 1 deletion desktop-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ edition = "2024"
[dependencies]
chrono = { version = "0.4.40", features = ["serde"] }
google-oauth = "1.11.3"
iced = "0.13.1"
iced = { version = "0.13.1", features = ["tokio"] }
reqwest = { version = "0.12.15", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = "1.0.228"
webbrowser = "1.0.6"
serde_json = "1.0.148"

[package.metadata.bundle]
name = "desktop-client"
identifier = "com.iButcat.desktop-client"
icon = ["assets/icons/icon.png"]
version = "1.0.0"
copyright = "Copyright 2025 iButcat"
2 changes: 1 addition & 1 deletion desktop-client/src/app/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
Self {
api_base_url: "http://localhost:8080".to_string(),
api_base_url: "http://0.0.0.0:8080".to_string(),
}
}
}
10 changes: 7 additions & 3 deletions desktop-client/src/app/message.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::app::Page;
use crate::models::User;

#[derive(Debug, Clone)]
pub enum Message {
Expand All @@ -8,15 +9,18 @@ pub enum Message {
SetError(String),
ClearError,

Login(String, String),
Logout,

FetchData,
DataLoaded(Result<Vec<String>, String>),

ButtonPressed,
TextChanged(String),
UsernameChanged(String),
PasswordChanged(String),
CheckboxToggled(bool),

LoginWithGoogle,
AuthUrlReceived(Result<(String, String), String>),
TokenInputChanged(String),
SubmitToken,
UserFetched(Result<Option<User>, String>),
}
Loading