From 98c53c0321e9800b64059d667a2414341f05de96 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Fri, 18 Jul 2025 01:10:35 +0200 Subject: [PATCH] fix build --- apps/login/src/components/button.tsx | 11 +- apps/login/src/components/checkbox.tsx | 2 +- apps/login/src/components/input.tsx | 2 +- .../login/src/components/sign-in-with-idp.tsx | 2 +- apps/login/src/components/state-badge.tsx | 2 +- apps/login/src/helpers/colors.ts | 115 +++++++++++++++--- apps/login/src/lib/fingerprint.ts | 4 +- apps/login/src/lib/i18n.ts | 1 + apps/login/src/lib/saml.ts | 2 +- apps/login/src/lib/server/cookie.ts | 5 + apps/login/src/lib/server/idp.ts | 4 +- apps/login/src/lib/server/password.ts | 2 +- apps/login/src/lib/server/register.ts | 11 ++ apps/login/src/lib/server/session.ts | 2 +- apps/login/src/lib/verify-helper.ts | 28 +++++ apps/login/src/lib/zitadel.ts | 83 ++++++++++++- package.json | 3 +- pnpm-lock.yaml | 64 ++++++++++ turbo.json | 16 ++- 19 files changed, 316 insertions(+), 43 deletions(-) diff --git a/apps/login/src/components/button.tsx b/apps/login/src/components/button.tsx index c4439ab28f..59f7af39d1 100644 --- a/apps/login/src/components/button.tsx +++ b/apps/login/src/components/button.tsx @@ -1,7 +1,7 @@ import { clsx } from "clsx"; import { ButtonHTMLAttributes, DetailedHTMLProps, forwardRef } from "react"; -enum ButtonSizes { +export enum ButtonSizes { Small = "Small", Large = "Large", } @@ -9,15 +9,16 @@ enum ButtonSizes { export enum ButtonVariants { Primary = "Primary", Secondary = "Secondary", - } + Destructive = "Destructive", +} -enum ButtonColors { +export enum ButtonColors { Neutral = "Neutral", Primary = "Primary", Warn = "Warn", } -type ButtonProps = DetailedHTMLProps< +export type ButtonProps = DetailedHTMLProps< ButtonHTMLAttributes, HTMLButtonElement > & { @@ -26,7 +27,7 @@ type ButtonProps = DetailedHTMLProps< color?: ButtonColors; }; -const getButtonClasses = ( +export const getButtonClasses = ( size: ButtonSizes, variant: ButtonVariants, color: ButtonColors, diff --git a/apps/login/src/components/checkbox.tsx b/apps/login/src/components/checkbox.tsx index 0aa3e311ac..aa0c066f24 100644 --- a/apps/login/src/components/checkbox.tsx +++ b/apps/login/src/components/checkbox.tsx @@ -7,7 +7,7 @@ import { useState, } from "react"; -type CheckboxProps = DetailedHTMLProps< +export type CheckboxProps = DetailedHTMLProps< InputHTMLAttributes, HTMLInputElement > & { diff --git a/apps/login/src/components/input.tsx b/apps/login/src/components/input.tsx index c7fc5762ea..7d29fce691 100644 --- a/apps/login/src/components/input.tsx +++ b/apps/login/src/components/input.tsx @@ -10,7 +10,7 @@ import { ReactNode, } from "react"; -type TextInputProps = DetailedHTMLProps< +export type TextInputProps = DetailedHTMLProps< InputHTMLAttributes, HTMLInputElement > & { diff --git a/apps/login/src/components/sign-in-with-idp.tsx b/apps/login/src/components/sign-in-with-idp.tsx index b0d2fb93b3..755416f6c9 100644 --- a/apps/login/src/components/sign-in-with-idp.tsx +++ b/apps/login/src/components/sign-in-with-idp.tsx @@ -17,7 +17,7 @@ import { SignInWithGitlab } from "./idps/sign-in-with-gitlab"; import { SignInWithGoogle } from "./idps/sign-in-with-google"; import { Translated } from "./translated"; -interface SignInWithIDPProps { +export interface SignInWithIDPProps { children?: ReactNode; identityProviders: IdentityProvider[]; requestId?: string; diff --git a/apps/login/src/components/state-badge.tsx b/apps/login/src/components/state-badge.tsx index 4f5e397a64..cbc6f32ea6 100644 --- a/apps/login/src/components/state-badge.tsx +++ b/apps/login/src/components/state-badge.tsx @@ -8,7 +8,7 @@ export enum BadgeState { Alert = "alert", } -type StateBadgeProps = { +export type StateBadgeProps = { state: BadgeState; children: ReactNode; evenPadding?: boolean; diff --git a/apps/login/src/helpers/colors.ts b/apps/login/src/helpers/colors.ts index c2e207a96b..bdb07cecfd 100644 --- a/apps/login/src/helpers/colors.ts +++ b/apps/login/src/helpers/colors.ts @@ -1,30 +1,57 @@ import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; import tinycolor from "tinycolor2"; -interface Color { +export interface Color { name: string; hex: string; rgb: string; contrastColor: string; } -type MapName = "background" | "primary" | "warn" | "text" | "link"; +export type MapName = "background" | "primary" | "warn" | "text" | "link"; -type ColorMap = { - [_key in MapName]: Color[]; +export type ColorName = + | "50" + | "100" + | "200" + | "300" + | "400" + | "500" + | "600" + | "700" + | "800" + | "C900" + | "A100" + | "A200" + | "A400" + | "A700"; + +export type ColorMap = { + [key in MapName]: Color[]; }; -const DARK_PRIMARY = "#2073c4"; -const PRIMARY = "#5469d4"; +export const DARK_PRIMARY = "#2073c4"; +export const PRIMARY = "#5469d4"; -const DARK_WARN = "#ff3b5b"; -const WARN = "#cd3d56"; +export const DARK_WARN = "#ff3b5b"; +export const WARN = "#cd3d56"; -const DARK_BACKGROUND = "#111827"; -const BACKGROUND = "#fafafa"; +export const DARK_BACKGROUND = "#111827"; +export const BACKGROUND = "#fafafa"; -const DARK_TEXT = "#ffffff"; -const TEXT = "#000000"; +export const DARK_TEXT = "#ffffff"; +export const TEXT = "#000000"; + +export type LabelPolicyColors = { + backgroundColor: string; + backgroundColorDark: string; + fontColor: string; + fontColorDark: string; + warnColor: string; + warnColorDark: string; + primaryColor: string; + primaryColorDark: string; +}; type BrandingColors = { lightTheme: { @@ -155,7 +182,7 @@ function getContrast(color: string): string { } } -function computeMap(branding: BrandingColors, dark: boolean): ColorMap { +export function computeMap(branding: BrandingColors, dark: boolean): ColorMap { return { background: computeColors( dark @@ -186,7 +213,7 @@ export interface ColorShade { 900: string; } -const COLORS = [ +export const COLORS = [ { 500: "#ef4444", 200: "#fecaca", @@ -336,7 +363,7 @@ export function getColorHash(value: string): ColorShade { return COLORS[hash % COLORS.length]; } -function hashCode(str: string, seed = 0): number { +export function hashCode(str: string, seed = 0): number { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; for (let i = 0, ch; i < str.length; i++) { @@ -352,3 +379,61 @@ function hashCode(str: string, seed = 0): number { Math.imul(h1 ^ (h1 >>> 13), 3266489909); return 4294967296 * (2097151 & h2) + (h1 >>> 0); } + +export function getMembershipColor(role: string): ColorShade { + const hash = hashCode(role); + let color = COLORS[hash % COLORS.length]; + + switch (role) { + case "IAM_OWNER": + color = COLORS[0]; + break; + case "IAM_OWNER_VIEWER": + color = COLORS[14]; + break; + case "IAM_ORG_MANAGER": + color = COLORS[11]; + break; + case "IAM_USER_MANAGER": + color = COLORS[8]; + break; + + case "ORG_OWNER": + color = COLORS[16]; + break; + case "ORG_USER_MANAGER": + color = COLORS[8]; + break; + case "ORG_OWNER_VIEWER": + color = COLORS[14]; + break; + case "ORG_USER_PERMISSION_EDITOR": + color = COLORS[7]; + break; + case "ORG_PROJECT_PERMISSION_EDITOR": + color = COLORS[11]; + break; + case "ORG_PROJECT_CREATOR": + color = COLORS[12]; + break; + + case "PROJECT_OWNER": + color = COLORS[9]; + break; + case "PROJECT_OWNER_VIEWER": + color = COLORS[10]; + break; + case "PROJECT_OWNER_GLOBAL": + color = COLORS[11]; + break; + case "PROJECT_OWNER_VIEWER_GLOBAL": + color = COLORS[12]; + break; + + default: + color = COLORS[hash % COLORS.length]; + break; + } + + return color; +} diff --git a/apps/login/src/lib/fingerprint.ts b/apps/login/src/lib/fingerprint.ts index a90178ac15..55b59dadc8 100644 --- a/apps/login/src/lib/fingerprint.ts +++ b/apps/login/src/lib/fingerprint.ts @@ -7,11 +7,11 @@ import { cookies, headers } from "next/headers"; import { userAgent } from "next/server"; import { v4 as uuidv4 } from "uuid"; -async function getFingerprintId() { +export async function getFingerprintId() { return uuidv4(); } -async function setFingerprintIdCookie(fingerprintId: string) { +export async function setFingerprintIdCookie(fingerprintId: string) { const cookiesList = await cookies(); return cookiesList.set({ diff --git a/apps/login/src/lib/i18n.ts b/apps/login/src/lib/i18n.ts index 7c7cf6b349..5a101dcc8f 100644 --- a/apps/login/src/lib/i18n.ts +++ b/apps/login/src/lib/i18n.ts @@ -35,3 +35,4 @@ export const LANGS: Lang[] = [ ]; export const LANGUAGE_COOKIE_NAME = "NEXT_LOCALE"; +export const LANGUAGE_HEADER_NAME = "accept-language"; diff --git a/apps/login/src/lib/saml.ts b/apps/login/src/lib/saml.ts index c52295fba0..3bbc1cdca1 100644 --- a/apps/login/src/lib/saml.ts +++ b/apps/login/src/lib/saml.ts @@ -19,7 +19,7 @@ type LoginWithSAMLAndSession = { request: NextRequest; }; -async function getSAMLFormUID() { +export async function getSAMLFormUID() { return uuidv4(); } diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 90246dd57e..841fc06b3a 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -15,6 +15,7 @@ import { ErrorDetail, } from "@zitadel/proto/zitadel/message_pb"; import { + Challenges, RequestChallenges, } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; @@ -185,6 +186,10 @@ export async function createSessionForIdpAndUpdateCookie({ }); } +export type SessionWithChallenges = Session & { + challenges: Challenges | undefined; +}; + export async function setSessionAndUpdateCookie( recentCookie: CustomCookieData, checks?: Checks, diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 06fd99a75a..87f88a7c32 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -13,7 +13,7 @@ import { getServiceUrlFromHeaders } from "../service-url"; import { checkEmailVerification } from "../verify-helper"; import { createSessionForIdpAndUpdateCookie } from "./cookie"; -type RedirectToIdpState = { error?: string | null } | undefined; +export type RedirectToIdpState = { error?: string | null } | undefined; export async function redirectToIdp( prevState: RedirectToIdpState, @@ -63,7 +63,7 @@ export async function redirectToIdp( return { error: "Unexpected response from IDP flow" }; } -type StartIDPFlowCommand = { +export type StartIDPFlowCommand = { serviceUrl: string; host: string; idpId: string; diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 29240cc4b2..5c6fb03aa5 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -81,7 +81,7 @@ export async function resetPassword(command: ResetPasswordCommand) { }); } -type UpdateSessionCommand = { +export type UpdateSessionCommand = { loginName: string; organization?: string; checks: Checks; diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 3d1963bda3..f84b4c8d51 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -11,6 +11,7 @@ import { getUserByID, } from "@/lib/zitadel"; import { create } from "@zitadel/client"; +import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { ChecksJson, ChecksSchema, @@ -29,6 +30,11 @@ type RegisterUserCommand = { requestId?: string; }; +export type RegisterUserResponse = { + userId: string; + sessionId: string; + factors: Factors | undefined; +}; export async function registerUser(command: RegisterUserCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -151,6 +157,11 @@ type RegisterUserAndLinkToIDPommand = { idpUserName: string; }; +export type registerUserAndLinkToIDPResponse = { + userId: string; + sessionId: string; + factors: Factors | undefined; +}; export async function registerUserAndLinkToIDP( command: RegisterUserAndLinkToIDPommand, ) { diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index 67c7e2af09..d04d1d25b4 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -105,7 +105,7 @@ export async function continueWithSession({ } } -type UpdateSessionCommand = { +export type UpdateSessionCommand = { loginName?: string; sessionId?: string; organization?: string; diff --git a/apps/login/src/lib/verify-helper.ts b/apps/login/src/lib/verify-helper.ts index a4310d27ac..dbd9b2796b 100644 --- a/apps/login/src/lib/verify-helper.ts +++ b/apps/login/src/lib/verify-helper.ts @@ -47,6 +47,34 @@ export function checkPasswordChangeRequired( } } +export function checkEmailVerified( + session: Session, + humanUser?: HumanUser, + organization?: string, + requestId?: string, +) { + if (!humanUser?.email?.isVerified) { + const paramsVerify = new URLSearchParams({ + loginName: session.factors?.user?.loginName as string, + userId: session.factors?.user?.id as string, // verify needs user id + send: "true", // we request a new email code once the page is loaded + }); + + if (organization || session.factors?.user?.organizationId) { + paramsVerify.append( + "organization", + organization ?? (session.factors?.user?.organizationId as string), + ); + } + + if (requestId) { + paramsVerify.append("requestId", requestId); + } + + return { redirect: "/verify?" + paramsVerify }; + } +} + export function checkEmailVerification( session: Session, humanUser?: HumanUser, diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 2e7e338f1f..59b3c2801e 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -40,6 +40,8 @@ import { SendInviteCodeSchema } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AddHumanUserRequest, AddHumanUserRequestSchema, + ResendEmailCodeRequest, + ResendEmailCodeRequestSchema, SendEmailCodeRequestSchema, SetPasswordRequest, SetPasswordRequestSchema, @@ -62,6 +64,41 @@ async function cacheWrapper(callback: Promise) { return callback; } +export async function getHostedLoginTranslation({ + serviceUrl, + organization, + locale, +}: { + serviceUrl: string; + organization?: string; + locale?: string; +}) { + const settingsService: Client = + await createServiceForHost(SettingsService, serviceUrl); + + const callback = settingsService + .getHostedLoginTranslation( + { + level: organization + ? { + case: "organizationId", + value: organization, + } + : { + case: "instance", + value: true, + }, + locale: locale, + }, + {}, + ) + .then((resp) => { + return resp.translations ? resp.translations : undefined; + }); + + return useCache ? cacheWrapper(callback) : callback; +} + export async function getBrandingSettings({ serviceUrl, organization, @@ -205,6 +242,21 @@ export async function registerTOTP({ return userService.registerTOTP({ userId }, {}); } +export async function getGeneralSettings({ + serviceUrl, +}: { + serviceUrl: string; +}) { + const settingsService: Client = + await createServiceForHost(SettingsService, serviceUrl); + + const callback = settingsService + .getGeneralSettings({}, {}) + .then((resp) => resp.supportedLanguages); + + return useCache ? cacheWrapper(callback) : callback; +} + export async function getLegalAndSupportSettings({ serviceUrl, organization, @@ -375,7 +427,7 @@ export async function listSessions({ serviceUrl, ids }: ListSessionsCommand) { ); } -type AddHumanUserData = { +export type AddHumanUserData = { serviceUrl: string; firstName: string; lastName: string; @@ -587,7 +639,7 @@ export async function createInviteCode({ ); } -type ListUsersCommand = { +export type ListUsersCommand = { serviceUrl: string; loginName?: string; userName?: string; @@ -1151,6 +1203,33 @@ export async function verifyEmail({ ); } +export async function resendEmailCode({ + serviceUrl, + userId, + urlTemplate, +}: { + serviceUrl: string; + userId: string; + urlTemplate: string; +}) { + let request: ResendEmailCodeRequest = create(ResendEmailCodeRequestSchema, { + userId, + }); + + const medium = create(SendEmailVerificationCodeSchema, { + urlTemplate, + }); + + request = { ...request, verification: { case: "sendCode", value: medium } }; + + const userService: Client = await createServiceForHost( + UserService, + serviceUrl, + ); + + return userService.resendEmailCode(request, {}); +} + export async function retrieveIDPIntent({ serviceUrl, id, diff --git a/package.json b/package.json index a87f215062..2c645c16e9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ } }, "devDependencies": { - "@changesets/cli": "^2.29.5" + "@changesets/cli": "^2.29.5", + "turbo": "2.5.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd0da04fcf..c9986f8fa0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,9 @@ importers: '@changesets/cli': specifier: ^2.29.5 version: 2.29.5 + turbo: + specifier: 2.5.5 + version: 2.5.5 apps/login: dependencies: @@ -13036,6 +13039,40 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + turbo-darwin-64@2.5.5: + resolution: {integrity: sha512-RYnTz49u4F5tDD2SUwwtlynABNBAfbyT2uU/brJcyh5k6lDLyNfYKdKmqd3K2ls4AaiALWrFKVSBsiVwhdFNzQ==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.5.5: + resolution: {integrity: sha512-Tk+ZeSNdBobZiMw9aFypQt0DlLsWSFWu1ymqsAdJLuPoAH05qCfYtRxE1pJuYHcJB5pqI+/HOxtJoQ40726Btw==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.5.5: + resolution: {integrity: sha512-2/XvMGykD7VgsvWesZZYIIVXMlgBcQy+ZAryjugoTcvJv8TZzSU/B1nShcA7IAjZ0q7OsZ45uP2cOb8EgKT30w==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.5.5: + resolution: {integrity: sha512-DW+8CjCjybu0d7TFm9dovTTVg1VRnlkZ1rceO4zqsaLrit3DgHnN4to4uwyuf9s2V/BwS3IYcRy+HG9BL596Iw==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.5.5: + resolution: {integrity: sha512-q5p1BOy8ChtSZfULuF1BhFMYIx6bevXu4fJ+TE/hyNfyHJIfjl90Z6jWdqAlyaFLmn99X/uw+7d6T/Y/dr5JwQ==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.5.5: + resolution: {integrity: sha512-AXbF1KmpHUq3PKQwddMGoKMYhHsy5t1YBQO8HZ04HLMR0rWv9adYlQ8kaeQJTko1Ay1anOBFTqaxfVOOsu7+1Q==} + cpu: [arm64] + os: [win32] + + turbo@2.5.5: + resolution: {integrity: sha512-eZ7wI6KjtT1eBqCnh2JPXWNUAxtoxxfi6VdBdZFvil0ychCOTxbm7YLRBi1JSt7U3c+u3CLxpoPxLdvr/Npr3A==} + hasBin: true + tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -30716,6 +30753,33 @@ snapshots: dependencies: safe-buffer: 5.2.1 + turbo-darwin-64@2.5.5: + optional: true + + turbo-darwin-arm64@2.5.5: + optional: true + + turbo-linux-64@2.5.5: + optional: true + + turbo-linux-arm64@2.5.5: + optional: true + + turbo-windows-64@2.5.5: + optional: true + + turbo-windows-arm64@2.5.5: + optional: true + + turbo@2.5.5: + optionalDependencies: + turbo-darwin-64: 2.5.5 + turbo-darwin-arm64: 2.5.5 + turbo-linux-64: 2.5.5 + turbo-linux-arm64: 2.5.5 + turbo-windows-64: 2.5.5 + turbo-windows-arm64: 2.5.5 + tweetnacl@0.14.5: {} type-check@0.4.0: diff --git a/turbo.json b/turbo.json index 301d602e9d..8e09c33a4e 100644 --- a/turbo.json +++ b/turbo.json @@ -21,16 +21,14 @@ "cache": true }, "build": {}, - "build:login:standalone": {}, - "build:client:standalone": {}, - "test": {}, - "start": {}, - "start:built": {}, - "test:unit": {}, - "test:unit:standalone": {}, - "test:watch": { - "persistent": true + "quality": { + "with": [ + "lint", + "test:unit" + ] }, + "start": {}, + "test:unit": {}, "lint": {}, "lint:fix": {}, "dev": {