From a6564e9f63d521c82ab16a22f4aa2534b0982f27 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:39:13 +0000 Subject: [PATCH 1/2] chore(login): bumped base to v4.18.0 + reapplied branding --- .github/workflows/cs-iam-login.yml | 82 +++++++ .github/workflows/lint_test_build.yml | 14 +- .github/workflows/pack.yml | 139 ++--------- .github/workflows/release.yml | 26 +-- .github/workflows/upstream-bump.yml | 217 ++++++++++++++++++ apps/login/package.json | 3 +- .../components/idps/sign-in-with-generic.tsx | 98 +++++++- cs-iam-login.Dockerfile | 63 +++++ cs-iam-login.Dockerfile.dockerignore | 11 + cs-iam-login.base-version | 1 + pnpm-lock.yaml | 11 + 11 files changed, 506 insertions(+), 159 deletions(-) create mode 100644 .github/workflows/cs-iam-login.yml create mode 100644 .github/workflows/upstream-bump.yml create mode 100644 cs-iam-login.Dockerfile create mode 100644 cs-iam-login.Dockerfile.dockerignore create mode 100644 cs-iam-login.base-version diff --git a/.github/workflows/cs-iam-login.yml b/.github/workflows/cs-iam-login.yml new file mode 100644 index 00000000000..3fd221a5a2e --- /dev/null +++ b/.github/workflows/cs-iam-login.yml @@ -0,0 +1,82 @@ +name: 🐳 CS-IAM Login Image + +# BAUER GROUP fork build: publishes the branded Zitadel Login v2 image to GHCR. +# Upstream zitadel workflows stay disabled (Actions UI); this is our pipeline. +# Self-contained build (docker/build-push-action) — robust in a public fork. +# Production = release line (push); feature/*** PRs are validated build-only. + +on: + push: + branches: [production] + paths: + - "apps/login/**" + - "packages/**" + - "proto/**" + - "cs-iam-login.Dockerfile" + - "cs-iam-login.Dockerfile.dockerignore" + - "cs-iam-login.base-version" + - "pnpm-lock.yaml" + - ".github/workflows/cs-iam-login.yml" + pull_request: + branches: [production] + workflow_dispatch: + +permissions: + contents: read + packages: write + +concurrency: + group: cs-iam-login-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: 🐳 Build & Push login + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Resolve image version + id: ver + run: | + set -euo pipefail + V="$(tr -d '[:space:]' < cs-iam-login.base-version)" + V="${V#v}" # tag the image with the numeric upstream version (no leading v) + echo "version=$V" >> "$GITHUB_OUTPUT" + echo "::notice::Building login image version $V" + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/bauer-group/ep-zitadel/zitadel-login + tags: | + type=raw,value=${{ steps.ver.outputs.version }} + type=raw,value=stable + type=raw,value=latest,enable={{is_default_branch}} + type=sha + + - name: Build & push + uses: docker/build-push-action@v6 + with: + context: . + file: ./cs-iam-login.Dockerfile + platforms: linux/amd64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/lint_test_build.yml b/.github/workflows/lint_test_build.yml index f78ac575452..fb85a2250bc 100644 --- a/.github/workflows/lint_test_build.yml +++ b/.github/workflows/lint_test_build.yml @@ -55,23 +55,13 @@ jobs: run: pnpm install --frozen-lockfile - name: Set SHAs for nx affected commands uses: nrwl/nx-set-shas@v4 - # The Depot runner image points /etc/resolv.conf directly at public DNS (8.8.8.8), - # bypassing the systemd-resolved stub, so *.integration.localhost (synthesized by - # systemd-resolved) no longer resolves and all API integration tests hang. - # Route DNS back through the stub until the image is fixed or the tests stop - # depending on wildcard localhost DNS. - - name: Restore systemd-resolved stub for *.localhost resolution - run: | - set -ex - test -e /run/systemd/resolve/stub-resolv.conf - sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf - getent hosts foo.integration.localhost - name: Lint, Test and Build env: NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN || secrets.NX_CLOUD_ACCESS_TOKEN_READONLY }} + # NX_NATIVE_LOGGING: "nx::native::cache,nx::native::db" NX_DAEMON: "false" NX_DISABLE_DB: "true" - run: pnpm nx affected --nxBail --targets test build --exclude @zitadel/docs + run: pnpm nx affected --nxBail --targets lint test build --exclude @zitadel/docs - name: Check for uncommitted changes (Codegen mismatch) run: git diff --exit-code - name: Suggest Pipeline Fix By Nx Cloud AI diff --git a/.github/workflows/pack.yml b/.github/workflows/pack.yml index 649d4dc3216..8a92edbd6c1 100644 --- a/.github/workflows/pack.yml +++ b/.github/workflows/pack.yml @@ -40,7 +40,7 @@ jobs: semantic_version: ${{ inputs.semantic_version }} dry_run: true - pack-archives: + pack: runs-on: group: zitadel-public environment: ${{ github.ref_protected == 'true' && 'Protected' || null }} @@ -67,6 +67,10 @@ jobs: with: node-version: ${{ inputs.node_version }} cache: "pnpm" + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: v0.28.0 - name: Install dependencies run: pnpm install --frozen-lockfile - name: Pack API and Login @@ -84,59 +88,11 @@ jobs: with: name: zitadel-archives path: .artifacts/pack - - docker: - runs-on: - group: zitadel-public - environment: ${{ github.ref_protected == 'true' && 'Protected' || null }} - needs: [version, pack-archives] - permissions: - contents: read - packages: write - strategy: - matrix: - include: - - gofips140: "off" - tag_suffix: "" - push_ghcr: true - - gofips140: "certified" - tag_suffix: "-fips" - push_ghcr: false - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - filter: tree:0 - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - - name: Set up pnpm - uses: pnpm/action-setup@v4 - with: - run_install: false - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ inputs.node_version }} - cache: "pnpm" - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Build Linux API binaries - env: - ZITADEL_GOFIPS140: ${{ matrix.gofips140 }} - ZITADEL_VERSION: ${{ needs.version.outputs.version }} - NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN || secrets.NX_CLOUD_ACCESS_TOKEN_READONLY }} - run: pnpm nx run-many --nxBail -p @zitadel/api -t pack-linux-amd64,pack-linux-arm64 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - with: - version: v0.28.0 - name: Login to Docker registry - if: matrix.push_ghcr uses: docker/login-action@v3 with: registry: ghcr.io @@ -148,36 +104,24 @@ jobs: registry: europe-docker.pkg.dev username: _json_key_base64 password: ${{ secrets.GCR_JSON_KEY_BASE64 }} - - name: Set API image names - id: api-images - run: | - if [ "${{ matrix.push_ghcr }}" = "true" ]; then - { - echo "${{ inputs.image_name_github_api }}" - echo "${{ inputs.image_name_google_api }}" - } > api-images.txt - else - echo "${{ inputs.image_name_google_api }}" > api-images.txt - fi - echo "list<> "$GITHUB_OUTPUT" - cat api-images.txt >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - name: Generate Standard Tags and Labels from the GitHub Context for the API Scratch Container Image id: scratch-meta uses: docker/metadata-action@v5 with: - images: ${{ steps.api-images.outputs.list }} + images: | + ${{ inputs.image_name_github_api }} + ${{ inputs.image_name_google_api }} labels: ${{ env.default_labels}} tags: | - type=sha,prefix=,suffix=${{ matrix.tag_suffix }},format=long + type=sha,prefix=,suffix=,format=long - name: Build and Push the SHA-tagged API Scratch Container Image id: build-scratch uses: docker/build-push-action@v6 timeout-minutes: 3 with: context: . - cache-from: type=gha,scope=api-scratch-${{ matrix.gofips140 }} - cache-to: type=gha,mode=max,scope=api-scratch-${{ matrix.gofips140 }} + cache-from: type=gha + cache-to: type=gha,mode=max file: apps/api/Dockerfile target: final platforms: linux/amd64,linux/arm64 @@ -188,19 +132,20 @@ jobs: id: debug-meta uses: docker/metadata-action@v5 with: - images: ${{ steps.api-images.outputs.list }} + images: | + ${{ inputs.image_name_github_api }} + ${{ inputs.image_name_google_api }} labels: ${{ env.default_labels}} tags: | - type=sha,prefix=,suffix=${{ matrix.tag_suffix }}-debug,format=long + type=sha,prefix=,suffix=-debug,format=long - name: Build and Push the SHA-tagged API Debug Container Image - if: matrix.push_ghcr id: build-debug uses: docker/build-push-action@v6 timeout-minutes: 5 with: context: . - cache-from: type=gha,scope=api-debug-${{ matrix.gofips140 }} - cache-to: type=gha,mode=max,scope=api-debug-${{ matrix.gofips140 }} + cache-from: type=gha + cache-to: type=gha,mode=max file: apps/api/Dockerfile target: builder platforms: linux/amd64,linux/arm64 @@ -208,25 +153,7 @@ jobs: labels: ${{ steps.debug-meta.outputs.labels }} tags: ${{ steps.debug-meta.outputs.tags }} outputs: type=image,name=${{ inputs.image_name_github_api }},name-canonical=true - - name: Build and Push the SHA-tagged API Debug Container Image (GAR only) - if: ${{ !matrix.push_ghcr }} - uses: docker/build-push-action@v6 - timeout-minutes: 5 - with: - context: . - cache-from: type=gha,scope=api-debug-${{ matrix.gofips140 }} - cache-to: type=gha,mode=max,scope=api-debug-${{ matrix.gofips140 }} - file: apps/api/Dockerfile - target: builder - platforms: linux/amd64,linux/arm64 - push: true - labels: ${{ steps.debug-meta.outputs.labels }} - tags: ${{ steps.debug-meta.outputs.tags }} - - name: Build Login standalone - if: matrix.push_ghcr - run: pnpm nx run --nxBail @zitadel/login:build - name: Generate Standard Tags and Labels from the GitHub Context for the Login Container Image - if: matrix.push_ghcr id: login-meta uses: docker/metadata-action@v5 with: @@ -239,42 +166,14 @@ jobs: tags: | type=sha,prefix=,suffix=,format=long - name: Build and Push the SHA-tagged Login Container Image - if: matrix.push_ghcr id: build-login uses: docker/build-push-action@v6 timeout-minutes: 3 with: context: apps/login - cache-from: type=gha,scope=login - cache-to: type=gha,mode=max,scope=login + cache-from: type=gha + cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 push: true labels: ${{ steps.login-meta.outputs.labels }} tags: ${{ steps.login-meta.outputs.tags }} - - name: Build Login standalone - if: ${{ !matrix.push_ghcr }} - run: pnpm nx run --nxBail @zitadel/login:build - - name: Generate tags for FIPS Login image - if: ${{ !matrix.push_ghcr }} - id: login-fips-meta - uses: docker/metadata-action@v5 - with: - images: ${{ inputs.image_name_google_login }} - labels: | - org.opencontainers.image.licenses=MIT - ${{ env.default_labels}} - tags: | - type=sha,prefix=,suffix=-fips,format=long - - name: Build and Push FIPS Login image (GAR only) - if: ${{ !matrix.push_ghcr }} - uses: docker/build-push-action@v6 - timeout-minutes: 3 - with: - context: apps/login - file: apps/login/Dockerfile.fips - cache-from: type=gha,scope=login-fips - cache-to: type=gha,mode=max,scope=login-fips - platforms: linux/amd64,linux/arm64 - push: true - labels: ${{ steps.login-fips-meta.outputs.labels }} - tags: ${{ steps.login-fips-meta.outputs.tags }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7fbead1f652..b68fb62a0ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -103,24 +103,15 @@ jobs: docker buildx imagetools create \ --tag ${{ inputs.image_name_google_api }}:${{ needs.version.outputs.version }} \ ${{ inputs.image_name_google_api }}:${{ github.sha }} - docker buildx imagetools create \ - --tag ${{ inputs.image_name_google_api }}:${{ needs.version.outputs.version }}-fips \ - ${{ inputs.image_name_google_api }}:${{ github.sha }}-fips - docker buildx imagetools create \ - --tag ${{ inputs.image_name_google_api }}:${{ needs.version.outputs.version }}-fips-debug \ - ${{ inputs.image_name_google_api }}:${{ github.sha }}-fips-debug docker buildx imagetools create \ --tag ${{ inputs.image_name_github_login }}:${{ needs.version.outputs.version }} \ ${{ inputs.image_name_github_login }}:${{ github.sha }} docker buildx imagetools create \ --tag ${{ inputs.image_name_google_login }}:${{ needs.version.outputs.version }} \ ${{ inputs.image_name_google_login }}:${{ github.sha }} - docker buildx imagetools create \ - --tag ${{ inputs.image_name_google_login }}:${{ needs.version.outputs.version }}-fips \ - ${{ inputs.image_name_google_login }}:${{ github.sha }}-fips - name: Publish latest - if: ${{ github.ref_name == 'v4.x' }} + if: ${{ github.ref_name == 'next' }} run: | docker buildx imagetools create \ --tag ${{ inputs.image_name_github_api }}:latest \ @@ -131,20 +122,11 @@ jobs: docker buildx imagetools create \ --tag ${{ inputs.image_name_github_login }}:latest \ ${{ inputs.image_name_github_login }}:${{ github.sha }} - docker buildx imagetools create \ - --tag ${{ inputs.image_name_google_api }}:latest-fips \ - ${{ inputs.image_name_google_api }}:${{ github.sha }}-fips - docker buildx imagetools create \ - --tag ${{ inputs.image_name_google_api }}:latest-fips-debug \ - ${{ inputs.image_name_google_api }}:${{ github.sha }}-fips-debug - docker buildx imagetools create \ - --tag ${{ inputs.image_name_google_login }}:latest-fips \ - ${{ inputs.image_name_google_login }}:${{ github.sha }}-fips homebrew-tap: runs-on: ubuntu-22.04 needs: version - if: needs.version.outputs.published == 'true' && github.ref_name == 'v4.x' + if: needs.version.outputs.published == 'true' && github.ref_name == 'next' continue-on-error: true steps: - name: generate token @@ -164,7 +146,7 @@ jobs: helm-chart: runs-on: ubuntu-22.04 needs: version - if: needs.version.outputs.published == 'true' && github.ref_name == 'v4.x' + if: needs.version.outputs.published == 'true' && github.ref_name == 'next' continue-on-error: true steps: - name: generate token @@ -184,7 +166,7 @@ jobs: npm-packages: runs-on: ubuntu-latest needs: version - if: needs.version.outputs.published == 'true' && github.ref_name == 'v4.x' + if: needs.version.outputs.published == 'true' && github.ref_name == 'next' continue-on-error: true steps: - name: Checkout code diff --git a/.github/workflows/upstream-bump.yml b/.github/workflows/upstream-bump.yml new file mode 100644 index 00000000000..43e7e352d61 --- /dev/null +++ b/.github/workflows/upstream-bump.yml @@ -0,0 +1,217 @@ +name: ⬆️ Upstream Login Bump + +# Automatic maintenance: keeps the branded Login v2 image current with upstream. +# +# On a new stable zitadel release (daily check, or manual dispatch) it: +# 1. branches feature/bump- from that tag and re-applies the BAUER GROUP +# branding (the one-file patch + simple-icons + lockfile), +# 2. BUILDS + PUBLISHES the login image (this is the gate — a release that +# breaks our patch fails here and nothing else happens), +# 3. only then FORCE-ROLLS `production` to the new base (production is a +# "release tag + our patch" line, not an accumulating branch). +# +# The published image is PINNED by consumers (CS-IAM LOGIN_BASE_VERSION), so a +# new base never auto-changes a running stack — CS-IAM adopts deliberately, in +# lockstep with the core (BASE_ZITADEL_VERSION). This job just makes sure the +# current image always exists. + +on: + schedule: + - cron: "0 6 * * *" # daily 06:00 UTC — skips fast when already current + workflow_dispatch: + inputs: + target_tag: + description: "Upstream zitadel tag to bump to (blank = latest stable release)" + required: false + type: string + +permissions: + contents: write # force-roll production + packages: write # publish the login image + +concurrency: + group: upstream-bump + cancel-in-progress: false + +jobs: + bump: + name: Track, build & adopt + runs-on: ubuntu-latest + timeout-minutes: 40 + env: + INPUT_TAG: ${{ inputs.target_tag }} + steps: + - name: Checkout production + uses: actions/checkout@v4 + with: + ref: production + fetch-depth: 0 + + - name: Resolve current + target versions + id: ver + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + CURRENT="$(tr -d '[:space:]' < cs-iam-login.base-version 2>/dev/null || true)" + MAJOR="${CURRENT#v}"; MAJOR="${MAJOR%%.*}" + TARGET="$INPUT_TAG" + MANUAL=true + if [ -z "$TARGET" ]; then + # Track the highest STABLE release on the major line we're on (v${MAJOR}.x). + # + # NOT `releases/latest`: zitadel ships parallel maintenance lines, so a + # v3.x backport published *after* the newest v4.x flips GitHub's "latest" + # to v3 (it picks most-recent-by-DATE, not highest semver). That would + # downgrade us across a major and break — apps/login doesn't exist before + # v4. Major jumps are deliberate: dispatch manually with target_tag. + MANUAL=false + TARGET="$(gh api 'repos/zitadel/zitadel/releases?per_page=100' \ + | jq -r --arg m "$MAJOR" ' + [ .[] + | select(.draft == false and .prerelease == false) + | .tag_name + | select((ltrimstr("v") | split(".")) + | (length == 3 and .[0] == $m and all(test("^[0-9]+$")))) + ] + | sort_by(ltrimstr("v") | split(".") | map(tonumber)) + | last // ""')" + fi + if [ -z "$TARGET" ]; then + echo "::error::No stable upstream release resolved (current=$CURRENT, major=$MAJOR)." + exit 1 + fi + DOCKER_VER="${TARGET#v}" # numeric tag for the image (no leading v) + SKIP=false + if [ "$CURRENT" = "$TARGET" ]; then + SKIP=true + echo "::notice::Login already based on $CURRENT — nothing to bump." + elif [ "$MANUAL" = "false" ]; then + # Forward-only on the automatic path: never roll backwards even if the + # resolved tag is somehow older (belt-and-suspenders to the major pin). + HIGHER="$(printf '%s\n%s\n' "${CURRENT#v}" "${TARGET#v}" | sort -V | tail -n1)" + if [ "$HIGHER" = "${CURRENT#v}" ]; then + SKIP=true + echo "::notice::Latest v${MAJOR}.x ($TARGET) is not newer than current ($CURRENT) — nothing to bump." + fi + fi + if [ "$SKIP" = "false" ]; then + echo "::notice::Bumping login base $CURRENT -> $TARGET" + fi + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + echo "target=$TARGET" >> "$GITHUB_OUTPUT" + echo "docker_ver=$DOCKER_VER" >> "$GITHUB_OUTPUT" + echo "skip=$SKIP" >> "$GITHUB_OUTPUT" + + - name: Set up pnpm + if: steps.ver.outputs.skip == 'false' + uses: pnpm/action-setup@v4 + + - name: Set up Node + if: steps.ver.outputs.skip == 'false' + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Prepare the new base + re-apply branding + if: steps.ver.outputs.skip == 'false' + env: + TARGET: ${{ steps.ver.outputs.target }} + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git remote add upstream https://github.com/zitadel/zitadel.git + git fetch --depth 1 upstream "refs/tags/${TARGET}:refs/tags/${TARGET}" + git checkout -b "feature/bump-${TARGET}" "${TARGET}" + # Bring our (additive + one-file) customization from production. + git checkout production -- \ + cs-iam-login.Dockerfile \ + cs-iam-login.Dockerfile.dockerignore \ + apps/login/src/components/idps/sign-in-with-generic.tsx + # Adopt our COMPLETE CI-infra: replace upstream's workflow tree with + # production's verbatim. Two reasons, both load-bearing: + # 1. The final adopt step pushes with GITHUB_TOKEN, which is FORBIDDEN + # from creating/updating any file under .github/workflows/ (a hard + # GitHub guardrail, not grantable via `permissions:`). Restoring the + # whole tree makes the workflow files byte-identical to production, + # so the force-push introduces zero workflow changes and is allowed. + # 2. Our workflows ARE the CI-infra (production = upstream + CI-infra). + # Cherry-picking only a couple would let upstream's versions of the + # shared workflows leak in and silently overwrite our customizations. + # rm-then-checkout (not a plain checkout) so any upstream-only workflow + # file is dropped too — the tree ends up exactly equal to production's. + git rm -rq .github/workflows + git checkout production -- .github/workflows + # Re-add the simple-icons dependency + record the new base version. + node -e "const f='apps/login/package.json';const p=require('./'+f);p.dependencies=p.dependencies||{};p.dependencies['simple-icons']='^16.23.0';require('fs').writeFileSync(f,JSON.stringify(p,null,2)+'\n');" + echo "${TARGET}" > cs-iam-login.base-version + corepack enable + pnpm install --lockfile-only --no-frozen-lockfile + git add -A + git commit -m "chore(login): bumped base to ${TARGET} + reapplied branding" + + - name: Set up Buildx + if: steps.ver.outputs.skip == 'false' + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + if: steps.ver.outputs.skip == 'false' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Image metadata + if: steps.ver.outputs.skip == 'false' + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/bauer-group/ep-zitadel/zitadel-login + tags: | + type=raw,value=${{ steps.ver.outputs.docker_ver }} + type=raw,value=stable + type=raw,value=latest + type=sha + + - name: Build & publish (the gate) + if: steps.ver.outputs.skip == 'false' + uses: docker/build-push-action@v6 + with: + context: . + file: ./cs-iam-login.Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Adopt — force-roll production to the new base + if: steps.ver.outputs.skip == 'false' + run: | + set -euo pipefail + # Reached only when the build above succeeded. + git push origin "HEAD:production" --force + git push origin --delete "feature/bump-${{ steps.ver.outputs.target }}" 2>/dev/null || true + + - name: Summary + if: steps.ver.outputs.skip == 'false' + env: + CURRENT: ${{ steps.ver.outputs.current }} + TARGET: ${{ steps.ver.outputs.target }} + DOCKER_VER: ${{ steps.ver.outputs.docker_ver }} + run: | + { + echo "## ⬆️ Login base bumped" + echo "" + echo "- Base: \`${CURRENT}\` → \`${TARGET}\`" + echo "- Published: \`ghcr.io/bauer-group/ep-zitadel/zitadel-login:${DOCKER_VER}\` (+ stable, latest)" + echo "- \`production\` rolled to the new base." + echo "" + echo "### Next (deliberate, in CS-IAM)" + echo "Bump in lockstep to keep core + login matched:" + echo "- \`LOGIN_BASE_VERSION\` → \`${DOCKER_VER}\` (src/login overlay base)" + echo "- \`BASE_ZITADEL_VERSION\` → \`${TARGET}\` (src/zitadel core)" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/apps/login/package.json b/apps/login/package.json index d186252a889..f6fab6199c8 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -56,7 +56,8 @@ "server-only": "^0.0.1", "tinycolor2": "^1.6.0", "uuid": "^14.0.0", - "winston": "^3.19.0" + "winston": "^3.19.0", + "simple-icons": "^16.23.0" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/apps/login/src/components/idps/sign-in-with-generic.tsx b/apps/login/src/components/idps/sign-in-with-generic.tsx index 9c01547cbc4..28d9f78f561 100644 --- a/apps/login/src/components/idps/sign-in-with-generic.tsx +++ b/apps/login/src/components/idps/sign-in-with-generic.tsx @@ -1,14 +1,104 @@ "use client"; -import { forwardRef } from "react"; +import { forwardRef, type CSSProperties } from "react"; +import { + siFacebook, + siKakaotalk, + siLine, + siNaver, + siQq, + siSinaweibo, + siTiktok, + siVk, + siWechat, + siX, + siZalo, + type SimpleIcon, +} from "simple-icons"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; +// Generic OAuth2/OIDC identity providers have no dedicated branded button, so +// upstream renders them as a plain name while native providers (Google, GitHub, +// Apple, …) show their logo. Match the configured display name to a well-known +// brand glyph (simple-icons) and render the real logo — making social/regional +// providers (Facebook, LINE, WeChat, KakaoTalk, Naver, X, TikTok, QQ, Weibo, +// VK, Zalo) instantly recognisable. Names that don't match keep the original +// name-only button, so there is no behavioural change for unknown providers. +// +// Matches use word boundaries so a token can't hit a substring of an unrelated +// name (e.g. "meta" must not match "Metadata SSO"). +const BRAND_ICONS: { match: RegExp; icon: SimpleIcon }[] = [ + { match: /\bfacebook\b|\bmeta\b/i, icon: siFacebook }, + { match: /\bwechat\b|\bweixin\b|微信/i, icon: siWechat }, + { match: /\bkakao(talk)?\b/i, icon: siKakaotalk }, + { match: /\bnaver\b/i, icon: siNaver }, + { match: /\btiktok\b|\bdouyin\b/i, icon: siTiktok }, + { match: /\bweibo\b|微博/i, icon: siSinaweibo }, + { match: /\bzalo\b/i, icon: siZalo }, + { match: /\bline\b/i, icon: siLine }, + { match: /\bqq\b|\btencent\b/i, icon: siQq }, + { match: /\bvk\b|\bvkontakte\b/i, icon: siVk }, + { match: /\b(x|twitter)\b/i, icon: siX }, +]; + +function brandIcon(name?: string): SimpleIcon | undefined { + if (!name) return undefined; + return BRAND_ICONS.find((b) => b.match.test(name))?.icon; +} + +// simple-icons ship a single brand colour, which can be illegible against one +// theme: near-black brands (X, TikTok #000) vanish on the dark background, and +// near-white ones (KakaoTalk #FFCD00) vanish on the light one. Keep the brand +// colour where it has contrast and only swap the illegible end per theme. The +// fills are exposed as CSS variables + Tailwind `dark:` so the theme switch is +// pure CSS (no JS theme read → no hydration flash). +function brandFills(hex: string): { light: string; dark: string } { + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; + const brand = `#${hex}`; + return { + light: luminance > 0.7 ? "#18181b" : brand, // too light for the white login bg + dark: luminance < 0.2 ? "#ffffff" : brand, // too dark for the dark login bg + }; +} + export const SignInWithGeneric = forwardRef( function SignInWithGeneric(props, ref) { - const { children, name = "", className = "h-[50px]", ...restProps } = props; + const { children, name = "", className, ...restProps } = props; + const icon = brandIcon(name); + + // Recognised brand: mirror the native branded buttons (Google/GitHub). The + // icon row sets the button height (so no name-only "h-[50px] pl-20" default + // is wanted here), and the visible name is the accessible label — the glyph + // is therefore decorative (aria-hidden, no role/title) to avoid the screen + // reader announcing the provider name twice. + if (icon) { + const fills = brandFills(icon.hex); + return ( + +
+ +
+ {children ? children : {name}} +
+ ); + } + + // Unknown provider: unchanged upstream name-only button. return ( - - {children ? children : {name}} + + {children ? children : {name}} ); }, diff --git a/cs-iam-login.Dockerfile b/cs-iam-login.Dockerfile new file mode 100644 index 00000000000..6ed055f9cb5 --- /dev/null +++ b/cs-iam-login.Dockerfile @@ -0,0 +1,63 @@ +# =============================================================================== +# BAUER GROUP — Zitadel Login v2 (branded, per-IdP brand logos) +# =============================================================================== +# Self-contained multi-stage build of apps/login from this monorepo. The +# workspace packages (@zitadel/client, @zitadel/proto) + buf proto codegen are +# all present here, so it builds exactly as upstream does — no published-package +# shortcut. Only customization: src/components/idps/sign-in-with-generic.tsx +# (real brand logos via simple-icons). +# +# docker build -f cs-iam-login.Dockerfile -t cs-iam/login . +# =============================================================================== + +# --------------------------------------------------------------------------- +# Stage 1: Builder — produce the standalone Next build +# --------------------------------------------------------------------------- +FROM node:24-alpine AS builder +ENV PNPM_HOME=/pnpm +ENV PATH=$PNPM_HOME:$PATH +RUN corepack enable && apk add --no-cache git ca-certificates +WORKDIR /repo + +COPY . . +RUN pnpm install --no-frozen-lockfile +# Generate the protobuf TS (buf) that @zitadel/client + apps/login depend on. +RUN pnpm --filter @zitadel/proto generate +# Build the login (nx orchestrates @zitadel/client → @zitadel/login standalone). +RUN pnpm nx run @zitadel/login:build + +# --------------------------------------------------------------------------- +# Stage 2: Runtime — mirrors upstream apps/login/Dockerfile +# --------------------------------------------------------------------------- +FROM node:24-alpine + +LABEL vendor="BAUER GROUP" +LABEL maintainer="Karl Bauer " + +LABEL org.opencontainers.image.title="EP-Zitadel Login v2" +LABEL org.opencontainers.image.description="Zitadel Login v2 with per-IdP brand logos - BAUER GROUP fork (EP-Zitadel)" +LABEL org.opencontainers.image.vendor="BAUER GROUP" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.source="https://github.com/bauer-group/EP-Zitadel" + +WORKDIR /app +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs +RUN mkdir -p /.env-file && touch /.env-file/.env && chown -R nextjs:nodejs /.env-file + +COPY --from=builder --chown=nextjs:nodejs /repo/apps/login/.next/standalone ./ + +USER nextjs +ENV HOSTNAME="::" \ + PORT="3000" \ + NODE_ENV="production" \ + NODE_OPTIONS="--use-openssl-ca --require /app/load-ssl-cert-dir.cjs" \ + SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt" \ + ZITADEL_TLS_ENABLED="false" \ + OTEL_SERVICE_NAME="zitadel-login" \ + OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" + +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD ["/usr/local/bin/node", "/app/healthcheck.mjs", "/ui/v2/login/ready"] + +ENTRYPOINT ["/app/entrypoint.sh", "node", "apps/login/server.js"] diff --git a/cs-iam-login.Dockerfile.dockerignore b/cs-iam-login.Dockerfile.dockerignore new file mode 100644 index 00000000000..27cd1c57130 --- /dev/null +++ b/cs-iam-login.Dockerfile.dockerignore @@ -0,0 +1,11 @@ +# Scoped ignore for the CS-IAM login build (buildkit uses this for +# cs-iam-login.Dockerfile and leaves upstream .dockerignore untouched). +# Cut the heavy artifacts; keep ALL source so nx can build the workspace graph. +.git +**/node_modules +**/.next +**/.nx +**/.turbo +**/dist +**/.artifacts +**/*.log diff --git a/cs-iam-login.base-version b/cs-iam-login.base-version new file mode 100644 index 00000000000..5a74a287689 --- /dev/null +++ b/cs-iam-login.base-version @@ -0,0 +1 @@ +v4.18.0 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09e97bb15f7..401c3e68cc1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -289,6 +289,9 @@ importers: server-only: specifier: ^0.0.1 version: 0.0.1 + simple-icons: + specifier: ^16.23.0 + version: 16.32.0 tinycolor2: specifier: ^1.6.0 version: 1.6.0 @@ -966,6 +969,7 @@ packages: '@angular/animations@21.2.20': resolution: {integrity: sha512-H3Abe/dfPKNaGaoaqjrN9tZYNrAcoPnx5STLiAmpFSO4wz82/wwPO2mZCOBG4qjefredkNuuW/e+uuqZccXabw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + deprecated: '@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.' peerDependencies: '@angular/core': 21.2.20 @@ -1096,6 +1100,7 @@ packages: '@angular/platform-browser-dynamic@21.2.20': resolution: {integrity: sha512-n3+eLj0F/hjf+cgLaNWsBcqZcaeGoh5TCMe+VXUnh6XRwbwXorxP91ypfMT/rj0QnroY6NoZcvvPlx1ezsIVrw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + deprecated: '@angular/platform-browser-dynamic is deprecated. Use `@angular/platform-browser` instead.' peerDependencies: '@angular/common': 21.2.20 '@angular/compiler': 21.2.20 @@ -10582,6 +10587,10 @@ packages: resolution: {integrity: sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==} engines: {node: ^20.17.0 || >=22.9.0} + simple-icons@16.32.0: + resolution: {integrity: sha512-BwqATHxAulx7X6kNdTkecy7PBjLkgtAHcgrwYLd9iA+cD2At9DzhJwdwaSELk2+aZe01IfiM/Wxtyp68Dvup2A==} + engines: {node: '>=0.12.18'} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -23198,6 +23207,8 @@ snapshots: transitivePeerDependencies: - supports-color + simple-icons@16.32.0: {} + simple-update-notifier@2.0.0: dependencies: semver: 7.8.5 From 5bad8dab8c4ab5b15a8389ae226d8ecebcbd032c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:47:04 +0000 Subject: [PATCH 2/2] chore(deps-dev): bump browserslist from 4.28.6 to 4.28.8 in /benchmark Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.28.6 to 4.28.8. - [Release notes](https://github.com/browserslist/browserslist/releases) - [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md) - [Commits](https://github.com/browserslist/browserslist/compare/4.28.6...4.28.8) --- updated-dependencies: - dependency-name: browserslist dependency-version: 4.28.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- benchmark/package-lock.json | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json index c827800c342..5f76fc6850f 100644 --- a/benchmark/package-lock.json +++ b/benchmark/package-lock.json @@ -2159,9 +2159,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.43", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", - "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "version": "2.11.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz", + "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2183,9 +2183,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", - "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", "dev": true, "funding": [ { @@ -2203,11 +2203,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001803", - "electron-to-chromium": "^1.5.389", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -2224,9 +2224,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001805", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", - "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "dev": true, "funding": [ { @@ -2404,9 +2404,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.391", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.391.tgz", - "integrity": "sha512-YmCu4856jkgKT1Nh6fwRdeVrM6Ydf/fBnq51tpmSfX+jOcUMTxh31yH6hjKScRenhB2oDSvA9oooxcpjogPeig==", + "version": "1.5.433", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.433.tgz", + "integrity": "sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==", "dev": true, "license": "ISC" }, @@ -3101,9 +3101,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.51", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", - "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "version": "2.0.56", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz", + "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==", "dev": true, "license": "MIT", "engines": { @@ -3773,9 +3773,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", "dev": true, "funding": [ {