...
This commit is contained in:
parent
4274fdaf93
commit
433606eb25
@ -2,16 +2,6 @@
|
|||||||
|
|
||||||
This guide provides detailed instructions on how to integrate Gitea authentication into your Hostbasket application.
|
This guide provides detailed instructions on how to integrate Gitea authentication into your Hostbasket application.
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
1. [Introduction](#introduction)
|
|
||||||
2. [Prerequisites](#prerequisites)
|
|
||||||
3. [Setting Up Gitea OAuth Application](#setting-up-gitea-oauth-application)
|
|
||||||
4. [Configuring Hostbasket for Gitea Authentication](#configuring-hostbasket-for-gitea-authentication)
|
|
||||||
5. [Implementing the OAuth Flow](#implementing-the-oauth-flow)
|
|
||||||
6. [Testing the Integration](#testing-the-integration)
|
|
||||||
7. [Troubleshooting](#troubleshooting)
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Gitea is a self-hosted Git service that provides a GitHub-like interface. By integrating Gitea authentication into your Hostbasket application, you can allow users to log in using their Gitea accounts, simplifying the authentication process and providing a seamless experience.
|
Gitea is a self-hosted Git service that provides a GitHub-like interface. By integrating Gitea authentication into your Hostbasket application, you can allow users to log in using their Gitea accounts, simplifying the authentication process and providing a seamless experience.
|
||||||
@ -27,7 +17,7 @@ Before you begin, ensure you have:
|
|||||||
## Setting Up Gitea OAuth Application
|
## Setting Up Gitea OAuth Application
|
||||||
|
|
||||||
1. Log in to your Gitea instance as an administrator
|
1. Log in to your Gitea instance as an administrator
|
||||||
2. Navigate to **Site Administration** > **Applications**
|
2. Navigate to **Site Administration** > **Integrations** > **Applications**
|
||||||
3. Click **Create a New OAuth2 Application**
|
3. Click **Create a New OAuth2 Application**
|
||||||
4. Fill in the application details:
|
4. Fill in the application details:
|
||||||
- **Application Name**: Hostbasket
|
- **Application Name**: Hostbasket
|
||||||
@ -96,11 +86,11 @@ impl GiteaOAuthConfig {
|
|||||||
.expect("Missing GITEA_CLIENT_SECRET environment variable");
|
.expect("Missing GITEA_CLIENT_SECRET environment variable");
|
||||||
let instance_url = env::var("GITEA_INSTANCE_URL")
|
let instance_url = env::var("GITEA_INSTANCE_URL")
|
||||||
.expect("Missing GITEA_INSTANCE_URL environment variable");
|
.expect("Missing GITEA_INSTANCE_URL environment variable");
|
||||||
|
|
||||||
// Create OAuth client
|
// Create OAuth client
|
||||||
let auth_url = format!("{}/login/oauth/authorize", instance_url);
|
let auth_url = format!("{}/login/oauth/authorize", instance_url);
|
||||||
let token_url = format!("{}/login/oauth/access_token", instance_url);
|
let token_url = format!("{}/login/oauth/access_token", instance_url);
|
||||||
|
|
||||||
let client = BasicClient::new(
|
let client = BasicClient::new(
|
||||||
ClientId::new(client_id),
|
ClientId::new(client_id),
|
||||||
Some(ClientSecret::new(client_secret)),
|
Some(ClientSecret::new(client_secret)),
|
||||||
@ -110,7 +100,7 @@ impl GiteaOAuthConfig {
|
|||||||
.set_redirect_uri(
|
.set_redirect_uri(
|
||||||
RedirectUrl::new("http://localhost:9999/auth/gitea/callback".to_string()).unwrap(),
|
RedirectUrl::new("http://localhost:9999/auth/gitea/callback".to_string()).unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
instance_url,
|
instance_url,
|
||||||
@ -158,16 +148,16 @@ impl GiteaAuthController {
|
|||||||
.add_scope(Scope::new("read:user".to_string()))
|
.add_scope(Scope::new("read:user".to_string()))
|
||||||
.add_scope(Scope::new("user:email".to_string()))
|
.add_scope(Scope::new("user:email".to_string()))
|
||||||
.url();
|
.url();
|
||||||
|
|
||||||
// Store the CSRF token in the session
|
// Store the CSRF token in the session
|
||||||
session.insert("oauth_csrf_token", csrf_token.secret())?;
|
session.insert("oauth_csrf_token", csrf_token.secret())?;
|
||||||
|
|
||||||
// Redirect to the authorization URL
|
// Redirect to the authorization URL
|
||||||
Ok(HttpResponse::Found()
|
Ok(HttpResponse::Found()
|
||||||
.append_header((header::LOCATION, auth_url.to_string()))
|
.append_header((header::LOCATION, auth_url.to_string()))
|
||||||
.finish())
|
.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the OAuth callback
|
// Handle the OAuth callback
|
||||||
pub async fn callback(
|
pub async fn callback(
|
||||||
oauth_config: web::Data<GiteaOAuthConfig>,
|
oauth_config: web::Data<GiteaOAuthConfig>,
|
||||||
@ -177,11 +167,11 @@ impl GiteaAuthController {
|
|||||||
// Verify the CSRF token
|
// Verify the CSRF token
|
||||||
let csrf_token = session.get::<String>("oauth_csrf_token")?
|
let csrf_token = session.get::<String>("oauth_csrf_token")?
|
||||||
.ok_or_else(|| actix_web::error::ErrorBadRequest("Missing CSRF token"))?;
|
.ok_or_else(|| actix_web::error::ErrorBadRequest("Missing CSRF token"))?;
|
||||||
|
|
||||||
if csrf_token != query.state {
|
if csrf_token != query.state {
|
||||||
return Err(actix_web::error::ErrorBadRequest("Invalid CSRF token"));
|
return Err(actix_web::error::ErrorBadRequest("Invalid CSRF token"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange the authorization code for an access token
|
// Exchange the authorization code for an access token
|
||||||
let token = oauth_config
|
let token = oauth_config
|
||||||
.client
|
.client
|
||||||
@ -189,11 +179,11 @@ impl GiteaAuthController {
|
|||||||
.request_async(oauth2::reqwest::async_http_client)
|
.request_async(oauth2::reqwest::async_http_client)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("Token exchange error: {}", e)))?;
|
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("Token exchange error: {}", e)))?;
|
||||||
|
|
||||||
// Get the user information from Gitea
|
// Get the user information from Gitea
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let user_info_url = format!("{}/api/v1/user", oauth_config.instance_url);
|
let user_info_url = format!("{}/api/v1/user", oauth_config.instance_url);
|
||||||
|
|
||||||
let gitea_user = client
|
let gitea_user = client
|
||||||
.get(&user_info_url)
|
.get(&user_info_url)
|
||||||
.bearer_auth(token.access_token().secret())
|
.bearer_auth(token.access_token().secret())
|
||||||
@ -203,26 +193,26 @@ impl GiteaAuthController {
|
|||||||
.json::<crate::config::oauth::GiteaUser>()
|
.json::<crate::config::oauth::GiteaUser>()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("JSON parsing error: {}", e)))?;
|
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("JSON parsing error: {}", e)))?;
|
||||||
|
|
||||||
// Create or update the user in your system
|
// Create or update the user in your system
|
||||||
let mut user = User::new(
|
let mut user = User::new(
|
||||||
gitea_user.full_name.clone(),
|
gitea_user.full_name.clone(),
|
||||||
gitea_user.email.clone(),
|
gitea_user.email.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set the user ID and role
|
// Set the user ID and role
|
||||||
user.id = Some(gitea_user.id as i32);
|
user.id = Some(gitea_user.id as i32);
|
||||||
user.role = UserRole::User;
|
user.role = UserRole::User;
|
||||||
|
|
||||||
// Generate JWT token
|
// Generate JWT token
|
||||||
let token = AuthController::generate_token(&user.email, &user.role)
|
let token = AuthController::generate_token(&user.email, &user.role)
|
||||||
.map_err(|_| actix_web::error::ErrorInternalServerError("Failed to generate token"))?;
|
.map_err(|_| actix_web::error::ErrorInternalServerError("Failed to generate token"))?;
|
||||||
|
|
||||||
// Store user data in session
|
// Store user data in session
|
||||||
let user_json = serde_json::to_string(&user).unwrap();
|
let user_json = serde_json::to_string(&user).unwrap();
|
||||||
session.insert("user", &user_json)?;
|
session.insert("user", &user_json)?;
|
||||||
session.insert("auth_token", &token)?;
|
session.insert("auth_token", &token)?;
|
||||||
|
|
||||||
// Create a cookie with the JWT token
|
// Create a cookie with the JWT token
|
||||||
let cookie = Cookie::build("auth_token", token)
|
let cookie = Cookie::build("auth_token", token)
|
||||||
.path("/")
|
.path("/")
|
||||||
@ -230,7 +220,7 @@ impl GiteaAuthController {
|
|||||||
.secure(false) // Set to true in production with HTTPS
|
.secure(false) // Set to true in production with HTTPS
|
||||||
.max_age(actix_web::cookie::time::Duration::hours(24))
|
.max_age(actix_web::cookie::time::Duration::hours(24))
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
// Redirect to the home page with JWT token in cookie
|
// Redirect to the home page with JWT token in cookie
|
||||||
Ok(HttpResponse::Found()
|
Ok(HttpResponse::Found()
|
||||||
.cookie(cookie)
|
.cookie(cookie)
|
||||||
@ -258,7 +248,7 @@ use crate::config::oauth::GiteaOAuthConfig;
|
|||||||
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
|
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
|
||||||
// Create the OAuth configuration
|
// Create the OAuth configuration
|
||||||
let oauth_config = web::Data::new(GiteaOAuthConfig::new());
|
let oauth_config = web::Data::new(GiteaOAuthConfig::new());
|
||||||
|
|
||||||
// Configure session middleware with the consistent key
|
// Configure session middleware with the consistent key
|
||||||
let session_middleware = SessionMiddleware::builder(
|
let session_middleware = SessionMiddleware::builder(
|
||||||
CookieSessionStore::default(),
|
CookieSessionStore::default(),
|
||||||
@ -273,7 +263,7 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
|
|||||||
.wrap(session_middleware)
|
.wrap(session_middleware)
|
||||||
.app_data(oauth_config.clone())
|
.app_data(oauth_config.clone())
|
||||||
// Existing routes...
|
// Existing routes...
|
||||||
|
|
||||||
// Gitea OAuth routes
|
// Gitea OAuth routes
|
||||||
.route("/auth/gitea", web::get().to(GiteaAuthController::login))
|
.route("/auth/gitea", web::get().to(GiteaAuthController::login))
|
||||||
.route("/auth/gitea/callback", web::get().to(GiteaAuthController::callback))
|
.route("/auth/gitea/callback", web::get().to(GiteaAuthController::callback))
|
||||||
@ -310,9 +300,9 @@ Update your login page template (`src/views/auth/login.html`) to include a "Logi
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Login</button>
|
<button type="submit" class="btn btn-primary">Login</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p>Or login with:</p>
|
<p>Or login with:</p>
|
||||||
<a href="/auth/gitea" class="btn btn-secondary">
|
<a href="/auth/gitea" class="btn btn-secondary">
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
#[cfg(feature = "gitea")]
|
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
|
||||||
|
|
||||||
/// Gitea OAuth configuration
|
/// Gitea OAuth configuration
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GiteaOAuthConfig {
|
pub struct GiteaOAuthConfig {
|
||||||
/// OAuth client
|
/// OAuth client
|
||||||
@ -15,7 +11,6 @@ pub struct GiteaOAuthConfig {
|
|||||||
pub instance_url: String,
|
pub instance_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
impl GiteaOAuthConfig {
|
impl GiteaOAuthConfig {
|
||||||
/// Creates a new Gitea OAuth configuration
|
/// Creates a new Gitea OAuth configuration
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -53,7 +48,6 @@ impl GiteaOAuthConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gitea user information structure
|
/// Gitea user information structure
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct GiteaUser {
|
pub struct GiteaUser {
|
||||||
/// User ID
|
/// User ID
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder, Result, http::header, cookie::Cookie};
|
use crate::models::user::{LoginCredentials, RegistrationData, User, UserRole};
|
||||||
use actix_session::Session;
|
|
||||||
use tera::Tera;
|
|
||||||
use crate::models::user::{User, LoginCredentials, RegistrationData, UserRole};
|
|
||||||
use crate::utils::render_template;
|
use crate::utils::render_template;
|
||||||
use jsonwebtoken::{encode, Header, EncodingKey};
|
use actix_session::Session;
|
||||||
use serde::{Deserialize, Serialize};
|
use actix_web::{cookie::Cookie, http::header, web, HttpResponse, Responder, Result};
|
||||||
use chrono::{Utc, Duration};
|
use chrono::{Duration, Utc};
|
||||||
|
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::env;
|
||||||
|
use tera::Tera;
|
||||||
|
|
||||||
// JWT Claims structure
|
// JWT Claims structure
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@ -19,7 +20,8 @@ pub struct Claims {
|
|||||||
|
|
||||||
// JWT Secret key
|
// JWT Secret key
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref JWT_SECRET: String = std::env::var("JWT_SECRET").unwrap_or_else(|_| "your_jwt_secret_key".to_string());
|
static ref JWT_SECRET: String =
|
||||||
|
std::env::var("JWT_SECRET").unwrap_or_else(|_| "your_jwt_secret_key".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controller for handling authentication-related routes
|
/// Controller for handling authentication-related routes
|
||||||
@ -27,7 +29,10 @@ pub struct AuthController;
|
|||||||
|
|
||||||
impl AuthController {
|
impl AuthController {
|
||||||
/// Generate a JWT token for a user
|
/// Generate a JWT token for a user
|
||||||
pub fn generate_token(email: &str, role: &UserRole) -> Result<String, jsonwebtoken::errors::Error> {
|
pub fn generate_token(
|
||||||
|
email: &str,
|
||||||
|
role: &UserRole,
|
||||||
|
) -> Result<String, jsonwebtoken::errors::Error> {
|
||||||
let role_str = match role {
|
let role_str = match role {
|
||||||
UserRole::Admin => "admin",
|
UserRole::Admin => "admin",
|
||||||
UserRole::User => "user",
|
UserRole::User => "user",
|
||||||
@ -48,7 +53,7 @@ impl AuthController {
|
|||||||
encode(
|
encode(
|
||||||
&Header::default(),
|
&Header::default(),
|
||||||
&claims,
|
&claims,
|
||||||
&EncodingKey::from_secret(JWT_SECRET.as_bytes())
|
&EncodingKey::from_secret(JWT_SECRET.as_bytes()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,8 +66,12 @@ impl AuthController {
|
|||||||
/// Renders the login page
|
/// Renders the login page
|
||||||
pub async fn login_page(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
|
pub async fn login_page(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
|
||||||
let mut ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
|
let is_gitea_flow_active = env::var("GITEA_CLIENT_ID")
|
||||||
|
.ok()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.is_some();
|
||||||
|
ctx.insert("gitea_enabled", &is_gitea_flow_active);
|
||||||
ctx.insert("active_page", "login");
|
ctx.insert("active_page", "login");
|
||||||
|
|
||||||
// Add user to context if available
|
// Add user to context if available
|
||||||
if let Ok(Some(user_json)) = session.get::<String>("user") {
|
if let Ok(Some(user_json)) = session.get::<String>("user") {
|
||||||
// Keep the raw JSON for backward compatibility
|
// Keep the raw JSON for backward compatibility
|
||||||
@ -73,7 +82,7 @@ impl AuthController {
|
|||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
log::info!("Successfully parsed user in login_page: {:?}", user);
|
log::info!("Successfully parsed user in login_page: {:?}", user);
|
||||||
ctx.insert("user", &user);
|
ctx.insert("user", &user);
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to parse user JSON in login_page: {}", e);
|
log::error!("Failed to parse user JSON in login_page: {}", e);
|
||||||
log::error!("User JSON: {}", user_json);
|
log::error!("User JSON: {}", user_json);
|
||||||
@ -88,14 +97,11 @@ impl AuthController {
|
|||||||
pub async fn login(
|
pub async fn login(
|
||||||
form: web::Form<LoginCredentials>,
|
form: web::Form<LoginCredentials>,
|
||||||
session: Session,
|
session: Session,
|
||||||
_tmpl: web::Data<Tera>
|
_tmpl: web::Data<Tera>,
|
||||||
) -> Result<impl Responder> {
|
) -> Result<impl Responder> {
|
||||||
// For simplicity, always log in the user without checking credentials
|
// For simplicity, always log in the user without checking credentials
|
||||||
// Create a user object with admin role
|
// Create a user object with admin role
|
||||||
let mut test_user = User::new(
|
let mut test_user = User::new("Admin User".to_string(), form.email.clone());
|
||||||
"Admin User".to_string(),
|
|
||||||
form.email.clone()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set the ID and admin role
|
// Set the ID and admin role
|
||||||
test_user.id = Some(1);
|
test_user.id = Some(1);
|
||||||
@ -132,6 +138,12 @@ impl AuthController {
|
|||||||
let mut ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
ctx.insert("active_page", "register");
|
ctx.insert("active_page", "register");
|
||||||
|
|
||||||
|
let is_gitea_flow_active = env::var("GITEA_CLIENT_ID")
|
||||||
|
.ok()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.is_some();
|
||||||
|
ctx.insert("gitea_enabled", &is_gitea_flow_active);
|
||||||
|
|
||||||
// Add user to context if available
|
// Add user to context if available
|
||||||
if let Ok(Some(user_json)) = session.get::<String>("user") {
|
if let Ok(Some(user_json)) = session.get::<String>("user") {
|
||||||
// Keep the raw JSON for backward compatibility
|
// Keep the raw JSON for backward compatibility
|
||||||
@ -142,7 +154,7 @@ impl AuthController {
|
|||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
log::info!("Successfully parsed user in register_page: {:?}", user);
|
log::info!("Successfully parsed user in register_page: {:?}", user);
|
||||||
ctx.insert("user", &user);
|
ctx.insert("user", &user);
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to parse user JSON in register_page: {}", e);
|
log::error!("Failed to parse user JSON in register_page: {}", e);
|
||||||
log::error!("User JSON: {}", user_json);
|
log::error!("User JSON: {}", user_json);
|
||||||
@ -157,13 +169,10 @@ impl AuthController {
|
|||||||
pub async fn register(
|
pub async fn register(
|
||||||
form: web::Form<RegistrationData>,
|
form: web::Form<RegistrationData>,
|
||||||
session: Session,
|
session: Session,
|
||||||
_tmpl: web::Data<Tera>
|
_tmpl: web::Data<Tera>,
|
||||||
) -> Result<impl Responder> {
|
) -> Result<impl Responder> {
|
||||||
// Skip validation and always create an admin user
|
// Skip validation and always create an admin user
|
||||||
let mut user = User::new(
|
let mut user = User::new(form.name.clone(), form.email.clone());
|
||||||
form.name.clone(),
|
|
||||||
form.email.clone()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set the ID and admin role
|
// Set the ID and admin role
|
||||||
user.id = Some(1);
|
user.id = Some(1);
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse, Responder, Result, http::header, cookie::Cookie};
|
use actix_web::{web, HttpRequest, HttpResponse, Responder, Result, http::header, cookie::Cookie};
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
|
use oauth2::{AuthorizationCode, CsrfToken, Scope, TokenResponse};
|
||||||
|
use reqwest::Client;
|
||||||
|
use crate::config::oauth::GiteaOAuthConfig;
|
||||||
use crate::models::user::{User, UserRole};
|
use crate::models::user::{User, UserRole};
|
||||||
use crate::controllers::auth::AuthController;
|
use crate::controllers::auth::AuthController;
|
||||||
|
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
use oauth2::{AuthorizationCode, CsrfToken, Scope, TokenResponse};
|
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
use reqwest::Client;
|
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
use crate::config::oauth::GiteaOAuthConfig;
|
|
||||||
|
|
||||||
|
|
||||||
/// Controller for handling Gitea authentication
|
/// Controller for handling Gitea authentication
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
pub struct GiteaAuthController;
|
pub struct GiteaAuthController;
|
||||||
|
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
impl GiteaAuthController {
|
impl GiteaAuthController {
|
||||||
/// Initiate the OAuth flow
|
/// Initiate the OAuth flow
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
@ -166,14 +160,22 @@ impl GiteaAuthController {
|
|||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let user_info_url = format!("{}/api/v1/user", oauth_config.instance_url);
|
let user_info_url = format!("{}/api/v1/user", oauth_config.instance_url);
|
||||||
|
|
||||||
let gitea_user = client
|
log::info!("Gitea instance URL from config: {}", oauth_config.instance_url);
|
||||||
|
|
||||||
|
let access_token_secret = token.access_token().secret();
|
||||||
|
log::info!("Using access token for /api/v1/user: {}", access_token_secret);
|
||||||
|
|
||||||
|
let response = client
|
||||||
.get(&user_info_url)
|
.get(&user_info_url)
|
||||||
.bearer_auth(token.access_token().secret())
|
.bearer_auth(access_token_secret)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("API request error: {}", e)))?
|
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("API request error: {}", e)))?;
|
||||||
.json::<crate::config::oauth::GiteaUser>()
|
|
||||||
.await
|
let response_body = response.text().await.map_err(|e| actix_web::error::ErrorInternalServerError(format!("Failed to get response body: {}", e)))?;
|
||||||
|
log::info!("Raw Gitea user info response: {}", response_body);
|
||||||
|
|
||||||
|
let gitea_user: crate::config::oauth::GiteaUser = serde_json::from_str(&response_body)
|
||||||
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("JSON parsing error: {}", e)))?;
|
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("JSON parsing error: {}", e)))?;
|
||||||
|
|
||||||
// Create or update the user in your system
|
// Create or update the user in your system
|
||||||
@ -213,7 +215,6 @@ impl GiteaAuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Query parameters for the OAuth callback
|
/// Query parameters for the OAuth callback
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
pub struct CallbackQuery {
|
pub struct CallbackQuery {
|
||||||
pub code: String,
|
pub code: String,
|
||||||
|
@ -2,6 +2,7 @@ use actix_web::{web, Result, Responder};
|
|||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
use crate::utils::render_template;
|
use crate::utils::render_template;
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
/// Controller for handling home-related routes
|
/// Controller for handling home-related routes
|
||||||
pub struct HomeController;
|
pub struct HomeController;
|
||||||
@ -12,6 +13,12 @@ impl HomeController {
|
|||||||
let mut ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
ctx.insert("active_page", "home");
|
ctx.insert("active_page", "home");
|
||||||
|
|
||||||
|
let is_gitea_flow_active = env::var("GITEA_CLIENT_ID")
|
||||||
|
.ok()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.is_some();
|
||||||
|
ctx.insert("gitea_enabled", &is_gitea_flow_active);
|
||||||
|
|
||||||
// Add user to context if available
|
// Add user to context if available
|
||||||
if let Ok(Some(user_json)) = session.get::<String>("user") {
|
if let Ok(Some(user_json)) = session.get::<String>("user") {
|
||||||
// Keep the raw JSON for backward compatibility
|
// Keep the raw JSON for backward compatibility
|
||||||
@ -36,6 +43,11 @@ impl HomeController {
|
|||||||
/// Renders the about page
|
/// Renders the about page
|
||||||
pub async fn about(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
|
pub async fn about(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
|
||||||
let mut ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
|
let is_gitea_flow_active = env::var("GITEA_CLIENT_ID")
|
||||||
|
.ok()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.is_some();
|
||||||
|
ctx.insert("gitea_enabled", &is_gitea_flow_active);
|
||||||
ctx.insert("active_page", "about");
|
ctx.insert("active_page", "about");
|
||||||
|
|
||||||
// Add user to context if available
|
// Add user to context if available
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// Export controllers
|
// Export controllers
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
pub mod gitea_auth;
|
pub mod gitea_auth;
|
||||||
pub mod home;
|
pub mod home;
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
#[cfg(feature = "gitea")]
|
|
||||||
use crate::config::oauth::GiteaOAuthConfig;
|
use crate::config::oauth::GiteaOAuthConfig;
|
||||||
use crate::controllers::auth::AuthController;
|
use crate::controllers::auth::AuthController;
|
||||||
use crate::controllers::debug::DebugController;
|
use crate::controllers::debug::DebugController;
|
||||||
#[cfg(feature = "gitea")]
|
|
||||||
use crate::controllers::gitea_auth::GiteaAuthController;
|
use crate::controllers::gitea_auth::GiteaAuthController;
|
||||||
use crate::controllers::home::HomeController;
|
use crate::controllers::home::HomeController;
|
||||||
use crate::middleware::JwtAuth;
|
use crate::middleware::JwtAuth;
|
||||||
use crate::SESSION_KEY;
|
use crate::SESSION_KEY;
|
||||||
use actix_session::{storage::CookieSessionStore, SessionMiddleware};
|
use actix_session::{storage::CookieSessionStore, SessionMiddleware};
|
||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
/// Configures all application routes
|
/// Configures all application routes
|
||||||
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
|
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
|
||||||
@ -26,35 +25,44 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
|
|||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let mut scope = web::scope("")
|
// Build the main scope with common routes
|
||||||
.wrap(session_middleware)
|
let mut main_scope = web::scope("")
|
||||||
|
.wrap(session_middleware) // Wrap with session middleware
|
||||||
// Home routes
|
// Home routes
|
||||||
.route("/", web::get().to(HomeController::index))
|
.route("/", web::get().to(HomeController::index))
|
||||||
.route("/about", web::get().to(HomeController::about))
|
.route("/about", web::get().to(HomeController::about));
|
||||||
// Auth routes
|
|
||||||
.route("/login", web::get().to(AuthController::login_page))
|
|
||||||
.route("/login", web::post().to(AuthController::login))
|
|
||||||
.route("/register", web::get().to(AuthController::register_page))
|
|
||||||
.route("/register", web::post().to(AuthController::register))
|
|
||||||
.route("/logout", web::get().to(AuthController::logout))
|
|
||||||
// Debug routes
|
|
||||||
.route("/debug", web::get().to(DebugController::debug_info));
|
|
||||||
|
|
||||||
#[cfg(feature = "gitea")]
|
// Conditionally add authentication routes based on GITEA_CLIENT_ID environment variable
|
||||||
{
|
if env::var("GITEA_CLIENT_ID").ok().filter(|s| !s.is_empty()).is_some() {
|
||||||
// Create the OAuth configuration
|
// Use Gitea OAuth flow
|
||||||
|
// Create the OAuth configuration and add it to the scope
|
||||||
let oauth_config = web::Data::new(GiteaOAuthConfig::new());
|
let oauth_config = web::Data::new(GiteaOAuthConfig::new());
|
||||||
// Gitea OAuth configuration and routes
|
main_scope = main_scope
|
||||||
scope = scope
|
.app_data(oauth_config) // Add oauth_config data
|
||||||
.app_data(oauth_config.clone())
|
// Gitea OAuth routes
|
||||||
|
.route("/login", web::get().to(GiteaAuthController::login)) // Add /login route for gitea
|
||||||
.route("/auth/gitea", web::get().to(GiteaAuthController::login))
|
.route("/auth/gitea", web::get().to(GiteaAuthController::login))
|
||||||
.route(
|
.route(
|
||||||
"/auth/gitea/callback",
|
"/auth/gitea/callback",
|
||||||
web::get().to(GiteaAuthController::callback),
|
web::get().to(GiteaAuthController::callback),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Use standard username/password login
|
||||||
|
main_scope = main_scope
|
||||||
|
.route("/login", web::get().to(AuthController::login_page))
|
||||||
|
.route("/login", web::post().to(AuthController::login))
|
||||||
|
.route("/register", web::get().to(AuthController::register_page))
|
||||||
|
.route("/register", web::post().to(AuthController::register));
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.service(scope);
|
// Add common auth and debug routes (logout is common to both flows)
|
||||||
|
main_scope = main_scope
|
||||||
|
.route("/logout", web::get().to(AuthController::logout))
|
||||||
|
// Debug routes
|
||||||
|
.route("/debug", web::get().to(DebugController::debug_info));
|
||||||
|
|
||||||
|
// Register the main scope service
|
||||||
|
cfg.service(main_scope);
|
||||||
|
|
||||||
// Protected routes that require authentication
|
// Protected routes that require authentication
|
||||||
cfg.service(
|
cfg.service(
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %} {% block title %}Login - Hostbasket{% endblock %} {%
|
||||||
|
block content %}
|
||||||
{% block title %}Login - Hostbasket{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@ -11,39 +8,67 @@
|
|||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
{% if not gitea_enabled %}
|
||||||
<form method="post" action="/login">
|
<form method="post" action="/login">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="email" class="form-label">Email</label>
|
<label for="email" class="form-label">Email</label>
|
||||||
<input type="email" class="form-control" id="email" name="email" required>
|
<input
|
||||||
|
type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="password" class="form-label">Password</label>
|
<label for="password" class="form-label"
|
||||||
<input type="password" class="form-control" id="password" name="password" required>
|
>Password</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Login</button>
|
<button type="submit" class="btn btn-primary">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
<hr>
|
<hr />
|
||||||
|
|
||||||
{% if gitea_enabled %}
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p>Or login with:</p>
|
<p>Or login with:</p>
|
||||||
<a href="/auth/gitea" class="btn btn-secondary">
|
<a href="/auth/gitea" class="btn btn-secondary">
|
||||||
<img src="/static/images/gitea-logo.svg" alt="Gitea" width="20" height="20"
|
<img
|
||||||
style="margin-right: 5px;">
|
src="/static/images/gitea-logo.svg"
|
||||||
|
alt="Gitea"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
style="margin-right: 5px"
|
||||||
|
/>
|
||||||
Login with Gitea
|
Login with Gitea
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr>
|
<hr />
|
||||||
|
|
||||||
|
{% if not gitea_enabled %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p>Don't have an account? <a href="/register">Register</a></p>
|
<p>
|
||||||
|
Don't have an account?
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %} {% block title %}Register - Hostbasket{% endblock %}
|
||||||
|
|
||||||
{% block title %}Register - Hostbasket{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
@ -14,45 +11,65 @@
|
|||||||
<form method="post" action="/register">
|
<form method="post" action="/register">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="name" class="form-label">Name</label>
|
<label for="name" class="form-label">Name</label>
|
||||||
<input type="text" class="form-control" id="name" name="name" required>
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="email" class="form-label">Email</label>
|
<label for="email" class="form-label">Email</label>
|
||||||
<input type="email" class="form-control" id="email" name="email" required>
|
<input
|
||||||
|
type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="password" class="form-label">Password</label>
|
<label for="password" class="form-label"
|
||||||
<input type="password" class="form-control" id="password" name="password" required>
|
>Password</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="password_confirmation" class="form-label">Confirm Password</label>
|
<label
|
||||||
<input type="password" class="form-control" id="password_confirmation"
|
for="password_confirmation"
|
||||||
name="password_confirmation" required>
|
class="form-label"
|
||||||
|
>Confirm Password</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password_confirmation"
|
||||||
|
name="password_confirmation"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Register</button>
|
<button type="submit" class="btn btn-primary">
|
||||||
|
Register
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<hr>
|
<hr />
|
||||||
|
|
||||||
{% if gitea_enabled %}
|
|
||||||
<div class="text-center">
|
|
||||||
<p>Or register with:</p>
|
|
||||||
<a href="/auth/gitea" class="btn btn-secondary">
|
|
||||||
<img src="/static/images/gitea-logo.svg" alt="Gitea" width="20" height="20"
|
|
||||||
style="margin-right: 5px;">
|
|
||||||
Register with Gitea
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p>Already have an account? <a href="/login">Login</a></p>
|
<p>
|
||||||
|
Already have an account? <a href="/login">Login</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -43,11 +43,13 @@
|
|||||||
<a class="nav-link {{ active_class(current=active_page, page=" login") }}"
|
<a class="nav-link {{ active_class(current=active_page, page=" login") }}"
|
||||||
href="/login">Login</a>
|
href="/login">Login</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if gitea_enabled == false %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {{ active_class(current=active_page, page=" register") }}"
|
<a class="nav-link {{ active_class(current=active_page, page=" register") }}"
|
||||||
href="/register">Register</a>
|
href="/register">Register</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -61,6 +63,7 @@
|
|||||||
<summary>Debug Info</summary>
|
<summary>Debug Info</summary>
|
||||||
<pre>user_json: {{ user_json }}</pre>
|
<pre>user_json: {{ user_json }}</pre>
|
||||||
<pre>user object exists: {{ user is defined }}</pre>
|
<pre>user object exists: {{ user is defined }}</pre>
|
||||||
|
<pre>is_gitea_flow_active: {{ gitea_enabled }}</pre>
|
||||||
{% if user is defined %}
|
{% if user is defined %}
|
||||||
<pre>user.name: {{ user.name }}</pre>
|
<pre>user.name: {{ user.name }}</pre>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -81,4 +84,4 @@
|
|||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
4
start.sh
Normal file → Executable file
4
start.sh
Normal file → Executable file
@ -4,5 +4,7 @@
|
|||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
export SECRET_KEY=1234
|
export SECRET_KEY=1234
|
||||||
|
export GITEA_CLIENT_ID=""
|
||||||
|
export GITEA_CLIENT_SECRET=""
|
||||||
|
export GITEA_INSTANCE_URL="https://git.ourworld.tf"
|
||||||
cargo run
|
cargo run
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
# Get the directory of the script and change to it
|
# Get the directory of the script and change to it
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
export GITEA_CLIENT_ID="your_client_id"
|
export GITEA_CLIENT_ID="9f409b35-6258-4ac3-8370-05adc187c1f5"
|
||||||
export GITEA_CLIENT_SECRET="your_client_secret"
|
export GITEA_CLIENT_SECRET="gto_4s77ae33m5ernlf2423wx6wjyyqatqoe567rym7fcu3sqmu5azea"
|
||||||
export GITEA_INSTANCE_URL="https://gitea.example.com"
|
export GITEA_INSTANCE_URL="https://git.ourworld.tf"
|
||||||
export APP_URL="http://localhost:9999"
|
export APP_URL="http://localhost:9999"
|
||||||
cargo run --features gitea
|
cargo run
|
||||||
|
Loading…
Reference in New Issue
Block a user