@burdenoff/microfe-auth 2026.518.4 → 2026.519.2

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.
Files changed (46) hide show
  1. package/dist/auth/hooks/usePasswordOperations.d.ts.map +1 -1
  2. package/dist/auth/hooks/usePasswordOperations.js +11 -4
  3. package/dist/auth/hooks/usePasswordOperations.js.map +1 -1
  4. package/dist/auth/hooks/useSessionOperations.d.ts +12 -1
  5. package/dist/auth/hooks/useSessionOperations.d.ts.map +1 -1
  6. package/dist/auth/hooks/useSessionOperations.js +62 -46
  7. package/dist/auth/hooks/useSessionOperations.js.map +1 -1
  8. package/dist/auth/pages/LoginPage.d.ts.map +1 -1
  9. package/dist/auth/pages/LoginPage.js +2 -0
  10. package/dist/auth/pages/LoginPage.js.map +1 -1
  11. package/dist/auth/pages/MfaSetupPage.d.ts.map +1 -1
  12. package/dist/auth/pages/MfaSetupPage.js +9 -4
  13. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  14. package/dist/auth/pages/PasswordResetPage.d.ts.map +1 -1
  15. package/dist/auth/pages/PasswordResetPage.js +9 -1
  16. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  17. package/dist/auth/pages/RegisterPage.d.ts.map +1 -1
  18. package/dist/auth/pages/RegisterPage.js +8 -0
  19. package/dist/auth/pages/RegisterPage.js.map +1 -1
  20. package/dist/auth/pages/SSODepositPage.d.ts.map +1 -1
  21. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  22. package/dist/auth/pages/SecurityPage.d.ts.map +1 -1
  23. package/dist/auth/pages/SecurityPage.js +9 -0
  24. package/dist/auth/pages/SecurityPage.js.map +1 -1
  25. package/dist/auth/pages/SessionsPage.d.ts +2 -5
  26. package/dist/auth/pages/SessionsPage.d.ts.map +1 -1
  27. package/dist/auth/pages/SessionsPage.js +175 -152
  28. package/dist/auth/pages/SessionsPage.js.map +1 -1
  29. package/dist/auth/pages/SsoConfigPage.d.ts.map +1 -1
  30. package/dist/auth/pages/SsoConfigPage.js +39 -1
  31. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  32. package/dist/auth/pages/SsoLoginPage.d.ts.map +1 -1
  33. package/dist/auth/pages/SsoLoginPage.js +8 -5
  34. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  35. package/dist/auth/pages/UserDetailsPage.d.ts +1 -1
  36. package/dist/auth/pages/UserDetailsPage.d.ts.map +1 -1
  37. package/dist/auth/pages/UserDetailsPage.js +251 -174
  38. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  39. package/dist/generated/global-operations.d.ts +2 -1
  40. package/dist/generated/global-operations.d.ts.map +1 -1
  41. package/dist/generated/global-operations.js +55 -80
  42. package/dist/generated/global-operations.js.map +1 -1
  43. package/dist/microfe-auth.css +1 -1
  44. package/dist/translations/en.d.ts +60 -0
  45. package/dist/translations/en.d.ts.map +1 -1
  46. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"SessionsPage.js","names":[],"sources":["../../../src/auth/pages/SessionsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useSessionOperations } from \"../hooks\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\nimport {\n formatRelativeTime,\n parseDeviceName,\n parseBrowserName,\n} from \"../utils\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\n\n/**\n * Active sessions management page with professional UI\n *\n * Fragment: SessionCard_Session (graphql/operations/global/fragments/SessionCard.fragment.graphql)\n * Query: ActiveSessions (graphql/operations/global/queries/sessions.graphql)\n *\n * TODO: When activeSessions backend migrates to SessionConnection type,\n * update query to use: activeSessions { sessions: [...], totalCount, hasMore }\n */\nexport function SessionsPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const { activeSessions, revokeSession, revokeAllSessions, isLoading } =\n useSessionOperations({ disableTokenInfoQuery: true });\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingSessionId, setPendingSessionId] = useState<string | null>(null);\n const [showRevokeAllDialog, setShowRevokeAllDialog] = useState(false);\n\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation();\n const [exportError, setExportError] = useState<string | null>(null);\n\n const handleExport = async () => {\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: \"Sessions Export\",\n // vibecontrols_sessions is the closest session entity type in the export system\n // TODO: confirm correct entity type with export team when auth sessions are registered\n entityTypes: [\"vibecontrols_sessions\"],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n const handleRevokeSession = async (sessionId: string) => {\n try {\n await revokeSession(sessionId);\n setSuccessMessage(\"Session revoked successfully\");\n setPendingSessionId(null);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to revoke session\");\n }\n };\n\n const handleRevokeAll = async () => {\n try {\n await revokeAllSessions();\n setSuccessMessage(\"All other sessions have been revoked\");\n setShowRevokeAllDialog(false);\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to revoke all sessions\",\n );\n }\n };\n\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"w-16 h-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon\n className=\"w-8 h-8 text-status-warning-text\"\n aria-hidden=\"true\"\n />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.sessions.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.sessions.signInToView\",\n \"Please sign in to view your active sessions\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingSessionId !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPendingSessionId(null);\n }\n }}\n title=\"Revoke this session?\"\n description=\"The selected device will be signed out immediately and will need to authenticate again.\"\n confirmText=\"Revoke session\"\n variant=\"danger\"\n isPending={isLoading}\n onConfirm={async () => {\n if (pendingSessionId) {\n await handleRevokeSession(pendingSessionId);\n }\n }}\n />\n <ConfirmActionDialog\n open={showRevokeAllDialog}\n onOpenChange={setShowRevokeAllDialog}\n title=\"Revoke every other session?\"\n description=\"All devices except your current one will be signed out immediately.\"\n confirmText=\"Revoke all other sessions\"\n variant=\"warning\"\n isPending={isLoading}\n onConfirm={handleRevokeAll}\n />\n\n {/* Live regions for screen reader announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {successMessage}\n </div>\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n >\n {error}\n </div>\n\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <div>\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.sessions.title\", \"Active Sessions\")}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\"auth.sessions.sessionCount\", \"You have\")}{\" \"}\n {activeSessions.length}{\" \"}\n {tr(\n activeSessions.length !== 1\n ? \"auth.sessions.sessionsPlural\"\n : \"auth.sessions.sessionsSingular\",\n activeSessions.length !== 1\n ? \"active sessions\"\n : \"active session\",\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-3\">\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\n \"auth.sessions.exportAriaLabel\",\n \"Export sessions list\",\n )}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon\n className=\"w-4 h-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"auth.sessions.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"w-4 h-4\"\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=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.sessions.export\", \"Export\")}\n </>\n )}\n </button>\n {activeSessions.length > 1 && (\n <button\n type=\"button\"\n onClick={() => setShowRevokeAllDialog(true)}\n className=\"px-4 py-2.5 rounded-xl border border-status-error-border text-status-error-text font-medium hover:bg-status-error-bg-subtle transition-colors duration-200\"\n >\n {tr(\"auth.sessions.revokeAll\", \"Revoke All Sessions\")}\n </button>\n )}\n </div>\n </div>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"w-5 h-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {/* Success Message */}\n {successMessage && (\n <div\n role=\"status\"\n className=\"mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\"\n >\n <p className=\"text-status-success-text\">{successMessage}</p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline transition-colors duration-200\"\n >\n Dismiss\n </button>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"w-5 h-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\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 transition-colors duration-200\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {isLoading ? (\n <div\n aria-live=\"polite\"\n aria-busy=\"true\"\n className=\"flex items-center justify-center py-12\"\n >\n <SpinnerIcon\n className=\"w-8 h-8 text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <span className=\"ml-3 text-text-secondary\">\n {tr(\"auth.sessions.loading\", \"Loading sessions...\")}\n </span>\n </div>\n ) : activeSessions.length === 0 ? (\n <div\n className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\"\n role=\"status\"\n >\n <div className=\"w-16 h-16 mx-auto mb-4 rounded-full bg-bg-muted flex items-center justify-center\">\n <svg\n className=\"w-8 h-8 text-text-tertiary\"\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={1.5}\n d=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n <p className=\"text-text-secondary\">\n {tr(\"auth.sessions.noSessions\", \"No active sessions found\")}\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={isLoading}>\n {activeSessions.map((session) => (\n <div\n key={session.id}\n className=\"p-6 rounded-xl border border-border-default bg-bg-surface shadow-sm hover:shadow-md transition-shadow\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-start gap-4\">\n {/* Device Icon */}\n <div\n className=\"w-12 h-12 rounded-xl bg-bg-accent flex items-center justify-center flex-shrink-0\"\n aria-hidden=\"true\"\n >\n <svg\n className=\"w-6 h-6 text-text-link\"\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=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3 mb-1\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {session.isCurrent &&\n parseDeviceName(session.userAgent) === \"Unknown Device\"\n ? tr(\"auth.sessions.thisDevice\", \"This Device\")\n : parseDeviceName(session.userAgent)}\n </h3>\n {session.isCurrent && (\n <span\n role=\"status\"\n className=\"px-2.5 py-1 rounded-full bg-status-success-bg-subtle text-status-success-text text-xs font-medium\"\n >\n {tr(\n \"auth.sessions.currentSession\",\n \"Current Session\",\n )}\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mb-2\">\n {parseBrowserName(session.userAgent) ===\n \"Unknown Browser\" && session.isCurrent\n ? tr(\"auth.sessions.currentBrowser\", \"Current Browser\")\n : parseBrowserName(session.userAgent)}\n </p>\n <dl className=\"flex flex-wrap gap-4 text-sm text-text-secondary\">\n {session.ipAddress && session.ipAddress !== \"unknown\" && (\n <div className=\"flex items-center gap-1.5\">\n <svg\n className=\"w-4 h-4\"\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=\"M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9\"\n />\n </svg>\n <dt className=\"sr-only\">IP address</dt>\n <dd>{session.ipAddress}</dd>\n </div>\n )}\n {session.location && (\n <div className=\"flex items-center gap-1.5\">\n <svg\n className=\"w-4 h-4\"\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=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <dt className=\"sr-only\">Location</dt>\n <dd>{session.location}</dd>\n </div>\n )}\n <div className=\"flex items-center gap-1.5\">\n <svg\n className=\"w-4 h-4\"\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=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <dt className=\"sr-only\">Last active</dt>\n <dd>\n {tr(\"auth.sessions.lastActive\", \"Last active\")}{\" \"}\n {formatRelativeTime(session.lastActivityAt)}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n\n {!session.isCurrent && (\n <button\n type=\"button\"\n onClick={() => setPendingSessionId(session.id)}\n aria-label={`${tr(\"auth.sessions.revoke\", \"Revoke\")} ${parseDeviceName(session.userAgent)} session`}\n className=\"ml-4 px-4 py-2 rounded-lg text-sm font-medium border border-status-error-border text-status-error-text bg-bg-surface hover:bg-status-error-bg-subtle transition-colors duration-200\"\n >\n {tr(\"auth.sessions.revoke\", \"Revoke\")}\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SessionsPage.js","names":[],"sources":["../../../src/auth/pages/SessionsPage.tsx"],"sourcesContent":["import { useState, useMemo } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useSessionOperations } from \"../hooks\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport type { SessionCard_SessionFragment } from \"../../generated/global-operations\";\nimport {\n formatRelativeTime,\n parseDeviceName,\n parseBrowserName,\n} from \"../utils\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\n\n/**\n * Active sessions management page with professional UI\n *\n * Fragment: SessionCard_Session (src/operations/global/fragments/SessionCard.fragment.graphql)\n * Query: ActiveSessions (src/operations/global/queries/sessions.graphql)\n */\nexport function SessionsPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const {\n activeSessions,\n revokeSession,\n revokeAllSessions,\n isLoading,\n page,\n totalPages,\n totalCount,\n hasMore,\n goToNextPage,\n goToPrevPage,\n } = useSessionOperations({ disableTokenInfoQuery: true });\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingSessionId, setPendingSessionId] = useState<string | null>(null);\n const [showRevokeAllDialog, setShowRevokeAllDialog] = useState(false);\n\n // Deduplicate: keep newest session per device name\n const dedupedSessions = useMemo(() => {\n const byDevice = new Map<string, SessionCard_SessionFragment>();\n for (const s of activeSessions) {\n const device = parseDeviceName(s.userAgent ?? \"\");\n const existing = byDevice.get(device);\n if (\n !existing ||\n new Date(s.lastActivityAt) > new Date(existing.lastActivityAt)\n ) {\n byDevice.set(device, s);\n }\n }\n return [...byDevice.values()];\n }, [activeSessions]);\n\n const handleRevokeSession = async (sessionId: string) => {\n try {\n await revokeSession(sessionId);\n setSuccessMessage(\"Session revoked successfully\");\n setPendingSessionId(null);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to revoke session\");\n }\n };\n\n const handleRevokeAll = async () => {\n try {\n await revokeAllSessions();\n setSuccessMessage(\"All other sessions have been revoked\");\n setShowRevokeAllDialog(false);\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to revoke all sessions\",\n );\n }\n };\n\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"w-16 h-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon\n className=\"w-8 h-8 text-status-warning-text\"\n aria-hidden=\"true\"\n />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.sessions.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.sessions.signInToView\",\n \"Please sign in to view your active sessions\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingSessionId !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPendingSessionId(null);\n }\n }}\n title=\"Revoke this session?\"\n description=\"The selected device will be signed out immediately and will need to authenticate again.\"\n confirmText=\"Revoke session\"\n variant=\"danger\"\n isPending={isLoading}\n onConfirm={async () => {\n if (pendingSessionId) {\n await handleRevokeSession(pendingSessionId);\n }\n }}\n />\n <ConfirmActionDialog\n open={showRevokeAllDialog}\n onOpenChange={setShowRevokeAllDialog}\n title=\"Revoke every other session?\"\n description=\"All devices except your current one will be signed out immediately.\"\n confirmText=\"Revoke all other sessions\"\n variant=\"warning\"\n isPending={isLoading}\n onConfirm={handleRevokeAll}\n />\n\n {/* Live regions for screen reader announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {successMessage}\n </div>\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n >\n {error}\n </div>\n\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <div>\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.sessions.title\", \"Active Sessions\")}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\"auth.sessions.sessionCount\", \"You have\")} {totalCount}{\" \"}\n {tr(\n totalCount !== 1\n ? \"auth.sessions.sessionsPlural\"\n : \"auth.sessions.sessionsSingular\",\n totalCount !== 1 ? \"active sessions\" : \"active session\",\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-3\">\n {totalCount > 1 && (\n <button\n type=\"button\"\n onClick={() => setShowRevokeAllDialog(true)}\n className=\"px-4 py-2.5 rounded-xl border border-status-error-border text-status-error-text font-medium hover:bg-status-error-bg-subtle transition-colors duration-200\"\n >\n {tr(\"auth.sessions.revokeAll\", \"Revoke All Sessions\")}\n </button>\n )}\n </div>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div\n role=\"status\"\n className=\"mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\"\n >\n <p className=\"text-status-success-text\">{successMessage}</p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline transition-colors duration-200\"\n >\n Dismiss\n </button>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"w-5 h-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\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 transition-colors duration-200\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {isLoading ? (\n <div\n aria-live=\"polite\"\n aria-busy=\"true\"\n className=\"flex items-center justify-center py-12\"\n >\n <SpinnerIcon\n className=\"w-8 h-8 text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <span className=\"ml-3 text-text-secondary\">\n {tr(\"auth.sessions.loading\", \"Loading sessions...\")}\n </span>\n </div>\n ) : dedupedSessions.length === 0 ? (\n <div\n className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\"\n role=\"status\"\n >\n <div className=\"w-16 h-16 mx-auto mb-4 rounded-full bg-bg-muted flex items-center justify-center\">\n <svg\n className=\"w-8 h-8 text-text-tertiary\"\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={1.5}\n d=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n <p className=\"text-text-secondary\">\n {tr(\"auth.sessions.noSessions\", \"No active sessions found\")}\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={isLoading}>\n {dedupedSessions.map((session) => (\n <div\n key={session.id}\n className=\"p-6 rounded-xl border border-border-default bg-bg-surface shadow-sm hover:shadow-md transition-shadow\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-start gap-4\">\n {/* Device Icon */}\n <div\n className=\"w-12 h-12 rounded-xl bg-bg-accent flex items-center justify-center flex-shrink-0\"\n aria-hidden=\"true\"\n >\n <svg\n className=\"w-6 h-6 text-text-link\"\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=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3 mb-1\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {session.isCurrent &&\n parseDeviceName(session.userAgent) === \"Unknown Device\"\n ? tr(\"auth.sessions.thisDevice\", \"This Device\")\n : parseDeviceName(session.userAgent)}\n </h3>\n {session.isCurrent && (\n <span\n role=\"status\"\n className=\"px-2.5 py-1 rounded-full bg-status-success-bg-subtle text-status-success-text text-xs font-medium\"\n >\n {tr(\n \"auth.sessions.currentSession\",\n \"Current Session\",\n )}\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mb-2\">\n {parseBrowserName(session.userAgent) ===\n \"Unknown Browser\" && session.isCurrent\n ? tr(\"auth.sessions.currentBrowser\", \"Current Browser\")\n : parseBrowserName(session.userAgent)}\n </p>\n <dl className=\"flex flex-wrap gap-4 text-sm text-text-secondary\">\n {session.ipAddress && session.ipAddress !== \"unknown\" && (\n <div className=\"flex items-center gap-1.5\">\n <svg\n className=\"w-4 h-4\"\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=\"M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9\"\n />\n </svg>\n <dt className=\"sr-only\">IP address</dt>\n <dd>{session.ipAddress}</dd>\n </div>\n )}\n {session.location && (\n <div className=\"flex items-center gap-1.5\">\n <svg\n className=\"w-4 h-4\"\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=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <dt className=\"sr-only\">Location</dt>\n <dd>{session.location}</dd>\n </div>\n )}\n <div className=\"flex items-center gap-1.5\">\n <svg\n className=\"w-4 h-4\"\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=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <dt className=\"sr-only\">Last active</dt>\n <dd>\n {tr(\"auth.sessions.lastActive\", \"Last active\")}{\" \"}\n {formatRelativeTime(session.lastActivityAt)}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n\n {!session.isCurrent && (\n <button\n type=\"button\"\n onClick={() => setPendingSessionId(session.id)}\n aria-label={`${tr(\"auth.sessions.revoke\", \"Revoke\")} ${parseDeviceName(session.userAgent)} session`}\n className=\"ml-4 px-4 py-2 rounded-lg text-sm font-medium border border-status-error-border text-status-error-text bg-bg-surface hover:bg-status-error-bg-subtle transition-colors duration-200\"\n >\n {tr(\"auth.sessions.revoke\", \"Revoke\")}\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Pagination controls */}\n {!isLoading && totalPages > 1 && (\n <div className=\"flex items-center justify-between mt-6 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={goToPrevPage}\n disabled={page === 0}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium text-sm\"\n aria-label={tr(\"auth.sessions.prevPage\", \"Previous page\")}\n >\n <svg\n className=\"w-4 h-4\"\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=\"M15 19l-7-7 7-7\"\n />\n </svg>\n {tr(\"auth.sessions.prev\", \"Previous\")}\n </button>\n\n <span className=\"text-sm text-text-secondary\">\n {tr(\"auth.sessions.pageOf\", \"Page\")}{\" \"}\n <span className=\"font-medium text-text-primary\">{page + 1}</span>{\" \"}\n {tr(\"auth.sessions.of\", \"of\")}{\" \"}\n <span className=\"font-medium text-text-primary\">{totalPages}</span>\n </span>\n\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!hasMore}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium text-sm\"\n aria-label={tr(\"auth.sessions.nextPage\", \"Next page\")}\n >\n {tr(\"auth.sessions.next\", \"Next\")}\n <svg\n className=\"w-4 h-4\"\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=\"M9 5l7 7-7 7\"\n />\n </svg>\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SsoConfigPage.d.ts","sourceRoot":"","sources":["../../../src/auth/pages/SsoConfigPage.tsx"],"names":[],"mappings":"AAgHA;;;GAGG;AACH,wBAAgB,aAAa,4CA+7C5B"}
1
+ {"version":3,"file":"SsoConfigPage.d.ts","sourceRoot":"","sources":["../../../src/auth/pages/SsoConfigPage.tsx"],"names":[],"mappings":"AAgHA;;;GAGG;AACH,wBAAgB,aAAa,4CAqgD5B"}
@@ -110,12 +110,14 @@ function y() {
110
110
  className: "flex items-center gap-4",
111
111
  children: [/* @__PURE__ */ h("button", {
112
112
  onClick: () => a("/auth/security"),
113
+ "aria-label": k("auth.sso.backToSecurity", "Back to Security"),
113
114
  className: "p-2 rounded-lg hover:bg-bg-sunken transition-colors",
114
115
  children: /* @__PURE__ */ h("svg", {
115
116
  className: "w-5 h-5 text-text-secondary",
116
117
  fill: "none",
117
118
  viewBox: "0 0 24 24",
118
119
  stroke: "currentColor",
120
+ "aria-hidden": "true",
119
121
  children: /* @__PURE__ */ h("path", {
120
122
  strokeLinecap: "round",
121
123
  strokeLinejoin: "round",
@@ -331,10 +333,12 @@ function y() {
331
333
  }), /* @__PURE__ */ g("div", {
332
334
  className: "flex gap-2",
333
335
  children: [/* @__PURE__ */ h("input", {
336
+ id: "sso-metadata-url",
334
337
  type: "url",
335
338
  value: B,
336
339
  onChange: (e) => V(e.target.value),
337
340
  placeholder: "https://idp.example.com/metadata.xml",
341
+ "aria-label": k("auth.sso.fetchFromUrl", "Fetch from URL"),
338
342
  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"
339
343
  }), /* @__PURE__ */ g("button", {
340
344
  type: "button",
@@ -354,7 +358,7 @@ function y() {
354
358
  ssoUrl: t.ssoUrl,
355
359
  sloUrl: t.sloUrl || "",
356
360
  certificate: t.certificate
357
- }), z("SAML metadata imported successfully! Review and save the configuration."), N(!0), V("")) : L(k("auth.sso.parseUrlError", "Failed to parse SAML metadata from URL."));
361
+ }), z(k("auth.sso.importSuccessFromUrl", "SAML metadata imported successfully! Review and save the configuration.")), N(!0), V("")) : L(k("auth.sso.parseUrlError", "Failed to parse SAML metadata from URL."));
358
362
  } catch (e) {
359
363
  L(e instanceof Error ? e.message : k("auth.sso.fetchError", "Failed to fetch metadata from URL"));
360
364
  } finally {
@@ -452,17 +456,20 @@ function y() {
452
456
  children: [
453
457
  /* @__PURE__ */ g("div", { children: [
454
458
  /* @__PURE__ */ g("label", {
459
+ htmlFor: "sso-entity-id",
455
460
  className: "block text-sm font-medium text-text-primary mb-2",
456
461
  children: [
457
462
  k("auth.sso.idpEntityId", "IdP Entity ID / Issuer URL"),
458
463
  " ",
459
464
  /* @__PURE__ */ h("span", {
460
465
  className: "text-status-error-text",
466
+ "aria-hidden": "true",
461
467
  children: "*"
462
468
  })
463
469
  ]
464
470
  }),
465
471
  /* @__PURE__ */ h("input", {
472
+ id: "sso-entity-id",
466
473
  type: "url",
467
474
  value: Q.entityId,
468
475
  onChange: (e) => $({
@@ -472,26 +479,32 @@ function y() {
472
479
  disabled: !M && !!x,
473
480
  placeholder: k("auth.sso.idpEntityIdPlaceholder", "https://your-idp.example.com/saml/metadata"),
474
481
  required: !0,
482
+ "aria-required": "true",
483
+ "aria-describedby": "sso-entity-id-hint",
475
484
  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"
476
485
  }),
477
486
  /* @__PURE__ */ h("p", {
487
+ id: "sso-entity-id-hint",
478
488
  className: "mt-1 text-xs text-text-secondary",
479
489
  children: k("auth.sso.idpEntityIdHelper", "The unique identifier of your Identity Provider")
480
490
  })
481
491
  ] }),
482
492
  /* @__PURE__ */ g("div", { children: [
483
493
  /* @__PURE__ */ g("label", {
494
+ htmlFor: "sso-sso-url",
484
495
  className: "block text-sm font-medium text-text-primary mb-2",
485
496
  children: [
486
497
  k("auth.sso.ssoUrl", "SSO URL (Login URL)"),
487
498
  " ",
488
499
  /* @__PURE__ */ h("span", {
489
500
  className: "text-status-error-text",
501
+ "aria-hidden": "true",
490
502
  children: "*"
491
503
  })
492
504
  ]
493
505
  }),
494
506
  /* @__PURE__ */ h("input", {
507
+ id: "sso-sso-url",
495
508
  type: "url",
496
509
  value: Q.ssoUrl,
497
510
  onChange: (e) => $({
@@ -501,19 +514,24 @@ function y() {
501
514
  disabled: !M && !!x,
502
515
  placeholder: k("auth.sso.ssoUrlPlaceholder", "https://your-idp.example.com/saml/sso"),
503
516
  required: !0,
517
+ "aria-required": "true",
518
+ "aria-describedby": "sso-sso-url-hint",
504
519
  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"
505
520
  }),
506
521
  /* @__PURE__ */ h("p", {
522
+ id: "sso-sso-url-hint",
507
523
  className: "mt-1 text-xs text-text-secondary",
508
524
  children: k("auth.sso.ssoUrlHelper", "The URL where users are redirected to authenticate")
509
525
  })
510
526
  ] }),
511
527
  /* @__PURE__ */ g("div", { children: [
512
528
  /* @__PURE__ */ h("label", {
529
+ htmlFor: "sso-slo-url",
513
530
  className: "block text-sm font-medium text-text-primary mb-2",
514
531
  children: k("auth.sso.sloUrl", "Single Logout (SLO) URL")
515
532
  }),
516
533
  /* @__PURE__ */ h("input", {
534
+ id: "sso-slo-url",
517
535
  type: "url",
518
536
  value: Q.sloUrl,
519
537
  onChange: (e) => $({
@@ -522,26 +540,31 @@ function y() {
522
540
  }),
523
541
  disabled: !M && !!x,
524
542
  placeholder: k("auth.sso.sloUrlPlaceholder", "https://your-idp.example.com/saml/slo"),
543
+ "aria-describedby": "sso-slo-url-hint",
525
544
  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"
526
545
  }),
527
546
  /* @__PURE__ */ h("p", {
547
+ id: "sso-slo-url-hint",
528
548
  className: "mt-1 text-xs text-text-secondary",
529
549
  children: k("auth.sso.sloUrlHelper", "Optional: URL for single logout across all applications")
530
550
  })
531
551
  ] }),
532
552
  /* @__PURE__ */ g("div", { children: [
533
553
  /* @__PURE__ */ g("label", {
554
+ htmlFor: "sso-certificate",
534
555
  className: "block text-sm font-medium text-text-primary mb-2",
535
556
  children: [
536
557
  k("auth.sso.certificate", "X.509 Certificate"),
537
558
  " ",
538
559
  /* @__PURE__ */ h("span", {
539
560
  className: "text-status-error-text",
561
+ "aria-hidden": "true",
540
562
  children: "*"
541
563
  })
542
564
  ]
543
565
  }),
544
566
  /* @__PURE__ */ h("textarea", {
567
+ id: "sso-certificate",
545
568
  value: Q.certificate,
546
569
  onChange: (e) => $({
547
570
  ...Q,
@@ -550,10 +573,13 @@ function y() {
550
573
  disabled: !M && !!x,
551
574
  placeholder: k("auth.sso.certificatePlaceholder", "-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAX...\n-----END CERTIFICATE-----"),
552
575
  required: !0,
576
+ "aria-required": "true",
577
+ "aria-describedby": "sso-certificate-hint",
553
578
  rows: 6,
554
579
  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"
555
580
  }),
556
581
  /* @__PURE__ */ h("p", {
582
+ id: "sso-certificate-hint",
557
583
  className: "mt-1 text-xs text-text-secondary",
558
584
  children: k("auth.sso.certificateHelper", "The public X.509 certificate from your IdP for signature verification")
559
585
  })
@@ -836,16 +862,19 @@ function y() {
836
862
  className: "space-y-5",
837
863
  children: [
838
864
  /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
865
+ htmlFor: "oidc-client-name",
839
866
  className: "block text-sm font-medium text-text-primary mb-2",
840
867
  children: [
841
868
  k("auth.sso.clientName", "Client Name"),
842
869
  " ",
843
870
  /* @__PURE__ */ h("span", {
844
871
  className: "text-status-error-text",
872
+ "aria-hidden": "true",
845
873
  children: "*"
846
874
  })
847
875
  ]
848
876
  }), /* @__PURE__ */ h("input", {
877
+ id: "oidc-client-name",
849
878
  type: "text",
850
879
  value: K.client_name,
851
880
  onChange: (e) => q({
@@ -853,21 +882,25 @@ function y() {
853
882
  client_name: e.target.value
854
883
  }),
855
884
  placeholder: k("auth.sso.clientNamePlaceholder", "My Application"),
885
+ "aria-required": "true",
856
886
  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"
857
887
  })] }),
858
888
  /* @__PURE__ */ g("div", { children: [
859
889
  /* @__PURE__ */ g("label", {
890
+ htmlFor: "oidc-redirect-uris",
860
891
  className: "block text-sm font-medium text-text-primary mb-2",
861
892
  children: [
862
893
  k("auth.sso.redirectUris", "Redirect URIs"),
863
894
  " ",
864
895
  /* @__PURE__ */ h("span", {
865
896
  className: "text-status-error-text",
897
+ "aria-hidden": "true",
866
898
  children: "*"
867
899
  })
868
900
  ]
869
901
  }),
870
902
  /* @__PURE__ */ h("input", {
903
+ id: "oidc-redirect-uris",
871
904
  type: "text",
872
905
  value: K.redirect_uris,
873
906
  onChange: (e) => q({
@@ -875,9 +908,12 @@ function y() {
875
908
  redirect_uris: e.target.value
876
909
  }),
877
910
  placeholder: k("auth.sso.redirectUrisPlaceholder", "http://localhost:3000/callback, https://app.example.com/callback"),
911
+ "aria-required": "true",
912
+ "aria-describedby": "oidc-redirect-uris-hint",
878
913
  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"
879
914
  }),
880
915
  /* @__PURE__ */ h("p", {
916
+ id: "oidc-redirect-uris-hint",
881
917
  className: "mt-1 text-xs text-text-secondary",
882
918
  children: k("auth.sso.redirectUrisHelper", "Separate multiple URIs with commas")
883
919
  })
@@ -913,9 +949,11 @@ function y() {
913
949
  }, e))
914
950
  })] }),
915
951
  /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
952
+ htmlFor: "oidc-token-auth-method",
916
953
  className: "block text-sm font-medium text-text-primary mb-2",
917
954
  children: k("auth.sso.tokenEndpointAuth", "Token Endpoint Auth Method")
918
955
  }), /* @__PURE__ */ g("select", {
956
+ id: "oidc-token-auth-method",
919
957
  value: K.token_endpoint_auth_method,
920
958
  onChange: (e) => q({
921
959
  ...K,