@burdenoff/microfe-auth 2026.528.2 → 2026.528.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"useMfaOperations.js","names":[],"sources":["../../../src/auth/hooks/useMfaOperations.ts"],"sourcesContent":["import { useMutation } from \"@apollo/client/react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport {\n loginProfile,\n type ProfileUser,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport {\n SetupMfaDocument,\n EnableMfaDocument,\n VerifyMfaDocument,\n DisableMfaDocument,\n} from \"../../generated/global-operations\";\nimport type {\n MFAMethod,\n MFASetupResult,\n MFAVerifyInput,\n AuthResult,\n User,\n} from \"../types\";\n\nfunction toProfileUser(user: User): ProfileUser {\n return { ...user };\n}\n\nfunction setShellSsoCookie(userId: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({\n id: userId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n // Best effort only.\n }\n}\n\n// =============================================================================\n// GRAPHQL RESPONSE INTERFACES\n// =============================================================================\n\ninterface SetupMFAResponse {\n setupMFA: MFASetupResult;\n}\n\ninterface SetupMFAVariables {\n method: MFAMethod;\n}\n\ninterface EnableMFAResponse {\n enableMFA: boolean;\n}\n\ninterface EnableMFAVariables {\n code: string;\n}\n\ninterface VerifyMFAResponse {\n verifyMFA: AuthResult;\n}\n\ninterface VerifyMFAVariables {\n input: MFAVerifyInput;\n}\n\ninterface DisableMFAResponse {\n disableMFA: boolean;\n}\n\n// GraphQL Operations\n\n/**\n * Hook for MFA operations\n */\nexport function useMfaOperations() {\n const store = useAuthStore();\n const context = useAuth();\n const eventBus = useEventBus();\n const [setupMfaMutation] = useMutation<SetupMFAResponse, SetupMFAVariables>(\n SetupMfaDocument,\n );\n const [enableMfaMutation] = useMutation<\n EnableMFAResponse,\n EnableMFAVariables\n >(EnableMfaDocument);\n const [verifyMfaMutation] = useMutation<\n VerifyMFAResponse,\n VerifyMFAVariables\n >(VerifyMfaDocument);\n const [disableMfaMutation] =\n useMutation<DisableMFAResponse>(DisableMfaDocument);\n\n /**\n * Setup MFA for the user\n */\n const setupMFA = async (method: MFAMethod): Promise<MFASetupResult> => {\n try {\n store.setLoading(true);\n\n const { data } = await setupMfaMutation({\n variables: { method },\n });\n\n return data!.setupMFA;\n } catch (error) {\n if (import.meta.env.DEV) {\n console.error(\"MFA setup failed:\", error);\n }\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Enable MFA after setup with verification code\n */\n const enableMFA = async (code: string): Promise<boolean> => {\n try {\n store.setLoading(true);\n\n const { data } = await enableMfaMutation({\n variables: { code },\n });\n\n if (data!.enableMFA && store.user) {\n store.setUser({ ...store.user, mfaEnabled: true });\n }\n\n return data!.enableMFA;\n } catch (error) {\n if (import.meta.env.DEV) {\n console.error(\"Failed to enable MFA:\", error);\n }\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Verify MFA code during login\n */\n const verifyMFA = async (input: MFAVerifyInput): Promise<AuthResult> => {\n try {\n store.setLoading(true);\n\n const { data, error } = await verifyMfaMutation({\n variables: { input },\n });\n\n const result = data?.verifyMFA;\n\n // Server returned no data (GraphQL errors, network failure, or null payload).\n // Surface a friendly message instead of letting `result.success` throw a\n // \"Cannot read properties of null\" error to the UI.\n if (!result) {\n return {\n success: false,\n error:\n error?.message || \"Invalid verification code. Please try again.\",\n } as AuthResult;\n }\n\n if (result.success && result.user && result.token) {\n store.setUser(result.user);\n store.setAuthTokens(result.token, result.refreshToken || \"\");\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n const expiresAt = result.expiresIn\n ? Date.now() + result.expiresIn * 1000\n : undefined;\n loginProfile(toProfileUser(result.user), {\n accessToken: result.token,\n refreshToken: result.refreshToken || \"\",\n expiresAt,\n });\n setShellSsoCookie(result.user.id);\n\n eventBus.emit(\n \"auth.signed_in\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"useMfaOperations\" },\n );\n\n context.onAuthSuccess?.(result.user, result.token, result.refreshToken);\n } else {\n console.warn(\n \"[useMfaOperations] NOT saving tokens - condition failed:\",\n {\n success: result.success,\n hasUser: !!result.user,\n hasToken: !!result.token,\n },\n );\n }\n\n return result;\n } catch (error) {\n if (import.meta.env.DEV) {\n console.error(\"MFA verification failed:\", error);\n }\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Disable MFA\n */\n const disableMFA = async (): Promise<boolean> => {\n try {\n store.setLoading(true);\n\n const { data } = await disableMfaMutation();\n\n if (data!.disableMFA && store.user) {\n store.setUser({ ...store.user, mfaEnabled: false });\n }\n\n return data!.disableMFA;\n } catch (error) {\n if (import.meta.env.DEV) {\n console.error(\"Failed to disable MFA:\", error);\n }\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n // Legacy aliases\n const enableMfa = enableMFA;\n const verifyMfa = verifyMFA;\n const disableMfa = disableMFA;\n\n return {\n setupMFA,\n enableMFA,\n verifyMFA,\n disableMFA,\n // Legacy aliases\n enableMfa,\n verifyMfa,\n disableMfa,\n // State\n mfaRequired: store.mfaRequired,\n mfaChallengeId: store.mfaChallengeId,\n isLoading: store.isLoading,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useMfaOperations.js","names":[],"sources":["../../../src/auth/hooks/useMfaOperations.ts"],"sourcesContent":["import { useMutation } from \"@apollo/client/react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport {\n loginProfile,\n type ProfileUser,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport {\n SetupMfaDocument,\n EnableMfaDocument,\n VerifyMfaDocument,\n DisableMfaDocument,\n} from \"../../generated/global-operations\";\nimport type {\n MFAMethod,\n MFASetupResult,\n MFAVerifyInput,\n AuthResult,\n User,\n} from \"../types\";\n\nfunction toProfileUser(user: User): ProfileUser {\n return { ...user };\n}\n\nfunction setShellSsoCookie(userId: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({\n id: userId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n // Best effort only.\n }\n}\n\n// =============================================================================\n// GRAPHQL RESPONSE INTERFACES\n// =============================================================================\n\ninterface SetupMFAResponse {\n setupMFA: MFASetupResult;\n}\n\ninterface SetupMFAVariables {\n method: MFAMethod;\n}\n\ninterface EnableMFAResponse {\n enableMFA: boolean;\n}\n\ninterface EnableMFAVariables {\n code: string;\n}\n\ninterface VerifyMFAResponse {\n verifyMFA: AuthResult;\n}\n\ninterface VerifyMFAVariables {\n input: MFAVerifyInput;\n}\n\ninterface DisableMFAResponse {\n disableMFA: boolean;\n}\n\n// GraphQL Operations\n\n/**\n * Hook for MFA operations\n */\nexport function useMfaOperations() {\n const store = useAuthStore();\n const context = useAuth();\n const eventBus = useEventBus();\n const [setupMfaMutation] = useMutation<SetupMFAResponse, SetupMFAVariables>(\n SetupMfaDocument,\n );\n const [enableMfaMutation] = useMutation<\n EnableMFAResponse,\n EnableMFAVariables\n >(EnableMfaDocument);\n const [verifyMfaMutation] = useMutation<\n VerifyMFAResponse,\n VerifyMFAVariables\n >(VerifyMfaDocument);\n const [disableMfaMutation] =\n useMutation<DisableMFAResponse>(DisableMfaDocument);\n\n /**\n * Setup MFA for the user\n */\n const setupMFA = async (method: MFAMethod): Promise<MFASetupResult> => {\n try {\n store.setLoading(true);\n\n const { data } = await setupMfaMutation({\n variables: { method },\n });\n\n return data!.setupMFA;\n } catch (error) {\n if (import.meta.env.DEV) {\n console.error(\"MFA setup failed:\", error);\n }\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Enable MFA after setup with verification code\n */\n const enableMFA = async (code: string): Promise<boolean> => {\n try {\n store.setLoading(true);\n\n const { data } = await enableMfaMutation({\n variables: { code },\n });\n\n if (data!.enableMFA && store.user) {\n store.setUser({ ...store.user, mfaEnabled: true });\n }\n\n return data!.enableMFA;\n } catch (error) {\n if (import.meta.env.DEV) {\n console.error(\"Failed to enable MFA:\", error);\n }\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Verify MFA code during login\n */\n const verifyMFA = async (input: MFAVerifyInput): Promise<AuthResult> => {\n try {\n store.setLoading(true);\n\n const { data, error } = await verifyMfaMutation({\n variables: { input },\n });\n\n const result = data?.verifyMFA;\n\n // Server returned no data (GraphQL errors, network failure, or null payload).\n // Surface a friendly message instead of letting `result.success` throw a\n // \"Cannot read properties of null\" error to the UI. Apollo Client v4\n // exposes a single `error` (ApolloError) on MutateResult, not an array.\n if (!result) {\n const gqlMessage = error?.message;\n return {\n success: false,\n error: gqlMessage || \"Invalid verification code. Please try again.\",\n } as AuthResult;\n }\n\n if (result.success && result.user && result.token) {\n store.setUser(result.user);\n store.setAuthTokens(result.token, result.refreshToken || \"\");\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n const expiresAt = result.expiresIn\n ? Date.now() + result.expiresIn * 1000\n : undefined;\n loginProfile(toProfileUser(result.user), {\n accessToken: result.token,\n refreshToken: result.refreshToken || \"\",\n expiresAt,\n });\n setShellSsoCookie(result.user.id);\n\n eventBus.emit(\n \"auth.signed_in\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"useMfaOperations\" },\n );\n\n context.onAuthSuccess?.(result.user, result.token, result.refreshToken);\n } else {\n console.warn(\n \"[useMfaOperations] NOT saving tokens - condition failed:\",\n {\n success: result.success,\n hasUser: !!result.user,\n hasToken: !!result.token,\n },\n );\n }\n\n return result;\n } catch (error) {\n if (import.meta.env.DEV) {\n console.error(\"MFA verification failed:\", error);\n }\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Disable MFA\n */\n const disableMFA = async (): Promise<boolean> => {\n try {\n store.setLoading(true);\n\n const { data } = await disableMfaMutation();\n\n if (data!.disableMFA && store.user) {\n store.setUser({ ...store.user, mfaEnabled: false });\n }\n\n return data!.disableMFA;\n } catch (error) {\n if (import.meta.env.DEV) {\n console.error(\"Failed to disable MFA:\", error);\n }\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n // Legacy aliases\n const enableMfa = enableMFA;\n const verifyMfa = verifyMFA;\n const disableMfa = disableMFA;\n\n return {\n setupMFA,\n enableMFA,\n verifyMFA,\n disableMFA,\n // Legacy aliases\n enableMfa,\n verifyMfa,\n disableMfa,\n // State\n mfaRequired: store.mfaRequired,\n mfaChallengeId: store.mfaChallengeId,\n isLoading: store.isLoading,\n };\n}\n"],"mappings":"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"}
@@ -4,8 +4,8 @@ import { useAuthPermissions as r } from "../hooks/useAuthPermissions.js";
4
4
  import { ConfirmActionDialog as i } from "../components/ConfirmActionDialog.js";
5
5
  import { nativeCopyText as a, nativeImpact as o, nativeNotify as s } from "../../utils/nativeBridge.js";
6
6
  import { useCallback as c, useEffect as l, useRef as ee, useState as u } from "react";
7
- import { useNavigate as te, useSearchParams as d } from "react-router-dom";
8
- import { useI18n as ne } from "@burdenoff/fe-libs/shared/providers/shell";
7
+ import { useNavigate as te, useSearchParams as ne } from "react-router-dom";
8
+ import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell";
9
9
  import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
10
10
  import { useFeatureFlagDetails as re } from "@burdenoff/fe-libs/shared/feature-flags";
11
11
  //#region src/auth/pages/SsoConfigPage.tsx
@@ -46,7 +46,7 @@ function g(e) {
46
46
  }
47
47
  }
