refactor: architecture cleanup — OIDC dedup, PKCE, K8s hardening
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 2m59s

- Extract OidcProviderHelper for shared discovery + JWK source construction
- Add SystemRole.normalizeScope() to centralize role normalization
- Merge duplicate claim extraction in OidcTokenExchanger
- Add PKCE (S256) to OIDC authorization flow (frontend + backend)
- Add SecurityContext (runAsNonRoot) to all K8s deployments
- Fix postgres probe to use $POSTGRES_USER instead of hardcoded username
- Remove default credentials from Dockerfile
- Extract sanitize_branch() to shared .gitea/sanitize-branch.sh
- Fix sidebar to use /exchanges/ paths directly, remove legacy redirects
- Centralize basePath computation in router.tsx via config module

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-06 21:57:29 +02:00
parent 07ff576eb6
commit c502a42f17
19 changed files with 191 additions and 169 deletions

View File

@@ -11,6 +11,22 @@ interface OidcInfo {
authorizationEndpoint: string;
}
/** Generate a random code_verifier for PKCE (RFC 7636). */
function generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
/** Derive the S256 code_challenge from a code_verifier. */
async function deriveCodeChallenge(verifier: string): Promise<string> {
const data = new TextEncoder().encode(verifier);
const digest = await crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
const SUBTITLES = [
"Prove you're not a mirage",
"Only authorized cameleers beyond this dune",
@@ -68,14 +84,20 @@ export function LoginPage() {
if (oidc && !forceLocal && !autoRedirected.current) {
autoRedirected.current = true;
const redirectUri = `${window.location.origin}${config.basePath}oidc/callback`;
const params = new URLSearchParams({
response_type: 'code',
client_id: oidc.clientId,
redirect_uri: redirectUri,
scope: 'openid email profile',
prompt: 'none',
const verifier = generateCodeVerifier();
sessionStorage.setItem('oidc-code-verifier', verifier);
deriveCodeChallenge(verifier).then((challenge) => {
const params = new URLSearchParams({
response_type: 'code',
client_id: oidc.clientId,
redirect_uri: redirectUri,
scope: 'openid email profile',
prompt: 'none',
code_challenge: challenge,
code_challenge_method: 'S256',
});
window.location.href = `${oidc.authorizationEndpoint}?${params}`;
});
window.location.href = `${oidc.authorizationEndpoint}?${params}`;
}
}, [oidc, forceLocal]);
@@ -86,15 +108,20 @@ export function LoginPage() {
login(username, password);
};
const handleOidcLogin = () => {
const handleOidcLogin = async () => {
if (!oidc) return;
setOidcLoading(true);
const redirectUri = `${window.location.origin}${config.basePath}oidc/callback`;
const verifier = generateCodeVerifier();
sessionStorage.setItem('oidc-code-verifier', verifier);
const challenge = await deriveCodeChallenge(verifier);
const params = new URLSearchParams({
response_type: 'code',
client_id: oidc.clientId,
redirect_uri: redirectUri,
scope: 'openid email profile',
code_challenge: challenge,
code_challenge_method: 'S256',
});
window.location.href = `${oidc.authorizationEndpoint}?${params}`;
};