Implemented userinfo endpoint and finalized OAuth2.0 flow
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@ -25,4 +25,8 @@ target/
|
||||
|
||||
config.toml
|
||||
|
||||
.idea/
|
||||
.idea/
|
||||
|
||||
.env
|
||||
|
||||
.DS_Store
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -353,6 +353,7 @@ dependencies = [
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"axum-keycloak-auth",
|
||||
"base64",
|
||||
"chrono",
|
||||
"log",
|
||||
"oauth2",
|
||||
|
||||
@ -18,3 +18,4 @@ toml = "0.9.7"
|
||||
oauth2 = "5.0.0"
|
||||
uuid = "1.18.1"
|
||||
axum-extra = { version = "0.10.3", features = ["cookie"] }
|
||||
base64 = "0.22.1"
|
||||
|
||||
10
src/api.rs
10
src/api.rs
@ -1,6 +1,6 @@
|
||||
use crate::gsd::dto::GSDResponseDTO;
|
||||
use crate::middleware::AppState;
|
||||
use crate::util::set_and_log_session;
|
||||
use crate::util::{decode_payload_unchecked, set_and_log_session};
|
||||
use axum::Extension;
|
||||
use axum::body::Body;
|
||||
use axum::extract::Request;
|
||||
@ -8,6 +8,7 @@ use axum::http::{HeaderValue, StatusCode};
|
||||
use axum::response::IntoResponse;
|
||||
use log::{error, info};
|
||||
use std::sync::Arc;
|
||||
use crate::model::{User};
|
||||
|
||||
pub async fn handle_post(
|
||||
Extension(state): Extension<Arc<AppState>>,
|
||||
@ -94,4 +95,9 @@ pub async fn handle_post(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_login() -> impl IntoResponse {}
|
||||
pub async fn userinfo(request: Request<Body>) -> impl IntoResponse {
|
||||
let access_token_string = &request.headers().get("authorization").unwrap().to_str().unwrap().to_string()[7..];
|
||||
let user = decode_payload_unchecked::<User>(access_token_string).unwrap();
|
||||
|
||||
serde_json::to_string(&user.employee).unwrap().into_response()
|
||||
}
|
||||
|
||||
22
src/auth.rs
22
src/auth.rs
@ -1,8 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::config::Config;
|
||||
use crate::middleware::AppState;
|
||||
use crate::repository::RedisRepository;
|
||||
use axum::http::{StatusCode, header};
|
||||
use axum::response::Response;
|
||||
use axum::response::{Html, Response};
|
||||
use axum::{
|
||||
Router,
|
||||
extract::{Query, State},
|
||||
@ -22,6 +23,7 @@ use oauth2::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use axum::routing::post;
|
||||
use log::info;
|
||||
|
||||
pub type OAuthClient = Client<
|
||||
BasicErrorResponse,
|
||||
@ -188,23 +190,13 @@ async fn callback(
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, "Login failed").into_response();
|
||||
}
|
||||
|
||||
log::info!("Successfully created session {} for user", session_id);
|
||||
|
||||
let cookie = format!(
|
||||
"session_id={}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400",
|
||||
session_id
|
||||
);
|
||||
info!("Successfully created session {} for user", session_id);
|
||||
info!("Token scopes: {:?}", token.extra_fields());
|
||||
|
||||
// 4. Redirect to frontend
|
||||
let redirect_url = format!("{}?login=success", cloned_state.frontend_url);
|
||||
let redirect_url = format!("{}/?session_id={}", cloned_state.frontend_url.clone(), session_id);
|
||||
|
||||
Response::builder()
|
||||
.status(StatusCode::FOUND)
|
||||
.header(header::SET_COOKIE, cookie)
|
||||
.header(header::LOCATION, redirect_url.clone())
|
||||
.body::<String>(format!("Redirecting to {}", redirect_url).into())
|
||||
.unwrap()
|
||||
.into_response()
|
||||
Redirect::to(redirect_url.as_str()).into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Token exchange failed: {:?}", e);
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::api::handle_post;
|
||||
use crate::api::{handle_post, userinfo};
|
||||
use crate::config::load_config;
|
||||
use crate::middleware::AppState;
|
||||
use crate::repository::RedisRepository;
|
||||
use crate::util::initialize_logging;
|
||||
use axum::routing::post;
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Extension, Router};
|
||||
use axum_keycloak_auth::instance::{KeycloakAuthInstance, KeycloakConfig};
|
||||
use axum_keycloak_auth::layer::KeycloakAuthLayer;
|
||||
@ -18,12 +18,14 @@ mod gsd;
|
||||
mod middleware;
|
||||
mod repository;
|
||||
mod util;
|
||||
mod model;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = load_config()?;
|
||||
initialize_logging(&config);
|
||||
|
||||
info!("Redirect URI: {}", config.keycloak.redirect_url);
|
||||
info!("Logging initialized");
|
||||
info!("Starting Holzleitner Delivery Backend");
|
||||
|
||||
@ -51,6 +53,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let auth_router = auth::router(state.clone());
|
||||
let proxy_router = Router::new()
|
||||
.route("/{*wildcard}", post(handle_post))
|
||||
.route("/userinfo", get(userinfo))
|
||||
.route_layer(Extension(state.clone()))
|
||||
.route_layer(axum::middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
|
||||
@ -172,5 +172,10 @@ pub async fn gsd_decorate_header(
|
||||
HeaderValue::from_str(state_cloned.config.gsd_app_key.as_str()).unwrap(),
|
||||
);
|
||||
|
||||
next.run(request).await
|
||||
let response = next.run(request).await;
|
||||
|
||||
info!("Response: {:?}", response);
|
||||
|
||||
response
|
||||
|
||||
}
|
||||
|
||||
13
src/model.rs
Normal file
13
src/model.rs
Normal file
@ -0,0 +1,13 @@
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct User {
|
||||
pub employee: Employee,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Employee {
|
||||
pub number: String,
|
||||
pub last_name: String,
|
||||
pub first_name: String,
|
||||
pub mail: String
|
||||
}
|
||||
12
src/util.rs
12
src/util.rs
@ -6,6 +6,18 @@ use log::{LevelFilter, error, info};
|
||||
use simplelog::{ColorChoice, CombinedLogger, TermLogger, TerminalMode, WriteLogger};
|
||||
use std::fs::File;
|
||||
use std::sync::Arc;
|
||||
use serde::de::DeserializeOwned;
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
||||
|
||||
pub fn decode_payload_unchecked<T: DeserializeOwned>(token: &str) -> Result<T, Box<dyn std::error::Error>> {
|
||||
let mut parts = token.split('.');
|
||||
let _header = parts.next().ok_or("missing header")?;
|
||||
let payload_b64 = parts.next().ok_or("missing payload")?;
|
||||
// signature is parts.next() but we ignore it here
|
||||
let payload = URL_SAFE_NO_PAD.decode(payload_b64.as_bytes())?;
|
||||
let claims = serde_json::from_slice::<T>(&payload)?;
|
||||
Ok(claims)
|
||||
}
|
||||
|
||||
pub fn initialize_logging(config: &Config) {
|
||||
CombinedLogger::init(vec![
|
||||
|
||||
Reference in New Issue
Block a user