From ae70b8f14dd62f259f994725b7d88e829f228559 Mon Sep 17 00:00:00 2001 From: Javid Date: Sun, 30 Aug 2026 10:14:39 +0200 Subject: [PATCH 01/33] feat: add CoreLink Keycloak identity foundation --- .dockerignore | 6 +++++ .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++ .gitignore | 10 ++++++++ .prettierrc.json | 1 + Dockerfile | 17 ++++++++++++++ docs/ARCHITECTURE.md | 31 +++++++++++++++++++++++++ docs/DEPLOYMENT-CONTRACT.md | 42 ++++++++++++++++++++++++++++++++++ index.html | 13 +++++++++++ package.json | 31 +++++++++++++++++++++++++ public/brand/corelink-mark.svg | 1 + public/img/corelink-mark.svg | 1 + src/kc.gen.tsx | 15 ++++++++++++ src/login/KcContext.ts | 10 ++++++++ src/login/KcPage.tsx | 40 ++++++++++++++++++++++++++++++++ src/login/i18n.ts | 15 ++++++++++++ src/login/messages/fa.ts | 18 +++++++++++++++ src/main.tsx | 21 +++++++++++++++++ src/styles/corelink.css | 1 + tsconfig.json | 21 +++++++++++++++++ tsconfig.node.json | 10 ++++++++ vite.config.ts | 16 +++++++++++++ 21 files changed, 352 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 Dockerfile create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/DEPLOYMENT-CONTRACT.md create mode 100644 index.html create mode 100644 package.json create mode 100644 public/brand/corelink-mark.svg create mode 100644 public/img/corelink-mark.svg create mode 100644 src/kc.gen.tsx create mode 100644 src/login/KcContext.ts create mode 100644 src/login/KcPage.tsx create mode 100644 src/login/i18n.ts create mode 100644 src/login/messages/fa.ts create mode 100644 src/main.tsx create mode 100644 src/styles/corelink.css create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5081e2d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +dist_keycloak +.git +.github +README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..24e59dd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: Identity CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + build-theme: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: package.json + - name: Install dependencies + run: npm install + - name: Typecheck and build + run: npm run check + - name: Build Keycloak theme + run: npm run build:keycloak + - name: Upload theme JAR + uses: actions/upload-artifact@v4 + with: + name: corelink-keycloak-theme + path: dist_keycloak/*.jar + if-no-files-found: error diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac3b2ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +dist/ +dist_keycloak/ +*.jar +.env +.env.* +!.env.example +.DS_Store +.idea/ +.vscode/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..8f731ae --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{"semi":true,"singleQuote":false,"tabWidth":2,"trailingComma":"none","printWidth":100} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cc35108 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +ARG KEYCLOAK_VERSION=26.7.2 + +FROM node:22-alpine AS theme-builder +WORKDIR /src +COPY package.json ./ +RUN npm install +COPY . . +RUN npm run build:keycloak + +FROM quay.io/keycloak/keycloak:${KEYCLOAK_VERSION} AS keycloak-builder +COPY --from=theme-builder /src/dist_keycloak/corelink-26.2-and-above.jar /opt/keycloak/providers/corelink-theme.jar +RUN /opt/keycloak/bin/kc.sh build + +FROM quay.io/keycloak/keycloak:${KEYCLOAK_VERSION} +COPY --from=keycloak-builder /opt/keycloak/ /opt/keycloak/ +ENTRYPOINT ["/opt/keycloak/bin/kc.sh"] +CMD ["start", "--optimized"] diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..3c548b7 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,31 @@ +# CoreLink Identity architecture + +## Scope + +Identity owns the visual authentication surface delivered by Keycloak. Business authorization remains in CoreLink Platform; realm/client provisioning belongs to Deployment. + +## Boundaries + +- **Identity:** Keycloakify theme, brand tokens, translations, theme JAR, Keycloak image contract. +- **Deployment:** database, realm bootstrap/import, clients, redirect URIs, secrets, reverse proxy, health checks. +- **Console:** OIDC client integration and application session UX. + +## Theme contract + +- Theme name: `corelink` +- Supported build target: Keycloak 26.2+ +- Production baseline: Keycloak 26.7.2 +- Default languages: English + Persian; Arabic remains compatible through Keycloak's built-in i18n. +- Direction is derived from active language and set on ``. + +## Security rules + +1. Authentication JavaScript/CSS/assets ship inside the theme JAR. +2. Do not load executable resources from Console or tenant domains. +3. Never commit realm secrets or client secrets to this repository. +4. Pin Keycloak production images; upgrades require rebuilding and smoke-testing the theme. +5. CSP and proxy headers are configured by Deployment, not by theme code. + +## White-label roadmap + +Tenant branding should be data-driven and restricted to safe values (logo URL or bundled asset, color tokens, display name). Executable HTML/JS is never tenant-configurable. diff --git a/docs/DEPLOYMENT-CONTRACT.md b/docs/DEPLOYMENT-CONTRACT.md new file mode 100644 index 0000000..7dfaa91 --- /dev/null +++ b/docs/DEPLOYMENT-CONTRACT.md @@ -0,0 +1,42 @@ +# Deployment contract + +This repository intentionally does not contain CoreLink production credentials or realm exports. + +## Image + +Build: + +```bash +docker build -t ghcr.io/corelinkplatform/identity:26.7.2-corelink.1 . +``` + +The resulting image contains the `corelink` login theme and starts Keycloak with `start --optimized`. + +## Required runtime configuration + +Deployment must provide at minimum: + +- `KC_DB` +- `KC_DB_URL` +- `KC_DB_USERNAME` +- `KC_DB_PASSWORD` +- `KC_HOSTNAME` +- bootstrap admin credentials only for first initialization + +Behind a reverse proxy, Deployment must configure Keycloak's proxy/hostname settings for the actual topology. + +## Realm configuration + +Set **Realm settings → Themes → Login theme** to `corelink`. + +Before production integration, test at minimum: + +- password login +- registration (if enabled) +- forgot/reset password +- email verification +- OTP/TOTP +- required actions / update password +- invalid credentials and system error pages +- Persian RTL and English LTR +- mobile viewport diff --git a/index.html b/index.html new file mode 100644 index 0000000..55198dc --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + CoreLink Identity + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..8eec65b --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "@corelinkplatform/identity", + "version": "0.1.0", + "private": true, + "description": "CoreLink Identity theme for Keycloak", + "repository": {"type":"git","url":"https://github.com/CoreLinkPlatform/Identity.git"}, + "type": "module", + "scripts": { + "dev": "vite", + "typecheck": "tsc --noEmit", + "build": "tsc && vite build", + "build:keycloak": "npm run build && keycloakify build", + "format": "prettier . --write", + "check": "npm run typecheck && npm run build" + }, + "license": "MIT", + "dependencies": { + "keycloakify": "^11.15.13", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.24", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.7.0", + "prettier": "^3.6.2", + "typescript": "^5.9.2", + "vite": "^5.4.21" + }, + "engines": {"node": ">=20"} +} diff --git a/public/brand/corelink-mark.svg b/public/brand/corelink-mark.svg new file mode 100644 index 0000000..58264af --- /dev/null +++ b/public/brand/corelink-mark.svg @@ -0,0 +1 @@ + diff --git a/public/img/corelink-mark.svg b/public/img/corelink-mark.svg new file mode 100644 index 0000000..58264af --- /dev/null +++ b/public/img/corelink-mark.svg @@ -0,0 +1 @@ + diff --git a/src/kc.gen.tsx b/src/kc.gen.tsx new file mode 100644 index 0000000..f6b5a50 --- /dev/null +++ b/src/kc.gen.tsx @@ -0,0 +1,15 @@ +/* eslint-disable */ +// @ts-nocheck +import { lazy, Suspense, type ReactNode } from "react"; +export type ThemeName = "corelink"; +export const themeNames: ThemeName[] = ["corelink"]; +export type KcEnvName = never; +export const kcEnvNames: KcEnvName[] = []; +export const kcEnvDefaults: Record = {}; +export type KcContext = import("./login/KcContext").KcContext; +declare global { interface Window { kcContext?: KcContext; } } +export const KcLoginPage = lazy(() => import("./login/KcPage")); +export function KcPage(props: { kcContext: KcContext; fallback?: ReactNode }) { + return {props.kcContext.themeType === "login" ? : null}; +} +export const BASE_URL = import.meta.env.BASE_URL; diff --git a/src/login/KcContext.ts b/src/login/KcContext.ts new file mode 100644 index 0000000..ca8a79f --- /dev/null +++ b/src/login/KcContext.ts @@ -0,0 +1,10 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type */ +import type { ExtendKcContext } from "keycloakify/login"; +import type { KcEnvName, ThemeName } from "../kc.gen"; + +export type KcContextExtension = { + themeName: ThemeName; + properties: Record & {}; +}; +export type KcContextExtensionPerPage = {}; +export type KcContext = ExtendKcContext; diff --git a/src/login/KcPage.tsx b/src/login/KcPage.tsx new file mode 100644 index 0000000..a025fb7 --- /dev/null +++ b/src/login/KcPage.tsx @@ -0,0 +1,40 @@ +import { lazy, Suspense, useEffect } from "react"; +import DefaultPage from "keycloakify/login/DefaultPage"; +import Template from "keycloakify/login/Template"; +import type { KcContext } from "./KcContext"; +import { useI18n } from "./i18n"; + +const UserProfileFormFields = lazy(() => import("keycloakify/login/UserProfileFormFields")); + +export default function KcPage({ kcContext }: { kcContext: KcContext }) { + const { i18n } = useI18n({ kcContext }); + const lang = i18n.currentLanguage.languageTag; + const isRtl = lang === "fa" || lang === "ar"; + + useEffect(() => { + document.documentElement.lang = lang; + document.documentElement.dir = isRtl ? "rtl" : "ltr"; + document.body.dataset.corelinkPage = kcContext.pageId; + }, [lang, isRtl, kcContext.pageId]); + + return ( + + + +
+ CoreLink IdentitySecure connected intelligence +
+
+ ); +} diff --git a/src/login/i18n.ts b/src/login/i18n.ts new file mode 100644 index 0000000..a3d2d9a --- /dev/null +++ b/src/login/i18n.ts @@ -0,0 +1,15 @@ +import { i18nBuilder } from "keycloakify/login"; +import type { ThemeName } from "../kc.gen"; + +const { useI18n, ofTypeI18n } = i18nBuilder + .withThemeName() + .withExtraLanguages({ + fa: { + label: "فارسی", + getMessages: () => import("./messages/fa").then(m => m.messages) + } + }) + .build(); + +export type I18n = typeof ofTypeI18n; +export { useI18n }; diff --git a/src/login/messages/fa.ts b/src/login/messages/fa.ts new file mode 100644 index 0000000..d8efa96 --- /dev/null +++ b/src/login/messages/fa.ts @@ -0,0 +1,18 @@ +export const messages = { + doLogIn: "ورود", + doRegister: "ایجاد حساب", + doForgotPassword: "رمز عبور را فراموش کرده‌اید؟", + loginAccountTitle: "ورود به CoreLink", + loginTitle: "ورود به {0}", + username: "نام کاربری", + usernameOrEmail: "ایمیل یا نام کاربری", + password: "رمز عبور", + rememberMe: "مرا به خاطر بسپار", + noAccount: "حساب ندارید؟", + emailForgotTitle: "بازیابی دسترسی", + backToLogin: "بازگشت به ورود", + proceedWithAction: "ادامه", + emailVerifyTitle: "تأیید ایمیل", + updatePasswordTitle: "تغییر رمز عبور", + configureTotpTitle: "راه‌اندازی احراز هویت دومرحله‌ای" +} as const; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..4f81e4b --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,21 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { KcPage } from "./kc.gen"; +import "./styles/corelink.css"; + +createRoot(document.getElementById("root")!).render( + + {window.kcContext ? ( + + ) : ( +
+
+ CoreLink +

CoreLink Identity

+

Run this theme in a Keycloak context to preview authentication pages.

+ npm run build:keycloak +
+
+ )} +
+); diff --git a/src/styles/corelink.css b/src/styles/corelink.css new file mode 100644 index 0000000..4708909 --- /dev/null +++ b/src/styles/corelink.css @@ -0,0 +1 @@ +:root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#e8eef7;background:#07111f;font-synthesis:none;--cl-bg:#07111f;--cl-panel:#0d1a2b;--cl-panel-2:#111f32;--cl-text:#edf4ff;--cl-muted:#8fa3bc;--cl-border:#22334a;--cl-primary:#2f81f7;--cl-primary-hover:#4b93f8;--cl-danger:#ff6b6b;--cl-radius:16px}*{box-sizing:border-box}html,body,#root{min-height:100%;margin:0}body{min-height:100vh;background:radial-gradient(circle at 15% 0%,#102c50 0,transparent 32rem),radial-gradient(circle at 100% 100%,#0b253d 0,transparent 34rem),var(--cl-bg);color:var(--cl-text)}body:before{content:"";position:fixed;inset:0;pointer-events:none;background-image:linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);background-size:32px 32px;mask-image:linear-gradient(to bottom,black,transparent 75%)}a{color:#70a9ff;text-decoration:none}a:hover{text-decoration:underline}.corelink-brand-bar{position:fixed;top:28px;inset-inline-start:32px;display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:.01em;z-index:2}.corelink-brand-bar img{width:30px;height:30px}.corelink-auth-footer{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);display:flex;gap:9px;align-items:center;color:var(--cl-muted);font-size:12px;white-space:nowrap}#kc-header{display:none}#kc-login,#kc-register-form,#kc-reset-password-form,#kc-passwd-update-form,#kc-totp-settings-form{width:100%}#kc-content{width:min(100% - 32px,440px);margin:0 auto;padding:96px 0 84px}#kc-content-wrapper{background:linear-gradient(180deg,rgba(17,31,50,.96),rgba(10,23,39,.96));border:1px solid var(--cl-border);border-radius:var(--cl-radius);padding:30px;box-shadow:0 24px 70px rgba(0,0,0,.38);backdrop-filter:blur(18px)}#kc-page-title{margin:0 0 24px;text-align:center;font-size:24px;line-height:1.25;letter-spacing:-.025em}form{display:grid;gap:18px}label{display:block;margin-bottom:7px;color:#c7d4e5;font-size:13px;font-weight:600}input[type=text],input[type=email],input[type=password],input[type=tel],input[type=number],select{width:100%;min-height:44px;border:1px solid var(--cl-border);border-radius:10px;background:#091625;color:var(--cl-text);padding:10px 12px;outline:none;transition:border-color .15s,box-shadow .15s}input:focus,select:focus{border-color:var(--cl-primary);box-shadow:0 0 0 3px rgba(47,129,247,.17)}input[type=checkbox]{accent-color:var(--cl-primary)}button,input[type=submit],.pf-c-button{width:100%;min-height:44px;border:0;border-radius:10px;background:var(--cl-primary);color:#fff;font:inherit;font-weight:700;cursor:pointer;padding:10px 16px;transition:background .15s,transform .1s}button:hover,input[type=submit]:hover,.pf-c-button:hover{background:var(--cl-primary-hover);text-decoration:none}button:active,input[type=submit]:active{transform:translateY(1px)}#kc-form-options,#kc-form-buttons,#kc-registration,#kc-info,#kc-social-providers{margin-top:16px}.alert,.pf-c-alert{padding:11px 13px;border:1px solid var(--cl-border);border-radius:10px;background:var(--cl-panel-2);font-size:13px}.alert-error,.pf-m-danger{border-color:rgba(255,107,107,.4);color:#ffb3b3}.instruction,.subtitle,#kc-info-wrapper{color:var(--cl-muted);font-size:13px;line-height:1.6}.corelink-dev-shell{display:grid;place-items:center;min-height:100vh;padding:24px}.corelink-dev-card{width:min(100%,460px);padding:32px;border:1px solid var(--cl-border);border-radius:var(--cl-radius);background:var(--cl-panel);text-align:center}.corelink-dev-card h1{margin:16px 0 8px}.corelink-dev-card p{color:var(--cl-muted)}.corelink-dev-card code{display:block;margin-top:20px;padding:12px;border-radius:9px;background:#07111f;color:#a9c7ef}html[dir=rtl] body{font-family:Vazirmatn,Tahoma,Arial,sans-serif}html[dir=rtl] input{text-align:right}@media(max-width:600px){.corelink-brand-bar{top:20px;inset-inline-start:20px}.corelink-auth-footer{bottom:16px}.corelink-auth-footer span:last-child{display:none}#kc-content{padding-top:80px}#kc-content-wrapper{padding:24px 20px}} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3abb777 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{"path":"./tsconfig.node.json"}] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..5eef90e --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..460a182 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { keycloakify } from "keycloakify/vite-plugin"; + +export default defineConfig({ + plugins: [ + react(), + keycloakify({ + themeName: "corelink", + accountThemeImplementation: "none", + keycloakVersionTargets: { + "26.2-and-above": "corelink-26.2-and-above.jar" + } + }) + ] +}); From 555732cd4aae2eb91393e7d2661f3efb23f3387d Mon Sep 17 00:00:00 2001 From: Javid Date: Sun, 30 Aug 2026 10:15:49 +0200 Subject: [PATCH 02/33] fix: use built-in Persian locale with custom translations --- src/login/i18n.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/login/i18n.ts b/src/login/i18n.ts index a3d2d9a..2cb10f6 100644 --- a/src/login/i18n.ts +++ b/src/login/i18n.ts @@ -3,10 +3,28 @@ import type { ThemeName } from "../kc.gen"; const { useI18n, ofTypeI18n } = i18nBuilder .withThemeName() - .withExtraLanguages({ + .withCustomTranslations({ + en: { + loginAccountTitle: "Sign in to CoreLink", + doLogIn: "Sign in" + }, fa: { - label: "فارسی", - getMessages: () => import("./messages/fa").then(m => m.messages) + doLogIn: "ورود", + doRegister: "ایجاد حساب", + doForgotPassword: "رمز عبور را فراموش کرده‌اید؟", + loginAccountTitle: "ورود به CoreLink", + loginTitle: "ورود به {0}", + username: "نام کاربری", + usernameOrEmail: "ایمیل یا نام کاربری", + password: "رمز عبور", + rememberMe: "مرا به خاطر بسپار", + noAccount: "حساب ندارید؟", + emailForgotTitle: "بازیابی دسترسی", + backToLogin: "بازگشت به ورود", + proceedWithAction: "ادامه", + emailVerifyTitle: "تأیید ایمیل", + updatePasswordTitle: "تغییر رمز عبور", + configureTotpTitle: "راه‌اندازی احراز هویت دومرحله‌ای" } }) .build(); From 00b42723d097728cae39ef98a2597b83a7ce4ca9 Mon Sep 17 00:00:00 2001 From: Javid Date: Sun, 30 Aug 2026 10:15:56 +0200 Subject: [PATCH 03/33] chore: remove duplicate Persian locale source --- src/login/messages/fa.ts | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/login/messages/fa.ts diff --git a/src/login/messages/fa.ts b/src/login/messages/fa.ts deleted file mode 100644 index d8efa96..0000000 --- a/src/login/messages/fa.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const messages = { - doLogIn: "ورود", - doRegister: "ایجاد حساب", - doForgotPassword: "رمز عبور را فراموش کرده‌اید؟", - loginAccountTitle: "ورود به CoreLink", - loginTitle: "ورود به {0}", - username: "نام کاربری", - usernameOrEmail: "ایمیل یا نام کاربری", - password: "رمز عبور", - rememberMe: "مرا به خاطر بسپار", - noAccount: "حساب ندارید؟", - emailForgotTitle: "بازیابی دسترسی", - backToLogin: "بازگشت به ورود", - proceedWithAction: "ادامه", - emailVerifyTitle: "تأیید ایمیل", - updatePasswordTitle: "تغییر رمز عبور", - configureTotpTitle: "راه‌اندازی احراز هویت دومرحله‌ای" -} as const; From e9d68d71fde0026396e3d26a7d13ae2e850839b6 Mon Sep 17 00:00:00 2001 From: Javid Date: Sun, 30 Aug 2026 10:16:32 +0200 Subject: [PATCH 04/33] fix: avoid npm cache without lockfile --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24e59dd..61cc478 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 22 - cache: npm - cache-dependency-path: package.json - name: Install dependencies run: npm install - name: Typecheck and build From cb96438edfe5b1b79a20028bfe919fb10bfa9934 Mon Sep 17 00:00:00 2001 From: Javid Date: Sun, 30 Aug 2026 10:20:52 +0200 Subject: [PATCH 05/33] feat: make theme name configurable --- vite.config.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 460a182..a6536ce 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,14 +2,16 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { keycloakify } from "keycloakify/vite-plugin"; +const themeName = process.env.KEYCLOAK_THEME_NAME ?? "corelink"; + export default defineConfig({ plugins: [ react(), keycloakify({ - themeName: "corelink", + themeName, accountThemeImplementation: "none", keycloakVersionTargets: { - "26.2-and-above": "corelink-26.2-and-above.jar" + "26.2-and-above": `${themeName}-26.2-and-above.jar` } }) ] From 0637f7a8fc0a93d876b54c459290c20b74bb7f79 Mon Sep 17 00:00:00 2001 From: Javid Date: Sun, 30 Aug 2026 10:21:02 +0200 Subject: [PATCH 06/33] feat: make branding configurable --- src/login/KcPage.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/login/KcPage.tsx b/src/login/KcPage.tsx index a025fb7..c3193e0 100644 --- a/src/login/KcPage.tsx +++ b/src/login/KcPage.tsx @@ -6,6 +6,10 @@ import { useI18n } from "./i18n"; const UserProfileFormFields = lazy(() => import("keycloakify/login/UserProfileFormFields")); +const brandName = import.meta.env.VITE_BRAND_NAME || "CoreLink"; +const brandTagline = import.meta.env.VITE_BRAND_TAGLINE || "Secure connected intelligence"; +const brandMark = import.meta.env.VITE_BRAND_MARK || "img/corelink-mark.svg"; + export default function KcPage({ kcContext }: { kcContext: KcContext }) { const { i18n } = useI18n({ kcContext }); const lang = i18n.currentLanguage.languageTag; @@ -14,14 +18,14 @@ export default function KcPage({ kcContext }: { kcContext: KcContext }) { useEffect(() => { document.documentElement.lang = lang; document.documentElement.dir = isRtl ? "rtl" : "ltr"; - document.body.dataset.corelinkPage = kcContext.pageId; + document.body.dataset.identityPage = kcContext.pageId; }, [lang, isRtl, kcContext.pageId]); return ( -