mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-01-29 22:00:59 +03:00
Added SSO and OIDC
This commit is contained in:
176
src/pages/api/sso/applications.ts
Normal file
176
src/pages/api/sso/applications.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import type { APIContext } from "astro";
|
||||
import { createSecureErrorResponse } from "@/lib/utils";
|
||||
import { requireAuth } from "@/lib/utils/auth-helpers";
|
||||
import { db, oauthApplications } from "@/lib/db";
|
||||
import { nanoid } from "nanoid";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { generateRandomString } from "@/lib/utils";
|
||||
|
||||
// GET /api/sso/applications - List all OAuth applications
|
||||
export async function GET(context: APIContext) {
|
||||
try {
|
||||
const { user, response } = await requireAuth(context);
|
||||
if (response) return response;
|
||||
|
||||
const applications = await db.select().from(oauthApplications);
|
||||
|
||||
// Don't send client secrets in list response
|
||||
const sanitizedApps = applications.map(app => ({
|
||||
...app,
|
||||
clientSecret: undefined,
|
||||
}));
|
||||
|
||||
return new Response(JSON.stringify(sanitizedApps), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return createSecureErrorResponse(error, "SSO applications API");
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/sso/applications - Create a new OAuth application
|
||||
export async function POST(context: APIContext) {
|
||||
try {
|
||||
const { user, response } = await requireAuth(context);
|
||||
if (response) return response;
|
||||
|
||||
const body = await context.request.json();
|
||||
const { name, redirectURLs, type = "web", metadata } = body;
|
||||
|
||||
// Validate required fields
|
||||
if (!name || !redirectURLs || redirectURLs.length === 0) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Name and at least one redirect URL are required" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Generate client credentials
|
||||
const clientId = `client_${generateRandomString(32)}`;
|
||||
const clientSecret = `secret_${generateRandomString(48)}`;
|
||||
|
||||
// Insert new application
|
||||
const [newApp] = await db
|
||||
.insert(oauthApplications)
|
||||
.values({
|
||||
id: nanoid(),
|
||||
clientId,
|
||||
clientSecret,
|
||||
name,
|
||||
redirectURLs: Array.isArray(redirectURLs) ? redirectURLs.join(",") : redirectURLs,
|
||||
type,
|
||||
metadata: metadata ? JSON.stringify(metadata) : null,
|
||||
userId: user.id,
|
||||
disabled: false,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return new Response(JSON.stringify(newApp), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return createSecureErrorResponse(error, "SSO applications API");
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/sso/applications/:id - Update an OAuth application
|
||||
export async function PUT(context: APIContext) {
|
||||
try {
|
||||
const { user, response } = await requireAuth(context);
|
||||
if (response) return response;
|
||||
|
||||
const url = new URL(context.request.url);
|
||||
const appId = url.pathname.split("/").pop();
|
||||
|
||||
if (!appId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Application ID is required" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const body = await context.request.json();
|
||||
const { name, redirectURLs, disabled, metadata } = body;
|
||||
|
||||
const updateData: any = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (redirectURLs !== undefined) {
|
||||
updateData.redirectURLs = Array.isArray(redirectURLs)
|
||||
? redirectURLs.join(",")
|
||||
: redirectURLs;
|
||||
}
|
||||
if (disabled !== undefined) updateData.disabled = disabled;
|
||||
if (metadata !== undefined) updateData.metadata = JSON.stringify(metadata);
|
||||
|
||||
const [updated] = await db
|
||||
.update(oauthApplications)
|
||||
.set({
|
||||
...updateData,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(oauthApplications.id, appId))
|
||||
.returning();
|
||||
|
||||
if (!updated) {
|
||||
return new Response(JSON.stringify({ error: "Application not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ ...updated, clientSecret: undefined }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return createSecureErrorResponse(error, "SSO applications API");
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/sso/applications/:id - Delete an OAuth application
|
||||
export async function DELETE(context: APIContext) {
|
||||
try {
|
||||
const { user, response } = await requireAuth(context);
|
||||
if (response) return response;
|
||||
|
||||
const url = new URL(context.request.url);
|
||||
const appId = url.searchParams.get("id");
|
||||
|
||||
if (!appId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Application ID is required" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const deleted = await db
|
||||
.delete(oauthApplications)
|
||||
.where(eq(oauthApplications.id, appId))
|
||||
.returning();
|
||||
|
||||
if (deleted.length === 0) {
|
||||
return new Response(JSON.stringify({ error: "Application not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return createSecureErrorResponse(error, "SSO applications API");
|
||||
}
|
||||
}
|
||||
69
src/pages/api/sso/discover.ts
Normal file
69
src/pages/api/sso/discover.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { APIContext } from "astro";
|
||||
import { createSecureErrorResponse } from "@/lib/utils";
|
||||
import { requireAuth } from "@/lib/utils/auth-helpers";
|
||||
|
||||
// POST /api/sso/discover - Discover OIDC configuration from issuer URL
|
||||
export async function POST(context: APIContext) {
|
||||
try {
|
||||
const { user, response } = await requireAuth(context);
|
||||
if (response) return response;
|
||||
|
||||
const { issuer } = await context.request.json();
|
||||
|
||||
if (!issuer) {
|
||||
return new Response(JSON.stringify({ error: "Issuer URL is required" }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure issuer URL ends without trailing slash for well-known discovery
|
||||
const cleanIssuer = issuer.replace(/\/$/, "");
|
||||
const discoveryUrl = `${cleanIssuer}/.well-known/openid-configuration`;
|
||||
|
||||
try {
|
||||
// Fetch OIDC discovery document
|
||||
const response = await fetch(discoveryUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch discovery document: ${response.status}`);
|
||||
}
|
||||
|
||||
const config = await response.json();
|
||||
|
||||
// Extract the essential endpoints
|
||||
const discoveredConfig = {
|
||||
issuer: config.issuer || cleanIssuer,
|
||||
authorizationEndpoint: config.authorization_endpoint,
|
||||
tokenEndpoint: config.token_endpoint,
|
||||
userInfoEndpoint: config.userinfo_endpoint,
|
||||
jwksEndpoint: config.jwks_uri,
|
||||
// Additional useful fields
|
||||
scopes: config.scopes_supported || ["openid", "profile", "email"],
|
||||
responseTypes: config.response_types_supported || ["code"],
|
||||
grantTypes: config.grant_types_supported || ["authorization_code"],
|
||||
// Suggested domain from issuer
|
||||
suggestedDomain: new URL(cleanIssuer).hostname.replace("www.", ""),
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(discoveredConfig), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("OIDC discovery error:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "Failed to discover OIDC configuration",
|
||||
details: error instanceof Error ? error.message : "Unknown error"
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return createSecureErrorResponse(error, "SSO discover API");
|
||||
}
|
||||
}
|
||||
152
src/pages/api/sso/providers.ts
Normal file
152
src/pages/api/sso/providers.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import type { APIContext } from "astro";
|
||||
import { createSecureErrorResponse } from "@/lib/utils";
|
||||
import { requireAuth } from "@/lib/utils/auth-helpers";
|
||||
import { db, ssoProviders } from "@/lib/db";
|
||||
import { nanoid } from "nanoid";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
// GET /api/sso/providers - List all SSO providers
|
||||
export async function GET(context: APIContext) {
|
||||
try {
|
||||
const { user, response } = await requireAuth(context);
|
||||
if (response) return response;
|
||||
|
||||
const providers = await db.select().from(ssoProviders);
|
||||
|
||||
return new Response(JSON.stringify(providers), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return createSecureErrorResponse(error, "SSO providers API");
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/sso/providers - Create a new SSO provider
|
||||
export async function POST(context: APIContext) {
|
||||
try {
|
||||
const { user, response } = await requireAuth(context);
|
||||
if (response) return response;
|
||||
|
||||
const body = await context.request.json();
|
||||
const {
|
||||
issuer,
|
||||
domain,
|
||||
clientId,
|
||||
clientSecret,
|
||||
authorizationEndpoint,
|
||||
tokenEndpoint,
|
||||
jwksEndpoint,
|
||||
userInfoEndpoint,
|
||||
mapping,
|
||||
providerId,
|
||||
organizationId,
|
||||
} = body;
|
||||
|
||||
// Validate required fields
|
||||
if (!issuer || !domain || !providerId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Missing required fields" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Check if provider ID already exists
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(ssoProviders)
|
||||
.where(eq(ssoProviders.providerId, providerId))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Provider ID already exists" }),
|
||||
{
|
||||
status: 409,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Create OIDC config object
|
||||
const oidcConfig = {
|
||||
clientId,
|
||||
clientSecret,
|
||||
authorizationEndpoint,
|
||||
tokenEndpoint,
|
||||
jwksEndpoint,
|
||||
userInfoEndpoint,
|
||||
mapping: mapping || {
|
||||
id: "sub",
|
||||
email: "email",
|
||||
emailVerified: "email_verified",
|
||||
name: "name",
|
||||
image: "picture",
|
||||
},
|
||||
};
|
||||
|
||||
// Insert new provider
|
||||
const [newProvider] = await db
|
||||
.insert(ssoProviders)
|
||||
.values({
|
||||
id: nanoid(),
|
||||
issuer,
|
||||
domain,
|
||||
oidcConfig: JSON.stringify(oidcConfig),
|
||||
userId: user.id,
|
||||
providerId,
|
||||
organizationId,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return new Response(JSON.stringify(newProvider), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return createSecureErrorResponse(error, "SSO providers API");
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/sso/providers - Delete a provider by ID
|
||||
export async function DELETE(context: APIContext) {
|
||||
try {
|
||||
const { user, response } = await requireAuth(context);
|
||||
if (response) return response;
|
||||
|
||||
const url = new URL(context.request.url);
|
||||
const providerId = url.searchParams.get("id");
|
||||
|
||||
if (!providerId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Provider ID is required" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const deleted = await db
|
||||
.delete(ssoProviders)
|
||||
.where(eq(ssoProviders.id, providerId))
|
||||
.returning();
|
||||
|
||||
if (deleted.length === 0) {
|
||||
return new Response(JSON.stringify({ error: "Provider not found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return createSecureErrorResponse(error, "SSO providers API");
|
||||
}
|
||||
}
|
||||
467
src/pages/docs/advanced.astro
Normal file
467
src/pages/docs/advanced.astro
Normal file
@@ -0,0 +1,467 @@
|
||||
---
|
||||
import MainLayout from '../../layouts/main.astro';
|
||||
---
|
||||
|
||||
<MainLayout title="Advanced Topics - Gitea Mirror">
|
||||
<main class="max-w-5xl mx-auto px-4 py-12">
|
||||
<div class="sticky top-4 z-10 mb-6">
|
||||
<a
|
||||
href="/docs/"
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-card text-foreground hover:bg-muted transition-colors border border-border focus:ring-2 focus:ring-ring outline-none"
|
||||
>
|
||||
<span aria-hidden="true">←</span> Back to Documentation
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<article class="bg-card rounded-2xl shadow-lg p-6 md:p-8 border border-border">
|
||||
<!-- Header -->
|
||||
<div class="mb-12 space-y-4">
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground mb-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
<span>Advanced</span>
|
||||
</div>
|
||||
<h1 class="text-4xl font-bold tracking-tight">Advanced Topics</h1>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed max-w-4xl">
|
||||
Advanced configuration options, deployment strategies, troubleshooting, and performance optimization for Gitea Mirror.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Environment Variables -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Environment Variables</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">
|
||||
Gitea Mirror can be configured using environment variables. These are particularly useful for containerized deployments.
|
||||
</p>
|
||||
|
||||
<div class="bg-muted/30 rounded-lg overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-border">
|
||||
<th class="text-left p-3 font-semibold">Variable</th>
|
||||
<th class="text-left p-3 font-semibold">Description</th>
|
||||
<th class="text-left p-3 font-semibold">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{[
|
||||
{ var: 'NODE_ENV', desc: 'Application environment', default: 'production' },
|
||||
{ var: 'PORT', desc: 'Server port', default: '4321' },
|
||||
{ var: 'HOST', desc: 'Server host', default: '0.0.0.0' },
|
||||
{ var: 'BETTER_AUTH_SECRET', desc: 'Authentication secret key', default: 'Auto-generated' },
|
||||
{ var: 'BETTER_AUTH_URL', desc: 'Authentication base URL', default: 'http://localhost:4321' },
|
||||
{ var: 'NODE_EXTRA_CA_CERTS', desc: 'Path to CA certificate file', default: 'None' },
|
||||
{ var: 'DATABASE_URL', desc: 'SQLite database path', default: './data/gitea-mirror.db' },
|
||||
].map((item, i) => (
|
||||
<tr class={i % 2 === 0 ? 'bg-muted/20' : ''}>
|
||||
<td class="p-3 font-mono text-xs">{item.var}</td>
|
||||
<td class="p-3">{item.desc}</td>
|
||||
<td class="p-3 text-muted-foreground">{item.default}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Database Management -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Database Management</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">
|
||||
Gitea Mirror uses SQLite for data storage. The database is automatically created on first run.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Database Commands</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<h4 class="font-semibold mb-2">Initialize Database</h4>
|
||||
<div class="bg-muted/30 rounded p-3 mb-2">
|
||||
<code class="text-sm">bun run init-db</code>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">Creates or recreates the database schema</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<h4 class="font-semibold mb-2">Check Database</h4>
|
||||
<div class="bg-muted/30 rounded p-3 mb-2">
|
||||
<code class="text-sm">bun run check-db</code>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">Verifies database integrity and displays statistics</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<h4 class="font-semibold mb-2">Fix Database</h4>
|
||||
<div class="bg-muted/30 rounded p-3 mb-2">
|
||||
<code class="text-sm">bun run fix-db</code>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">Attempts to repair common database issues</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<h4 class="font-semibold mb-2">Backup Database</h4>
|
||||
<div class="bg-muted/30 rounded p-3 mb-2">
|
||||
<code class="text-sm">cp data/gitea-mirror.db data/gitea-mirror.db.backup</code>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">Always backup before major changes</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4 mt-8">Database Schema Management</h3>
|
||||
|
||||
<div class="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
|
||||
<div class="flex gap-3">
|
||||
<div class="text-blue-600 dark:text-blue-500">
|
||||
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-blue-600 dark:text-blue-500 mb-1">Drizzle Kit</p>
|
||||
<p class="text-sm">Database schema is managed with Drizzle ORM. Use these commands for schema changes:</p>
|
||||
<ul class="mt-2 space-y-1 text-sm">
|
||||
<li><code class="bg-blue-500/10 px-1 rounded">bun run drizzle-kit generate</code> - Generate migration files</li>
|
||||
<li><code class="bg-blue-500/10 px-1 rounded">bun run drizzle-kit push</code> - Apply schema changes directly</li>
|
||||
<li><code class="bg-blue-500/10 px-1 rounded">bun run drizzle-kit studio</code> - Open database browser</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Performance Optimization -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Performance Optimization</h2>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Mirroring Performance</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
{[
|
||||
{
|
||||
title: 'Batch Operations',
|
||||
tips: [
|
||||
'Mirror multiple repositories at once',
|
||||
'Use organization-level mirroring',
|
||||
'Schedule mirroring during off-peak hours'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Network Optimization',
|
||||
tips: [
|
||||
'Use SSH URLs when possible',
|
||||
'Enable Git LFS only when needed',
|
||||
'Consider repository size limits'
|
||||
]
|
||||
}
|
||||
].map(section => (
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<h4 class="font-semibold mb-3">{section.title}</h4>
|
||||
<ul class="space-y-1 text-sm text-muted-foreground">
|
||||
{section.tips.map(tip => (
|
||||
<li class="flex gap-2">
|
||||
<span>•</span>
|
||||
<span>{tip}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Database Performance</h3>
|
||||
|
||||
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
|
||||
<h4 class="font-semibold text-amber-600 dark:text-amber-500 mb-2">Regular Maintenance</h4>
|
||||
<ul class="space-y-1 text-sm">
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Enable automatic cleanup in Configuration → Automation</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Periodically vacuum the SQLite database: <code class="bg-amber-500/10 px-1 rounded">sqlite3 data/gitea-mirror.db "VACUUM;"</code></span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Monitor database size and clean old events regularly</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Reverse Proxy Configuration -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Reverse Proxy Configuration</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">
|
||||
For production deployments, it's recommended to use a reverse proxy like Nginx or Caddy.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Nginx Example</h3>
|
||||
|
||||
<div class="bg-muted/30 rounded-lg p-4 mb-6">
|
||||
<pre class="text-sm overflow-x-auto"><code>{`server {
|
||||
listen 80;
|
||||
server_name gitea-mirror.example.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name gitea-mirror.example.com;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:4321;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# SSE endpoint needs special handling
|
||||
location /api/sse {
|
||||
proxy_pass http://localhost:4321;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection '';
|
||||
proxy_set_header Cache-Control 'no-cache';
|
||||
proxy_set_header X-Accel-Buffering 'no';
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
}`}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Caddy Example</h3>
|
||||
|
||||
<div class="bg-muted/30 rounded-lg p-4">
|
||||
<pre class="text-sm"><code>{`gitea-mirror.example.com {
|
||||
reverse_proxy localhost:4321
|
||||
}`}</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Monitoring and Health Checks -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Monitoring and Health Checks</h2>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Health Check Endpoint</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6 mb-6">
|
||||
<p class="text-sm text-muted-foreground mb-4">Monitor application health using the built-in endpoint:</p>
|
||||
|
||||
<div class="bg-muted/30 rounded p-3 mb-4">
|
||||
<code class="text-sm">GET /api/health</code>
|
||||
</div>
|
||||
|
||||
<p class="text-sm font-semibold mb-2">Response:</p>
|
||||
<div class="bg-muted/30 rounded p-3">
|
||||
<pre class="text-sm"><code>{`{
|
||||
"status": "ok",
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
"database": "connected",
|
||||
"version": "1.0.0"
|
||||
}`}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Monitoring with Prometheus</h3>
|
||||
|
||||
<p class="text-sm text-muted-foreground mb-4">
|
||||
While Gitea Mirror doesn't have built-in Prometheus metrics, you can monitor it using:
|
||||
</p>
|
||||
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex gap-2">
|
||||
<span>•</span>
|
||||
<span>Blackbox exporter for endpoint monitoring</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span>•</span>
|
||||
<span>Node exporter for system metrics</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span>•</span>
|
||||
<span>Custom scripts to check database metrics</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Backup and Recovery -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Backup and Recovery</h2>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">What to Backup</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<h4 class="font-semibold mb-2">Essential Files</h4>
|
||||
<ul class="space-y-1 text-sm text-muted-foreground">
|
||||
<li class="font-mono">• data/gitea-mirror.db</li>
|
||||
<li class="font-mono">• .env (if using)</li>
|
||||
<li class="font-mono">• Custom CA certificates</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<h4 class="font-semibold mb-2">Optional Files</h4>
|
||||
<ul class="space-y-1 text-sm text-muted-foreground">
|
||||
<li class="font-mono">• Docker volumes</li>
|
||||
<li class="font-mono">• Custom configurations</li>
|
||||
<li class="font-mono">• Logs for auditing</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Backup Script Example</h3>
|
||||
|
||||
<div class="bg-muted/30 rounded-lg p-4">
|
||||
<pre class="text-sm"><code>{`#!/bin/bash
|
||||
BACKUP_DIR="/backups/gitea-mirror"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# Create backup directory
|
||||
mkdir -p "$BACKUP_DIR/$DATE"
|
||||
|
||||
# Backup database
|
||||
cp data/gitea-mirror.db "$BACKUP_DIR/$DATE/"
|
||||
|
||||
# Backup environment
|
||||
cp .env "$BACKUP_DIR/$DATE/" 2>/dev/null || true
|
||||
|
||||
# Create tarball
|
||||
tar -czf "$BACKUP_DIR/backup_$DATE.tar.gz" -C "$BACKUP_DIR" "$DATE"
|
||||
|
||||
# Clean up
|
||||
rm -rf "$BACKUP_DIR/$DATE"
|
||||
|
||||
# Keep only last 7 backups
|
||||
ls -t "$BACKUP_DIR"/backup_*.tar.gz | tail -n +8 | xargs rm -f`}</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Troubleshooting Guide -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Troubleshooting Guide</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
{[
|
||||
{
|
||||
issue: 'Application won\'t start',
|
||||
solutions: [
|
||||
'Check port availability: `lsof -i :4321`',
|
||||
'Verify environment variables are set correctly',
|
||||
'Check database file permissions',
|
||||
'Review logs for startup errors'
|
||||
]
|
||||
},
|
||||
{
|
||||
issue: 'Authentication failures',
|
||||
solutions: [
|
||||
'Ensure BETTER_AUTH_SECRET is set and consistent',
|
||||
'Check BETTER_AUTH_URL matches your deployment',
|
||||
'Clear browser cookies and try again',
|
||||
'Verify database contains user records'
|
||||
]
|
||||
},
|
||||
{
|
||||
issue: 'Mirroring failures',
|
||||
solutions: [
|
||||
'Test GitHub/Gitea connections individually',
|
||||
'Verify access tokens have correct permissions',
|
||||
'Check network connectivity and firewall rules',
|
||||
'Review Activity Log for detailed error messages'
|
||||
]
|
||||
},
|
||||
{
|
||||
issue: 'Performance issues',
|
||||
solutions: [
|
||||
'Check database size and run cleanup',
|
||||
'Monitor system resources (CPU, memory, disk)',
|
||||
'Reduce concurrent mirroring operations',
|
||||
'Consider upgrading deployment resources'
|
||||
]
|
||||
}
|
||||
].map(item => (
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<h4 class="font-semibold text-amber-600 dark:text-amber-500 mb-2">{item.issue}</h4>
|
||||
<ul class="space-y-1 text-sm">
|
||||
{item.solutions.map(solution => (
|
||||
<li class="flex gap-2">
|
||||
<span class="text-primary">→</span>
|
||||
<span>{solution}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Migration Guide -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-6">Migration Guide</h2>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Migrating from JWT to Better Auth</h3>
|
||||
|
||||
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary">
|
||||
<p class="mb-4">If you're upgrading from an older version using JWT authentication:</p>
|
||||
|
||||
<ol class="space-y-3 text-sm">
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-xs font-semibold">1</span>
|
||||
<div>
|
||||
<strong>Backup your database</strong>
|
||||
<p class="text-muted-foreground">Always create a backup before migration</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-xs font-semibold">2</span>
|
||||
<div>
|
||||
<strong>Update environment variables</strong>
|
||||
<p class="text-muted-foreground">Replace JWT_SECRET with BETTER_AUTH_SECRET</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-xs font-semibold">3</span>
|
||||
<div>
|
||||
<strong>Run database migrations</strong>
|
||||
<p class="text-muted-foreground">New auth tables will be created automatically</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-xs font-semibold">4</span>
|
||||
<div>
|
||||
<strong>Users will need to log in again</strong>
|
||||
<p class="text-muted-foreground">Previous sessions will be invalidated</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
</MainLayout>
|
||||
@@ -47,7 +47,8 @@ import MainLayout from '../../layouts/main.astro';
|
||||
{ name: 'Shadcn UI', desc: 'UI component library built on Tailwind CSS' },
|
||||
{ name: 'SQLite', desc: 'Database for storing configuration, state, and events' },
|
||||
{ name: 'Bun', desc: 'JavaScript runtime and package manager' },
|
||||
{ name: 'Drizzle ORM', desc: 'Type-safe ORM for database interactions' }
|
||||
{ name: 'Drizzle ORM', desc: 'Type-safe ORM for database interactions' },
|
||||
{ name: 'Better Auth', desc: 'Modern authentication library with SSO/OIDC support' }
|
||||
].map(tech => (
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-2 h-2 rounded-full bg-primary mt-2"></div>
|
||||
@@ -184,7 +185,8 @@ import MainLayout from '../../layouts/main.astro';
|
||||
|
||||
<div class="space-y-3">
|
||||
{[
|
||||
'Authentication and user management',
|
||||
'Authentication with Better Auth (email/password, SSO, OIDC)',
|
||||
'OAuth2/OIDC provider functionality',
|
||||
'GitHub API integration',
|
||||
'Gitea API integration',
|
||||
'Mirroring operations and job queue',
|
||||
@@ -213,11 +215,13 @@ import MainLayout from '../../layouts/main.astro';
|
||||
|
||||
<div class="space-y-3">
|
||||
{[
|
||||
'User accounts and authentication data',
|
||||
'User accounts and authentication data (Better Auth)',
|
||||
'OAuth applications and SSO provider configurations',
|
||||
'GitHub and Gitea configuration',
|
||||
'Repository and organization information',
|
||||
'Mirroring job history and status',
|
||||
'Event notifications and their read status'
|
||||
'Event notifications and their read status',
|
||||
'OAuth tokens and consent records'
|
||||
].map(item => (
|
||||
<div class="flex gap-3">
|
||||
<span class="text-primary font-mono text-sm">▸</span>
|
||||
@@ -238,7 +242,7 @@ import MainLayout from '../../layouts/main.astro';
|
||||
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary">
|
||||
<ol class="space-y-4">
|
||||
{[
|
||||
{ title: 'User Authentication', desc: 'Users authenticate through the frontend, which communicates with the backend to validate credentials.' },
|
||||
{ title: 'User Authentication', desc: 'Users authenticate via Better Auth using email/password, SSO providers, or as OIDC clients.' },
|
||||
{ title: 'Configuration', desc: 'Users configure GitHub and Gitea settings through the UI, which are stored in the SQLite database.' },
|
||||
{ title: 'Repository Discovery', desc: 'The backend queries the GitHub API to discover repositories based on user configuration.' },
|
||||
{ title: 'Mirroring Process', desc: 'When triggered, the backend fetches repository data from GitHub and pushes it to Gitea.' },
|
||||
|
||||
535
src/pages/docs/authentication.astro
Normal file
535
src/pages/docs/authentication.astro
Normal file
@@ -0,0 +1,535 @@
|
||||
---
|
||||
import MainLayout from '../../layouts/main.astro';
|
||||
---
|
||||
|
||||
<MainLayout title="Authentication & SSO - Gitea Mirror">
|
||||
<main class="max-w-5xl mx-auto px-4 py-12">
|
||||
<div class="sticky top-4 z-10 mb-6">
|
||||
<a
|
||||
href="/docs/"
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-card text-foreground hover:bg-muted transition-colors border border-border focus:ring-2 focus:ring-ring outline-none"
|
||||
>
|
||||
<span aria-hidden="true">←</span> Back to Documentation
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<article class="bg-card rounded-2xl shadow-lg p-6 md:p-8 border border-border">
|
||||
<!-- Header -->
|
||||
<div class="mb-12 space-y-4">
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground mb-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
|
||||
</svg>
|
||||
<span>Authentication</span>
|
||||
</div>
|
||||
<h1 class="text-4xl font-bold tracking-tight">Authentication & SSO Configuration</h1>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed max-w-4xl">
|
||||
Configure authentication methods including email/password, Single Sign-On (SSO), and OIDC provider functionality for Gitea Mirror.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Overview -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Authentication Overview</h2>
|
||||
|
||||
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary mb-6">
|
||||
<p class="text-base leading-relaxed">
|
||||
Gitea Mirror uses <strong>Better Auth</strong>, a modern authentication library that supports multiple authentication methods.
|
||||
All authentication settings can be configured through the web UI without editing configuration files.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-semibold mb-4">Supported Authentication Methods</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{[
|
||||
{
|
||||
icon: '✉️',
|
||||
title: 'Email & Password',
|
||||
desc: 'Traditional authentication with email and password. Always enabled by default.',
|
||||
status: 'Always Enabled'
|
||||
},
|
||||
{
|
||||
icon: '🌐',
|
||||
title: 'Single Sign-On (SSO)',
|
||||
desc: 'Allow users to sign in using external OIDC providers like Google, Okta, or Azure AD.',
|
||||
status: 'Optional'
|
||||
},
|
||||
{
|
||||
icon: '🔑',
|
||||
title: 'OIDC Provider',
|
||||
desc: 'Act as an OIDC provider, allowing other applications to authenticate through Gitea Mirror.',
|
||||
status: 'Optional'
|
||||
}
|
||||
].map(method => (
|
||||
<div class="bg-card rounded-lg border border-border p-4 hover:border-primary/50 transition-colors">
|
||||
<div class="text-2xl mb-3">{method.icon}</div>
|
||||
<h4 class="font-semibold mb-2">{method.title}</h4>
|
||||
<p class="text-sm text-muted-foreground mb-3">{method.desc}</p>
|
||||
<span class={`text-xs px-2 py-1 rounded-full ${method.status === 'Always Enabled' ? 'bg-green-500/10 text-green-600 dark:text-green-500' : 'bg-blue-500/10 text-blue-600 dark:text-blue-500'}`}>
|
||||
{method.status}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Accessing Authentication Settings -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Accessing Authentication Settings</h2>
|
||||
|
||||
<ol class="space-y-3">
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">1</span>
|
||||
<span>Navigate to the <strong>Configuration</strong> page</span>
|
||||
</li>
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">2</span>
|
||||
<span>Click on the <strong>Authentication</strong> tab</span>
|
||||
</li>
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">3</span>
|
||||
<span>Configure SSO providers or OAuth applications as needed</span>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- SSO Configuration -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Single Sign-On (SSO) Configuration</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">
|
||||
SSO allows your users to authenticate using external identity providers. This is useful for organizations that already have centralized authentication systems.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Adding an SSO Provider</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6 mb-6">
|
||||
<h4 class="font-semibold mb-4">Required Information</h4>
|
||||
|
||||
<div class="space-y-4">
|
||||
{[
|
||||
{ name: 'Issuer URL', desc: 'The OIDC issuer URL of your provider', example: 'https://accounts.google.com' },
|
||||
{ name: 'Domain', desc: 'The email domain for this provider', example: 'example.com' },
|
||||
{ name: 'Provider ID', desc: 'A unique identifier for this provider', example: 'google-sso' },
|
||||
{ name: 'Client ID', desc: 'OAuth client ID from your provider', example: '123456789.apps.googleusercontent.com' },
|
||||
{ name: 'Client Secret', desc: 'OAuth client secret from your provider', example: 'GOCSPX-...' }
|
||||
].map(field => (
|
||||
<div class="border-l-2 border-muted pl-4">
|
||||
<div class="flex items-baseline gap-2 mb-1">
|
||||
<strong class="text-sm">{field.name}</strong>
|
||||
<span class="text-xs text-muted-foreground">Required</span>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">{field.desc}</p>
|
||||
<code class="text-xs bg-muted px-2 py-0.5 rounded mt-1 inline-block">{field.example}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4 mb-6">
|
||||
<div class="flex gap-3">
|
||||
<div class="text-blue-600 dark:text-blue-500">
|
||||
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-blue-600 dark:text-blue-500 mb-1">Auto-Discovery</p>
|
||||
<p class="text-sm">Most OIDC providers support auto-discovery. Simply enter the Issuer URL and click "Discover" to automatically populate the endpoint URLs.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Redirect URL Configuration</h3>
|
||||
|
||||
<div class="bg-muted/30 rounded-lg p-4">
|
||||
<p class="text-sm mb-2">When configuring your SSO provider, use this redirect URL:</p>
|
||||
<code class="bg-muted rounded px-3 py-2 block">https://your-domain.com/api/auth/sso/callback/{`{provider-id}`}</code>
|
||||
<p class="text-xs text-muted-foreground mt-2">Replace <code>{`{provider-id}`}</code> with your chosen Provider ID (e.g., google-sso)</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Example SSO Configurations -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Example SSO Configurations</h2>
|
||||
|
||||
<!-- Google Example -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<img src="https://www.google.com/favicon.ico" alt="Google" class="w-5 h-5" />
|
||||
Google SSO
|
||||
</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6">
|
||||
<ol class="space-y-4">
|
||||
<li>
|
||||
<strong>1. Create OAuth Client in Google Cloud Console</strong>
|
||||
<ul class="mt-2 space-y-1 text-sm text-muted-foreground pl-4">
|
||||
<li>• Go to <a href="https://console.cloud.google.com/" class="text-primary hover:underline">Google Cloud Console</a></li>
|
||||
<li>• Create a new OAuth 2.0 Client ID</li>
|
||||
<li>• Add authorized redirect URI: <code class="bg-muted px-1 rounded">https://your-domain.com/api/auth/sso/callback/google-sso</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>2. Configure in Gitea Mirror</strong>
|
||||
<div class="mt-2 bg-muted/30 rounded-lg p-3 text-sm">
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<div><strong>Issuer URL:</strong> <code>https://accounts.google.com</code></div>
|
||||
<div><strong>Domain:</strong> <code>your-company.com</code></div>
|
||||
<div><strong>Provider ID:</strong> <code>google-sso</code></div>
|
||||
<div><strong>Client ID:</strong> <code>[Your Google Client ID]</code></div>
|
||||
<div><strong>Client Secret:</strong> <code>[Your Google Client Secret]</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<strong>3. Use Auto-Discovery</strong>
|
||||
<p class="text-sm text-muted-foreground mt-1">Click "Discover" to automatically populate the endpoint URLs</p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Okta Example -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<span class="w-5 h-5 bg-blue-600 rounded flex items-center justify-center text-white text-xs font-bold">O</span>
|
||||
Okta SSO
|
||||
</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6">
|
||||
<ol class="space-y-4">
|
||||
<li>
|
||||
<strong>1. Create OIDC Application in Okta</strong>
|
||||
<ul class="mt-2 space-y-1 text-sm text-muted-foreground pl-4">
|
||||
<li>• In Okta Admin Console, create a new OIDC Web Application</li>
|
||||
<li>• Set Sign-in redirect URI: <code class="bg-muted px-1 rounded">https://your-domain.com/api/auth/sso/callback/okta-sso</code></li>
|
||||
<li>• Note the Client ID and Client Secret</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>2. Configure in Gitea Mirror</strong>
|
||||
<div class="mt-2 bg-muted/30 rounded-lg p-3 text-sm">
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<div><strong>Issuer URL:</strong> <code>https://your-okta-domain.okta.com</code></div>
|
||||
<div><strong>Domain:</strong> <code>your-company.com</code></div>
|
||||
<div><strong>Provider ID:</strong> <code>okta-sso</code></div>
|
||||
<div><strong>Client ID:</strong> <code>[Your Okta Client ID]</code></div>
|
||||
<div><strong>Client Secret:</strong> <code>[Your Okta Client Secret]</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Azure AD Example -->
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
||||
<span class="w-5 h-5 bg-blue-500 rounded flex items-center justify-center text-white text-xs">M</span>
|
||||
Azure AD / Microsoft Entra ID
|
||||
</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6">
|
||||
<ol class="space-y-4">
|
||||
<li>
|
||||
<strong>1. Register Application in Azure Portal</strong>
|
||||
<ul class="mt-2 space-y-1 text-sm text-muted-foreground pl-4">
|
||||
<li>• Go to Azure Portal → Azure Active Directory → App registrations</li>
|
||||
<li>• Create a new registration</li>
|
||||
<li>• Add redirect URI: <code class="bg-muted px-1 rounded">https://your-domain.com/api/auth/sso/callback/azure-sso</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>2. Configure in Gitea Mirror</strong>
|
||||
<div class="mt-2 bg-muted/30 rounded-lg p-3 text-sm">
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<div><strong>Issuer URL:</strong> <code>https://login.microsoftonline.com/{`{tenant-id}`}/v2.0</code></div>
|
||||
<div><strong>Domain:</strong> <code>your-company.com</code></div>
|
||||
<div><strong>Provider ID:</strong> <code>azure-sso</code></div>
|
||||
<div><strong>Client ID:</strong> <code>[Your Application ID]</code></div>
|
||||
<div><strong>Client Secret:</strong> <code>[Your Client Secret]</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- OIDC Provider Configuration -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">OIDC Provider Configuration</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">
|
||||
The OIDC Provider feature allows Gitea Mirror to act as an authentication provider for other applications.
|
||||
This is useful when you want to centralize authentication through Gitea Mirror.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Creating OAuth Applications</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6 mb-6">
|
||||
<ol class="space-y-4">
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">1</span>
|
||||
<div>
|
||||
<strong>Navigate to OAuth Applications</strong>
|
||||
<p class="text-sm text-muted-foreground mt-1">Go to Configuration → Authentication → OAuth Applications</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">2</span>
|
||||
<div>
|
||||
<strong>Create New Application</strong>
|
||||
<p class="text-sm text-muted-foreground mt-1">Click "Create Application" and provide:</p>
|
||||
<ul class="mt-2 space-y-1 text-sm text-muted-foreground pl-4">
|
||||
<li>• Application Name</li>
|
||||
<li>• Application Type (Web, Mobile, or Desktop)</li>
|
||||
<li>• Redirect URLs (one per line)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 rounded-full flex items-center justify-center text-sm font-semibold">3</span>
|
||||
<div>
|
||||
<strong>Save Credentials</strong>
|
||||
<p class="text-sm text-muted-foreground mt-1">You'll receive a Client ID and Client Secret. Store these securely!</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">OIDC Endpoints</h3>
|
||||
|
||||
<div class="bg-muted/30 rounded-lg p-4 mb-6">
|
||||
<p class="text-sm mb-3">Applications can use these standard OIDC endpoints:</p>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex gap-2">
|
||||
<strong class="w-32">Discovery:</strong>
|
||||
<code class="bg-muted px-2 py-0.5 rounded flex-1">https://your-domain.com/.well-known/openid-configuration</code>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<strong class="w-32">Authorization:</strong>
|
||||
<code class="bg-muted px-2 py-0.5 rounded flex-1">https://your-domain.com/api/auth/oauth2/authorize</code>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<strong class="w-32">Token:</strong>
|
||||
<code class="bg-muted px-2 py-0.5 rounded flex-1">https://your-domain.com/api/auth/oauth2/token</code>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<strong class="w-32">UserInfo:</strong>
|
||||
<code class="bg-muted px-2 py-0.5 rounded flex-1">https://your-domain.com/api/auth/oauth2/userinfo</code>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<strong class="w-32">JWKS:</strong>
|
||||
<code class="bg-muted px-2 py-0.5 rounded flex-1">https://your-domain.com/api/auth/jwks</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Supported Scopes</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{[
|
||||
{ scope: 'openid', desc: 'Required - provides user ID', claims: 'sub' },
|
||||
{ scope: 'profile', desc: 'User profile information', claims: 'name, username, picture' },
|
||||
{ scope: 'email', desc: 'Email address', claims: 'email, email_verified' }
|
||||
].map(item => (
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<code class="text-sm font-semibold text-primary">{item.scope}</code>
|
||||
<p class="text-sm text-muted-foreground mt-2">{item.desc}</p>
|
||||
<p class="text-xs text-muted-foreground mt-2">Claims: {item.claims}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- User Experience -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">User Experience</h2>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Login Flow with SSO</h3>
|
||||
|
||||
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary mb-6">
|
||||
<p class="mb-4">When SSO is configured, users will see authentication options on the login page:</p>
|
||||
<ol class="space-y-2 text-sm">
|
||||
<li class="flex gap-2"><span class="font-semibold">1.</span> Email & Password tab for traditional login</li>
|
||||
<li class="flex gap-2"><span class="font-semibold">2.</span> SSO tab with provider buttons or email input</li>
|
||||
<li class="flex gap-2"><span class="font-semibold">3.</span> Automatic redirect to the appropriate provider</li>
|
||||
<li class="flex gap-2"><span class="font-semibold">4.</span> Return to Gitea Mirror after successful authentication</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">OAuth Consent Flow</h3>
|
||||
|
||||
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary">
|
||||
<p class="mb-4">When an application requests authentication through Gitea Mirror:</p>
|
||||
<ol class="space-y-2 text-sm">
|
||||
<li class="flex gap-2"><span class="font-semibold">1.</span> User is redirected to Gitea Mirror</li>
|
||||
<li class="flex gap-2"><span class="font-semibold">2.</span> Login prompt if not already authenticated</li>
|
||||
<li class="flex gap-2"><span class="font-semibold">3.</span> Consent screen showing requested permissions</li>
|
||||
<li class="flex gap-2"><span class="font-semibold">4.</span> User approves or denies the request</li>
|
||||
<li class="flex gap-2"><span class="font-semibold">5.</span> Redirect back to the application with auth code</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Security Considerations -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Security Considerations</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{[
|
||||
{
|
||||
icon: '🔒',
|
||||
title: 'Client Secrets',
|
||||
items: [
|
||||
'Store OAuth client secrets securely',
|
||||
'Never commit secrets to version control',
|
||||
'Rotate secrets regularly'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '🔗',
|
||||
title: 'Redirect URLs',
|
||||
items: [
|
||||
'Only add trusted redirect URLs',
|
||||
'Use HTTPS in production',
|
||||
'Validate exact URL matches'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '🛡️',
|
||||
title: 'Scopes & Permissions',
|
||||
items: [
|
||||
'Grant minimum required scopes',
|
||||
'Review requested permissions',
|
||||
'Users can revoke access anytime'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '⏱️',
|
||||
title: 'Token Security',
|
||||
items: [
|
||||
'Access tokens have expiration',
|
||||
'Refresh tokens for long-lived access',
|
||||
'Tokens can be revoked'
|
||||
]
|
||||
}
|
||||
].map(section => (
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<span class="text-2xl">{section.icon}</span>
|
||||
<h4 class="font-semibold">{section.title}</h4>
|
||||
</div>
|
||||
<ul class="space-y-1 text-sm text-muted-foreground">
|
||||
{section.items.map(item => (
|
||||
<li class="flex gap-2">
|
||||
<span>•</span>
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Troubleshooting -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Troubleshooting</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
|
||||
<h4 class="font-semibold text-amber-600 dark:text-amber-500 mb-2">SSO Login Issues</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<div>
|
||||
<strong>"Invalid origin" error:</strong> Check that your Gitea Mirror URL matches the configured redirect URI
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<div>
|
||||
<strong>"Provider not found" error:</strong> Ensure the provider is properly configured and saved
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<div>
|
||||
<strong>Redirect loop:</strong> Verify the redirect URI in both Gitea Mirror and the SSO provider match exactly
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
|
||||
<h4 class="font-semibold text-amber-600 dark:text-amber-500 mb-2">OIDC Provider Issues</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<div>
|
||||
<strong>Application not found:</strong> Ensure the client ID is correct and the app is not disabled
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<div>
|
||||
<strong>Invalid redirect URI:</strong> The redirect URI must match exactly what's configured
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<div>
|
||||
<strong>Consent not working:</strong> Check browser cookies are enabled and not blocked
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Migration from JWT -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-6">Migration from JWT Authentication</h2>
|
||||
|
||||
<div class="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
|
||||
<div class="flex gap-3">
|
||||
<div class="text-blue-600 dark:text-blue-500">
|
||||
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-blue-600 dark:text-blue-500 mb-2">For Existing Users</p>
|
||||
<ul class="space-y-1 text-sm">
|
||||
<li>• Email/password authentication continues to work</li>
|
||||
<li>• No action required from existing users</li>
|
||||
<li>• SSO can be added as an additional option</li>
|
||||
<li>• JWT_SECRET is no longer required in environment variables</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
</MainLayout>
|
||||
475
src/pages/docs/ca-certificates.astro
Normal file
475
src/pages/docs/ca-certificates.astro
Normal file
@@ -0,0 +1,475 @@
|
||||
---
|
||||
import MainLayout from '../../layouts/main.astro';
|
||||
---
|
||||
|
||||
<MainLayout title="CA Certificates - Gitea Mirror">
|
||||
<main class="max-w-5xl mx-auto px-4 py-12">
|
||||
<div class="sticky top-4 z-10 mb-6">
|
||||
<a
|
||||
href="/docs/"
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-card text-foreground hover:bg-muted transition-colors border border-border focus:ring-2 focus:ring-ring outline-none"
|
||||
>
|
||||
<span aria-hidden="true">←</span> Back to Documentation
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<article class="bg-card rounded-2xl shadow-lg p-6 md:p-8 border border-border">
|
||||
<!-- Header -->
|
||||
<div class="mb-12 space-y-4">
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground mb-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
||||
</svg>
|
||||
<span>Security</span>
|
||||
</div>
|
||||
<h1 class="text-4xl font-bold tracking-tight">CA Certificates Configuration</h1>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed max-w-4xl">
|
||||
Configure custom Certificate Authority (CA) certificates for connecting to self-signed or privately signed Gitea instances.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Overview -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Overview</h2>
|
||||
|
||||
<div class="bg-gradient-to-r from-primary/5 to-transparent rounded-lg p-6 border-l-4 border-primary mb-6">
|
||||
<p class="text-base leading-relaxed">
|
||||
When your Gitea instance uses a self-signed certificate or a certificate signed by a private Certificate Authority (CA),
|
||||
you need to configure Gitea Mirror to trust these certificates. This guide explains how to add custom CA certificates
|
||||
for different deployment methods.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
|
||||
<div class="flex gap-3">
|
||||
<div class="text-amber-600 dark:text-amber-500">
|
||||
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-amber-600 dark:text-amber-500 mb-1">Important</p>
|
||||
<p class="text-sm">Without proper CA certificate configuration, you'll encounter SSL/TLS errors when connecting to Gitea instances with custom certificates.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Common SSL/TLS Errors -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Common SSL/TLS Errors</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-4">If you see any of these errors, you likely need to configure CA certificates:</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
{[
|
||||
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
||||
'SELF_SIGNED_CERT_IN_CHAIN',
|
||||
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
|
||||
'CERT_UNTRUSTED',
|
||||
'unable to verify the first certificate'
|
||||
].map(error => (
|
||||
<div class="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
|
||||
<code class="text-sm text-red-600 dark:text-red-500">{error}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Docker Configuration -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Docker Configuration</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">For Docker deployments, you have several options to add custom CA certificates:</p>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Method 1: Volume Mount (Recommended)</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6 mb-6">
|
||||
<ol class="space-y-4">
|
||||
<li>
|
||||
<strong>1. Create a certificates directory</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>mkdir -p ./certs</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<strong>2. Copy your CA certificate(s)</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>cp /path/to/your-ca-cert.crt ./certs/</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<strong>3. Update docker-compose.yml</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>{`version: '3.8'
|
||||
services:
|
||||
gitea-mirror:
|
||||
image: raylabs/gitea-mirror:latest
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./certs:/usr/local/share/ca-certificates:ro
|
||||
environment:
|
||||
- NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/your-ca-cert.crt`}</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<strong>4. Restart the container</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>docker-compose down && docker-compose up -d</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Method 2: Custom Docker Image</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6">
|
||||
<p class="text-sm text-muted-foreground mb-4">For permanent certificate inclusion, create a custom Docker image:</p>
|
||||
|
||||
<div class="bg-muted/30 rounded-lg p-4">
|
||||
<pre class="text-sm"><code>{`FROM raylabs/gitea-mirror:latest
|
||||
|
||||
# Copy CA certificates
|
||||
COPY ./certs/*.crt /usr/local/share/ca-certificates/
|
||||
|
||||
# Update CA certificates
|
||||
RUN update-ca-certificates
|
||||
|
||||
# Set environment variable
|
||||
ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/your-ca-cert.crt`}</code></pre>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-muted-foreground mt-4">Build and use your custom image:</p>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>docker build -t my-gitea-mirror .</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Native/Bun Configuration -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Native/Bun Configuration</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">For native Bun deployments, configure CA certificates using environment variables:</p>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Method 1: Environment Variable</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6 mb-6">
|
||||
<ol class="space-y-4">
|
||||
<li>
|
||||
<strong>1. Export the certificate path</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>export NODE_EXTRA_CA_CERTS=/path/to/your-ca-cert.crt</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<strong>2. Run Gitea Mirror</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>bun run start</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Method 2: .env File</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6 mb-6">
|
||||
<p class="text-sm text-muted-foreground mb-4">Add to your .env file:</p>
|
||||
<div class="bg-muted/30 rounded-lg p-3">
|
||||
<pre class="text-sm"><code>NODE_EXTRA_CA_CERTS=/path/to/your-ca-cert.crt</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Method 3: System-wide CA Store</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6">
|
||||
<p class="text-sm text-muted-foreground mb-4">Add certificates to your system's CA store:</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<strong>Ubuntu/Debian:</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>{`sudo cp your-ca-cert.crt /usr/local/share/ca-certificates/
|
||||
sudo update-ca-certificates`}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>RHEL/CentOS/Fedora:</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>{`sudo cp your-ca-cert.crt /etc/pki/ca-trust/source/anchors/
|
||||
sudo update-ca-trust`}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>macOS:</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>{`sudo security add-trusted-cert -d -r trustRoot \\
|
||||
-k /Library/Keychains/System.keychain your-ca-cert.crt`}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- LXC Configuration -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">LXC Container Configuration</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">For LXC deployments on Proxmox VE:</p>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6">
|
||||
<ol class="space-y-4">
|
||||
<li>
|
||||
<strong>1. Enter the container</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>pct enter <container-id></code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<strong>2. Create certificates directory</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>mkdir -p /usr/local/share/ca-certificates</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<strong>3. Copy your CA certificate</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>cat > /usr/local/share/ca-certificates/your-ca.crt</code></pre>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground mt-1">Paste your certificate content and press Ctrl+D</p>
|
||||
</li>
|
||||
<li>
|
||||
<strong>4. Update the systemd service</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>{`cat >> /etc/systemd/system/gitea-mirror.service << EOF
|
||||
Environment="NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/your-ca.crt"
|
||||
EOF`}</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<strong>5. Reload and restart</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-3 mt-2">
|
||||
<pre class="text-sm"><code>{`systemctl daemon-reload
|
||||
systemctl restart gitea-mirror`}</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Multiple CA Certificates -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Multiple CA Certificates</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">If you need to trust multiple CA certificates:</p>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Option 1: Bundle Certificates</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6 mb-6">
|
||||
<p class="text-sm text-muted-foreground mb-4">Combine multiple certificates into one file:</p>
|
||||
<div class="bg-muted/30 rounded-lg p-3">
|
||||
<pre class="text-sm"><code>{`cat ca-cert1.crt ca-cert2.crt ca-cert3.crt > ca-bundle.crt
|
||||
export NODE_EXTRA_CA_CERTS=/path/to/ca-bundle.crt`}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold mb-4">Option 2: System CA Store</h3>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6">
|
||||
<p class="text-sm text-muted-foreground mb-4">Add all certificates to the system CA store (recommended for production):</p>
|
||||
<div class="bg-muted/30 rounded-lg p-3">
|
||||
<pre class="text-sm"><code>{`# Copy all certificates
|
||||
cp *.crt /usr/local/share/ca-certificates/
|
||||
update-ca-certificates`}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Verification -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Verifying Certificate Configuration</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-6">Test your certificate configuration:</p>
|
||||
|
||||
<div class="bg-card rounded-lg border border-border p-6">
|
||||
<h4 class="font-semibold mb-4">1. Test Gitea Connection</h4>
|
||||
<p class="text-sm text-muted-foreground mb-3">Use the "Test Connection" button in the Gitea configuration section</p>
|
||||
|
||||
<h4 class="font-semibold mb-4 mt-6">2. Check Logs</h4>
|
||||
<p class="text-sm text-muted-foreground mb-3">Look for SSL/TLS errors in the application logs:</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<strong class="text-sm">Docker:</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-2 mt-1">
|
||||
<code class="text-sm">docker logs gitea-mirror</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong class="text-sm">Native:</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-2 mt-1">
|
||||
<code class="text-sm">Check terminal output</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong class="text-sm">LXC:</strong>
|
||||
<div class="bg-muted/30 rounded-lg p-2 mt-1">
|
||||
<code class="text-sm">journalctl -u gitea-mirror -f</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="font-semibold mb-4 mt-6">3. Manual Certificate Test</h4>
|
||||
<p class="text-sm text-muted-foreground mb-3">Test SSL connection directly:</p>
|
||||
<div class="bg-muted/30 rounded-lg p-3">
|
||||
<pre class="text-sm"><code>openssl s_client -connect your-gitea-domain.com:443 -CAfile /path/to/ca-cert.crt</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Best Practices -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-6">Best Practices</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{[
|
||||
{
|
||||
icon: '🔒',
|
||||
title: 'Certificate Security',
|
||||
items: [
|
||||
'Keep CA certificates secure',
|
||||
'Use read-only mounts in Docker',
|
||||
'Limit certificate file permissions',
|
||||
'Regularly update certificates'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '📁',
|
||||
title: 'Certificate Management',
|
||||
items: [
|
||||
'Use descriptive certificate filenames',
|
||||
'Document certificate purposes',
|
||||
'Track certificate expiration dates',
|
||||
'Maintain certificate backups'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '🏢',
|
||||
title: 'Production Deployment',
|
||||
items: [
|
||||
'Use proper SSL certificates when possible',
|
||||
'Consider Let\'s Encrypt for public instances',
|
||||
'Implement certificate rotation procedures',
|
||||
'Monitor certificate expiration'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '🔍',
|
||||
title: 'Troubleshooting',
|
||||
items: [
|
||||
'Verify certificate format (PEM)',
|
||||
'Check certificate chain completeness',
|
||||
'Ensure proper file permissions',
|
||||
'Test with openssl commands'
|
||||
]
|
||||
}
|
||||
].map(section => (
|
||||
<div class="bg-card rounded-lg border border-border p-4">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<span class="text-2xl">{section.icon}</span>
|
||||
<h4 class="font-semibold">{section.title}</h4>
|
||||
</div>
|
||||
<ul class="space-y-1 text-sm text-muted-foreground">
|
||||
{section.items.map(item => (
|
||||
<li class="flex gap-2">
|
||||
<span>•</span>
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="my-12 h-px bg-border/50"></div>
|
||||
|
||||
<!-- Common Issues -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-6">Common Issues and Solutions</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
|
||||
<h4 class="font-semibold text-amber-600 dark:text-amber-500 mb-2">Certificate not being recognized</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Ensure the certificate is in PEM format</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Check that NODE_EXTRA_CA_CERTS points to the correct file</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Restart the application after adding certificates</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
|
||||
<h4 class="font-semibold text-amber-600 dark:text-amber-500 mb-2">Still getting SSL errors</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Verify the complete certificate chain is included</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Check if intermediate certificates are needed</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Ensure the certificate matches the server hostname</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
|
||||
<h4 class="font-semibold text-amber-600 dark:text-amber-500 mb-2">Certificate expired</h4>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Check certificate validity: <code class="bg-amber-500/10 px-1 rounded">openssl x509 -in cert.crt -noout -dates</code></span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Update with new certificate from your CA</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<span class="text-amber-600 dark:text-amber-500">•</span>
|
||||
<span>Restart Gitea Mirror after updating</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
</MainLayout>
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
import MainLayout from '../../layouts/main.astro';
|
||||
import { LuSettings, LuRocket, LuBookOpen } from 'react-icons/lu';
|
||||
import { LuSettings, LuRocket, LuBookOpen, LuShield, LuKey, LuNetwork } from 'react-icons/lu';
|
||||
|
||||
// Define our documentation pages directly
|
||||
const docs = [
|
||||
{
|
||||
slug: 'architecture',
|
||||
title: 'Architecture',
|
||||
description: 'Comprehensive overview of the Gitea Mirror application architecture.',
|
||||
slug: 'quickstart',
|
||||
title: 'Quick Start Guide',
|
||||
description: 'Get started with Gitea Mirror quickly.',
|
||||
order: 1,
|
||||
icon: LuBookOpen,
|
||||
href: '/docs/architecture'
|
||||
icon: LuRocket,
|
||||
href: '/docs/quickstart'
|
||||
},
|
||||
{
|
||||
slug: 'configuration',
|
||||
@@ -21,12 +21,36 @@ const docs = [
|
||||
href: '/docs/configuration'
|
||||
},
|
||||
{
|
||||
slug: 'quickstart',
|
||||
title: 'Quick Start Guide',
|
||||
description: 'Get started with Gitea Mirror quickly.',
|
||||
slug: 'authentication',
|
||||
title: 'Authentication & SSO',
|
||||
description: 'Configure authentication methods, SSO providers, and OIDC.',
|
||||
order: 3,
|
||||
icon: LuRocket,
|
||||
href: '/docs/quickstart'
|
||||
icon: LuKey,
|
||||
href: '/docs/authentication'
|
||||
},
|
||||
{
|
||||
slug: 'architecture',
|
||||
title: 'Architecture',
|
||||
description: 'Comprehensive overview of the Gitea Mirror application architecture.',
|
||||
order: 4,
|
||||
icon: LuBookOpen,
|
||||
href: '/docs/architecture'
|
||||
},
|
||||
{
|
||||
slug: 'ca-certificates',
|
||||
title: 'CA Certificates',
|
||||
description: 'Configure custom CA certificates for self-signed Gitea instances.',
|
||||
order: 5,
|
||||
icon: LuShield,
|
||||
href: '/docs/ca-certificates'
|
||||
},
|
||||
{
|
||||
slug: 'advanced',
|
||||
title: 'Advanced Topics',
|
||||
description: 'Advanced configuration, troubleshooting, and deployment options.',
|
||||
order: 6,
|
||||
icon: LuNetwork,
|
||||
href: '/docs/advanced'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ bun run start</code></pre>
|
||||
title: 'Create Admin Account',
|
||||
items: [
|
||||
"You'll be prompted on first access",
|
||||
'Choose a secure username and password',
|
||||
'Enter your email address and password',
|
||||
'This will be your administrator account'
|
||||
]
|
||||
},
|
||||
|
||||
28
src/pages/oauth/consent.astro
Normal file
28
src/pages/oauth/consent.astro
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
import '@/styles/global.css';
|
||||
import ConsentPage from '@/components/oauth/ConsentPage';
|
||||
import ThemeScript from '@/components/theme/ThemeScript.astro';
|
||||
import Providers from '@/components/layout/Providers';
|
||||
|
||||
// Check if user is authenticated
|
||||
const sessionCookie = Astro.cookies.get('better-auth-session');
|
||||
if (!sessionCookie) {
|
||||
return Astro.redirect('/login');
|
||||
}
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Authorize Application - Gitea Mirror</title>
|
||||
<ThemeScript />
|
||||
</head>
|
||||
<body>
|
||||
<Providers>
|
||||
<ConsentPage client:load />
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user