# Gitea Authentication Guide 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 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. ## Prerequisites Before you begin, ensure you have: - A running Gitea instance - Administrative access to your Gitea instance - A running Hostbasket application ## Setting Up Gitea OAuth Application 1. Log in to your Gitea instance as an administrator 2. Navigate to **Site Administration** > **Applications** 3. Click **Create a New OAuth2 Application** 4. Fill in the application details: - **Application Name**: Hostbasket - **Redirect URI**: `http://localhost:9999/auth/gitea/callback` (adjust the URL to match your Hostbasket instance) - **Confidential Client**: Check this box 5. Click **Create Application** 6. Note the **Client ID** and **Client Secret** that are generated ## Configuring Hostbasket for Gitea Authentication 1. Add the following environment variables to your Hostbasket configuration: ```bash export GITEA_CLIENT_ID=your_client_id export GITEA_CLIENT_SECRET=your_client_secret export GITEA_INSTANCE_URL=https://your-gitea-instance.com ``` 2. Alternatively, add these settings to your configuration file (`config/default.toml`): ```toml [oauth.gitea] client_id = "your_client_id" client_secret = "your_client_secret" instance_url = "https://your-gitea-instance.com" ``` ## Implementing the OAuth Flow To implement the OAuth flow, you need to add new routes and controllers to your Hostbasket application. ### 1. Add Required Dependencies Add the following dependencies to your `Cargo.toml`: ```toml [dependencies] oauth2 = "4.3" reqwest = { version = "0.11", features = ["json"] } ``` ### 2. Create a Gitea OAuth Configuration Module Create a new file `src/config/oauth.rs`: ```rust use oauth2::{ AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, basic::BasicClient, AuthorizationCode, CsrfToken, Scope, TokenResponse, }; use serde::{Deserialize, Serialize}; use std::env; #[derive(Clone, Debug)] pub struct GiteaOAuthConfig { pub client: BasicClient, pub instance_url: String, } impl GiteaOAuthConfig { pub fn new() -> Self { // Get configuration from environment variables let client_id = env::var("GITEA_CLIENT_ID") .expect("Missing GITEA_CLIENT_ID environment variable"); let client_secret = env::var("GITEA_CLIENT_SECRET") .expect("Missing GITEA_CLIENT_SECRET environment variable"); let instance_url = env::var("GITEA_INSTANCE_URL") .expect("Missing GITEA_INSTANCE_URL environment variable"); // Create OAuth client let auth_url = format!("{}/login/oauth/authorize", instance_url); let token_url = format!("{}/login/oauth/access_token", instance_url); let client = BasicClient::new( ClientId::new(client_id), Some(ClientSecret::new(client_secret)), AuthUrl::new(auth_url).unwrap(), Some(TokenUrl::new(token_url).unwrap()), ) .set_redirect_uri( RedirectUrl::new("http://localhost:9999/auth/gitea/callback".to_string()).unwrap(), ); Self { client, instance_url, } } } // Gitea user information structure #[derive(Debug, Deserialize, Serialize)] pub struct GiteaUser { pub id: i64, pub login: String, pub full_name: String, pub email: String, pub avatar_url: String, } ``` ### 3. Create a Gitea Authentication Controller Create a new file `src/controllers/gitea_auth.rs`: ```rust use actix_web::{web, HttpResponse, Responder, Result, http::header, cookie::Cookie}; 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::controllers::auth::AuthController; use serde_json::json; pub struct GiteaAuthController; impl GiteaAuthController { // Initiate the OAuth flow pub async fn login( oauth_config: web::Data, session: Session, ) -> Result { // Generate the authorization URL let (auth_url, csrf_token) = oauth_config .client .authorize_url(CsrfToken::new_random) .add_scope(Scope::new("read:user".to_string())) .add_scope(Scope::new("user:email".to_string())) .url(); // Store the CSRF token in the session session.insert("oauth_csrf_token", csrf_token.secret())?; // Redirect to the authorization URL Ok(HttpResponse::Found() .append_header((header::LOCATION, auth_url.to_string())) .finish()) } // Handle the OAuth callback pub async fn callback( oauth_config: web::Data, session: Session, query: web::Query, ) -> Result { // Verify the CSRF token let csrf_token = session.get::("oauth_csrf_token")? .ok_or_else(|| actix_web::error::ErrorBadRequest("Missing CSRF token"))?; if csrf_token != query.state { return Err(actix_web::error::ErrorBadRequest("Invalid CSRF token")); } // Exchange the authorization code for an access token let token = oauth_config .client .exchange_code(AuthorizationCode::new(query.code.clone())) .request_async(oauth2::reqwest::async_http_client) .await .map_err(|e| actix_web::error::ErrorInternalServerError(format!("Token exchange error: {}", e)))?; // Get the user information from Gitea let client = Client::new(); let user_info_url = format!("{}/api/v1/user", oauth_config.instance_url); let gitea_user = client .get(&user_info_url) .bearer_auth(token.access_token().secret()) .send() .await .map_err(|e| actix_web::error::ErrorInternalServerError(format!("API request error: {}", e)))? .json::() .await .map_err(|e| actix_web::error::ErrorInternalServerError(format!("JSON parsing error: {}", e)))?; // Create or update the user in your system let mut user = User::new( gitea_user.full_name.clone(), gitea_user.email.clone(), ); // Set the user ID and role user.id = Some(gitea_user.id as i32); user.role = UserRole::User; // Generate JWT token let token = AuthController::generate_token(&user.email, &user.role) .map_err(|_| actix_web::error::ErrorInternalServerError("Failed to generate token"))?; // Store user data in session let user_json = serde_json::to_string(&user).unwrap(); session.insert("user", &user_json)?; session.insert("auth_token", &token)?; // Create a cookie with the JWT token let cookie = Cookie::build("auth_token", token) .path("/") .http_only(true) .secure(false) // Set to true in production with HTTPS .max_age(actix_web::cookie::time::Duration::hours(24)) .finish(); // Redirect to the home page with JWT token in cookie Ok(HttpResponse::Found() .cookie(cookie) .append_header((header::LOCATION, "/")) .finish()) } } // Query parameters for the OAuth callback #[derive(serde::Deserialize)] pub struct CallbackQuery { pub code: String, pub state: String, } ``` ### 4. Update the Routes Configuration Update your `src/routes/mod.rs` file to include the new Gitea authentication routes: ```rust use crate::controllers::gitea_auth::GiteaAuthController; use crate::config::oauth::GiteaOAuthConfig; pub fn configure_routes(cfg: &mut web::ServiceConfig) { // Create the OAuth configuration let oauth_config = web::Data::new(GiteaOAuthConfig::new()); // Configure session middleware with the consistent key let session_middleware = SessionMiddleware::builder( CookieSessionStore::default(), SESSION_KEY.clone() ) .cookie_secure(false) // Set to true in production with HTTPS .build(); // Public routes that don't require authentication cfg.service( web::scope("") .wrap(session_middleware) .app_data(oauth_config.clone()) // Existing routes... // Gitea OAuth routes .route("/auth/gitea", web::get().to(GiteaAuthController::login)) .route("/auth/gitea/callback", web::get().to(GiteaAuthController::callback)) ); } ``` ### 5. Update the Login Page Update your login page template (`src/views/auth/login.html`) to include a "Login with Gitea" button: ```html {% extends "base.html" %} {% block title %}Login{% endblock %} {% block content %}

Login


Or login with:

Gitea Login with Gitea
{% endblock %} ``` ## Testing the Integration 1. Start your Hostbasket application 2. Navigate to the login page 3. Click the "Login with Gitea" button 4. You should be redirected to your Gitea instance's authorization page 5. Authorize the application 6. You should be redirected back to your Hostbasket application and logged in ## Troubleshooting ### Common Issues 1. **Redirect URI Mismatch**: - Error: `The redirect URI provided is missing or does not match` - Solution: Ensure that the redirect URI in your Gitea OAuth application settings matches the one in your code. 2. **Invalid Client ID or Secret**: - Error: `Invalid client_id or client_secret` - Solution: Double-check your client ID and secret. 3. **CSRF Token Mismatch**: - Error: `Invalid CSRF token` - Solution: Ensure that your session is properly configured and that the CSRF token is being stored and retrieved correctly. 4. **API Request Errors**: - Error: `API request error` - Solution: Check your Gitea instance URL and ensure that the API is accessible. 5. **JSON Parsing Errors**: - Error: `JSON parsing error` - Solution: Ensure that the API response matches the expected structure.