48
48
  function _({ bypassEnterprisePlanGate: a = !1 }) {
49
- let _ = te(), [v] = d(), { canManageSsoConfig: y, isLoading: ie } = r(), ae = re("workspace.auth.enterprise-sso"), oe = a || ae?.value === !0, b = ee(null), x = v.get("tenant"), S = v.get("org"), C = v.get("workspace"), { ssoConfig: w, oidcConfig: T, isLoading: E, isSaving: D, isDeleting: O, saveSsoConfig: k, deleteSsoConfig: se } = n(x ?? "burdenoff.com"), { t: A } = ne(), j = c((e, t) => {
49
+ let _ = te(), [v] = ne(), { canManageSsoConfig: ie, isLoading: ae } = r(), oe = re("workspace.auth.enterprise-sso"), se = a || oe?.value === !0, y = ee(null), b = v.get("tenant"), x = v.get("org"), S = v.get("workspace"), { ssoConfig: C, oidcConfig: w, isLoading: T, isSaving: E, isDeleting: D, saveSsoConfig: O, deleteSsoConfig: k } = n(b ?? "burdenoff.com"), { t: A } = d(), j = c((e, t) => {
50
50
  let n = A(e);
51
51
  return n === e ? t : n;
52
52
  }, [A]), [M, ce] = u("saml"), [N, P] = u(!1), [le, F] = u(!1), [I, L] = u(null), [R, z] = u(null), [B, V] = u(""), [H, U] = u(!1), [W, G] = u(!1), [K, q] = u({
@@ -62,14 +62,14 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
62
62
  enabled: !1
63
63
  });
64
64
  return l(() => {
65
- w && $({
66
- entityId: w.entityId || "",
67
- ssoUrl: w.ssoUrl || "",
68
- sloUrl: w.sloUrl || "",
69
- certificate: w.certificate || "",
70
- enabled: w.enabled || !1
65
+ C && $({
66
+ entityId: C.entityId || "",
67
+ ssoUrl: C.ssoUrl || "",
68
+ sloUrl: C.sloUrl || "",
69
+ certificate: C.certificate || "",
70
+ enabled: C.enabled || !1
71
71
  });
72
- }, [w]), ie ? /* @__PURE__ */ p("div", {
72
+ }, [C]), ae ? /* @__PURE__ */ p("div", {
73
73
  className: "max-w-5xl mx-auto p-6",
74
74
  children: /* @__PURE__ */ m("div", {
75
75
  className: "text-center py-16",
@@ -78,7 +78,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
78
78
  children: j("auth.sso.loadingPermissions", "Loading permissions...")
79
79
  })]
80
80
  })
81
- }) : y ? oe ? /* @__PURE__ */ m("div", {
81
+ }) : ie ? se ? /* @__PURE__ */ m("div", {
82
82
  className: "max-w-5xl mx-auto p-6",
83
83
  children: [
84
84
  /* @__PURE__ */ p(i, {
@@ -88,11 +88,11 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
88
88
  description: j("auth.sso.deleteConfirmDesc", "Users will immediately lose the ability to sign in with this SSO setup until a new configuration is saved."),
89
89
  confirmText: j("auth.sso.deleteConfirmButton", "Delete configuration"),
90
90
  variant: "danger",
91
- isPending: O,
91
+ isPending: D,
92
92
  onConfirm: async () => {
93
93
  L(null), z(null), o("medium");
94
94
  try {
95
- await se(), s("success"), z(j("auth.sso.deleteSuccess", "SSO configuration deleted successfully.")), F(!1), $({
95
+ await k(), s("success"), z(j("auth.sso.deleteSuccess", "SSO configuration deleted successfully.")), F(!1), $({
96
96
  entityId: "",
97
97
  ssoUrl: "",
98
98
  sloUrl: "",
@@ -133,7 +133,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
133
133
  className: "text-text-secondary mt-1",
134
134
  children: j("auth.sso.description", "Configure enterprise SSO with SAML 2.0 or OIDC")
135
135
  })] })]
136
- }), w && /* @__PURE__ */ m("div", {
136
+ }), C && /* @__PURE__ */ m("div", {
137
137
  className: "flex items-center gap-3",
138
138
  children: [/* @__PURE__ */ p("span", {
139
139
  className: `px-3 py-1 rounded-full text-sm font-medium ${Q.enabled ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-secondary"}`,
@@ -143,7 +143,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
143
143
  onClick: async () => {
144
144
  L(null);
145
145
  try {
146
- await k({
146
+ await O({
147
147
  ...Q,
148
148
  enabled: !Q.enabled
149
149
  }), z(Q.enabled ? j("auth.sso.ssoDisabled", "SSO disabled") : j("auth.sso.ssoEnabled", "SSO enabled"));
@@ -151,13 +151,13 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
151
151
  L(e instanceof Error ? e.message : j("auth.sso.statusUpdateFailed", "Failed to update status"));
152
152
  }
153
153
  },
154
- disabled: D,
154
+ disabled: E,
155
155
  className: "px-4 py-2 rounded-lg border border-border-default text-text-primary hover:bg-bg-sunken transition-colors text-sm font-medium",
156
156
  children: Q.enabled ? j("auth.sso.disable", "Disable") : j("auth.sso.enable", "Enable")
157
157
  })]
158
158
  })]
159
159
  }),
160
- a && (x ?? S ?? C) && /* @__PURE__ */ m("div", {
160
+ a && (b ?? x ?? S) && /* @__PURE__ */ m("div", {
161
161
  className: "mb-6 p-4 rounded-xl bg-status-info-bg-subtle border border-status-info-border",
162
162
  children: [/* @__PURE__ */ p("p", {
163
163
  className: "text-sm font-medium text-status-info-text mb-1",
@@ -165,7 +165,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
165
165
  }), /* @__PURE__ */ m("div", {
166
166
  className: "flex flex-wrap gap-4 text-xs text-status-info-text",
167
167
  children: [
168
- x && /* @__PURE__ */ m("span", { children: [
168
+ b && /* @__PURE__ */ m("span", { children: [
169
169
  /* @__PURE__ */ p("span", {
170
170
  className: "font-medium",
171
171
  children: j("auth.sso.tenant", "Tenant:")
@@ -173,10 +173,10 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
173
173
  " ",
174
174
  /* @__PURE__ */ p("code", {
175
175
  className: "font-mono",
176
- children: x
176
+ children: b
177
177
  })
178
178
  ] }),
179
- S && /* @__PURE__ */ m("span", { children: [
179
+ x && /* @__PURE__ */ m("span", { children: [
180
180
  /* @__PURE__ */ p("span", {
181
181
  className: "font-medium",
182
182
  children: j("auth.sso.organization", "Organization:")
@@ -184,10 +184,10 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
184
184
  " ",
185
185
  /* @__PURE__ */ p("code", {
186
186
  className: "font-mono",
187
- children: S
187
+ children: x
188
188
  })
189
189
  ] }),
190
- C && /* @__PURE__ */ m("span", { children: [
190
+ S && /* @__PURE__ */ m("span", { children: [
191
191
  /* @__PURE__ */ p("span", {
192
192
  className: "font-medium",
193
193
  children: j("auth.sso.workspace", "Workspace:")
@@ -195,7 +195,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
195
195
  " ",
196
196
  /* @__PURE__ */ p("code", {
197
197
  className: "font-mono",
198
- children: C
198
+ children: S
199
199
  })
200
200
  ] })
201
201
  ]
@@ -276,14 +276,14 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
276
276
  }, e.id))
277
277
  })
278
278
  }),
279
- E && /* @__PURE__ */ m("div", {
279
+ T && /* @__PURE__ */ m("div", {
280
280
  className: "flex items-center justify-center py-12",
281
281
  children: [/* @__PURE__ */ p(t, { className: "size-8 text-text-link animate-spin" }), /* @__PURE__ */ p("span", {
282
282
  className: "ml-3 text-text-secondary",
283
283
  children: j("auth.sso.loadingConfig", "Loading configuration...")
284
284
  })]
285
285
  }),
286
- !E && M === "saml" && /* @__PURE__ */ m("div", {
286
+ !T && M === "saml" && /* @__PURE__ */ m("div", {
287
287
  className: "space-y-6",
288
288
  children: [
289
289
  /* @__PURE__ */ m("div", {
@@ -328,7 +328,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
328
328
  children: j("auth.sso.uploadFile", "Upload Metadata File")
329
329
  }),
330
330
  /* @__PURE__ */ p("input", {
331
- ref: b,
331
+ ref: y,
332
332
  type: "file",
333
333
  accept: ".xml,application/xml,text/xml",
334
334
  onChange: async (e) => {
@@ -347,7 +347,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
347
347
  } catch {
348
348
  L(j("auth.sso.readError", "Failed to read metadata file"));
349
349
  } finally {
350
- U(!1), b.current && (b.current.value = "");
350
+ U(!1), y.current && (y.current.value = "");
351
351
  }
352
352
  }
353
353
  },
@@ -484,7 +484,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
484
484
  onSubmit: async (e) => {
485
485
  e.preventDefault(), L(null), z(null);
486
486
  try {
487
- await k(Q), z(j("auth.sso.saveSuccess", "SSO configuration saved successfully!")), P(!1);
487
+ await O(Q), z(j("auth.sso.saveSuccess", "SSO configuration saved successfully!")), P(!1);
488
488
  } catch (e) {
489
489
  L(e instanceof Error ? e.message : j("auth.sso.saveFailed", "Failed to save configuration"));
490
490
  }
@@ -496,7 +496,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
496
496
  children: [/* @__PURE__ */ p("h3", {
497
497
  className: "text-lg font-medium text-text-primary",
498
498
  children: j("auth.sso.idpSettings", "Identity Provider (IdP) Settings")
499
- }), !N && w && /* @__PURE__ */ p("button", {
499
+ }), !N && C && /* @__PURE__ */ p("button", {
500
500
  type: "button",
501
501
  onClick: () => P(!0),
502
502
  className: "px-4 py-2 rounded-lg border border-status-info-border text-text-link hover:bg-status-info-bg-subtle text-sm font-medium transition-colors",
@@ -529,7 +529,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
529
529
  ...Q,
530
530
  entityId: e.target.value
531
531
  }),
532
- disabled: !N && !!w,
532
+ disabled: !N && !!C,
533
533
  placeholder: j("auth.sso.idpEntityIdPlaceholder", "https://your-idp.example.com/saml/metadata"),
534
534
  required: !0,
535
535
  "aria-required": "true",
@@ -565,7 +565,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
565
565
  ...Q,
566
566
  ssoUrl: e.target.value
567
567
  }),
568
- disabled: !N && !!w,
568
+ disabled: !N && !!C,
569
569
  placeholder: j("auth.sso.ssoUrlPlaceholder", "https://your-idp.example.com/saml/sso"),
570
570
  required: !0,
571
571
  "aria-required": "true",
@@ -578,32 +578,6 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
578
578
  children: j("auth.sso.ssoUrlHelper", "The URL where users are redirected to authenticate")
579
579
  })
580
580
  ] }),
581
- /* @__PURE__ */ m("div", { children: [
582
- /* @__PURE__ */ p("label", {
583
- htmlFor: "sso-slo-url",
584
- className: "block text-sm font-medium text-text-primary mb-2",
585
- children: j("auth.sso.sloUrl", "Single Logout (SLO) URL")
586
- }),
587
- /* @__PURE__ */ p("input", {
588
- id: "sso-slo-url",
589
- type: "url",
590
- "aria-label": j("auth.sso.sloUrl", "Single Logout (SLO) URL"),
591
- value: Q.sloUrl,
592
- onChange: (e) => $({
593
- ...Q,
594
- sloUrl: e.target.value
595
- }),
596
- disabled: !N && !!w,
597
- placeholder: j("auth.sso.sloUrlPlaceholder", "https://your-idp.example.com/saml/slo"),
598
- "aria-describedby": "sso-slo-url-hint",
599
- className: "block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg disabled:opacity-60 disabled:cursor-not-allowed transition-all"
600
- }),
601
- /* @__PURE__ */ p("p", {
602
- id: "sso-slo-url-hint",
603
- className: "mt-1 text-xs text-text-secondary",
604
- children: j("auth.sso.sloUrlHelper", "Optional: URL for single logout across all applications")
605
- })
606
- ] }),
607
581
  /* @__PURE__ */ m("div", { children: [
608
582
  /* @__PURE__ */ m("label", {
609
583
  htmlFor: "sso-certificate",
@@ -626,7 +600,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
626
600
  ...Q,
627
601
  certificate: e.target.value
628
602
  }),
629
- disabled: !N && !!w,
603
+ disabled: !N && !!C,
630
604
  placeholder: j("auth.sso.certificatePlaceholder", "-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAX...\n-----END CERTIFICATE-----"),
631
605
  required: !0,
632
606
  "aria-required": "true",
@@ -642,22 +616,22 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
642
616
  ] })
643
617
  ]
644
618
  }),
645
- (N || !w) && /* @__PURE__ */ m("div", {
619
+ (N || !C) && /* @__PURE__ */ m("div", {
646
620
  className: "mt-6 flex items-center gap-3",
647
621
  children: [/* @__PURE__ */ p("button", {
648
622
  type: "submit",
649
- disabled: D,
623
+ disabled: E,
650
624
  className: "auth-primary-button flex items-center gap-2 px-6 py-3 font-medium",
651
- children: D ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(t, { className: "size-4 animate-spin" }), j("auth.sso.saving", "Saving...")] }) : j("auth.sso.saveConfig", "Save Configuration")
625
+ children: E ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(t, { className: "size-4 animate-spin" }), j("auth.sso.saving", "Saving...")] }) : j("auth.sso.saveConfig", "Save Configuration")
652
626
  }), N && /* @__PURE__ */ p("button", {
653
627
  type: "button",
654
628
  onClick: () => {
655
- P(!1), w && $({
656
- entityId: w.entityId || "",
657
- ssoUrl: w.ssoUrl || "",
658
- sloUrl: w.sloUrl || "",
659
- certificate: w.certificate || "",
660
- enabled: w.enabled || !1
629
+ P(!1), C && $({
630
+ entityId: C.entityId || "",
631
+ ssoUrl: C.ssoUrl || "",
632
+ sloUrl: C.sloUrl || "",
633
+ certificate: C.certificate || "",
634
+ enabled: C.enabled || !1
661
635
  });
662
636
  },
663
637
  className: "px-6 py-3 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors",
@@ -666,7 +640,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
666
640
  })
667
641
  ]
668
642
  }),
669
- w && /* @__PURE__ */ m("div", {
643
+ C && /* @__PURE__ */ m("div", {
670
644
  className: "p-6 rounded-xl border border-status-error-border bg-status-error-bg-subtle",
671
645
  children: [
672
646
  /* @__PURE__ */ p("h3", {
@@ -680,15 +654,15 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
680
654
  /* @__PURE__ */ p("button", {
681
655
  type: "button",
682
656
  onClick: () => F(!0),
683
- disabled: O,
657
+ disabled: D,
684
658
  className: "px-4 py-2 rounded-lg bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover disabled:opacity-50 transition-colors flex items-center gap-2",
685
- children: O ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(t, { className: "size-4 animate-spin" }), j("auth.sso.deleting", "Deleting...")] }) : j("auth.sso.deleteConfig", "Delete SSO Configuration")
659
+ children: D ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(t, { className: "size-4 animate-spin" }), j("auth.sso.deleting", "Deleting...")] }) : j("auth.sso.deleteConfig", "Delete SSO Configuration")
686
660
  })
687
661
  ]
688
662
  })
689
663
  ]
690
664
  }),
691
- !E && M === "oidc" && /* @__PURE__ */ m("div", {
665
+ !T && M === "oidc" && /* @__PURE__ */ m("div", {
692
666
  className: "space-y-6",
693
667
  children: [
694
668
  /* @__PURE__ */ m("div", {
@@ -716,21 +690,21 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
716
690
  className: "flex items-center gap-2",
717
691
  children: [/* @__PURE__ */ p("code", {
718
692
  className: "flex-1 px-3 py-2 rounded-lg bg-bg-sunken text-sm text-text-primary font-mono break-all",
719
- children: T?.issuer ? `${T.issuer}/.well-known/openid-configuration` : "Discovery endpoint unavailable until OIDC is configured"
693
+ children: w?.issuer ? `${w.issuer}/.well-known/openid-configuration` : "Discovery endpoint unavailable until OIDC is configured"
720
694
  }), /* @__PURE__ */ p("button", {
721
695
  type: "button",
722
696
  onClick: () => {
723
- let e = T?.issuer ? `${T.issuer}/.well-known/openid-configuration` : "";
697
+ let e = w?.issuer ? `${w.issuer}/.well-known/openid-configuration` : "";
724
698
  e && h(e);
725
699
  },
726
- disabled: !T?.issuer,
700
+ disabled: !w?.issuer,
727
701
  className: "px-3 py-2 rounded-lg border border-border-default text-text-secondary hover:bg-bg-sunken flex-shrink-0",
728
702
  children: j("auth.sso.copy", "Copy")
729
703
  })]
730
704
  })
731
705
  ]
732
706
  }),
733
- T && /* @__PURE__ */ m("div", {
707
+ w && /* @__PURE__ */ m("div", {
734
708
  className: "p-6 rounded-xl border border-border-subtle bg-bg-surface",
735
709
  children: [/* @__PURE__ */ p("h3", {
736
710
  className: "text-lg font-medium text-text-primary mb-4",
@@ -740,23 +714,23 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
740
714
  children: [
741
715
  {
742
716
  label: j("auth.sso.issuer", "Issuer"),
743
- value: T.issuer
717
+ value: w.issuer
744
718
  },
745
719
  {
746
720
  label: j("auth.sso.authorizationEndpoint", "Authorization Endpoint"),
747
- value: T.authorizationEndpoint
721
+ value: w.authorizationEndpoint
748
722
  },
749
723
  {
750
724
  label: j("auth.sso.tokenEndpoint", "Token Endpoint"),
751
- value: T.tokenEndpoint
725
+ value: w.tokenEndpoint
752
726
  },
753
727
  {
754
728
  label: j("auth.sso.userinfoEndpoint", "UserInfo Endpoint"),
755
- value: T.userinfoEndpoint
729
+ value: w.userinfoEndpoint
756
730
  },
757
731
  {
758
732
  label: j("auth.sso.jwksUri", "JWKS URI"),
759
- value: T.jwksUri
733
+ value: w.jwksUri
760
734
  }
761
735
  ].map((e) => /* @__PURE__ */ m("div", {
762
736
  className: "flex items-start gap-4",
@@ -813,7 +787,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
813
787
  })
814
788
  ]
815
789
  }),
816
- !E && M === "clients" && /* @__PURE__ */ m("div", {
790
+ !T && M === "clients" && /* @__PURE__ */ m("div", {
817
791
  className: "space-y-6",
818
792
  children: [
819
793
  /* @__PURE__ */ m("div", {
@@ -1052,7 +1026,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
1052
1026
  }
1053
1027
  Y(!0), L(null);
1054
1028
  try {
1055
- let e = T?.registrationEndpoint;
1029
+ let e = w?.registrationEndpoint;
1056
1030
  if (!e) throw Error(j("auth.sso.oidcNotConfigured", "OIDC dynamic client registration is not configured for this environment."));
1057
1031
  let t = await fetch(e, {
1058
1032
  method: "POST",
@@ -1165,7 +1139,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
1165
1139
  })
1166
1140
  ]
1167
1141
  }),
1168
- !E && M === "test" && /* @__PURE__ */ m("div", {
1142
+ !T && M === "test" && /* @__PURE__ */ m("div", {
1169
1143
  className: "space-y-6",
1170
1144
  children: [
1171
1145
  /* @__PURE__ */ m("div", {
@@ -1189,7 +1163,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
1189
1163
  className: "text-sm text-text-secondary mb-4",
1190
1164
  children: j("auth.sso.spInitiatedTestDesc", "Click the button below to initiate an SSO login flow. You will be redirected to your IdP to authenticate.")
1191
1165
  }),
1192
- w && w.enabled ? /* @__PURE__ */ m("a", {
1166
+ C && C.enabled ? /* @__PURE__ */ m("a", {
1193
1167
  href: `${window.location.origin}/auth/sso/login`,
1194
1168
  className: "inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-status-success-bg text-white font-medium shadow-sm hover:opacity-90 transition-all duration-200",
1195
1169
  children: [/* @__PURE__ */ p("svg", {
@@ -1206,7 +1180,7 @@ function _({ bypassEnterprisePlanGate: a = !1 }) {
1206
1180
  }), j("auth.sso.testSsoLoginButton", "Test SSO Login")]
1207
1181
  }) : /* @__PURE__ */ p("div", {
1208
1182
  className: "p-4 rounded-xl bg-bg-sunken/50 text-text-secondary text-sm",
1209
- children: w ? j("auth.sso.disabledTest", "SSO is disabled. Enable it to test the login flow.") : j("auth.sso.notConfigured", "SSO is not configured. Please configure your IdP settings first.")
1183
+ children: C ? j("auth.sso.disabledTest", "SSO is disabled. Enable it to test the login flow.") : j("auth.sso.notConfigured", "SSO is not configured. Please configure your IdP settings first.")
1210
1184
  })
1211
1185
  ]
1212
1186
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"SsoConfigPage.js","names":[],"sources":["../../../src/auth/pages/SsoConfigPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useNavigate, useSearchParams } from \"react-router-dom\";\nimport { useAuthPermissions } from \"../hooks/useAuthPermissions\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useFeatureFlagDetails } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport {\n useSsoOperations,\n type SSOConfigInput,\n} from \"../hooks/useSsoOperations\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport {\n nativeCopyText,\n nativeImpact,\n nativeNotify,\n} from \"../../utils/nativeBridge\";\n\nconst copyTextWithNative = async (text: string): Promise<void> => {\n void nativeImpact(\"light\");\n const ok = await nativeCopyText(text);\n void nativeNotify(ok ? \"success\" : \"error\");\n};\n\n// =============================================================================\n// SAML Metadata Parser\n// =============================================================================\n\ninterface ParsedSAMLMetadata {\n entityId: string;\n ssoUrl: string;\n sloUrl?: string;\n certificate: string;\n}\n\nfunction parseSAMLMetadata(xmlString: string): ParsedSAMLMetadata | null {\n try {\n const parser = new DOMParser();\n const doc = parser.parseFromString(xmlString, \"text/xml\");\n\n // Check for parse errors\n const parseError = doc.querySelector(\"parsererror\");\n if (parseError) {\n console.error(\"XML parse error:\", parseError.textContent);\n return null;\n }\n\n // Extract EntityID\n const entityDescriptor = doc.querySelector(\"EntityDescriptor\");\n const entityId = entityDescriptor?.getAttribute(\"entityID\") || \"\";\n\n // Find IDPSSODescriptor\n const idpDescriptor = doc.querySelector(\"IDPSSODescriptor\");\n if (!idpDescriptor) {\n console.error(\"No IDPSSODescriptor found\");\n return null;\n }\n\n // Extract SSO URL (HTTP-Redirect or HTTP-POST binding)\n const ssoServices = idpDescriptor.querySelectorAll(\"SingleSignOnService\");\n let ssoUrl = \"\";\n for (const service of ssoServices) {\n const binding = service.getAttribute(\"Binding\") || \"\";\n if (binding.includes(\"HTTP-Redirect\") || binding.includes(\"HTTP-POST\")) {\n ssoUrl = service.getAttribute(\"Location\") || \"\";\n break;\n }\n }\n\n // Extract SLO URL\n const sloServices = idpDescriptor.querySelectorAll(\"SingleLogoutService\");\n let sloUrl = \"\";\n for (const service of sloServices) {\n const binding = service.getAttribute(\"Binding\") || \"\";\n if (binding.includes(\"HTTP-Redirect\") || binding.includes(\"HTTP-POST\")) {\n sloUrl = service.getAttribute(\"Location\") || \"\";\n break;\n }\n }\n\n // Extract Certificate\n const certElement = doc.querySelector(\"X509Certificate\");\n let certificate = certElement?.textContent?.trim() || \"\";\n if (certificate && !certificate.includes(\"-----BEGIN\")) {\n certificate = `-----BEGIN CERTIFICATE-----\\n${certificate}\\n-----END CERTIFICATE-----`;\n }\n\n if (!entityId || !ssoUrl || !certificate) {\n console.error(\"Missing required SAML metadata fields\");\n return null;\n }\n\n return { entityId, ssoUrl, sloUrl, certificate };\n } catch (error) {\n console.error(\"Failed to parse SAML metadata:\", error);\n return null;\n }\n}\n\n// =============================================================================\n// OIDC Client Types\n// =============================================================================\n\ninterface OIDCClient {\n client_id: string;\n client_secret?: string;\n redirect_uris: string[];\n grant_types: string[];\n response_types: string[];\n token_endpoint_auth_method: string;\n client_name?: string;\n registration_access_token?: string;\n}\n\ninterface SsoConfigPageProps {\n /** When true, skips the enterprise-plan gate so admins always see the full UI */\n bypassEnterprisePlanGate?: boolean;\n}\n\n/**\n * SSO Configuration page for managing SAML/OIDC enterprise SSO.\n * Admin only - allows configuring Identity Provider settings.\n * In admin-app context, bypassEnterprisePlanGate=true and tenant/org/workspace\n * scoping is read from URL search params (?tenant=&org=&workspace=).\n */\nexport function SsoConfigPage({\n bypassEnterprisePlanGate = false,\n}: SsoConfigPageProps) {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { canManageSsoConfig, isLoading: permissionsLoading } =\n useAuthPermissions();\n // Enterprise SSO gate: only visible to workspaces on an enterprise plan.\n // When the flag is absent the section is hidden (default-deny).\n // bypassEnterprisePlanGate=true skips this check entirely (admin-app usage).\n const enterpriseSsoFlag = useFeatureFlagDetails(\n \"workspace.auth.enterprise-sso\",\n );\n const isEnterpriseSsoEnabled =\n bypassEnterprisePlanGate || enterpriseSsoFlag?.value === true;\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n // In admin context, read scope from URL params; otherwise use platform default\n const urlTenantId = searchParams.get(\"tenant\");\n const urlOrgId = searchParams.get(\"org\");\n const urlWorkspaceId = searchParams.get(\"workspace\");\n const effectiveTenantId = urlTenantId ?? \"burdenoff.com\";\n\n const {\n ssoConfig,\n oidcConfig,\n isLoading,\n isSaving,\n isDeleting,\n saveSsoConfig,\n deleteSsoConfig,\n } = useSsoOperations(effectiveTenantId);\n\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t],\n );\n\n const [activeTab, setActiveTab] = useState<\n \"saml\" | \"oidc\" | \"clients\" | \"test\"\n >(\"saml\");\n const [isEditing, setIsEditing] = useState(false);\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n\n // SAML Metadata import state\n const [metadataUrl, setMetadataUrl] = useState(\"\");\n const [isImporting, setIsImporting] = useState(false);\n\n // OIDC Client registration state\n const [showClientForm, setShowClientForm] = useState(false);\n const [newClient, setNewClient] = useState({\n client_name: \"\",\n redirect_uris: \"\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n token_endpoint_auth_method: \"none\",\n });\n const [isCreatingClient, setIsCreatingClient] = useState(false);\n const [createdClient, setCreatedClient] = useState<OIDCClient | null>(null);\n\n // Form state for SAML configuration\n const [formData, setFormData] = useState<SSOConfigInput>({\n entityId: \"\",\n ssoUrl: \"\",\n sloUrl: \"\",\n certificate: \"\",\n enabled: false,\n });\n\n // Handle SAML metadata file upload\n const handleMetadataFileUpload = async (\n event: React.ChangeEvent<HTMLInputElement>,\n ) => {\n const file = event.target.files?.[0];\n if (!file) return;\n\n setError(null);\n setIsImporting(true);\n\n try {\n const text = await file.text();\n const parsed = parseSAMLMetadata(text);\n\n if (parsed) {\n setFormData({\n ...formData,\n entityId: parsed.entityId,\n ssoUrl: parsed.ssoUrl,\n sloUrl: parsed.sloUrl || \"\",\n certificate: parsed.certificate,\n });\n setSuccess(\n tr(\n \"auth.sso.importSuccess\",\n \"SAML metadata imported successfully! Review and save the configuration.\",\n ),\n );\n setIsEditing(true);\n } else {\n setError(\n tr(\n \"auth.sso.parseError\",\n \"Failed to parse SAML metadata. Make sure the file contains valid IdP metadata XML.\",\n ),\n );\n }\n } catch {\n setError(tr(\"auth.sso.readError\", \"Failed to read metadata file\"));\n } finally {\n setIsImporting(false);\n if (fileInputRef.current) {\n fileInputRef.current.value = \"\";\n }\n }\n };\n\n // Handle SAML metadata URL fetch\n const handleMetadataUrlFetch = async () => {\n if (!metadataUrl) {\n setError(tr(\"auth.sso.enterUrl\", \"Please enter a metadata URL\"));\n return;\n }\n\n setError(null);\n setIsImporting(true);\n\n try {\n const response = await fetch(metadataUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch: ${response.statusText}`);\n }\n\n const text = await response.text();\n const parsed = parseSAMLMetadata(text);\n\n if (parsed) {\n setFormData({\n ...formData,\n entityId: parsed.entityId,\n ssoUrl: parsed.ssoUrl,\n sloUrl: parsed.sloUrl || \"\",\n certificate: parsed.certificate,\n });\n setSuccess(\n tr(\n \"auth.sso.importSuccessFromUrl\",\n \"SAML metadata imported successfully! Review and save the configuration.\",\n ),\n );\n setIsEditing(true);\n setMetadataUrl(\"\");\n } else {\n setError(\n tr(\n \"auth.sso.parseUrlError\",\n \"Failed to parse SAML metadata from URL.\",\n ),\n );\n }\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.fetchError\", \"Failed to fetch metadata from URL\"),\n );\n } finally {\n setIsImporting(false);\n }\n };\n\n // Register new OIDC client\n const handleRegisterClient = async () => {\n if (!newClient.client_name || !newClient.redirect_uris) {\n setError(\n tr(\n \"auth.sso.clientRequired\",\n \"Client name and redirect URI are required\",\n ),\n );\n return;\n }\n\n setIsCreatingClient(true);\n setError(null);\n\n try {\n const registrationEndpoint = oidcConfig?.registrationEndpoint;\n\n if (!registrationEndpoint) {\n throw new Error(\n tr(\n \"auth.sso.oidcNotConfigured\",\n \"OIDC dynamic client registration is not configured for this environment.\",\n ),\n );\n }\n\n const response = await fetch(registrationEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n client_name: newClient.client_name,\n redirect_uris: newClient.redirect_uris\n .split(\",\")\n .map((uri) => uri.trim()),\n grant_types: newClient.grant_types,\n response_types: [\"code\"],\n token_endpoint_auth_method: newClient.token_endpoint_auth_method,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json();\n throw new Error(\n errorData.error_description ||\n errorData.error ||\n \"Registration failed\",\n );\n }\n\n const client = await response.json();\n setCreatedClient(client);\n setSuccess(\n tr(\n \"auth.sso.clientRegistered\",\n \"OIDC client registered successfully! Save the client credentials.\",\n ),\n );\n setShowClientForm(false);\n setNewClient({\n client_name: \"\",\n redirect_uris: \"\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n token_endpoint_auth_method: \"none\",\n });\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.registerFailed\", \"Failed to register client\"),\n );\n } finally {\n setIsCreatingClient(false);\n }\n };\n\n // Sync form with loaded config\n useEffect(() => {\n if (ssoConfig) {\n setFormData({\n entityId: ssoConfig.entityId || \"\",\n ssoUrl: ssoConfig.ssoUrl || \"\",\n sloUrl: ssoConfig.sloUrl || \"\",\n certificate: ssoConfig.certificate || \"\",\n enabled: ssoConfig.enabled || false,\n });\n }\n }, [ssoConfig]);\n\n const handleSave = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(null);\n\n try {\n await saveSsoConfig(formData);\n setSuccess(\n tr(\"auth.sso.saveSuccess\", \"SSO configuration saved successfully!\"),\n );\n setIsEditing(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.saveFailed\", \"Failed to save configuration\"),\n );\n }\n };\n\n const handleDelete = async () => {\n setError(null);\n setSuccess(null);\n void nativeImpact(\"medium\");\n\n try {\n await deleteSsoConfig();\n void nativeNotify(\"success\");\n setSuccess(\n tr(\"auth.sso.deleteSuccess\", \"SSO configuration deleted successfully.\"),\n );\n setShowDeleteDialog(false);\n setFormData({\n entityId: \"\",\n ssoUrl: \"\",\n sloUrl: \"\",\n certificate: \"\",\n enabled: false,\n });\n } catch (err) {\n void nativeNotify(\"error\");\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.deleteFailed\", \"Failed to delete configuration\"),\n );\n }\n };\n\n const handleToggleEnabled = async () => {\n setError(null);\n try {\n await saveSsoConfig({ ...formData, enabled: !formData.enabled });\n setSuccess(\n formData.enabled\n ? tr(\"auth.sso.ssoDisabled\", \"SSO disabled\")\n : tr(\"auth.sso.ssoEnabled\", \"SSO enabled\"),\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.statusUpdateFailed\", \"Failed to update status\"),\n );\n }\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"max-w-5xl mx-auto p-6\">\n <div className=\"text-center py-16\">\n <SpinnerIcon className=\"size-8 mx-auto text-text-secondary animate-spin\" />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.sso.loadingPermissions\", \"Loading permissions...\")}\n </p>\n </div>\n </div>\n );\n }\n\n if (!canManageSsoConfig) {\n return (\n <div className=\"max-w-5xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-error-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.sso.adminRequired\", \"Admin Access Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.sso.adminRequiredDesc\",\n \"You need administrator privileges to configure SSO settings.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n // Enterprise SSO is a plan-gated feature. Only show the full configuration\n // to workspaces on an enterprise plan (flag: workspace.auth.enterprise-sso).\n if (!isEnterpriseSsoEnabled) {\n return (\n <div className=\"max-w-5xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-warning-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.sso.enterprisePlanRequired\", \"Enterprise Plan Required\")}\n </h2>\n <p className=\"text-text-secondary mb-2\">\n {tr(\n \"auth.sso.enterprisePlanRequiredDesc\",\n \"Single Sign-On (SAML / OIDC) configuration is available on the Enterprise plan.\",\n )}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.sso.contactSales\",\n \"Contact sales to upgrade your workspace to the Enterprise plan.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-5xl mx-auto p-6\">\n <ConfirmActionDialog\n open={showDeleteDialog}\n onOpenChange={setShowDeleteDialog}\n title={tr(\"auth.sso.deleteConfirmTitle\", \"Delete SSO configuration?\")}\n description={tr(\n \"auth.sso.deleteConfirmDesc\",\n \"Users will immediately lose the ability to sign in with this SSO setup until a new configuration is saved.\",\n )}\n confirmText={tr(\"auth.sso.deleteConfirmButton\", \"Delete configuration\")}\n variant=\"danger\"\n isPending={isDeleting}\n onConfirm={handleDelete}\n />\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/security\")}\n aria-label={tr(\"auth.sso.backToSecurity\", \"Back to Security\")}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors\"\n >\n <svg\n className=\"size-5 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n </button>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {tr(\"auth.sso.title\", \"Single Sign-On (SSO)\")}\n </h1>\n <p className=\"text-text-secondary mt-1\">\n {tr(\n \"auth.sso.description\",\n \"Configure enterprise SSO with SAML 2.0 or OIDC\",\n )}\n </p>\n </div>\n </div>\n\n {ssoConfig && (\n <div className=\"flex items-center gap-3\">\n <span\n className={`px-3 py-1 rounded-full text-sm font-medium ${\n formData.enabled\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : \"bg-bg-sunken text-text-secondary\"\n }`}\n >\n {formData.enabled\n ? tr(\"auth.sso.enabled\", \"Enabled\")\n : tr(\"auth.sso.disabled\", \"Disabled\")}\n </span>\n <button\n type=\"button\"\n onClick={handleToggleEnabled}\n disabled={isSaving}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary hover:bg-bg-sunken transition-colors text-sm font-medium\"\n >\n {formData.enabled\n ? tr(\"auth.sso.disable\", \"Disable\")\n : tr(\"auth.sso.enable\", \"Enable\")}\n </button>\n </div>\n )}\n </div>\n\n {/* Admin context banner — shown when scoping SSO config to a specific tenant/org/workspace */}\n {bypassEnterprisePlanGate &&\n (urlTenantId ?? urlOrgId ?? urlWorkspaceId) && (\n <div className=\"mb-6 p-4 rounded-xl bg-status-info-bg-subtle border border-status-info-border\">\n <p className=\"text-sm font-medium text-status-info-text mb-1\">\n {tr(\"auth.sso.adminContext\", \"Configuring SSO for\")}\n </p>\n <div className=\"flex flex-wrap gap-4 text-xs text-status-info-text\">\n {urlTenantId && (\n <span>\n <span className=\"font-medium\">\n {tr(\"auth.sso.tenant\", \"Tenant:\")}\n </span>{\" \"}\n <code className=\"font-mono\">{urlTenantId}</code>\n </span>\n )}\n {urlOrgId && (\n <span>\n <span className=\"font-medium\">\n {tr(\"auth.sso.organization\", \"Organization:\")}\n </span>{\" \"}\n <code className=\"font-mono\">{urlOrgId}</code>\n </span>\n )}\n {urlWorkspaceId && (\n <span>\n <span className=\"font-medium\">\n {tr(\"auth.sso.workspace\", \"Workspace:\")}\n </span>{\" \"}\n <code className=\"font-mono\">{urlWorkspaceId}</code>\n </span>\n )}\n </div>\n </div>\n )}\n\n {/* Alerts */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\">\n <AlertIcon className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-status-error-text hover:underline\"\n >\n {tr(\"auth.sso.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {success && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\">\n <svg\n className=\"size-5 text-status-success-text flex-shrink-0 mt-0.5\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\n clipRule=\"evenodd\"\n />\n </svg>\n <div className=\"flex-1\">\n <p className=\"text-sm text-status-success-text\">{success}</p>\n <button\n type=\"button\"\n onClick={() => setSuccess(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline\"\n >\n {tr(\"auth.sso.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Tabs */}\n <div className=\"mb-6 border-b border-border-subtle\">\n <nav className=\"flex gap-6\">\n {[\n {\n id: \"saml\",\n label: tr(\"auth.sso.samlTab\", \"SAML 2.0\"),\n icon: \"🔐\",\n },\n {\n id: \"oidc\",\n label: tr(\"auth.sso.oidcTab\", \"OIDC Provider\"),\n icon: \"🌐\",\n },\n {\n id: \"clients\",\n label: tr(\"auth.sso.clientsTab\", \"OIDC Clients\"),\n icon: \"🔑\",\n },\n {\n id: \"test\",\n label: tr(\"auth.sso.testTab\", \"Test SSO\"),\n icon: \"🧪\",\n },\n ].map((tab) => (\n <button\n type=\"button\"\n key={tab.id}\n onClick={() => setActiveTab(tab.id as typeof activeTab)}\n className={`pb-3 text-sm font-medium transition-colors relative ${\n activeTab === tab.id\n ? \"text-text-link\"\n : \"text-text-secondary hover:text-text-primary\"\n }`}\n >\n <span className=\"flex items-center gap-2\">\n <span>{tab.icon}</span>\n {tab.label}\n </span>\n {activeTab === tab.id && (\n <span className=\"absolute bottom-0 left-0 right-0 h-0.5 bg-action-primary-bg\" />\n )}\n </button>\n ))}\n </nav>\n </div>\n\n {/* Loading */}\n {isLoading && (\n <div className=\"flex items-center justify-center py-12\">\n <SpinnerIcon className=\"size-8 text-text-link animate-spin\" />\n <span className=\"ml-3 text-text-secondary\">\n {tr(\"auth.sso.loadingConfig\", \"Loading configuration...\")}\n </span>\n </div>\n )}\n\n {/* SAML Configuration Tab */}\n {!isLoading && activeTab === \"saml\" && (\n <div className=\"space-y-6\">\n {/* Info Card */}\n <div className=\"p-4 rounded-xl bg-status-info-bg-subtle border border-status-info-border\">\n <h3 className=\"font-medium text-status-info-text mb-2\">\n {tr(\"auth.sso.samlConfigTitle\", \"SAML 2.0 Configuration\")}\n </h3>\n <p className=\"text-sm text-status-info-text\">\n {tr(\n \"auth.sso.samlConfigDesc\",\n \"Configure your Identity Provider (IdP) to enable enterprise SSO. You'll need to get the following information from your IdP (Okta, Azure AD, OneLogin, etc.):\",\n )}\n </p>\n <ul className=\"mt-2 text-sm text-status-info-text list-disc list-inside\">\n <li>{tr(\"auth.sso.entityIdItem\", \"Entity ID (Issuer URL)\")}</li>\n <li>{tr(\"auth.sso.ssoUrlItem\", \"SSO URL (Login URL)\")}</li>\n <li>{tr(\"auth.sso.certificateItem\", \"X.509 Certificate\")}</li>\n </ul>\n </div>\n\n {/* Metadata Import Card */}\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.importMetadata\", \"Import IdP Metadata\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.sso.importMetadataDesc\",\n \"Automatically configure SSO by importing your IdP's metadata XML file or URL.\",\n )}\n </p>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n {/* File Upload */}\n <div className=\"space-y-3\">\n <label className=\"block text-sm font-medium text-text-primary\">\n {tr(\"auth.sso.uploadFile\", \"Upload Metadata File\")}\n </label>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".xml,application/xml,text/xml\"\n onChange={handleMetadataFileUpload}\n className=\"hidden\"\n id=\"metadata-file\"\n aria-label={tr(\"auth.sso.uploadFile\", \"Upload Metadata File\")}\n />\n <label\n htmlFor=\"metadata-file\"\n className=\"flex items-center justify-center gap-2 w-full px-4 py-3 border-2 border-dashed border-border-default rounded-xl cursor-pointer hover:border-action-primary-bg transition-colors\"\n >\n {isImporting ? (\n <SpinnerIcon className=\"size-5 animate-spin text-text-link\" />\n ) : (\n <svg\n className=\"size-5 text-text-tertiary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12\"\n />\n </svg>\n )}\n <span className=\"text-sm text-text-secondary\">\n {isImporting\n ? tr(\"auth.sso.importing\", \"Importing...\")\n : tr(\"auth.sso.chooseFile\", \"Choose XML file\")}\n </span>\n </label>\n </div>\n\n {/* URL Fetch */}\n <div className=\"space-y-3\">\n <label className=\"block text-sm font-medium text-text-primary\">\n {tr(\"auth.sso.fetchFromUrl\", \"Fetch from URL\")}\n </label>\n <div className=\"flex gap-2\">\n <input\n id=\"sso-metadata-url\"\n type=\"url\"\n value={metadataUrl}\n onChange={(e) => setMetadataUrl(e.target.value)}\n placeholder=\"https://idp.example.com/metadata.xml\"\n aria-label={tr(\"auth.sso.fetchFromUrl\", \"Fetch from URL\")}\n className=\"flex-1 px-4 py-2.5 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all text-sm\"\n />\n <button\n type=\"button\"\n onClick={handleMetadataUrlFetch}\n disabled={isImporting || !metadataUrl}\n className=\"px-4 py-2.5 rounded-xl bg-action-primary-bg text-white font-medium hover:bg-action-primary-bg-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-2\"\n >\n {isImporting ? (\n <SpinnerIcon className=\"size-4 animate-spin\" />\n ) : (\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n )}\n {tr(\"auth.sso.fetch\", \"Fetch\")}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* SP Metadata Card */}\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.spMetadata\", \"Service Provider (SP) Metadata\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.sso.spMetadataDesc\",\n \"Provide these values to your Identity Provider when setting up the SAML integration:\",\n )}\n </p>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr(\"auth.sso.spEntityId\", \"SP Entity ID / Audience URI\")}\n </label>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-sunken text-sm text-text-primary font-mono\">\n {window.location.origin}/auth/saml/metadata\n </code>\n <button\n type=\"button\"\n onClick={() =>\n void copyTextWithNative(\n `${window.location.origin}/auth/saml/metadata`,\n )\n }\n className=\"px-3 py-2 rounded-lg border border-border-default text-text-secondary hover:bg-bg-sunken\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr(\n \"auth.sso.acsUrl\",\n \"Assertion Consumer Service (ACS) URL\",\n )}\n </label>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-sunken text-sm text-text-primary font-mono\">\n {window.location.origin}/auth/sso/callback\n </code>\n <button\n type=\"button\"\n onClick={() =>\n void copyTextWithNative(\n `${window.location.origin}/auth/sso/callback`,\n )\n }\n className=\"px-3 py-2 rounded-lg border border-border-default text-text-secondary hover:bg-bg-sunken\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* IdP Configuration Form */}\n <form\n onSubmit={handleSave}\n className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\"\n >\n <div className=\"flex items-center justify-between mb-6\">\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr(\"auth.sso.idpSettings\", \"Identity Provider (IdP) Settings\")}\n </h3>\n {!isEditing && ssoConfig && (\n <button\n type=\"button\"\n onClick={() => setIsEditing(true)}\n className=\"px-4 py-2 rounded-lg border border-status-info-border text-text-link hover:bg-status-info-bg-subtle text-sm font-medium transition-colors\"\n >\n {tr(\"auth.sso.editConfig\", \"Edit Configuration\")}\n </button>\n )}\n </div>\n\n <div className=\"space-y-5\">\n <div>\n <label\n htmlFor=\"sso-entity-id\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.idpEntityId\", \"IdP Entity ID / Issuer URL\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <input\n id=\"sso-entity-id\"\n type=\"url\"\n aria-label={tr(\"auth.sso.idpEntityId\", \"IdP Entity ID\")}\n value={formData.entityId}\n onChange={(e) =>\n setFormData({ ...formData, entityId: e.target.value })\n }\n disabled={!isEditing && !!ssoConfig}\n placeholder={tr(\n \"auth.sso.idpEntityIdPlaceholder\",\n \"https://your-idp.example.com/saml/metadata\",\n )}\n required\n aria-required=\"true\"\n aria-describedby=\"sso-entity-id-hint\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg disabled:opacity-60 disabled:cursor-not-allowed transition-all\"\n />\n <p\n id=\"sso-entity-id-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.idpEntityIdHelper\",\n \"The unique identifier of your Identity Provider\",\n )}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"sso-sso-url\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.ssoUrl\", \"SSO URL (Login URL)\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <input\n id=\"sso-sso-url\"\n type=\"url\"\n aria-label={tr(\"auth.sso.ssoUrl\", \"SSO URL\")}\n value={formData.ssoUrl}\n onChange={(e) =>\n setFormData({ ...formData, ssoUrl: e.target.value })\n }\n disabled={!isEditing && !!ssoConfig}\n placeholder={tr(\n \"auth.sso.ssoUrlPlaceholder\",\n \"https://your-idp.example.com/saml/sso\",\n )}\n required\n aria-required=\"true\"\n aria-describedby=\"sso-sso-url-hint\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg disabled:opacity-60 disabled:cursor-not-allowed transition-all\"\n />\n <p\n id=\"sso-sso-url-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.ssoUrlHelper\",\n \"The URL where users are redirected to authenticate\",\n )}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"sso-slo-url\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.sloUrl\", \"Single Logout (SLO) URL\")}\n </label>\n <input\n id=\"sso-slo-url\"\n type=\"url\"\n aria-label={tr(\"auth.sso.sloUrl\", \"Single Logout (SLO) URL\")}\n value={formData.sloUrl}\n onChange={(e) =>\n setFormData({ ...formData, sloUrl: e.target.value })\n }\n disabled={!isEditing && !!ssoConfig}\n placeholder={tr(\n \"auth.sso.sloUrlPlaceholder\",\n \"https://your-idp.example.com/saml/slo\",\n )}\n aria-describedby=\"sso-slo-url-hint\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg disabled:opacity-60 disabled:cursor-not-allowed transition-all\"\n />\n <p\n id=\"sso-slo-url-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.sloUrlHelper\",\n \"Optional: URL for single logout across all applications\",\n )}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"sso-certificate\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.certificate\", \"X.509 Certificate\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <textarea\n id=\"sso-certificate\"\n aria-label={tr(\"auth.sso.certificate\", \"X.509 Certificate\")}\n value={formData.certificate}\n onChange={(e) =>\n setFormData({ ...formData, certificate: e.target.value })\n }\n disabled={!isEditing && !!ssoConfig}\n placeholder={tr(\n \"auth.sso.certificatePlaceholder\",\n `-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAX...\n-----END CERTIFICATE-----`,\n )}\n required\n aria-required=\"true\"\n aria-describedby=\"sso-certificate-hint\"\n rows={6}\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg disabled:opacity-60 disabled:cursor-not-allowed transition-all font-mono text-sm\"\n />\n <p\n id=\"sso-certificate-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.certificateHelper\",\n \"The public X.509 certificate from your IdP for signature verification\",\n )}\n </p>\n </div>\n </div>\n\n {(isEditing || !ssoConfig) && (\n <div className=\"mt-6 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isSaving}\n className=\"auth-primary-button flex items-center gap-2 px-6 py-3 font-medium\"\n >\n {isSaving ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.sso.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.sso.saveConfig\", \"Save Configuration\")\n )}\n </button>\n {isEditing && (\n <button\n type=\"button\"\n onClick={() => {\n setIsEditing(false);\n if (ssoConfig) {\n setFormData({\n entityId: ssoConfig.entityId || \"\",\n ssoUrl: ssoConfig.ssoUrl || \"\",\n sloUrl: ssoConfig.sloUrl || \"\",\n certificate: ssoConfig.certificate || \"\",\n enabled: ssoConfig.enabled || false,\n });\n }\n }}\n className=\"px-6 py-3 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.sso.cancel\", \"Cancel\")}\n </button>\n )}\n </div>\n )}\n </form>\n\n {/* Danger Zone */}\n {ssoConfig && (\n <div className=\"p-6 rounded-xl border border-status-error-border bg-status-error-bg-subtle\">\n <h3 className=\"text-lg font-medium text-status-error-text mb-2\">\n {tr(\"auth.sso.dangerZone\", \"Danger Zone\")}\n </h3>\n <p className=\"text-sm text-status-error-text mb-4\">\n {tr(\n \"auth.sso.dangerZoneDesc\",\n \"Deleting the SSO configuration will prevent users from signing in with their enterprise credentials. This action cannot be undone.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => setShowDeleteDialog(true)}\n disabled={isDeleting}\n className=\"px-4 py-2 rounded-lg bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover disabled:opacity-50 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.sso.deleting\", \"Deleting...\")}\n </>\n ) : (\n tr(\"auth.sso.deleteConfig\", \"Delete SSO Configuration\")\n )}\n </button>\n </div>\n )}\n </div>\n )}\n\n {/* OIDC Provider Tab */}\n {!isLoading && activeTab === \"oidc\" && (\n <div className=\"space-y-6\">\n <div className=\"p-4 rounded-xl bg-accent-purple-subtle border border-accent-purple-subtle\">\n <h3 className=\"font-medium text-accent-purple mb-2\">\n {tr(\"auth.sso.oidcProviderInfo\", \"OIDC Provider Information\")}\n </h3>\n <p className=\"text-sm text-accent-purple\">\n {tr(\n \"auth.sso.oidcProviderInfoDesc\",\n \"Your auth service acts as an OpenID Connect Provider. This allows other applications to authenticate users through your system using standard OAuth 2.0/OIDC flows.\",\n )}\n </p>\n </div>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.discoveryEndpoint\", \"Discovery Endpoint\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.sso.discoveryEndpointDesc\",\n \"Applications can auto-configure by fetching the OpenID Connect Discovery document:\",\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-sunken text-sm text-text-primary font-mono break-all\">\n {oidcConfig?.issuer\n ? `${oidcConfig.issuer}/.well-known/openid-configuration`\n : \"Discovery endpoint unavailable until OIDC is configured\"}\n </code>\n <button\n type=\"button\"\n onClick={() => {\n const url = oidcConfig?.issuer\n ? `${oidcConfig.issuer}/.well-known/openid-configuration`\n : \"\";\n if (!url) {\n return;\n }\n void copyTextWithNative(url);\n }}\n disabled={!oidcConfig?.issuer}\n className=\"px-3 py-2 rounded-lg border border-border-default text-text-secondary hover:bg-bg-sunken flex-shrink-0\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n\n {oidcConfig && (\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.oidcEndpoints\", \"OIDC Endpoints\")}\n </h3>\n <div className=\"space-y-4\">\n {[\n {\n label: tr(\"auth.sso.issuer\", \"Issuer\"),\n value: oidcConfig.issuer,\n },\n {\n label: tr(\n \"auth.sso.authorizationEndpoint\",\n \"Authorization Endpoint\",\n ),\n value: oidcConfig.authorizationEndpoint,\n },\n {\n label: tr(\"auth.sso.tokenEndpoint\", \"Token Endpoint\"),\n value: oidcConfig.tokenEndpoint,\n },\n {\n label: tr(\"auth.sso.userinfoEndpoint\", \"UserInfo Endpoint\"),\n value: oidcConfig.userinfoEndpoint,\n },\n {\n label: tr(\"auth.sso.jwksUri\", \"JWKS URI\"),\n value: oidcConfig.jwksUri,\n },\n ].map((item) => (\n <div key={item.label} className=\"flex items-start gap-4\">\n <span className=\"w-48 text-sm font-medium text-text-primary flex-shrink-0\">\n {item.label}\n </span>\n <code className=\"text-sm text-text-secondary font-mono break-all\">\n {item.value}\n </code>\n </div>\n ))}\n </div>\n </div>\n )}\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.supportedFeatures\", \"Supported Features\")}\n </h3>\n <div className=\"grid grid-cols-2 gap-4\">\n {[\n {\n name: tr(\n \"auth.sso.featAuthCodePkce\",\n \"Authorization Code + PKCE\",\n ),\n status: true,\n },\n {\n name: tr(\"auth.sso.featClientCreds\", \"Client Credentials\"),\n status: true,\n },\n {\n name: tr(\"auth.sso.featDeviceAuth\", \"Device Authorization\"),\n status: true,\n },\n {\n name: tr(\"auth.sso.featRefreshTokens\", \"Refresh Tokens\"),\n status: true,\n },\n {\n name: tr(\n \"auth.sso.featTokenIntrospection\",\n \"Token Introspection\",\n ),\n status: true,\n },\n {\n name: tr(\"auth.sso.featTokenRevocation\", \"Token Revocation\"),\n status: true,\n },\n ].map((feature) => (\n <div key={feature.name} className=\"flex items-center gap-2\">\n <span\n className={`size-2 rounded-full ${feature.status ? \"bg-status-success-text\" : \"bg-bg-sunken\"}`}\n />\n <span className=\"text-sm text-text-primary\">\n {feature.name}\n </span>\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* OIDC Clients Tab */}\n {!isLoading && activeTab === \"clients\" && (\n <div className=\"space-y-6\">\n <div className=\"p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <h3 className=\"font-medium text-status-warning-text mb-2\">\n {tr(\"auth.sso.clientManagement\", \"OIDC Client Management\")}\n </h3>\n <p className=\"text-sm text-status-warning-text\">\n {tr(\n \"auth.sso.clientManagementDesc\",\n \"Register applications that can authenticate users through your OIDC provider. Each client gets unique credentials to use in the OAuth 2.0 / OIDC flow.\",\n )}\n </p>\n </div>\n\n {/* Newly Created Client Display */}\n {createdClient && (\n <div className=\"p-6 rounded-xl border-2 border-status-success-border bg-status-success-bg-subtle\">\n <div className=\"flex items-start justify-between mb-4\">\n <div>\n <h3 className=\"text-lg font-medium text-status-success-text\">\n {tr(\n \"auth.sso.clientCreatedTitle\",\n \"Client Created Successfully!\",\n )}\n </h3>\n <p className=\"text-sm text-status-success-text mt-1\">\n {tr(\n \"auth.sso.clientCreatedDesc\",\n \"Save these credentials securely. The client secret will not be shown again.\",\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setCreatedClient(null)}\n aria-label={tr(\n \"auth.sso.dismissClientCredentials\",\n \"Dismiss\",\n )}\n className=\"text-status-success-text hover:text-status-success-text\"\n >\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M6 18L18 6M6 6l12 12\"\n />\n </svg>\n </button>\n </div>\n <div className=\"space-y-3\">\n <div>\n <label className=\"block text-xs font-medium text-status-success-text mb-1\">\n {tr(\"auth.sso.clientId\", \"Client ID\")}\n </label>\n <div className=\"flex gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-surface text-sm font-mono border border-status-success-border\">\n {createdClient.client_id}\n </code>\n <button\n type=\"button\"\n onClick={() =>\n void copyTextWithNative(createdClient.client_id)\n }\n className=\"px-3 py-2 rounded-lg bg-status-success-bg text-white text-sm hover:bg-status-success-bg\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n {createdClient.client_secret && (\n <div>\n <label className=\"block text-xs font-medium text-status-success-text mb-1\">\n {tr(\"auth.sso.clientSecret\", \"Client Secret (save now!)\")}\n </label>\n <div className=\"flex gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-surface text-sm font-mono border border-status-success-border\">\n {createdClient.client_secret}\n </code>\n <button\n type=\"button\"\n onClick={() =>\n void copyTextWithNative(\n createdClient.client_secret || \"\",\n )\n }\n className=\"px-3 py-2 rounded-lg bg-status-success-bg text-white text-sm hover:bg-status-success-bg\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n )}\n <div>\n <label className=\"block text-xs font-medium text-status-success-text mb-1\">\n {tr(\"auth.sso.redirectUris\", \"Redirect URIs\")}\n </label>\n <code className=\"block px-3 py-2 rounded-lg bg-bg-surface text-sm font-mono border border-status-success-border\">\n {createdClient.redirect_uris.join(\", \")}\n </code>\n </div>\n </div>\n </div>\n )}\n\n {/* Register New Client */}\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <div className=\"flex items-center justify-between mb-6\">\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr(\"auth.sso.registerClient\", \"Register New Client\")}\n </h3>\n {!showClientForm && (\n <button\n type=\"button\"\n onClick={() => setShowClientForm(true)}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-white font-medium hover:bg-action-primary-bg-hover transition-colors flex items-center gap-2\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n {tr(\"auth.sso.newClient\", \"New Client\")}\n </button>\n )}\n </div>\n\n {showClientForm ? (\n <div className=\"space-y-5\">\n <div>\n <label\n htmlFor=\"oidc-client-name\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.clientName\", \"Client Name\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <input\n id=\"oidc-client-name\"\n type=\"text\"\n aria-label={tr(\"auth.sso.oidcClientName\", \"Client Name\")}\n value={newClient.client_name}\n onChange={(e) =>\n setNewClient({\n ...newClient,\n client_name: e.target.value,\n })\n }\n placeholder={tr(\n \"auth.sso.clientNamePlaceholder\",\n \"My Application\",\n )}\n aria-required=\"true\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"oidc-redirect-uris\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.redirectUris\", \"Redirect URIs\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <input\n id=\"oidc-redirect-uris\"\n type=\"text\"\n aria-label={tr(\n \"auth.sso.oidcRedirectUris\",\n \"Redirect URIs\",\n )}\n value={newClient.redirect_uris}\n onChange={(e) =>\n setNewClient({\n ...newClient,\n redirect_uris: e.target.value,\n })\n }\n placeholder={tr(\n \"auth.sso.redirectUrisPlaceholder\",\n \"http://localhost:3000/callback, https://app.example.com/callback\",\n )}\n aria-required=\"true\"\n aria-describedby=\"oidc-redirect-uris-hint\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n />\n <p\n id=\"oidc-redirect-uris-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.redirectUrisHelper\",\n \"Separate multiple URIs with commas\",\n )}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {tr(\"auth.sso.grantTypes\", \"Grant Types\")}\n </label>\n <div className=\"flex flex-wrap gap-3\">\n {[\n \"authorization_code\",\n \"refresh_token\",\n \"client_credentials\",\n ].map((grant) => (\n <label\n key={grant}\n className=\"flex items-center gap-2 cursor-pointer\"\n >\n <input\n type=\"checkbox\"\n aria-label={grant}\n checked={newClient.grant_types.includes(grant)}\n onChange={(e) => {\n if (e.target.checked) {\n setNewClient({\n ...newClient,\n grant_types: [...newClient.grant_types, grant],\n });\n } else {\n setNewClient({\n ...newClient,\n grant_types: newClient.grant_types.filter(\n (g) => g !== grant,\n ),\n });\n }\n }}\n className=\"size-4 rounded border-border-default text-text-link focus:ring-action-primary-bg\"\n />\n <span className=\"text-sm text-text-primary\">\n {grant.replace(/_/g, \" \")}\n </span>\n </label>\n ))}\n </div>\n </div>\n\n <div>\n <label\n htmlFor=\"oidc-token-auth-method\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\n \"auth.sso.tokenEndpointAuth\",\n \"Token Endpoint Auth Method\",\n )}\n </label>\n <select\n id=\"oidc-token-auth-method\"\n value={newClient.token_endpoint_auth_method}\n onChange={(e) =>\n setNewClient({\n ...newClient,\n token_endpoint_auth_method: e.target.value,\n })\n }\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n >\n <option value=\"none\">\n {tr(\n \"auth.sso.tokenAuthNone\",\n \"None (Public Client - SPAs, Mobile Apps)\",\n )}\n </option>\n <option value=\"client_secret_basic\">\n {tr(\"auth.sso.tokenAuthBasic\", \"Client Secret Basic\")}\n </option>\n <option value=\"client_secret_post\">\n {tr(\"auth.sso.tokenAuthPost\", \"Client Secret Post\")}\n </option>\n </select>\n </div>\n\n <div className=\"flex items-center gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={handleRegisterClient}\n disabled={isCreatingClient}\n className=\"auth-primary-button flex items-center gap-2 px-6 py-3 font-medium\"\n >\n {isCreatingClient ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.sso.registering\", \"Registering...\")}\n </>\n ) : (\n tr(\"auth.sso.registerClientButton\", \"Register Client\")\n )}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setShowClientForm(false);\n setNewClient({\n client_name: \"\",\n redirect_uris: \"\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n token_endpoint_auth_method: \"none\",\n });\n }}\n className=\"px-6 py-3 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.sso.cancel\", \"Cancel\")}\n </button>\n </div>\n </div>\n ) : (\n <div className=\"text-center py-8 text-text-secondary\">\n <svg\n className=\"size-12 mx-auto mb-3 text-text-muted\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\"\n />\n </svg>\n <p className=\"text-sm\">\n {tr(\n \"auth.sso.emptyClients\",\n 'Click \"New Client\" to register an OAuth 2.0 / OIDC client application.',\n )}\n </p>\n </div>\n )}\n </div>\n\n {/* Default Clients Info */}\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.preconfiguredClients\", \"Pre-configured Clients\")}\n </h3>\n <div className=\"space-y-4\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50 border border-border-default\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"font-medium text-text-primary\">\n tenant-frontend\n </span>\n <span className=\"px-2 py-1 rounded-full text-xs bg-status-success-bg-subtle text-status-success-text\">\n {tr(\"auth.sso.active\", \"Active\")}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.sso.preconfigClientDesc\",\n \"Default frontend client for tenant authentication\",\n )}\n </p>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n <span className=\"px-2 py-0.5 rounded bg-bg-sunken text-xs text-text-primary\">\n authorization_code\n </span>\n <span className=\"px-2 py-0.5 rounded bg-bg-sunken text-xs text-text-primary\">\n refresh_token\n </span>\n <span className=\"px-2 py-0.5 rounded bg-bg-sunken text-xs text-text-primary\">\n {tr(\"auth.sso.pkceRequired\", \"PKCE required\")}\n </span>\n </div>\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* Test SSO Tab */}\n {!isLoading && activeTab === \"test\" && (\n <div className=\"space-y-6\">\n <div className=\"p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <h3 className=\"font-medium text-status-warning-text mb-2\">\n {tr(\"auth.sso.testLoginTitle\", \"Test SSO Login\")}\n </h3>\n <p className=\"text-sm text-status-warning-text\">\n {tr(\n \"auth.sso.testLoginDesc\",\n \"Use this section to test your SSO configuration before rolling it out to users. Make sure your IdP is properly configured with the SP metadata above.\",\n )}\n </p>\n </div>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.spInitiatedTest\", \"SP-Initiated Login Test\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.sso.spInitiatedTestDesc\",\n \"Click the button below to initiate an SSO login flow. You will be redirected to your IdP to authenticate.\",\n )}\n </p>\n\n {ssoConfig && ssoConfig.enabled ? (\n <a\n href={`${window.location.origin}/auth/sso/login`}\n className=\"inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-status-success-bg text-white font-medium shadow-sm hover:opacity-90 transition-all duration-200\"\n >\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1\"\n />\n </svg>\n {tr(\"auth.sso.testSsoLoginButton\", \"Test SSO Login\")}\n </a>\n ) : (\n <div className=\"p-4 rounded-xl bg-bg-sunken/50 text-text-secondary text-sm\">\n {!ssoConfig\n ? tr(\n \"auth.sso.notConfigured\",\n \"SSO is not configured. Please configure your IdP settings first.\",\n )\n : tr(\n \"auth.sso.disabledTest\",\n \"SSO is disabled. Enable it to test the login flow.\",\n )}\n </div>\n )}\n </div>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.testResults\", \"Test Results\")}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.sso.testResultsDesc\",\n \"After testing, results will appear here. Check the browser console for detailed logs.\",\n )}\n </p>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SsoConfigPage.js","names":[],"sources":["../../../src/auth/pages/SsoConfigPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useNavigate, useSearchParams } from \"react-router-dom\";\nimport { useAuthPermissions } from \"../hooks/useAuthPermissions\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useFeatureFlagDetails } from \"@burdenoff/fe-libs/shared/feature-flags\";\n// TODO(ux-sweep): wrap \"Advanced SAML / OIDC overrides\" (signing/encryption/\n// nameID-format/attribute-mapping) fields in <OptionalFields> once the\n// SsoConfigPage tabbed form is refactored away from raw tabs. The page is\n// 1700+ lines with no obvious mid-form advanced section to wrap today.\nimport { OptionalFields } from \"@burdenoff/fe-libs/ui\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport {\n useSsoOperations,\n type SSOConfigInput,\n} from \"../hooks/useSsoOperations\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport {\n nativeCopyText,\n nativeImpact,\n nativeNotify,\n} from \"../../utils/nativeBridge\";\n\nconst copyTextWithNative = async (text: string): Promise<void> => {\n void nativeImpact(\"light\");\n const ok = await nativeCopyText(text);\n void nativeNotify(ok ? \"success\" : \"error\");\n};\n\n// =============================================================================\n// SAML Metadata Parser\n// =============================================================================\n\ninterface ParsedSAMLMetadata {\n entityId: string;\n ssoUrl: string;\n sloUrl?: string;\n certificate: string;\n}\n\nfunction parseSAMLMetadata(xmlString: string): ParsedSAMLMetadata | null {\n try {\n const parser = new DOMParser();\n const doc = parser.parseFromString(xmlString, \"text/xml\");\n\n // Check for parse errors\n const parseError = doc.querySelector(\"parsererror\");\n if (parseError) {\n console.error(\"XML parse error:\", parseError.textContent);\n return null;\n }\n\n // Extract EntityID\n const entityDescriptor = doc.querySelector(\"EntityDescriptor\");\n const entityId = entityDescriptor?.getAttribute(\"entityID\") || \"\";\n\n // Find IDPSSODescriptor\n const idpDescriptor = doc.querySelector(\"IDPSSODescriptor\");\n if (!idpDescriptor) {\n console.error(\"No IDPSSODescriptor found\");\n return null;\n }\n\n // Extract SSO URL (HTTP-Redirect or HTTP-POST binding)\n const ssoServices = idpDescriptor.querySelectorAll(\"SingleSignOnService\");\n let ssoUrl = \"\";\n for (const service of ssoServices) {\n const binding = service.getAttribute(\"Binding\") || \"\";\n if (binding.includes(\"HTTP-Redirect\") || binding.includes(\"HTTP-POST\")) {\n ssoUrl = service.getAttribute(\"Location\") || \"\";\n break;\n }\n }\n\n // Extract SLO URL\n const sloServices = idpDescriptor.querySelectorAll(\"SingleLogoutService\");\n let sloUrl = \"\";\n for (const service of sloServices) {\n const binding = service.getAttribute(\"Binding\") || \"\";\n if (binding.includes(\"HTTP-Redirect\") || binding.includes(\"HTTP-POST\")) {\n sloUrl = service.getAttribute(\"Location\") || \"\";\n break;\n }\n }\n\n // Extract Certificate\n const certElement = doc.querySelector(\"X509Certificate\");\n let certificate = certElement?.textContent?.trim() || \"\";\n if (certificate && !certificate.includes(\"-----BEGIN\")) {\n certificate = `-----BEGIN CERTIFICATE-----\\n${certificate}\\n-----END CERTIFICATE-----`;\n }\n\n if (!entityId || !ssoUrl || !certificate) {\n console.error(\"Missing required SAML metadata fields\");\n return null;\n }\n\n return { entityId, ssoUrl, sloUrl, certificate };\n } catch (error) {\n console.error(\"Failed to parse SAML metadata:\", error);\n return null;\n }\n}\n\n// =============================================================================\n// OIDC Client Types\n// =============================================================================\n\ninterface OIDCClient {\n client_id: string;\n client_secret?: string;\n redirect_uris: string[];\n grant_types: string[];\n response_types: string[];\n token_endpoint_auth_method: string;\n client_name?: string;\n registration_access_token?: string;\n}\n\ninterface SsoConfigPageProps {\n /** When true, skips the enterprise-plan gate so admins always see the full UI */\n bypassEnterprisePlanGate?: boolean;\n}\n\n/**\n * SSO Configuration page for managing SAML/OIDC enterprise SSO.\n * Admin only - allows configuring Identity Provider settings.\n * In admin-app context, bypassEnterprisePlanGate=true and tenant/org/workspace\n * scoping is read from URL search params (?tenant=&org=&workspace=).\n */\nexport function SsoConfigPage({\n bypassEnterprisePlanGate = false,\n}: SsoConfigPageProps) {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { canManageSsoConfig, isLoading: permissionsLoading } =\n useAuthPermissions();\n // Enterprise SSO gate: only visible to workspaces on an enterprise plan.\n // When the flag is absent the section is hidden (default-deny).\n // bypassEnterprisePlanGate=true skips this check entirely (admin-app usage).\n const enterpriseSsoFlag = useFeatureFlagDetails(\n \"workspace.auth.enterprise-sso\",\n );\n const isEnterpriseSsoEnabled =\n bypassEnterprisePlanGate || enterpriseSsoFlag?.value === true;\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n // In admin context, read scope from URL params; otherwise use platform default\n const urlTenantId = searchParams.get(\"tenant\");\n const urlOrgId = searchParams.get(\"org\");\n const urlWorkspaceId = searchParams.get(\"workspace\");\n const effectiveTenantId = urlTenantId ?? \"burdenoff.com\";\n\n const {\n ssoConfig,\n oidcConfig,\n isLoading,\n isSaving,\n isDeleting,\n saveSsoConfig,\n deleteSsoConfig,\n } = useSsoOperations(effectiveTenantId);\n\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t],\n );\n\n const [activeTab, setActiveTab] = useState<\n \"saml\" | \"oidc\" | \"clients\" | \"test\"\n >(\"saml\");\n const [isEditing, setIsEditing] = useState(false);\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n\n // SAML Metadata import state\n const [metadataUrl, setMetadataUrl] = useState(\"\");\n const [isImporting, setIsImporting] = useState(false);\n\n // OIDC Client registration state\n const [showClientForm, setShowClientForm] = useState(false);\n const [newClient, setNewClient] = useState({\n client_name: \"\",\n redirect_uris: \"\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n token_endpoint_auth_method: \"none\",\n });\n const [isCreatingClient, setIsCreatingClient] = useState(false);\n const [createdClient, setCreatedClient] = useState<OIDCClient | null>(null);\n\n // Form state for SAML configuration\n const [formData, setFormData] = useState<SSOConfigInput>({\n entityId: \"\",\n ssoUrl: \"\",\n sloUrl: \"\",\n certificate: \"\",\n enabled: false,\n });\n\n // Handle SAML metadata file upload\n const handleMetadataFileUpload = async (\n event: React.ChangeEvent<HTMLInputElement>,\n ) => {\n const file = event.target.files?.[0];\n if (!file) return;\n\n setError(null);\n setIsImporting(true);\n\n try {\n const text = await file.text();\n const parsed = parseSAMLMetadata(text);\n\n if (parsed) {\n setFormData({\n ...formData,\n entityId: parsed.entityId,\n ssoUrl: parsed.ssoUrl,\n sloUrl: parsed.sloUrl || \"\",\n certificate: parsed.certificate,\n });\n setSuccess(\n tr(\n \"auth.sso.importSuccess\",\n \"SAML metadata imported successfully! Review and save the configuration.\",\n ),\n );\n setIsEditing(true);\n } else {\n setError(\n tr(\n \"auth.sso.parseError\",\n \"Failed to parse SAML metadata. Make sure the file contains valid IdP metadata XML.\",\n ),\n );\n }\n } catch {\n setError(tr(\"auth.sso.readError\", \"Failed to read metadata file\"));\n } finally {\n setIsImporting(false);\n if (fileInputRef.current) {\n fileInputRef.current.value = \"\";\n }\n }\n };\n\n // Handle SAML metadata URL fetch\n const handleMetadataUrlFetch = async () => {\n if (!metadataUrl) {\n setError(tr(\"auth.sso.enterUrl\", \"Please enter a metadata URL\"));\n return;\n }\n\n setError(null);\n setIsImporting(true);\n\n try {\n const response = await fetch(metadataUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch: ${response.statusText}`);\n }\n\n const text = await response.text();\n const parsed = parseSAMLMetadata(text);\n\n if (parsed) {\n setFormData({\n ...formData,\n entityId: parsed.entityId,\n ssoUrl: parsed.ssoUrl,\n sloUrl: parsed.sloUrl || \"\",\n certificate: parsed.certificate,\n });\n setSuccess(\n tr(\n \"auth.sso.importSuccessFromUrl\",\n \"SAML metadata imported successfully! Review and save the configuration.\",\n ),\n );\n setIsEditing(true);\n setMetadataUrl(\"\");\n } else {\n setError(\n tr(\n \"auth.sso.parseUrlError\",\n \"Failed to parse SAML metadata from URL.\",\n ),\n );\n }\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.fetchError\", \"Failed to fetch metadata from URL\"),\n );\n } finally {\n setIsImporting(false);\n }\n };\n\n // Register new OIDC client\n const handleRegisterClient = async () => {\n if (!newClient.client_name || !newClient.redirect_uris) {\n setError(\n tr(\n \"auth.sso.clientRequired\",\n \"Client name and redirect URI are required\",\n ),\n );\n return;\n }\n\n setIsCreatingClient(true);\n setError(null);\n\n try {\n const registrationEndpoint = oidcConfig?.registrationEndpoint;\n\n if (!registrationEndpoint) {\n throw new Error(\n tr(\n \"auth.sso.oidcNotConfigured\",\n \"OIDC dynamic client registration is not configured for this environment.\",\n ),\n );\n }\n\n const response = await fetch(registrationEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n client_name: newClient.client_name,\n redirect_uris: newClient.redirect_uris\n .split(\",\")\n .map((uri) => uri.trim()),\n grant_types: newClient.grant_types,\n response_types: [\"code\"],\n token_endpoint_auth_method: newClient.token_endpoint_auth_method,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json();\n throw new Error(\n errorData.error_description ||\n errorData.error ||\n \"Registration failed\",\n );\n }\n\n const client = await response.json();\n setCreatedClient(client);\n setSuccess(\n tr(\n \"auth.sso.clientRegistered\",\n \"OIDC client registered successfully! Save the client credentials.\",\n ),\n );\n setShowClientForm(false);\n setNewClient({\n client_name: \"\",\n redirect_uris: \"\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n token_endpoint_auth_method: \"none\",\n });\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.registerFailed\", \"Failed to register client\"),\n );\n } finally {\n setIsCreatingClient(false);\n }\n };\n\n // Sync form with loaded config\n useEffect(() => {\n if (ssoConfig) {\n setFormData({\n entityId: ssoConfig.entityId || \"\",\n ssoUrl: ssoConfig.ssoUrl || \"\",\n sloUrl: ssoConfig.sloUrl || \"\",\n certificate: ssoConfig.certificate || \"\",\n enabled: ssoConfig.enabled || false,\n });\n }\n }, [ssoConfig]);\n\n const handleSave = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(null);\n\n try {\n await saveSsoConfig(formData);\n setSuccess(\n tr(\"auth.sso.saveSuccess\", \"SSO configuration saved successfully!\"),\n );\n setIsEditing(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.saveFailed\", \"Failed to save configuration\"),\n );\n }\n };\n\n const handleDelete = async () => {\n setError(null);\n setSuccess(null);\n void nativeImpact(\"medium\");\n\n try {\n await deleteSsoConfig();\n void nativeNotify(\"success\");\n setSuccess(\n tr(\"auth.sso.deleteSuccess\", \"SSO configuration deleted successfully.\"),\n );\n setShowDeleteDialog(false);\n setFormData({\n entityId: \"\",\n ssoUrl: \"\",\n sloUrl: \"\",\n certificate: \"\",\n enabled: false,\n });\n } catch (err) {\n void nativeNotify(\"error\");\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.deleteFailed\", \"Failed to delete configuration\"),\n );\n }\n };\n\n const handleToggleEnabled = async () => {\n setError(null);\n try {\n await saveSsoConfig({ ...formData, enabled: !formData.enabled });\n setSuccess(\n formData.enabled\n ? tr(\"auth.sso.ssoDisabled\", \"SSO disabled\")\n : tr(\"auth.sso.ssoEnabled\", \"SSO enabled\"),\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sso.statusUpdateFailed\", \"Failed to update status\"),\n );\n }\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"max-w-5xl mx-auto p-6\">\n <div className=\"text-center py-16\">\n <SpinnerIcon className=\"size-8 mx-auto text-text-secondary animate-spin\" />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.sso.loadingPermissions\", \"Loading permissions...\")}\n </p>\n </div>\n </div>\n );\n }\n\n if (!canManageSsoConfig) {\n return (\n <div className=\"max-w-5xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-error-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.sso.adminRequired\", \"Admin Access Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.sso.adminRequiredDesc\",\n \"You need administrator privileges to configure SSO settings.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n // Enterprise SSO is a plan-gated feature. Only show the full configuration\n // to workspaces on an enterprise plan (flag: workspace.auth.enterprise-sso).\n if (!isEnterpriseSsoEnabled) {\n return (\n <div className=\"max-w-5xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-warning-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.sso.enterprisePlanRequired\", \"Enterprise Plan Required\")}\n </h2>\n <p className=\"text-text-secondary mb-2\">\n {tr(\n \"auth.sso.enterprisePlanRequiredDesc\",\n \"Single Sign-On (SAML / OIDC) configuration is available on the Enterprise plan.\",\n )}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.sso.contactSales\",\n \"Contact sales to upgrade your workspace to the Enterprise plan.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-5xl mx-auto p-6\">\n <ConfirmActionDialog\n open={showDeleteDialog}\n onOpenChange={setShowDeleteDialog}\n title={tr(\"auth.sso.deleteConfirmTitle\", \"Delete SSO configuration?\")}\n description={tr(\n \"auth.sso.deleteConfirmDesc\",\n \"Users will immediately lose the ability to sign in with this SSO setup until a new configuration is saved.\",\n )}\n confirmText={tr(\"auth.sso.deleteConfirmButton\", \"Delete configuration\")}\n variant=\"danger\"\n isPending={isDeleting}\n onConfirm={handleDelete}\n />\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/security\")}\n aria-label={tr(\"auth.sso.backToSecurity\", \"Back to Security\")}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors\"\n >\n <svg\n className=\"size-5 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n </button>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {tr(\"auth.sso.title\", \"Single Sign-On (SSO)\")}\n </h1>\n <p className=\"text-text-secondary mt-1\">\n {tr(\n \"auth.sso.description\",\n \"Configure enterprise SSO with SAML 2.0 or OIDC\",\n )}\n </p>\n </div>\n </div>\n\n {ssoConfig && (\n <div className=\"flex items-center gap-3\">\n <span\n className={`px-3 py-1 rounded-full text-sm font-medium ${\n formData.enabled\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : \"bg-bg-sunken text-text-secondary\"\n }`}\n >\n {formData.enabled\n ? tr(\"auth.sso.enabled\", \"Enabled\")\n : tr(\"auth.sso.disabled\", \"Disabled\")}\n </span>\n <button\n type=\"button\"\n onClick={handleToggleEnabled}\n disabled={isSaving}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary hover:bg-bg-sunken transition-colors text-sm font-medium\"\n >\n {formData.enabled\n ? tr(\"auth.sso.disable\", \"Disable\")\n : tr(\"auth.sso.enable\", \"Enable\")}\n </button>\n </div>\n )}\n </div>\n\n {/* Admin context banner — shown when scoping SSO config to a specific tenant/org/workspace */}\n {bypassEnterprisePlanGate &&\n (urlTenantId ?? urlOrgId ?? urlWorkspaceId) && (\n <div className=\"mb-6 p-4 rounded-xl bg-status-info-bg-subtle border border-status-info-border\">\n <p className=\"text-sm font-medium text-status-info-text mb-1\">\n {tr(\"auth.sso.adminContext\", \"Configuring SSO for\")}\n </p>\n <div className=\"flex flex-wrap gap-4 text-xs text-status-info-text\">\n {urlTenantId && (\n <span>\n <span className=\"font-medium\">\n {tr(\"auth.sso.tenant\", \"Tenant:\")}\n </span>{\" \"}\n <code className=\"font-mono\">{urlTenantId}</code>\n </span>\n )}\n {urlOrgId && (\n <span>\n <span className=\"font-medium\">\n {tr(\"auth.sso.organization\", \"Organization:\")}\n </span>{\" \"}\n <code className=\"font-mono\">{urlOrgId}</code>\n </span>\n )}\n {urlWorkspaceId && (\n <span>\n <span className=\"font-medium\">\n {tr(\"auth.sso.workspace\", \"Workspace:\")}\n </span>{\" \"}\n <code className=\"font-mono\">{urlWorkspaceId}</code>\n </span>\n )}\n </div>\n </div>\n )}\n\n {/* Alerts */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\">\n <AlertIcon className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-status-error-text hover:underline\"\n >\n {tr(\"auth.sso.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {success && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\">\n <svg\n className=\"size-5 text-status-success-text flex-shrink-0 mt-0.5\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\n clipRule=\"evenodd\"\n />\n </svg>\n <div className=\"flex-1\">\n <p className=\"text-sm text-status-success-text\">{success}</p>\n <button\n type=\"button\"\n onClick={() => setSuccess(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline\"\n >\n {tr(\"auth.sso.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Tabs */}\n <div className=\"mb-6 border-b border-border-subtle\">\n <nav className=\"flex gap-6\">\n {[\n {\n id: \"saml\",\n label: tr(\"auth.sso.samlTab\", \"SAML 2.0\"),\n icon: \"🔐\",\n },\n {\n id: \"oidc\",\n label: tr(\"auth.sso.oidcTab\", \"OIDC Provider\"),\n icon: \"🌐\",\n },\n {\n id: \"clients\",\n label: tr(\"auth.sso.clientsTab\", \"OIDC Clients\"),\n icon: \"🔑\",\n },\n {\n id: \"test\",\n label: tr(\"auth.sso.testTab\", \"Test SSO\"),\n icon: \"🧪\",\n },\n ].map((tab) => (\n <button\n type=\"button\"\n key={tab.id}\n onClick={() => setActiveTab(tab.id as typeof activeTab)}\n className={`pb-3 text-sm font-medium transition-colors relative ${\n activeTab === tab.id\n ? \"text-text-link\"\n : \"text-text-secondary hover:text-text-primary\"\n }`}\n >\n <span className=\"flex items-center gap-2\">\n <span>{tab.icon}</span>\n {tab.label}\n </span>\n {activeTab === tab.id && (\n <span className=\"absolute bottom-0 left-0 right-0 h-0.5 bg-action-primary-bg\" />\n )}\n </button>\n ))}\n </nav>\n </div>\n\n {/* Loading */}\n {isLoading && (\n <div className=\"flex items-center justify-center py-12\">\n <SpinnerIcon className=\"size-8 text-text-link animate-spin\" />\n <span className=\"ml-3 text-text-secondary\">\n {tr(\"auth.sso.loadingConfig\", \"Loading configuration...\")}\n </span>\n </div>\n )}\n\n {/* SAML Configuration Tab */}\n {!isLoading && activeTab === \"saml\" && (\n <div className=\"space-y-6\">\n {/* Info Card */}\n <div className=\"p-4 rounded-xl bg-status-info-bg-subtle border border-status-info-border\">\n <h3 className=\"font-medium text-status-info-text mb-2\">\n {tr(\"auth.sso.samlConfigTitle\", \"SAML 2.0 Configuration\")}\n </h3>\n <p className=\"text-sm text-status-info-text\">\n {tr(\n \"auth.sso.samlConfigDesc\",\n \"Configure your Identity Provider (IdP) to enable enterprise SSO. You'll need to get the following information from your IdP (Okta, Azure AD, OneLogin, etc.):\",\n )}\n </p>\n <ul className=\"mt-2 text-sm text-status-info-text list-disc list-inside\">\n <li>{tr(\"auth.sso.entityIdItem\", \"Entity ID (Issuer URL)\")}</li>\n <li>{tr(\"auth.sso.ssoUrlItem\", \"SSO URL (Login URL)\")}</li>\n <li>{tr(\"auth.sso.certificateItem\", \"X.509 Certificate\")}</li>\n </ul>\n </div>\n\n {/* Metadata Import Card */}\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.importMetadata\", \"Import IdP Metadata\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.sso.importMetadataDesc\",\n \"Automatically configure SSO by importing your IdP's metadata XML file or URL.\",\n )}\n </p>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n {/* File Upload */}\n <div className=\"space-y-3\">\n <label className=\"block text-sm font-medium text-text-primary\">\n {tr(\"auth.sso.uploadFile\", \"Upload Metadata File\")}\n </label>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".xml,application/xml,text/xml\"\n onChange={handleMetadataFileUpload}\n className=\"hidden\"\n id=\"metadata-file\"\n aria-label={tr(\"auth.sso.uploadFile\", \"Upload Metadata File\")}\n />\n <label\n htmlFor=\"metadata-file\"\n className=\"flex items-center justify-center gap-2 w-full px-4 py-3 border-2 border-dashed border-border-default rounded-xl cursor-pointer hover:border-action-primary-bg transition-colors\"\n >\n {isImporting ? (\n <SpinnerIcon className=\"size-5 animate-spin text-text-link\" />\n ) : (\n <svg\n className=\"size-5 text-text-tertiary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12\"\n />\n </svg>\n )}\n <span className=\"text-sm text-text-secondary\">\n {isImporting\n ? tr(\"auth.sso.importing\", \"Importing...\")\n : tr(\"auth.sso.chooseFile\", \"Choose XML file\")}\n </span>\n </label>\n </div>\n\n {/* URL Fetch */}\n <div className=\"space-y-3\">\n <label className=\"block text-sm font-medium text-text-primary\">\n {tr(\"auth.sso.fetchFromUrl\", \"Fetch from URL\")}\n </label>\n <div className=\"flex gap-2\">\n <input\n id=\"sso-metadata-url\"\n type=\"url\"\n value={metadataUrl}\n onChange={(e) => setMetadataUrl(e.target.value)}\n placeholder=\"https://idp.example.com/metadata.xml\"\n aria-label={tr(\"auth.sso.fetchFromUrl\", \"Fetch from URL\")}\n className=\"flex-1 px-4 py-2.5 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all text-sm\"\n />\n <button\n type=\"button\"\n onClick={handleMetadataUrlFetch}\n disabled={isImporting || !metadataUrl}\n className=\"px-4 py-2.5 rounded-xl bg-action-primary-bg text-white font-medium hover:bg-action-primary-bg-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-2\"\n >\n {isImporting ? (\n <SpinnerIcon className=\"size-4 animate-spin\" />\n ) : (\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n )}\n {tr(\"auth.sso.fetch\", \"Fetch\")}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* SP Metadata Card */}\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.spMetadata\", \"Service Provider (SP) Metadata\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.sso.spMetadataDesc\",\n \"Provide these values to your Identity Provider when setting up the SAML integration:\",\n )}\n </p>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr(\"auth.sso.spEntityId\", \"SP Entity ID / Audience URI\")}\n </label>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-sunken text-sm text-text-primary font-mono\">\n {window.location.origin}/auth/saml/metadata\n </code>\n <button\n type=\"button\"\n onClick={() =>\n void copyTextWithNative(\n `${window.location.origin}/auth/saml/metadata`,\n )\n }\n className=\"px-3 py-2 rounded-lg border border-border-default text-text-secondary hover:bg-bg-sunken\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr(\n \"auth.sso.acsUrl\",\n \"Assertion Consumer Service (ACS) URL\",\n )}\n </label>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-sunken text-sm text-text-primary font-mono\">\n {window.location.origin}/auth/sso/callback\n </code>\n <button\n type=\"button\"\n onClick={() =>\n void copyTextWithNative(\n `${window.location.origin}/auth/sso/callback`,\n )\n }\n className=\"px-3 py-2 rounded-lg border border-border-default text-text-secondary hover:bg-bg-sunken\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* IdP Configuration Form */}\n <form\n onSubmit={handleSave}\n className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\"\n >\n <div className=\"flex items-center justify-between mb-6\">\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr(\"auth.sso.idpSettings\", \"Identity Provider (IdP) Settings\")}\n </h3>\n {!isEditing && ssoConfig && (\n <button\n type=\"button\"\n onClick={() => setIsEditing(true)}\n className=\"px-4 py-2 rounded-lg border border-status-info-border text-text-link hover:bg-status-info-bg-subtle text-sm font-medium transition-colors\"\n >\n {tr(\"auth.sso.editConfig\", \"Edit Configuration\")}\n </button>\n )}\n </div>\n\n <div className=\"space-y-5\">\n <div>\n <label\n htmlFor=\"sso-entity-id\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.idpEntityId\", \"IdP Entity ID / Issuer URL\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <input\n id=\"sso-entity-id\"\n type=\"url\"\n aria-label={tr(\"auth.sso.idpEntityId\", \"IdP Entity ID\")}\n value={formData.entityId}\n onChange={(e) =>\n setFormData({ ...formData, entityId: e.target.value })\n }\n disabled={!isEditing && !!ssoConfig}\n placeholder={tr(\n \"auth.sso.idpEntityIdPlaceholder\",\n \"https://your-idp.example.com/saml/metadata\",\n )}\n required\n aria-required=\"true\"\n aria-describedby=\"sso-entity-id-hint\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg disabled:opacity-60 disabled:cursor-not-allowed transition-all\"\n />\n <p\n id=\"sso-entity-id-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.idpEntityIdHelper\",\n \"The unique identifier of your Identity Provider\",\n )}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"sso-sso-url\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.ssoUrl\", \"SSO URL (Login URL)\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <input\n id=\"sso-sso-url\"\n type=\"url\"\n aria-label={tr(\"auth.sso.ssoUrl\", \"SSO URL\")}\n value={formData.ssoUrl}\n onChange={(e) =>\n setFormData({ ...formData, ssoUrl: e.target.value })\n }\n disabled={!isEditing && !!ssoConfig}\n placeholder={tr(\n \"auth.sso.ssoUrlPlaceholder\",\n \"https://your-idp.example.com/saml/sso\",\n )}\n required\n aria-required=\"true\"\n aria-describedby=\"sso-sso-url-hint\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg disabled:opacity-60 disabled:cursor-not-allowed transition-all\"\n />\n <p\n id=\"sso-sso-url-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.ssoUrlHelper\",\n \"The URL where users are redirected to authenticate\",\n )}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"sso-certificate\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.certificate\", \"X.509 Certificate\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <textarea\n id=\"sso-certificate\"\n aria-label={tr(\"auth.sso.certificate\", \"X.509 Certificate\")}\n value={formData.certificate}\n onChange={(e) =>\n setFormData({ ...formData, certificate: e.target.value })\n }\n disabled={!isEditing && !!ssoConfig}\n placeholder={tr(\n \"auth.sso.certificatePlaceholder\",\n `-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAX...\n-----END CERTIFICATE-----`,\n )}\n required\n aria-required=\"true\"\n aria-describedby=\"sso-certificate-hint\"\n rows={6}\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg disabled:opacity-60 disabled:cursor-not-allowed transition-all font-mono text-sm\"\n />\n <p\n id=\"sso-certificate-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.certificateHelper\",\n \"The public X.509 certificate from your IdP for signature verification\",\n )}\n </p>\n </div>\n </div>\n\n {(isEditing || !ssoConfig) && (\n <div className=\"mt-6 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isSaving}\n className=\"auth-primary-button flex items-center gap-2 px-6 py-3 font-medium\"\n >\n {isSaving ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.sso.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.sso.saveConfig\", \"Save Configuration\")\n )}\n </button>\n {isEditing && (\n <button\n type=\"button\"\n onClick={() => {\n setIsEditing(false);\n if (ssoConfig) {\n setFormData({\n entityId: ssoConfig.entityId || \"\",\n ssoUrl: ssoConfig.ssoUrl || \"\",\n sloUrl: ssoConfig.sloUrl || \"\",\n certificate: ssoConfig.certificate || \"\",\n enabled: ssoConfig.enabled || false,\n });\n }\n }}\n className=\"px-6 py-3 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.sso.cancel\", \"Cancel\")}\n </button>\n )}\n </div>\n )}\n </form>\n\n {/* Danger Zone */}\n {ssoConfig && (\n <div className=\"p-6 rounded-xl border border-status-error-border bg-status-error-bg-subtle\">\n <h3 className=\"text-lg font-medium text-status-error-text mb-2\">\n {tr(\"auth.sso.dangerZone\", \"Danger Zone\")}\n </h3>\n <p className=\"text-sm text-status-error-text mb-4\">\n {tr(\n \"auth.sso.dangerZoneDesc\",\n \"Deleting the SSO configuration will prevent users from signing in with their enterprise credentials. This action cannot be undone.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => setShowDeleteDialog(true)}\n disabled={isDeleting}\n className=\"px-4 py-2 rounded-lg bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover disabled:opacity-50 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.sso.deleting\", \"Deleting...\")}\n </>\n ) : (\n tr(\"auth.sso.deleteConfig\", \"Delete SSO Configuration\")\n )}\n </button>\n </div>\n )}\n </div>\n )}\n\n {/* OIDC Provider Tab */}\n {!isLoading && activeTab === \"oidc\" && (\n <div className=\"space-y-6\">\n <div className=\"p-4 rounded-xl bg-accent-purple-subtle border border-accent-purple-subtle\">\n <h3 className=\"font-medium text-accent-purple mb-2\">\n {tr(\"auth.sso.oidcProviderInfo\", \"OIDC Provider Information\")}\n </h3>\n <p className=\"text-sm text-accent-purple\">\n {tr(\n \"auth.sso.oidcProviderInfoDesc\",\n \"Your auth service acts as an OpenID Connect Provider. This allows other applications to authenticate users through your system using standard OAuth 2.0/OIDC flows.\",\n )}\n </p>\n </div>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.discoveryEndpoint\", \"Discovery Endpoint\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.sso.discoveryEndpointDesc\",\n \"Applications can auto-configure by fetching the OpenID Connect Discovery document:\",\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-sunken text-sm text-text-primary font-mono break-all\">\n {oidcConfig?.issuer\n ? `${oidcConfig.issuer}/.well-known/openid-configuration`\n : \"Discovery endpoint unavailable until OIDC is configured\"}\n </code>\n <button\n type=\"button\"\n onClick={() => {\n const url = oidcConfig?.issuer\n ? `${oidcConfig.issuer}/.well-known/openid-configuration`\n : \"\";\n if (!url) {\n return;\n }\n void copyTextWithNative(url);\n }}\n disabled={!oidcConfig?.issuer}\n className=\"px-3 py-2 rounded-lg border border-border-default text-text-secondary hover:bg-bg-sunken flex-shrink-0\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n\n {oidcConfig && (\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.oidcEndpoints\", \"OIDC Endpoints\")}\n </h3>\n <div className=\"space-y-4\">\n {[\n {\n label: tr(\"auth.sso.issuer\", \"Issuer\"),\n value: oidcConfig.issuer,\n },\n {\n label: tr(\n \"auth.sso.authorizationEndpoint\",\n \"Authorization Endpoint\",\n ),\n value: oidcConfig.authorizationEndpoint,\n },\n {\n label: tr(\"auth.sso.tokenEndpoint\", \"Token Endpoint\"),\n value: oidcConfig.tokenEndpoint,\n },\n {\n label: tr(\"auth.sso.userinfoEndpoint\", \"UserInfo Endpoint\"),\n value: oidcConfig.userinfoEndpoint,\n },\n {\n label: tr(\"auth.sso.jwksUri\", \"JWKS URI\"),\n value: oidcConfig.jwksUri,\n },\n ].map((item) => (\n <div key={item.label} className=\"flex items-start gap-4\">\n <span className=\"w-48 text-sm font-medium text-text-primary flex-shrink-0\">\n {item.label}\n </span>\n <code className=\"text-sm text-text-secondary font-mono break-all\">\n {item.value}\n </code>\n </div>\n ))}\n </div>\n </div>\n )}\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.supportedFeatures\", \"Supported Features\")}\n </h3>\n <div className=\"grid grid-cols-2 gap-4\">\n {[\n {\n name: tr(\n \"auth.sso.featAuthCodePkce\",\n \"Authorization Code + PKCE\",\n ),\n status: true,\n },\n {\n name: tr(\"auth.sso.featClientCreds\", \"Client Credentials\"),\n status: true,\n },\n {\n name: tr(\"auth.sso.featDeviceAuth\", \"Device Authorization\"),\n status: true,\n },\n {\n name: tr(\"auth.sso.featRefreshTokens\", \"Refresh Tokens\"),\n status: true,\n },\n {\n name: tr(\n \"auth.sso.featTokenIntrospection\",\n \"Token Introspection\",\n ),\n status: true,\n },\n {\n name: tr(\"auth.sso.featTokenRevocation\", \"Token Revocation\"),\n status: true,\n },\n ].map((feature) => (\n <div key={feature.name} className=\"flex items-center gap-2\">\n <span\n className={`size-2 rounded-full ${feature.status ? \"bg-status-success-text\" : \"bg-bg-sunken\"}`}\n />\n <span className=\"text-sm text-text-primary\">\n {feature.name}\n </span>\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* OIDC Clients Tab */}\n {!isLoading && activeTab === \"clients\" && (\n <div className=\"space-y-6\">\n <div className=\"p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <h3 className=\"font-medium text-status-warning-text mb-2\">\n {tr(\"auth.sso.clientManagement\", \"OIDC Client Management\")}\n </h3>\n <p className=\"text-sm text-status-warning-text\">\n {tr(\n \"auth.sso.clientManagementDesc\",\n \"Register applications that can authenticate users through your OIDC provider. Each client gets unique credentials to use in the OAuth 2.0 / OIDC flow.\",\n )}\n </p>\n </div>\n\n {/* Newly Created Client Display */}\n {createdClient && (\n <div className=\"p-6 rounded-xl border-2 border-status-success-border bg-status-success-bg-subtle\">\n <div className=\"flex items-start justify-between mb-4\">\n <div>\n <h3 className=\"text-lg font-medium text-status-success-text\">\n {tr(\n \"auth.sso.clientCreatedTitle\",\n \"Client Created Successfully!\",\n )}\n </h3>\n <p className=\"text-sm text-status-success-text mt-1\">\n {tr(\n \"auth.sso.clientCreatedDesc\",\n \"Save these credentials securely. The client secret will not be shown again.\",\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setCreatedClient(null)}\n aria-label={tr(\n \"auth.sso.dismissClientCredentials\",\n \"Dismiss\",\n )}\n className=\"text-status-success-text hover:text-status-success-text\"\n >\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M6 18L18 6M6 6l12 12\"\n />\n </svg>\n </button>\n </div>\n <div className=\"space-y-3\">\n <div>\n <label className=\"block text-xs font-medium text-status-success-text mb-1\">\n {tr(\"auth.sso.clientId\", \"Client ID\")}\n </label>\n <div className=\"flex gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-surface text-sm font-mono border border-status-success-border\">\n {createdClient.client_id}\n </code>\n <button\n type=\"button\"\n onClick={() =>\n void copyTextWithNative(createdClient.client_id)\n }\n className=\"px-3 py-2 rounded-lg bg-status-success-bg text-white text-sm hover:bg-status-success-bg\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n {createdClient.client_secret && (\n <div>\n <label className=\"block text-xs font-medium text-status-success-text mb-1\">\n {tr(\"auth.sso.clientSecret\", \"Client Secret (save now!)\")}\n </label>\n <div className=\"flex gap-2\">\n <code className=\"flex-1 px-3 py-2 rounded-lg bg-bg-surface text-sm font-mono border border-status-success-border\">\n {createdClient.client_secret}\n </code>\n <button\n type=\"button\"\n onClick={() =>\n void copyTextWithNative(\n createdClient.client_secret || \"\",\n )\n }\n className=\"px-3 py-2 rounded-lg bg-status-success-bg text-white text-sm hover:bg-status-success-bg\"\n >\n {tr(\"auth.sso.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n )}\n <div>\n <label className=\"block text-xs font-medium text-status-success-text mb-1\">\n {tr(\"auth.sso.redirectUris\", \"Redirect URIs\")}\n </label>\n <code className=\"block px-3 py-2 rounded-lg bg-bg-surface text-sm font-mono border border-status-success-border\">\n {createdClient.redirect_uris.join(\", \")}\n </code>\n </div>\n </div>\n </div>\n )}\n\n {/* Register New Client */}\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <div className=\"flex items-center justify-between mb-6\">\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr(\"auth.sso.registerClient\", \"Register New Client\")}\n </h3>\n {!showClientForm && (\n <button\n type=\"button\"\n onClick={() => setShowClientForm(true)}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-white font-medium hover:bg-action-primary-bg-hover transition-colors flex items-center gap-2\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n {tr(\"auth.sso.newClient\", \"New Client\")}\n </button>\n )}\n </div>\n\n {showClientForm ? (\n <div className=\"space-y-5\">\n <div>\n <label\n htmlFor=\"oidc-client-name\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.clientName\", \"Client Name\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <input\n id=\"oidc-client-name\"\n type=\"text\"\n aria-label={tr(\"auth.sso.oidcClientName\", \"Client Name\")}\n value={newClient.client_name}\n onChange={(e) =>\n setNewClient({\n ...newClient,\n client_name: e.target.value,\n })\n }\n placeholder={tr(\n \"auth.sso.clientNamePlaceholder\",\n \"My Application\",\n )}\n aria-required=\"true\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"oidc-redirect-uris\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.sso.redirectUris\", \"Redirect URIs\")}{\" \"}\n <span className=\"text-status-error-text\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <input\n id=\"oidc-redirect-uris\"\n type=\"text\"\n aria-label={tr(\n \"auth.sso.oidcRedirectUris\",\n \"Redirect URIs\",\n )}\n value={newClient.redirect_uris}\n onChange={(e) =>\n setNewClient({\n ...newClient,\n redirect_uris: e.target.value,\n })\n }\n placeholder={tr(\n \"auth.sso.redirectUrisPlaceholder\",\n \"http://localhost:3000/callback, https://app.example.com/callback\",\n )}\n aria-required=\"true\"\n aria-describedby=\"oidc-redirect-uris-hint\"\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n />\n <p\n id=\"oidc-redirect-uris-hint\"\n className=\"mt-1 text-xs text-text-secondary\"\n >\n {tr(\n \"auth.sso.redirectUrisHelper\",\n \"Separate multiple URIs with commas\",\n )}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {tr(\"auth.sso.grantTypes\", \"Grant Types\")}\n </label>\n <div className=\"flex flex-wrap gap-3\">\n {[\n \"authorization_code\",\n \"refresh_token\",\n \"client_credentials\",\n ].map((grant) => (\n <label\n key={grant}\n className=\"flex items-center gap-2 cursor-pointer\"\n >\n <input\n type=\"checkbox\"\n aria-label={grant}\n checked={newClient.grant_types.includes(grant)}\n onChange={(e) => {\n if (e.target.checked) {\n setNewClient({\n ...newClient,\n grant_types: [...newClient.grant_types, grant],\n });\n } else {\n setNewClient({\n ...newClient,\n grant_types: newClient.grant_types.filter(\n (g) => g !== grant,\n ),\n });\n }\n }}\n className=\"size-4 rounded border-border-default text-text-link focus:ring-action-primary-bg\"\n />\n <span className=\"text-sm text-text-primary\">\n {grant.replace(/_/g, \" \")}\n </span>\n </label>\n ))}\n </div>\n </div>\n\n <div>\n <label\n htmlFor=\"oidc-token-auth-method\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\n \"auth.sso.tokenEndpointAuth\",\n \"Token Endpoint Auth Method\",\n )}\n </label>\n <select\n id=\"oidc-token-auth-method\"\n value={newClient.token_endpoint_auth_method}\n onChange={(e) =>\n setNewClient({\n ...newClient,\n token_endpoint_auth_method: e.target.value,\n })\n }\n className=\"block w-full px-4 py-3 border border-border-default rounded-xl bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n >\n <option value=\"none\">\n {tr(\n \"auth.sso.tokenAuthNone\",\n \"None (Public Client - SPAs, Mobile Apps)\",\n )}\n </option>\n <option value=\"client_secret_basic\">\n {tr(\"auth.sso.tokenAuthBasic\", \"Client Secret Basic\")}\n </option>\n <option value=\"client_secret_post\">\n {tr(\"auth.sso.tokenAuthPost\", \"Client Secret Post\")}\n </option>\n </select>\n </div>\n\n <div className=\"flex items-center gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={handleRegisterClient}\n disabled={isCreatingClient}\n className=\"auth-primary-button flex items-center gap-2 px-6 py-3 font-medium\"\n >\n {isCreatingClient ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.sso.registering\", \"Registering...\")}\n </>\n ) : (\n tr(\"auth.sso.registerClientButton\", \"Register Client\")\n )}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setShowClientForm(false);\n setNewClient({\n client_name: \"\",\n redirect_uris: \"\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n token_endpoint_auth_method: \"none\",\n });\n }}\n className=\"px-6 py-3 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.sso.cancel\", \"Cancel\")}\n </button>\n </div>\n </div>\n ) : (\n <div className=\"text-center py-8 text-text-secondary\">\n <svg\n className=\"size-12 mx-auto mb-3 text-text-muted\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\"\n />\n </svg>\n <p className=\"text-sm\">\n {tr(\n \"auth.sso.emptyClients\",\n 'Click \"New Client\" to register an OAuth 2.0 / OIDC client application.',\n )}\n </p>\n </div>\n )}\n </div>\n\n {/* Default Clients Info */}\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.preconfiguredClients\", \"Pre-configured Clients\")}\n </h3>\n <div className=\"space-y-4\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50 border border-border-default\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"font-medium text-text-primary\">\n tenant-frontend\n </span>\n <span className=\"px-2 py-1 rounded-full text-xs bg-status-success-bg-subtle text-status-success-text\">\n {tr(\"auth.sso.active\", \"Active\")}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.sso.preconfigClientDesc\",\n \"Default frontend client for tenant authentication\",\n )}\n </p>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n <span className=\"px-2 py-0.5 rounded bg-bg-sunken text-xs text-text-primary\">\n authorization_code\n </span>\n <span className=\"px-2 py-0.5 rounded bg-bg-sunken text-xs text-text-primary\">\n refresh_token\n </span>\n <span className=\"px-2 py-0.5 rounded bg-bg-sunken text-xs text-text-primary\">\n {tr(\"auth.sso.pkceRequired\", \"PKCE required\")}\n </span>\n </div>\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* Test SSO Tab */}\n {!isLoading && activeTab === \"test\" && (\n <div className=\"space-y-6\">\n <div className=\"p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <h3 className=\"font-medium text-status-warning-text mb-2\">\n {tr(\"auth.sso.testLoginTitle\", \"Test SSO Login\")}\n </h3>\n <p className=\"text-sm text-status-warning-text\">\n {tr(\n \"auth.sso.testLoginDesc\",\n \"Use this section to test your SSO configuration before rolling it out to users. Make sure your IdP is properly configured with the SP metadata above.\",\n )}\n </p>\n </div>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.spInitiatedTest\", \"SP-Initiated Login Test\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.sso.spInitiatedTestDesc\",\n \"Click the button below to initiate an SSO login flow. You will be redirected to your IdP to authenticate.\",\n )}\n </p>\n\n {ssoConfig && ssoConfig.enabled ? (\n <a\n href={`${window.location.origin}/auth/sso/login`}\n className=\"inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-status-success-bg text-white font-medium shadow-sm hover:opacity-90 transition-all duration-200\"\n >\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1\"\n />\n </svg>\n {tr(\"auth.sso.testSsoLoginButton\", \"Test SSO Login\")}\n </a>\n ) : (\n <div className=\"p-4 rounded-xl bg-bg-sunken/50 text-text-secondary text-sm\">\n {!ssoConfig\n ? tr(\n \"auth.sso.notConfigured\",\n \"SSO is not configured. Please configure your IdP settings first.\",\n )\n : tr(\n \"auth.sso.disabledTest\",\n \"SSO is disabled. Enable it to test the login flow.\",\n )}\n </div>\n )}\n </div>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface\">\n <h3 className=\"text-lg font-medium text-text-primary mb-4\">\n {tr(\"auth.sso.testResults\", \"Test Results\")}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.sso.testResultsDesc\",\n \"After testing, results will appear here. Check the browser console for detailed logs.\",\n )}\n </p>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-auth",
3
- "version": "2026.528.2",
3
+ "version": "2026.528.3",
4
4
  "description": "Auth microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "imports": {
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@apollo/client": "^4.0.9",
47
- "@burdenoff/fe-libs": "2026.518.6",
47
+ "@burdenoff/fe-libs": "2026.528.2",
48
48
  "@types/qrcode.react": "^3.0.0",
49
49
  "clsx": "^2.1.1",
50
50
  "graphql": "^16.12.0",