Skip to content

Commit

Permalink
Update login & callback routes
Browse files Browse the repository at this point in the history
  • Loading branch information
lucemans committed Dec 4, 2024
1 parent 63b98ac commit 6fdef1a
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 69 deletions.
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
services:
# Engine
engine:
image: ghcr.io/v3xlabs/v3x-property-engine:master
image: ghcr.io/v3xlabs/v3x-property/engine:master
ports:
- "3000:3000"
env_file: .env
Expand Down
2 changes: 1 addition & 1 deletion engine/.build/realm-export.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "v3x-property-secret",
"redirectUris": ["http://localhost:3000/callback"],
"redirectUris": ["http://localhost:3000/api/callback"],
"webOrigins": ["http://localhost:5173"],
"notBefore": 0,
"bearerOnly": false,
Expand Down
2 changes: 1 addition & 1 deletion engine/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# OpenID Connect OAuth 2.0
OPENID_CLIENT_ID=v3x-property
OPENID_CLIENT_SECRET=v3x-property-secret
OPENID_REDIRECT=http://localhost:3000/callback
OPENID_REDIRECT=http://localhost:3000/api/callback
OPENID_ISSUER=http://localhost:8080/realms/v3x-property

# Postgres Database
Expand Down
2 changes: 1 addition & 1 deletion engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ This can easily be done by heading to the `Clients` tab in the admin console.

Then you can click on `Create client` and create a basic new OpenID Connect client.
Choose a Client ID, and press `Next`.
Enable `Client Authentication` and specify the `Redirect URIs` to be `http://localhost:3000/callback`.
Enable `Client Authentication` and specify the `Redirect URIs` to be `http://localhost:3000/api/callback`.

Once done you can head to the `Credentials` tab to see your Client Secret, insert this in your `.env` file.
4 changes: 2 additions & 2 deletions engine/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use instance::InstanceApi;
use items::ItemsApi;
use me::MeApi;
use media::MediaApi;
use oauth::login::LoginApi;
use oauth::{callback::CallbackApi, login::LoginApi};
use poem::{
get, handler, listener::TcpListener, middleware::Cors, web::Html, EndpointExt, Route, Server,
};
Expand Down Expand Up @@ -52,6 +52,7 @@ fn get_api() -> impl OpenApi {
SessionsApi,
InstanceApi,
LoginApi,
CallbackApi,
)
}

