- Add a comprehensive .gitignore to manage project files. - Create the basic project structure including Cargo.toml, LICENSE, and README.md. - Add basic project documentation.
12 KiB
Gitea Authentication Guide
This guide provides detailed instructions on how to integrate Gitea authentication into your Hostbasket application.
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
- Log in to your Gitea instance as an administrator
- Navigate to Site Administration > Integrations > Applications
- Click Create a New OAuth2 Application
- 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
- Click Create Application
- Note the Client ID and Client Secret that are generated
Configuring Hostbasket for Gitea Authentication
- Add the following environment variables to your Hostbasket configuration:
export GITEA_CLIENT_ID=your_client_id
export GITEA_CLIENT_SECRET=your_client_secret
export GITEA_INSTANCE_URL=https://your-gitea-instance.com
- Alternatively, add these settings to your configuration file (
config/default.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
:
[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
:
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
:
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<GiteaOAuthConfig>,
session: Session,
) -> Result<impl Responder> {
// 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<GiteaOAuthConfig>,
session: Session,
query: web::Query<CallbackQuery>,
) -> Result<impl Responder> {
// Verify the CSRF token
let csrf_token = session.get::<String>("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::<crate::config::oauth::GiteaUser>()
.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:
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:
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card mt-5">
<div class="card-header">
<h2>Login</h2>
</div>
<div class="card-body">
<form method="post" action="/login">
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
<hr>
<div class="text-center">
<p>Or login with:</p>
<a href="/auth/gitea" class="btn btn-secondary">
<img src="/static/images/gitea-logo.svg" alt="Gitea" width="20" height="20">
Login with Gitea
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
Testing the Integration
- Start your Hostbasket application
- Navigate to the login page
- Click the "Login with Gitea" button
- You should be redirected to your Gitea instance's authorization page
- Authorize the application
- You should be redirected back to your Hostbasket application and logged in
Troubleshooting
Common Issues
-
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.
- Error:
-
Invalid Client ID or Secret:
- Error:
Invalid client_id or client_secret
- Solution: Double-check your client ID and secret.
- Error:
-
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.
- Error:
-
API Request Errors:
- Error:
API request error
- Solution: Check your Gitea instance URL and ensure that the API is accessible.
- Error:
-
JSON Parsing Errors:
- Error:
JSON parsing error
- Solution: Ensure that the API response matches the expected structure.
- Error: