diff --git a/.gitignore b/.gitignore index 8f0f1cc..c021301 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ backend/target/ build/ +e2e/node_modules/ +frontend/node_modules/ +frontend/dist/ diff --git a/README.md b/README.md index ac1a49e..b502271 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Keycloak Authentication Patterns +The 39-branch implementation registry is documented in +[`docs/keycloak-branch-index.md`](docs/keycloak-branch-index.md). + Keycloak을 중심으로 네 가지 브라우저 인증 통합 패턴을 같은 로컬 인프라에서 비교하는 학습 프로젝트입니다. diff --git a/docs/keycloak-branch-index.md b/docs/keycloak-branch-index.md new file mode 100644 index 0000000..b35cba6 --- /dev/null +++ b/docs/keycloak-branch-index.md @@ -0,0 +1,29 @@ +# Keycloak branch implementation index + +The source inventory contains 39 `feature-keycloak-*.md` branch notes. This +repository preserves one local Git feature branch for every note and merges it +with `--no-ff` into either the common `develop` baseline or one of the four +authentication-pattern branches. + +| Target | Meaning | +|---|---| +| `common` | Shared realm, federation, deployment, or governance contract. Merge into `develop`, then propagate to AP1–AP4. | +| `ap1` | Browser-based OAuth client: vanilla SPA, Authorization Code + PKCE, Resource Server. | +| `ap2` | Token-mediating confidential backend: browser receives access token only. | +| `ap3` | BFF: backend owns every OAuth token and browser owns only a session cookie. | +| `ap4` | Edge forward-auth: oauth2-proxy/Nginx owns login and backend trusts an isolated identity header. | + +The machine-readable registry is +[`keycloak-branch-manifest.tsv`](keycloak-branch-manifest.tsv). Run: + +```bash +./scripts/audit-keycloak-branches.sh +``` + +The audit succeeds only when all 39 note names have matching local feature +branches and each feature tip is reachable from its declared target branch. + +Google credentials are never committed. The default local acceptance harness +uses a second Keycloak realm as a controllable OIDC provider so claim mapping +and unsafe-linking failure paths can be reproduced. A real Google login remains +an explicit credentialed/public-HTTPS verification profile. diff --git a/docs/keycloak-branch-manifest.tsv b/docs/keycloak-branch-manifest.tsv new file mode 100644 index 0000000..bcbe1f7 --- /dev/null +++ b/docs/keycloak-branch-manifest.tsv @@ -0,0 +1,40 @@ +branch target delivery +feature/keycloak-account-linking-spa-ux ap1 documented-and-contract-tested +feature/keycloak-account-linking-sub-vs-email common documented-and-contract-tested +feature/keycloak-bff-csrf-samesite-defense ap3 locally-verified +feature/keycloak-bff-oauth2login-session ap3 locally-verified +feature/keycloak-bff-vs-spa-direct ap3 documented +feature/keycloak-docker-compose-stack common locally-verified +feature/keycloak-edge-forwardauth-google-federation ap4 documented-and-config-tested +feature/keycloak-edge-forwardauth-no-google ap4 documented-and-config-tested +feature/keycloak-federation-spa-zero-change ap1 contract-tested +feature/keycloak-first-broker-login-flow common locally-verified-with-mock-idp +feature/keycloak-four-pattern-tradeoff-matrix common documented-and-evidence-linked +feature/keycloak-google-claim-attribute-mapping common locally-verified-with-mock-idp +feature/keycloak-google-redirect-uri-policy common config-tested +feature/keycloak-header-spoofing-defense ap4 locally-verified +feature/keycloak-https-termination-caddy-nginx common config-tested +feature/keycloak-idp-brokering-google-client common locally-verified-with-mock-idp +feature/keycloak-idp-mappers-claim-to-role common locally-verified-with-mock-idp +feature/keycloak-internal-spa-direct-google-federation ap1 documented-and-contract-tested +feature/keycloak-internal-spa-direct-no-google ap1 documented-and-contract-tested +feature/keycloak-iss-claim-hostname-mismatch ap1 locally-verified +feature/keycloak-nginx-auth-request-integration ap4 locally-verified +feature/keycloak-oauth2-proxy-oidc-flow ap4 locally-verified +feature/keycloak-patterns common governance +feature/keycloak-pkce-flow-stages ap1 contract-tested +feature/keycloak-public-domain-tunneling common config-tested +feature/keycloak-realm-client-export common locally-verified +feature/keycloak-refresh-rotation-and-logout ap1 locally-verified +feature/keycloak-refresh-token-rotation ap1 contract-tested +feature/keycloak-reverse-proxy-headers common config-tested +feature/keycloak-single-ec2-google-federation ap1 documented-and-config-tested +feature/keycloak-single-ec2-no-google ap1 documented-and-contract-tested +feature/keycloak-spa-token-storage-tradeoff ap1 locally-verified +feature/keycloak-spring-rs-audience-validator ap1 locally-verified +feature/keycloak-spring-rs-role-mapping ap1 locally-verified +feature/keycloak-three-leg-trust-chain ap1 documented-and-contract-tested +feature/keycloak-token-mediating-access-handoff ap2 locally-verified +feature/keycloak-token-mediating-confidential-client ap2 locally-verified +feature/keycloak-traefik-forwardauth-alternative ap4 config-tested +feature/keycloak-vanilla-js-spa-pkce ap1 locally-verified diff --git a/scripts/audit-keycloak-branches.sh b/scripts/audit-keycloak-branches.sh new file mode 100755 index 0000000..b6279ae --- /dev/null +++ b/scripts/audit-keycloak-branches.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env sh +set -eu + +manifest="${1:-docs/keycloak-branch-manifest.tsv}" +notes_dir="${KEYCLOAK_BRANCH_NOTES_DIR:-/home/donghyeon/workspace/ai-tools/llm-wiki/raw/branch-notes}" + +expected_count="$(awk 'NR > 1 { count += 1 } END { print count + 0 }' "$manifest")" +if [ "$expected_count" -ne 39 ]; then + echo "manifest must contain exactly 39 Keycloak branches; found $expected_count" >&2 + exit 1 +fi + +note_count="$(find "$notes_dir" -maxdepth 1 -type f -name 'feature-keycloak-*.md' | wc -l)" +if [ "$note_count" -ne 39 ]; then + echo "branch-note inventory must contain exactly 39 files; found $note_count" >&2 + exit 1 +fi + +missing=0 +unmerged=0 +tab="$(printf '\t')" + +while IFS="$tab" read -r branch target delivery; do + [ "$branch" = "branch" ] && continue + + note_name="$(printf '%s\n' "$branch" | + sed 's#^feature/keycloak-#feature-keycloak-#').md" + if [ ! -f "$notes_dir/$note_name" ]; then + echo "missing branch note: $note_name" >&2 + missing=$((missing + 1)) + fi + + if ! git show-ref --verify --quiet "refs/heads/$branch"; then + echo "missing local branch: $branch" >&2 + missing=$((missing + 1)) + continue + fi + + case "$target" in + common) target_branch="develop" ;; + ap1) target_branch="develop-keycloak-pattern1" ;; + ap2) target_branch="develop-keycloak-pattern2" ;; + ap3) target_branch="develop-keycloak-pattern3" ;; + ap4) target_branch="develop-keycloak-pattern4" ;; + *) + echo "unknown target '$target' for $branch ($delivery)" >&2 + exit 1 + ;; + esac + + if ! git merge-base --is-ancestor "$branch" "$target_branch"; then + echo "feature tip is not merged: $branch -> $target_branch" >&2 + unmerged=$((unmerged + 1)) + fi +done < "$manifest" + +if [ "$missing" -ne 0 ] || [ "$unmerged" -ne 0 ]; then + echo "Keycloak branch audit failed: missing=$missing unmerged=$unmerged" >&2 + exit 1 +fi + +echo "Keycloak branch audit passed: 39/39 branches exist and are merged"