Expand All @@ -69,7 +70,6 @@ pub async fn serve(state: AppState) -> Result<(), poem::Error> {
let state = Arc::new(state);

let app = Route::new()
.at("/callback", get(oauth::callback::callback))
.nest("/api", api_service)
.nest("/openapi.json", spec)
.at("/docs", get(get_openapi_docs))
Expand Down
127 changes: 70 additions & 57 deletions engine/src/routes/oauth/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,83 +4,96 @@ use openid::Token;
use poem::{
handler,
http::HeaderMap,
web::{Data, Query, RealIp, Redirect, WithHeader},
web::{Data, RealIp, Redirect, WithHeader},
IntoResponse, Result,
};
use poem_openapi::{
param::Query,
payload::{PlainText, Response},
ApiResponse, Object, OpenApi,
};
use serde::Deserialize;
use tracing::info;
use url::Url;
use uuid::Uuid;

use super::super::ApiTags;
use crate::{
auth::hash::hash_session,
models::{sessions::Session, user::userentry::UserEntry},
state::AppState,
};

#[handler]
pub async fn callback(
state: Query<Option<String>>,
scope: Query<Option<String>>,
hd: Query<Option<String>>,
authuser: Query<Option<String>>,
code: Query<String>,
prompt: Query<Option<String>>,
app_state: Data<&Arc<AppState>>,
ip: RealIp,
headers: &HeaderMap,
) -> Result<WithHeader<Redirect>> {
let mut token = app_state.openid.request_token(&code).await.map_err(|_| {
poem::Error::from_response(
Redirect::temporary(app_state.openid.redirect_url()).into_response(),
)
})?;
pub struct CallbackApi;

let mut token = Token::from(token);
#[OpenApi]
impl CallbackApi {
#[oai(path = "/callback", method = "get", tag = "ApiTags::Auth")]
pub async fn callback(
&self,
state: Query<Option<String>>,
scope: Query<Option<String>>,
hd: Query<Option<String>>,
authuser: Query<Option<String>>,
code: Query<String>,
prompt: Query<Option<String>>,
app_state: Data<&Arc<AppState>>,
ip: RealIp,
headers: &HeaderMap,
) -> Result<()> {
let mut token = app_state.openid.request_token(&code).await.map_err(|_| {
poem::Error::from_response(
Redirect::temporary(app_state.openid.redirect_url()).into_response(),
)
})?;

let mut id_token = token.id_token.take().unwrap();
let mut token = Token::from(token);

app_state.openid.decode_token(&mut id_token).unwrap();
app_state
.openid
.validate_token(&id_token, None, None)
.unwrap();
let mut id_token = token.id_token.take().unwrap();

app_state.openid.decode_token(&mut id_token).unwrap();
app_state
.openid
.validate_token(&id_token, None, None)
.unwrap();

let oauth_userinfo = app_state.openid.request_userinfo(&token).await.unwrap();

let oauth_userinfo = app_state.openid.request_userinfo(&token).await.unwrap();
// Now we must verify the user information, decide wether they deserve access, and if so return a token.
let user = UserEntry::upsert(&oauth_userinfo, None, &app_state.database)
.await
.unwrap();

// Now we must verify the user information, decide wether they deserve access, and if so return a token.
let user = UserEntry::upsert(&oauth_userinfo, None, &app_state.database)
let user_agent = headers.get("user-agent").unwrap().to_str().unwrap();
let user_ip = ip.0.unwrap();

let token = Uuid::new_v4().to_string();
let hash = hash_session(&token).unwrap();

let _session = Session::new(
&app_state.database,
&hash,
user.user_id,
user_agent,
&user_ip.into(),
)
.await
.unwrap();

let user_agent = headers.get("user-agent").unwrap().to_str().unwrap();
let user_ip = ip.0.unwrap();

let token = Uuid::new_v4().to_string();
let hash = hash_session(&token).unwrap();

let _session = Session::new(
&app_state.database,
&hash,
user.user_id,
user_agent,
&user_ip.into(),
)
.await
.unwrap();

info!("Issued session token for user {}", user.user_id);

let mut redirect_url: Url = state
.0
.clone()
.unwrap_or("http://localhost:3000/me".to_string())
.parse()
.unwrap();
info!("Issued session token for user {}", user.user_id);

let mut redirect_url: Url = state
.0
.clone()
.unwrap_or("http://localhost:3000/me".to_string())
.parse()
.unwrap();

redirect_url.set_query(Some(&format!("token={}", token)));
redirect_url.set_query(Some(&format!("token={}", token)));
redirect_url.set_query(Some(&format!("token={}", token)));

Ok(Redirect::temporary(redirect_url).with_header(
"Set-Cookie",
format!("property.v3x.token={}; Secure; HttpOnly", token),
))
Err(poem::Error::from_response(
Redirect::temporary(redirect_url.to_string()).into_response(),
))
}
}
12 changes: 8 additions & 4 deletions engine/src/routes/oauth/login.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use std::{collections::HashSet, sync::Arc};

use openid::{Options, Prompt};
use poem::web::Data;
use poem::{web::{Data, Redirect}, IntoResponse, Result};
use poem_openapi::{param::Query, payload::PlainText, ApiResponse, OpenApi};

use crate::state::AppState;

use super::super::ApiTags;

pub struct LoginApi;

#[derive(ApiResponse)]
Expand All @@ -16,12 +18,12 @@ enum RedirectResponse {

#[OpenApi]
impl LoginApi {
#[oai(path = "/login", method = "get")]
#[oai(path = "/login", method = "get", tag = "ApiTags::Auth")]
pub async fn login(
&self,
redirect: Query<Option<String>>,
state: Data<&Arc<AppState>>,
) -> RedirectResponse {
) -> Result<()> {
// let discovery_url = "http://localhost:8080/realms/master/.well-known/openid-configuration";

// let http_client = reqwest::Client::new();
Expand All @@ -48,6 +50,8 @@ impl LoginApi {
println!("OpenID Connect Authorization URL: {}", authorize_url);

// redirect to the authorization URL
RedirectResponse::Redirect(PlainText(authorize_url.as_str().to_string()))
Err(poem::Error::from_response(
Redirect::temporary(authorize_url.as_str().to_string()).into_response(),
))
}
}
2 changes: 1 addition & 1 deletion web/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as DropdownMenu from '@/components/ui/Dropdown';

import { AvatarHolder, getInitials } from './UserProfile';

const LOGIN_URL = 'http://localhost:3000/login';
const LOGIN_URL = 'http://localhost:3000/api/login';

export const Navbar = () => {
const { token, clearAuthToken } = useAuth();
Expand Down
2 changes: 1 addition & 1 deletion web/src/routes/sessions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const Route = createFileRoute('/sessions')({

// eslint-disable-next-line no-undef
window.location.href =
'http://localhost:3000/login?redirect=' +
'http://localhost:3000/api/login?redirect=' +
encodeURIComponent(location.href);
}
},
Expand Down

0 comments on commit 6fdef1a

Please sign in to comment.