mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-06 19:46:44 +03:00
updated handling of url's from ENV vars
This commit is contained in:
@@ -18,7 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Enhanced error handling for all metadata mirroring operations
|
||||
- Individual try-catch blocks for issues, PRs, labels, milestones
|
||||
- Operations continue even if individual components fail
|
||||
- Support for BETTER_AUTH_TRUSTED_ORIGINS environment variable
|
||||
- Support for BETTER_AUTH_TRUSTED_ORIGINS environment variable (#63)
|
||||
- Enables access via multiple URLs (local IP + domain)
|
||||
- Comma-separated trusted origins configuration
|
||||
- Proper documentation for multi-URL access patterns
|
||||
- Comprehensive fix report documentation
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -24,8 +24,8 @@ Essential application settings required for running Gitea Mirror.
|
||||
| `PORT` | Server port | `4321` | No |
|
||||
| `DATABASE_URL` | Database connection URL | `sqlite://data/gitea-mirror.db` | No |
|
||||
| `BETTER_AUTH_SECRET` | Secret key for session signing (generate with: `openssl rand -base64 32`) | - | Yes |
|
||||
| `BETTER_AUTH_URL` | Base URL for authentication | `http://localhost:4321` | No |
|
||||
| `BETTER_AUTH_TRUSTED_ORIGINS` | Comma-separated list of trusted origins for OIDC | - | No |
|
||||
| `BETTER_AUTH_URL` | Primary base URL for authentication. This should be the main URL where your application is accessed. | `http://localhost:4321` | No |
|
||||
| `BETTER_AUTH_TRUSTED_ORIGINS` | Trusted origins for authentication requests. Comma-separated list of URLs. Use this to specify additional access URLs (e.g., local IP + domain: `http://10.10.20.45:4321,https://gitea-mirror.mydomain.tld`), SSO providers, reverse proxies, etc. | - | No |
|
||||
| `ENCRYPTION_SECRET` | Optional encryption key for tokens (generate with: `openssl rand -base64 48`) | - | No |
|
||||
|
||||
## GitHub Configuration
|
||||
@@ -246,7 +246,10 @@ services:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=file:data/gitea-mirror.db
|
||||
- BETTER_AUTH_SECRET=your-secure-secret-here
|
||||
- BETTER_AUTH_URL=https://your-domain.com
|
||||
# Primary access URL:
|
||||
- BETTER_AUTH_URL=https://gitea-mirror.mydomain.tld
|
||||
# Additional access URLs (local network + SSO providers):
|
||||
# - BETTER_AUTH_TRUSTED_ORIGINS=http://10.10.20.45:4321,http://192.168.1.100:4321,https://auth.provider.com
|
||||
|
||||
# GitHub Configuration
|
||||
- GITHUB_USERNAME=your-username
|
||||
@@ -282,6 +285,53 @@ services:
|
||||
- "4321:4321"
|
||||
```
|
||||
|
||||
## Authentication URL Configuration
|
||||
|
||||
### Multiple Access URLs
|
||||
|
||||
To allow access to Gitea Mirror through multiple URLs (e.g., local IP and public domain), use the `BETTER_AUTH_TRUSTED_ORIGINS` variable:
|
||||
|
||||
**Example Configuration:**
|
||||
```bash
|
||||
# Primary URL (required) - typically your public domain
|
||||
BETTER_AUTH_URL=https://gitea-mirror.mydomain.tld
|
||||
|
||||
# Additional access URLs (optional) - local IPs, alternate domains
|
||||
BETTER_AUTH_TRUSTED_ORIGINS=http://10.10.20.45:4321,http://192.168.1.100:4321
|
||||
```
|
||||
|
||||
This setup allows you to:
|
||||
- Access via local network IP: `http://10.10.20.45:4321`
|
||||
- Access via public domain: `https://gitea-mirror.mydomain.tld`
|
||||
- Both URLs will work for authentication and session management
|
||||
|
||||
### Trusted Origins
|
||||
|
||||
The `BETTER_AUTH_TRUSTED_ORIGINS` variable serves multiple purposes:
|
||||
|
||||
1. **SSO/OIDC Providers**: When using external authentication providers (Google, Authentik, Okta)
|
||||
2. **Reverse Proxies**: When running behind nginx, Traefik, or other proxies
|
||||
3. **Cross-Origin Requests**: When the frontend and backend are on different domains
|
||||
4. **Development**: When testing from different URLs
|
||||
|
||||
**Example Scenarios:**
|
||||
```bash
|
||||
# For Authentik SSO integration
|
||||
BETTER_AUTH_TRUSTED_ORIGINS=https://authentik.company.com,https://auth.company.com
|
||||
|
||||
# For reverse proxy setup
|
||||
BETTER_AUTH_TRUSTED_ORIGINS=https://proxy.internal,https://public.domain.com
|
||||
|
||||
# For development with multiple environments
|
||||
BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000,http://192.168.1.100:3000
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
- All URLs from `BETTER_AUTH_URL` are automatically trusted
|
||||
- URLs must be complete with protocol (http/https)
|
||||
- Multiple origins are separated by commas
|
||||
- No trailing slashes needed
|
||||
|
||||
## Notes
|
||||
|
||||
1. **First Run**: Environment variables are loaded when the container starts. The configuration is applied after the first user account is created.
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# Keycloak SSO Setup for Gitea Mirror
|
||||
|
||||
## 1. Access Keycloak Admin Console
|
||||
|
||||
1. Open http://localhost:8080
|
||||
2. Login with:
|
||||
- Username: `admin`
|
||||
- Password: `admin`
|
||||
|
||||
## 2. Create a New Realm (Optional)
|
||||
|
||||
1. Click on the realm dropdown (top-left, probably says "master")
|
||||
2. Click "Create Realm"
|
||||
3. Name it: `gitea-mirror`
|
||||
4. Click "Create"
|
||||
|
||||
## 3. Create a Client for Gitea Mirror
|
||||
|
||||
1. Go to "Clients" in the left menu
|
||||
2. Click "Create client"
|
||||
3. Fill in:
|
||||
- Client type: `OpenID Connect`
|
||||
- Client ID: `gitea-mirror`
|
||||
- Name: `Gitea Mirror Application`
|
||||
4. Click "Next"
|
||||
5. Enable:
|
||||
- Client authentication: `ON`
|
||||
- Authorization: `OFF`
|
||||
- Standard flow: `ON`
|
||||
- Direct access grants: `OFF`
|
||||
6. Click "Next"
|
||||
7. Set the following URLs:
|
||||
- Root URL: `http://localhost:4321`
|
||||
- Valid redirect URIs: `http://localhost:4321/api/auth/sso/callback/keycloak`
|
||||
- Valid post logout redirect URIs: `http://localhost:4321`
|
||||
- Web origins: `http://localhost:4321`
|
||||
8. Click "Save"
|
||||
|
||||
## 4. Get Client Credentials
|
||||
|
||||
1. Go to the "Credentials" tab of your client
|
||||
2. Copy the "Client secret"
|
||||
|
||||
## 5. Configure Keycloak SSO in Gitea Mirror
|
||||
|
||||
1. Go to your Gitea Mirror settings: http://localhost:4321/settings
|
||||
2. Navigate to "Authentication" → "SSO Settings"
|
||||
3. Click "Add SSO Provider"
|
||||
4. Fill in:
|
||||
- **Provider ID**: `keycloak`
|
||||
- **Issuer URL**: `http://localhost:8080/realms/master` (or `http://localhost:8080/realms/gitea-mirror` if you created a new realm)
|
||||
- **Client ID**: `gitea-mirror`
|
||||
- **Client Secret**: (paste the secret from step 4)
|
||||
- **Email Domain**: Leave empty or set a specific domain to restrict access
|
||||
- **Scopes**: Select the scopes you want to test:
|
||||
- `openid` (required)
|
||||
- `profile`
|
||||
- `email`
|
||||
- `offline_access` (Keycloak supports this!)
|
||||
|
||||
## 6. Optional: Create Test Users in Keycloak
|
||||
|
||||
1. Go to "Users" in the left menu
|
||||
2. Click "Add user"
|
||||
3. Fill in:
|
||||
- Username: `testuser`
|
||||
- Email: `testuser@example.com`
|
||||
- Email verified: `ON`
|
||||
4. Click "Create"
|
||||
5. Go to "Credentials" tab
|
||||
6. Click "Set password"
|
||||
7. Set a password and turn off "Temporary"
|
||||
|
||||
## 7. Test SSO Login
|
||||
|
||||
1. Logout from Gitea Mirror if you're logged in
|
||||
2. Go to the login page: http://localhost:4321/login
|
||||
3. Click "Continue with SSO"
|
||||
4. Enter the email address (e.g., `testuser@example.com`)
|
||||
5. You'll be redirected to Keycloak
|
||||
6. Login with your Keycloak user credentials
|
||||
7. You should be redirected back to Gitea Mirror and logged in!
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If you get SSL/TLS errors, make sure you're using the correct URLs (http for both Keycloak and Gitea Mirror)
|
||||
- Check the browser console and network tab for any errors
|
||||
- Keycloak logs: `docker logs gitea-mirror-keycloak`
|
||||
- The `offline_access` scope should work with Keycloak (unlike Google)
|
||||
190
src/lib/auth-multi-url.test.ts
Normal file
190
src/lib/auth-multi-url.test.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||
|
||||
describe("Multiple URL Support in BETTER_AUTH_URL", () => {
|
||||
let originalAuthUrl: string | undefined;
|
||||
let originalTrustedOrigins: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
// Save original environment variables
|
||||
originalAuthUrl = process.env.BETTER_AUTH_URL;
|
||||
originalTrustedOrigins = process.env.BETTER_AUTH_TRUSTED_ORIGINS;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original environment variables
|
||||
if (originalAuthUrl !== undefined) {
|
||||
process.env.BETTER_AUTH_URL = originalAuthUrl;
|
||||
} else {
|
||||
delete process.env.BETTER_AUTH_URL;
|
||||
}
|
||||
|
||||
if (originalTrustedOrigins !== undefined) {
|
||||
process.env.BETTER_AUTH_TRUSTED_ORIGINS = originalTrustedOrigins;
|
||||
} else {
|
||||
delete process.env.BETTER_AUTH_TRUSTED_ORIGINS;
|
||||
}
|
||||
});
|
||||
|
||||
test("should parse single URL correctly", () => {
|
||||
process.env.BETTER_AUTH_URL = "https://gitea-mirror.mydomain.tld";
|
||||
|
||||
const parseAuthUrls = () => {
|
||||
const urlEnv = process.env.BETTER_AUTH_URL || "http://localhost:4321";
|
||||
const urls = urlEnv.split(',').map(u => u.trim()).filter(Boolean);
|
||||
|
||||
// Find first valid URL
|
||||
for (const url of urls) {
|
||||
try {
|
||||
new URL(url);
|
||||
return { primary: url, all: urls };
|
||||
} catch {
|
||||
// Skip invalid
|
||||
}
|
||||
}
|
||||
return { primary: "http://localhost:4321", all: [] };
|
||||
};
|
||||
|
||||
const result = parseAuthUrls();
|
||||
expect(result.primary).toBe("https://gitea-mirror.mydomain.tld");
|
||||
expect(result.all).toEqual(["https://gitea-mirror.mydomain.tld"]);
|
||||
});
|
||||
|
||||
test("should parse multiple URLs and use first as primary", () => {
|
||||
process.env.BETTER_AUTH_URL = "http://10.10.20.45:4321,https://gitea-mirror.mydomain.tld";
|
||||
|
||||
const parseAuthUrls = () => {
|
||||
const urlEnv = process.env.BETTER_AUTH_URL || "http://localhost:4321";
|
||||
const urls = urlEnv.split(',').map(u => u.trim()).filter(Boolean);
|
||||
|
||||
// Find first valid URL
|
||||
for (const url of urls) {
|
||||
try {
|
||||
new URL(url);
|
||||
return { primary: url, all: urls };
|
||||
} catch {
|
||||
// Skip invalid
|
||||
}
|
||||
}
|
||||
return { primary: "http://localhost:4321", all: [] };
|
||||
};
|
||||
|
||||
const result = parseAuthUrls();
|
||||
expect(result.primary).toBe("http://10.10.20.45:4321");
|
||||
expect(result.all).toEqual([
|
||||
"http://10.10.20.45:4321",
|
||||
"https://gitea-mirror.mydomain.tld"
|
||||
]);
|
||||
});
|
||||
|
||||
test("should handle invalid URLs gracefully", () => {
|
||||
process.env.BETTER_AUTH_URL = "not-a-url,http://valid.url:4321,also-invalid";
|
||||
|
||||
const parseAuthUrls = () => {
|
||||
const urlEnv = process.env.BETTER_AUTH_URL || "http://localhost:4321";
|
||||
const urls = urlEnv.split(',').map(u => u.trim()).filter(Boolean);
|
||||
|
||||
const validUrls: string[] = [];
|
||||
let primaryUrl = "";
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
new URL(url);
|
||||
validUrls.push(url);
|
||||
if (!primaryUrl) {
|
||||
primaryUrl = url;
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid URLs
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
primary: primaryUrl || "http://localhost:4321",
|
||||
all: validUrls
|
||||
};
|
||||
};
|
||||
|
||||
const result = parseAuthUrls();
|
||||
expect(result.primary).toBe("http://valid.url:4321");
|
||||
expect(result.all).toEqual(["http://valid.url:4321"]);
|
||||
});
|
||||
|
||||
test("should include all URLs in trusted origins", () => {
|
||||
process.env.BETTER_AUTH_URL = "http://10.10.20.45:4321,https://gitea-mirror.mydomain.tld";
|
||||
process.env.BETTER_AUTH_TRUSTED_ORIGINS = "https://auth.provider.com";
|
||||
|
||||
const getTrustedOrigins = () => {
|
||||
const origins = [
|
||||
"http://localhost:4321",
|
||||
"http://localhost:8080",
|
||||
];
|
||||
|
||||
// Add all URLs from BETTER_AUTH_URL
|
||||
const urlEnv = process.env.BETTER_AUTH_URL || "";
|
||||
if (urlEnv) {
|
||||
const urls = urlEnv.split(',').map(u => u.trim()).filter(Boolean);
|
||||
urls.forEach(url => {
|
||||
try {
|
||||
new URL(url);
|
||||
origins.push(url);
|
||||
} catch {
|
||||
// Skip invalid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add additional trusted origins
|
||||
if (process.env.BETTER_AUTH_TRUSTED_ORIGINS) {
|
||||
origins.push(...process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(',').map(o => o.trim()));
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
return [...new Set(origins.filter(Boolean))];
|
||||
};
|
||||
|
||||
const origins = getTrustedOrigins();
|
||||
expect(origins).toContain("http://10.10.20.45:4321");
|
||||
expect(origins).toContain("https://gitea-mirror.mydomain.tld");
|
||||
expect(origins).toContain("https://auth.provider.com");
|
||||
expect(origins).toContain("http://localhost:4321");
|
||||
});
|
||||
|
||||
test("should handle empty BETTER_AUTH_URL", () => {
|
||||
delete process.env.BETTER_AUTH_URL;
|
||||
|
||||
const parseAuthUrls = () => {
|
||||
const urlEnv = process.env.BETTER_AUTH_URL || "http://localhost:4321";
|
||||
const urls = urlEnv.split(',').map(u => u.trim()).filter(Boolean);
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
new URL(url);
|
||||
return { primary: url, all: urls };
|
||||
} catch {
|
||||
// Skip invalid
|
||||
}
|
||||
}
|
||||
return { primary: "http://localhost:4321", all: ["http://localhost:4321"] };
|
||||
};
|
||||
|
||||
const result = parseAuthUrls();
|
||||
expect(result.primary).toBe("http://localhost:4321");
|
||||
});
|
||||
|
||||
test("should handle whitespace in comma-separated URLs", () => {
|
||||
process.env.BETTER_AUTH_URL = " http://10.10.20.45:4321 , https://gitea-mirror.mydomain.tld , http://localhost:3000 ";
|
||||
|
||||
const parseAuthUrls = () => {
|
||||
const urlEnv = process.env.BETTER_AUTH_URL || "http://localhost:4321";
|
||||
const urls = urlEnv.split(',').map(u => u.trim()).filter(Boolean);
|
||||
return urls;
|
||||
};
|
||||
|
||||
const urls = parseAuthUrls();
|
||||
expect(urls).toEqual([
|
||||
"http://10.10.20.45:4321",
|
||||
"https://gitea-mirror.mydomain.tld",
|
||||
"http://localhost:3000"
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -17,7 +17,7 @@ export const auth = betterAuth({
|
||||
// Secret for signing tokens
|
||||
secret: process.env.BETTER_AUTH_SECRET,
|
||||
|
||||
// Base URL configuration - ensure it's a valid URL
|
||||
// Base URL configuration - use the primary URL (Better Auth only supports single baseURL)
|
||||
baseURL: (() => {
|
||||
const url = process.env.BETTER_AUTH_URL || "http://localhost:4321";
|
||||
try {
|
||||
@@ -31,20 +31,30 @@ export const auth = betterAuth({
|
||||
})(),
|
||||
basePath: "/api/auth", // Specify the base path for auth endpoints
|
||||
|
||||
// Trusted origins for OAuth flows - parse from environment if set
|
||||
// Trusted origins - this is how we support multiple access URLs
|
||||
trustedOrigins: (() => {
|
||||
const origins = [
|
||||
"http://localhost:4321",
|
||||
"http://localhost:8080", // Keycloak
|
||||
process.env.BETTER_AUTH_URL || "http://localhost:4321"
|
||||
];
|
||||
|
||||
// Add trusted origins from environment if set
|
||||
// Add the primary URL from BETTER_AUTH_URL
|
||||
const primaryUrl = process.env.BETTER_AUTH_URL || "http://localhost:4321";
|
||||
try {
|
||||
new URL(primaryUrl);
|
||||
origins.push(primaryUrl);
|
||||
} catch {
|
||||
// Skip if invalid
|
||||
}
|
||||
|
||||
// Add additional trusted origins from environment
|
||||
// This is where users can specify multiple access URLs
|
||||
if (process.env.BETTER_AUTH_TRUSTED_ORIGINS) {
|
||||
origins.push(...process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(',').map(o => o.trim()));
|
||||
}
|
||||
|
||||
return origins.filter(Boolean);
|
||||
// Remove duplicates and return
|
||||
return [...new Set(origins.filter(Boolean))];
|
||||
})(),
|
||||
|
||||
// Authentication methods
|
||||
|
||||
Reference in New Issue
Block a user