@open-mercato/enterprise 0.7.1-develop.7152.1.a69e92f9c9 → 0.7.1-develop.7153.1.7145d295e6
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.
- package/dist/modules/security/backend/security/enforcement/page.js +1 -0
- package/dist/modules/security/backend/security/enforcement/page.js.map +2 -2
- package/dist/modules/security/backend/security/sudo/page.js +1 -0
- package/dist/modules/security/backend/security/sudo/page.js.map +2 -2
- package/dist/modules/security/backend/security/users/page.js +1 -0
- package/dist/modules/security/backend/security/users/page.js.map +2 -2
- package/dist/modules/security/components/EnforcementPolicyForm.js +1 -0
- package/dist/modules/security/components/EnforcementPolicyForm.js.map +2 -2
- package/dist/modules/security/components/PasswordChangeForm.js +1 -0
- package/dist/modules/security/components/PasswordChangeForm.js.map +2 -2
- package/dist/modules/security/components/SecurityUserForm.js +1 -0
- package/dist/modules/security/components/SecurityUserForm.js.map +2 -2
- package/dist/modules/security/components/SudoConfigCrudPage.js +1 -0
- package/dist/modules/security/components/SudoConfigCrudPage.js.map +2 -2
- package/dist/modules/sso/backend/page.js +1 -0
- package/dist/modules/sso/backend/page.js.map +2 -2
- package/package.json +5 -5
- package/src/modules/security/backend/security/enforcement/page.tsx +1 -0
- package/src/modules/security/backend/security/sudo/page.tsx +1 -0
- package/src/modules/security/backend/security/users/page.tsx +1 -0
- package/src/modules/security/components/EnforcementPolicyForm.tsx +1 -0
- package/src/modules/security/components/PasswordChangeForm.tsx +1 -0
- package/src/modules/security/components/SecurityUserForm.tsx +1 -0
- package/src/modules/security/components/SudoConfigCrudPage.tsx +1 -0
- package/src/modules/sso/backend/page.tsx +2 -1
|
@@ -198,6 +198,7 @@ function SecurityEnforcementPage() {
|
|
|
198
198
|
DataTable,
|
|
199
199
|
{
|
|
200
200
|
title: t("security.admin.enforcement.list.title", "Existing policies"),
|
|
201
|
+
titleHeadingLevel: 1,
|
|
201
202
|
actions: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/security/enforcement/create", children: t("security.admin.enforcement.list.new", "Create new policy") }) }),
|
|
202
203
|
columns,
|
|
203
204
|
data: pagedPolicies,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/security/backend/security/enforcement/page.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation.js'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Pencil, Trash2 } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport type { EnforcementPoliciesResponse, EnforcementPolicyDto } from './_shared'\n\nconst PAGE_SIZE = 20\n\nfunction buildTargetLabel(policy: EnforcementPolicyDto, allTenantsLabel: string): string {\n if (policy.scope === 'platform') return allTenantsLabel\n if (policy.scope === 'tenant') return policy.tenantName ?? policy.tenantId ?? '-'\n return `${policy.tenantName ?? policy.tenantId ?? '-'} / ${policy.organizationName ?? policy.organizationId ?? '-'}`\n}\n\nfunction renderTargetLabel(policy: EnforcementPolicyDto, allTenantsLabel: string) {\n if (policy.scope === 'platform') {\n return <span>{allTenantsLabel}</span>\n }\n\n const tenantLabel = policy.tenantName ?? policy.tenantId ?? '-'\n if (policy.scope === 'tenant') {\n return <span className=\"whitespace-normal break-words\">{tenantLabel}</span>\n }\n\n const organizationLabel = policy.organizationName ?? policy.organizationId ?? '-'\n return (\n <div className=\"space-y-1 whitespace-normal break-words\">\n <div>{tenantLabel}</div>\n <div className=\"text-muted-foreground\">{organizationLabel}</div>\n </div>\n )\n}\n\nexport default function SecurityEnforcementPage() {\n const router = useRouter()\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'deadline', desc: true }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [policies, setPolicies] = React.useState<EnforcementPolicyDto[]>([])\n const [saving, setSaving] = React.useState(false)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'security-enforcement-management',\n })\n\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { retryLastMutation },\n })\n },\n [retryLastMutation, runMutation],\n )\n\n const loadPolicies = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<EnforcementPoliciesResponse>('/api/security/enforcement')\n if (!response.ok || !response.result) {\n setPolicies([])\n setError(\n t(\n 'security.admin.enforcement.errors.load',\n 'Failed to load enforcement policies.',\n ),\n )\n setLoading(false)\n return\n }\n\n setPolicies(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadPolicies()\n }, [loadPolicies])\n\n const filteredPolicies = React.useMemo(() => {\n const normalizedSearch = search.trim().toLowerCase()\n const selectedScope = typeof filterValues.scope === 'string' ? filterValues.scope : ''\n const selectedStatus = typeof filterValues.status === 'string' ? filterValues.status : ''\n\n return policies.filter((policy) => {\n if (selectedScope && policy.scope !== selectedScope) return false\n if (selectedStatus === 'enforced' && !policy.isEnforced) return false\n if (selectedStatus === 'disabled' && policy.isEnforced) return false\n\n if (!normalizedSearch) return true\n const searchableChunks = [\n policy.scope,\n policy.tenantId ?? '',\n policy.tenantName ?? '',\n policy.organizationId ?? '',\n policy.organizationName ?? '',\n policy.allowedMethods?.join(',') ?? '',\n buildTargetLabel(policy, t('security.admin.enforcement.target.platform', 'All tenants')),\n ]\n return searchableChunks.some((chunk) => chunk.toLowerCase().includes(normalizedSearch))\n })\n }, [filterValues.scope, filterValues.status, policies, search, t])\n\n const total = filteredPolicies.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n\n React.useEffect(() => {\n if (page > totalPages) {\n setPage(totalPages)\n }\n }, [page, totalPages])\n\n const pagedPolicies = React.useMemo(() => {\n const start = (page - 1) * PAGE_SIZE\n return filteredPolicies.slice(start, start + PAGE_SIZE)\n }, [filteredPolicies, page])\n\n const filterDefs = React.useMemo<FilterDef[]>(() => [\n {\n id: 'scope',\n label: t('security.admin.enforcement.list.scope', 'Scope'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'platform', label: t('security.admin.enforcement.scope.platform', 'Platform') },\n { value: 'tenant', label: t('security.admin.enforcement.scope.tenant', 'Tenant') },\n { value: 'organisation', label: t('security.admin.enforcement.scope.organisation', 'Organisation') },\n ],\n },\n {\n id: 'status',\n label: t('security.admin.enforcement.list.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'enforced', label: t('security.admin.enforcement.status.enforced', 'Enforced') },\n { value: 'disabled', label: t('security.admin.enforcement.status.disabled', 'Disabled') },\n ],\n },\n ], [t])\n\n const columns = React.useMemo<ColumnDef<EnforcementPolicyDto>[]>(() => [\n {\n accessorKey: 'scope',\n header: t('security.admin.enforcement.list.scope', 'Scope'),\n cell: ({ row }) => t(`security.admin.enforcement.scope.${row.original.scope}`, row.original.scope),\n },\n {\n id: 'target',\n header: t('security.admin.enforcement.list.target', 'Target'),\n cell: ({ row }) => renderTargetLabel(\n row.original,\n t('security.admin.enforcement.target.platform', 'All tenants'),\n ),\n },\n {\n id: 'deadline',\n header: t('security.admin.enforcement.list.deadline', 'Deadline'),\n cell: ({ row }) => row.original.enforcementDeadline\n ? new Date(row.original.enforcementDeadline).toLocaleString()\n : t('security.admin.enforcement.deadline.none', 'No deadline'),\n },\n {\n id: 'methods',\n header: t('security.admin.enforcement.list.methods', 'Allowed methods'),\n cell: ({ row }) => row.original.allowedMethods?.length\n ? row.original.allowedMethods.join(', ')\n : t('security.admin.enforcement.methods.all', 'All'),\n },\n {\n id: 'status',\n header: t('security.admin.enforcement.list.status', 'Status'),\n cell: ({ row }) => row.original.isEnforced\n ? t('security.admin.enforcement.status.enforced', 'Enforced')\n : t('security.admin.enforcement.status.disabled', 'Disabled'),\n },\n ], [t])\n\n const handleDelete = React.useCallback(async (policy: EnforcementPolicyDto) => {\n const accepted = await confirm({\n title: t('security.admin.enforcement.delete.title', 'Delete enforcement policy?'),\n text: t(\n 'security.admin.enforcement.delete.text',\n 'This removes the selected policy. Continue?',\n ),\n variant: 'destructive',\n confirmText: t('ui.actions.delete', 'Delete'),\n cancelText: t('ui.actions.cancel', 'Cancel'),\n })\n\n if (!accepted) return\n\n setSaving(true)\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/security/enforcement/${encodeURIComponent(policy.id)}`, {\n method: 'DELETE',\n }),\n { id: policy.id },\n )\n flash(t('security.admin.enforcement.flash.deleted', 'Enforcement policy deleted.'), 'success')\n await loadPolicies()\n } catch {\n flash(t('security.admin.enforcement.flash.deleteError', 'Failed to delete enforcement policy.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [confirm, loadPolicies, runMutationWithContext, t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<EnforcementPolicyDto>\n title={t('security.admin.enforcement.list.title', 'Existing policies')}\n actions={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" asChild>\n <Link href=\"/backend/security/enforcement/create\">\n {t('security.admin.enforcement.list.new', 'Create new policy')}\n </Link>\n </Button>\n )}\n columns={columns}\n data={pagedPolicies}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('security.admin.enforcement.search', 'Search policies...')}\n filters={filterDefs}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n onRowClick={(policy) => {\n router.push(`/backend/security/enforcement/${encodeURIComponent(policy.id)}`)\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={{\n label: t('ui.actions.refresh', 'Refresh'),\n onRefresh: () => void loadPolicies(),\n isRefreshing: loading,\n }}\n perspective={{ tableId: extensionPoints.hosts.enforcementTable.tableId }}\n isLoading={loading}\n error={error ? (\n <div className=\"flex items-center justify-center gap-3\">\n <span>{error}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadPolicies()}>\n {t('ui.actions.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n emptyState={t('security.admin.enforcement.empty', 'No enforcement policies found.')}\n rowActions={(policy) => (\n <div className=\"flex items-center gap-1\">\n <IconButton\n asChild\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n title={t('ui.actions.edit', 'Edit')}\n >\n <Link href={`/backend/security/enforcement/${encodeURIComponent(policy.id)}`}>\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={t('ui.actions.delete', 'Delete')}\n title={t('ui.actions.delete', 'Delete')}\n onClick={() => void handleDelete(policy)}\n disabled={saving}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n )}\n />\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation.js'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Pencil, Trash2 } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport type { EnforcementPoliciesResponse, EnforcementPolicyDto } from './_shared'\n\nconst PAGE_SIZE = 20\n\nfunction buildTargetLabel(policy: EnforcementPolicyDto, allTenantsLabel: string): string {\n if (policy.scope === 'platform') return allTenantsLabel\n if (policy.scope === 'tenant') return policy.tenantName ?? policy.tenantId ?? '-'\n return `${policy.tenantName ?? policy.tenantId ?? '-'} / ${policy.organizationName ?? policy.organizationId ?? '-'}`\n}\n\nfunction renderTargetLabel(policy: EnforcementPolicyDto, allTenantsLabel: string) {\n if (policy.scope === 'platform') {\n return <span>{allTenantsLabel}</span>\n }\n\n const tenantLabel = policy.tenantName ?? policy.tenantId ?? '-'\n if (policy.scope === 'tenant') {\n return <span className=\"whitespace-normal break-words\">{tenantLabel}</span>\n }\n\n const organizationLabel = policy.organizationName ?? policy.organizationId ?? '-'\n return (\n <div className=\"space-y-1 whitespace-normal break-words\">\n <div>{tenantLabel}</div>\n <div className=\"text-muted-foreground\">{organizationLabel}</div>\n </div>\n )\n}\n\nexport default function SecurityEnforcementPage() {\n const router = useRouter()\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'deadline', desc: true }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [policies, setPolicies] = React.useState<EnforcementPolicyDto[]>([])\n const [saving, setSaving] = React.useState(false)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'security-enforcement-management',\n })\n\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { retryLastMutation },\n })\n },\n [retryLastMutation, runMutation],\n )\n\n const loadPolicies = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<EnforcementPoliciesResponse>('/api/security/enforcement')\n if (!response.ok || !response.result) {\n setPolicies([])\n setError(\n t(\n 'security.admin.enforcement.errors.load',\n 'Failed to load enforcement policies.',\n ),\n )\n setLoading(false)\n return\n }\n\n setPolicies(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadPolicies()\n }, [loadPolicies])\n\n const filteredPolicies = React.useMemo(() => {\n const normalizedSearch = search.trim().toLowerCase()\n const selectedScope = typeof filterValues.scope === 'string' ? filterValues.scope : ''\n const selectedStatus = typeof filterValues.status === 'string' ? filterValues.status : ''\n\n return policies.filter((policy) => {\n if (selectedScope && policy.scope !== selectedScope) return false\n if (selectedStatus === 'enforced' && !policy.isEnforced) return false\n if (selectedStatus === 'disabled' && policy.isEnforced) return false\n\n if (!normalizedSearch) return true\n const searchableChunks = [\n policy.scope,\n policy.tenantId ?? '',\n policy.tenantName ?? '',\n policy.organizationId ?? '',\n policy.organizationName ?? '',\n policy.allowedMethods?.join(',') ?? '',\n buildTargetLabel(policy, t('security.admin.enforcement.target.platform', 'All tenants')),\n ]\n return searchableChunks.some((chunk) => chunk.toLowerCase().includes(normalizedSearch))\n })\n }, [filterValues.scope, filterValues.status, policies, search, t])\n\n const total = filteredPolicies.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n\n React.useEffect(() => {\n if (page > totalPages) {\n setPage(totalPages)\n }\n }, [page, totalPages])\n\n const pagedPolicies = React.useMemo(() => {\n const start = (page - 1) * PAGE_SIZE\n return filteredPolicies.slice(start, start + PAGE_SIZE)\n }, [filteredPolicies, page])\n\n const filterDefs = React.useMemo<FilterDef[]>(() => [\n {\n id: 'scope',\n label: t('security.admin.enforcement.list.scope', 'Scope'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'platform', label: t('security.admin.enforcement.scope.platform', 'Platform') },\n { value: 'tenant', label: t('security.admin.enforcement.scope.tenant', 'Tenant') },\n { value: 'organisation', label: t('security.admin.enforcement.scope.organisation', 'Organisation') },\n ],\n },\n {\n id: 'status',\n label: t('security.admin.enforcement.list.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'enforced', label: t('security.admin.enforcement.status.enforced', 'Enforced') },\n { value: 'disabled', label: t('security.admin.enforcement.status.disabled', 'Disabled') },\n ],\n },\n ], [t])\n\n const columns = React.useMemo<ColumnDef<EnforcementPolicyDto>[]>(() => [\n {\n accessorKey: 'scope',\n header: t('security.admin.enforcement.list.scope', 'Scope'),\n cell: ({ row }) => t(`security.admin.enforcement.scope.${row.original.scope}`, row.original.scope),\n },\n {\n id: 'target',\n header: t('security.admin.enforcement.list.target', 'Target'),\n cell: ({ row }) => renderTargetLabel(\n row.original,\n t('security.admin.enforcement.target.platform', 'All tenants'),\n ),\n },\n {\n id: 'deadline',\n header: t('security.admin.enforcement.list.deadline', 'Deadline'),\n cell: ({ row }) => row.original.enforcementDeadline\n ? new Date(row.original.enforcementDeadline).toLocaleString()\n : t('security.admin.enforcement.deadline.none', 'No deadline'),\n },\n {\n id: 'methods',\n header: t('security.admin.enforcement.list.methods', 'Allowed methods'),\n cell: ({ row }) => row.original.allowedMethods?.length\n ? row.original.allowedMethods.join(', ')\n : t('security.admin.enforcement.methods.all', 'All'),\n },\n {\n id: 'status',\n header: t('security.admin.enforcement.list.status', 'Status'),\n cell: ({ row }) => row.original.isEnforced\n ? t('security.admin.enforcement.status.enforced', 'Enforced')\n : t('security.admin.enforcement.status.disabled', 'Disabled'),\n },\n ], [t])\n\n const handleDelete = React.useCallback(async (policy: EnforcementPolicyDto) => {\n const accepted = await confirm({\n title: t('security.admin.enforcement.delete.title', 'Delete enforcement policy?'),\n text: t(\n 'security.admin.enforcement.delete.text',\n 'This removes the selected policy. Continue?',\n ),\n variant: 'destructive',\n confirmText: t('ui.actions.delete', 'Delete'),\n cancelText: t('ui.actions.cancel', 'Cancel'),\n })\n\n if (!accepted) return\n\n setSaving(true)\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/security/enforcement/${encodeURIComponent(policy.id)}`, {\n method: 'DELETE',\n }),\n { id: policy.id },\n )\n flash(t('security.admin.enforcement.flash.deleted', 'Enforcement policy deleted.'), 'success')\n await loadPolicies()\n } catch {\n flash(t('security.admin.enforcement.flash.deleteError', 'Failed to delete enforcement policy.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [confirm, loadPolicies, runMutationWithContext, t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<EnforcementPolicyDto>\n title={t('security.admin.enforcement.list.title', 'Existing policies')}\n titleHeadingLevel={1}\n actions={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" asChild>\n <Link href=\"/backend/security/enforcement/create\">\n {t('security.admin.enforcement.list.new', 'Create new policy')}\n </Link>\n </Button>\n )}\n columns={columns}\n data={pagedPolicies}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('security.admin.enforcement.search', 'Search policies...')}\n filters={filterDefs}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n onRowClick={(policy) => {\n router.push(`/backend/security/enforcement/${encodeURIComponent(policy.id)}`)\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={{\n label: t('ui.actions.refresh', 'Refresh'),\n onRefresh: () => void loadPolicies(),\n isRefreshing: loading,\n }}\n perspective={{ tableId: extensionPoints.hosts.enforcementTable.tableId }}\n isLoading={loading}\n error={error ? (\n <div className=\"flex items-center justify-center gap-3\">\n <span>{error}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadPolicies()}>\n {t('ui.actions.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n emptyState={t('security.admin.enforcement.empty', 'No enforcement policies found.')}\n rowActions={(policy) => (\n <div className=\"flex items-center gap-1\">\n <IconButton\n asChild\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n title={t('ui.actions.edit', 'Edit')}\n >\n <Link href={`/backend/security/enforcement/${encodeURIComponent(policy.id)}`}>\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={t('ui.actions.delete', 'Delete')}\n title={t('ui.actions.delete', 'Delete')}\n onClick={() => void handleDelete(policy)}\n disabled={saving}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n )}\n />\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/security/backend/security/sudo/page.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport Link from 'next/link'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Pencil, Plus, ShieldAlert, Trash2 } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ChallengeMethod } from '../../../data/constants'\nimport { SudoProvider } from '../../../components/SudoProvider'\nimport { useSudoChallenge } from '../../../components/hooks/useSudoChallenge'\n\ntype SudoConfigRow = {\n id: string\n tenantId: string | null\n tenantName: string | null\n organizationId: string | null\n organizationName: string | null\n label: string | null\n targetIdentifier: string\n isEnabled: boolean\n isDeveloperDefault: boolean\n ttlSeconds: number\n challengeMethod: ChallengeMethod\n configuredBy: string | null\n createdAt: string\n updatedAt: string\n}\n\ntype SudoConfigListResponse = {\n items: SudoConfigRow[]\n}\n\nfunction renderScopeLabel(row: SudoConfigRow, platformLabel: string) {\n if (!row.tenantId && !row.organizationId) {\n return <span>{platformLabel}</span>\n }\n const tenantLabel = row.tenantName ?? row.tenantId ?? '-'\n if (!row.organizationId) {\n return <span className=\"whitespace-normal break-words\">{tenantLabel}</span>\n }\n const orgLabel = row.organizationName ?? row.organizationId ?? '-'\n return (\n <div className=\"space-y-1 whitespace-normal break-words\">\n <div>{tenantLabel}</div>\n <div className=\"text-muted-foreground\">{orgLabel}</div>\n </div>\n )\n}\n\nfunction SecuritySudoPageInner() {\n const t = useT()\n const { requireSudo } = useSudoChallenge()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'security-sudo-management',\n })\n\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [rows, setRows] = React.useState<SudoConfigRow[]>([])\n const [saving, setSaving] = React.useState(false)\n\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { retryLastMutation },\n })\n },\n [retryLastMutation, runMutation],\n )\n\n const loadRows = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<SudoConfigListResponse>('/api/security/sudo/configs')\n if (!response.ok || !response.result) {\n setRows([])\n setError(t('security.admin.sudo.errors.load', 'Failed to load sudo configuration.'))\n setLoading(false)\n return\n }\n setRows(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadRows()\n }, [loadRows])\n\n const requestToken = React.useCallback(async () => {\n const sudoToken = await requireSudo('security.sudo.manage')\n if (!sudoToken) {\n flash(t('security.admin.sudo.flash.cancelled', 'Sudo challenge cancelled.'), 'error')\n return null\n }\n return sudoToken\n }, [requireSudo, t])\n\n const handleDelete = React.useCallback(async (row: SudoConfigRow) => {\n const accepted = await confirm({\n title: t('security.admin.sudo.delete.title', 'Delete sudo rule?'),\n text: t('security.admin.sudo.delete.text', 'This removes the selected sudo protection rule.'),\n variant: 'destructive',\n confirmText: t('ui.actions.delete', 'Delete'),\n cancelText: t('ui.actions.cancel', 'Cancel'),\n })\n if (!accepted) return\n\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSaving(true)\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/security/sudo/configs/${encodeURIComponent(row.id)}`, {\n method: 'DELETE',\n headers: { 'x-sudo-token': sudoToken },\n }),\n { id: row.id },\n )\n flash(t('security.admin.sudo.flash.deleted', 'Sudo configuration deleted.'), 'success')\n await loadRows()\n } catch {\n flash(t('security.admin.sudo.flash.deleteError', 'Failed to delete sudo configuration.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [confirm, loadRows, requestToken, runMutationWithContext, t])\n\n const columns = React.useMemo<ColumnDef<SudoConfigRow>[]>(() => [\n {\n accessorKey: 'label',\n header: t('security.admin.sudo.table.label', 'Label'),\n cell: ({ row }) => row.original.label ?? '\u2014',\n },\n {\n accessorKey: 'targetIdentifier',\n header: t('security.admin.sudo.table.targetIdentifier', 'Target'),\n },\n {\n id: 'scope',\n header: t('security.admin.sudo.table.scope', 'Scope'),\n cell: ({ row }) => renderScopeLabel(row.original, t('security.admin.sudo.scope.platform', 'Platform')),\n },\n {\n accessorKey: 'challengeMethod',\n header: t('security.admin.sudo.table.challengeMethod', 'Method'),\n cell: ({ row }) => t(`security.admin.sudo.challengeMethod.${row.original.challengeMethod}`, row.original.challengeMethod),\n },\n {\n accessorKey: 'ttlSeconds',\n header: t('security.admin.sudo.table.ttl', 'TTL'),\n cell: ({ row }) => t('security.admin.sudo.table.ttlValue', '{value}s', { value: String(row.original.ttlSeconds) }),\n },\n {\n id: 'source',\n header: t('security.admin.sudo.table.source', 'Source'),\n cell: ({ row }) => row.original.isDeveloperDefault\n ? t('security.admin.sudo.source.developer', 'Developer default')\n : t('security.admin.sudo.source.admin', 'Admin override'),\n },\n {\n accessorKey: 'isEnabled',\n header: t('security.admin.sudo.table.status', 'Status'),\n cell: ({ row }) => row.original.isEnabled\n ? t('security.admin.sudo.status.enabled', 'Enabled')\n : t('security.admin.sudo.status.disabled', 'Disabled'),\n },\n {\n id: 'actions',\n header: t('security.admin.sudo.table.actions', 'Actions'),\n cell: ({ row }) => {\n const isDeveloperDefault = row.original.isDeveloperDefault\n return (\n <div className=\"flex items-center gap-2\">\n {isDeveloperDefault ? (\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n disabled\n >\n <Pencil className=\"size-4\" />\n </IconButton>\n ) : (\n <IconButton\n asChild\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n >\n <Link href={`/backend/security/sudo/${row.original.id}/edit`}>\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n )}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.delete', 'Delete')}\n disabled={isDeveloperDefault}\n onClick={() => void handleDelete(row.original)}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n )\n },\n },\n ], [handleDelete, t])\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div className=\"rounded-xl border bg-muted/30 p-4\">\n <div className=\"flex items-start gap-3\">\n <ShieldAlert className=\"mt-0.5 size-5 text-amber-600\" />\n <div className=\"space-y-1\">\n <h2 className=\"text-sm font-semibold\">\n {t('security.admin.sudo.notice.title', 'Sensitive operations require re-authentication')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'security.admin.sudo.notice.description',\n 'Use sudo rules to require a fresh password or MFA challenge for selected features, routes, modules, or packages.',\n )}\n </p>\n </div>\n </div>\n </div>\n\n <DataTable<SudoConfigRow>\n title={t('security.admin.sudo.title', 'Sudo protection')}\n columns={columns}\n data={rows}\n actions={(\n <Button\n asChild\n variant=\"outline\"\n size=\"sm\"\n >\n <Link href=\"/backend/security/sudo/create\">\n <Plus className=\"mr-2 size-4\" />\n {t('security.admin.sudo.actions.add', 'Add rule')}\n </Link>\n </Button>\n )}\n perspective={{ tableId: extensionPoints.hosts.sudoTable.tableId }}\n isLoading={loading}\n error={error ? <span>{error}</span> : null}\n />\n\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n\nexport default function SecuritySudoPage() {\n return (\n <SudoProvider>\n <SecuritySudoPageInner />\n </SudoProvider>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport Link from 'next/link'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Pencil, Plus, ShieldAlert, Trash2 } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ChallengeMethod } from '../../../data/constants'\nimport { SudoProvider } from '../../../components/SudoProvider'\nimport { useSudoChallenge } from '../../../components/hooks/useSudoChallenge'\n\ntype SudoConfigRow = {\n id: string\n tenantId: string | null\n tenantName: string | null\n organizationId: string | null\n organizationName: string | null\n label: string | null\n targetIdentifier: string\n isEnabled: boolean\n isDeveloperDefault: boolean\n ttlSeconds: number\n challengeMethod: ChallengeMethod\n configuredBy: string | null\n createdAt: string\n updatedAt: string\n}\n\ntype SudoConfigListResponse = {\n items: SudoConfigRow[]\n}\n\nfunction renderScopeLabel(row: SudoConfigRow, platformLabel: string) {\n if (!row.tenantId && !row.organizationId) {\n return <span>{platformLabel}</span>\n }\n const tenantLabel = row.tenantName ?? row.tenantId ?? '-'\n if (!row.organizationId) {\n return <span className=\"whitespace-normal break-words\">{tenantLabel}</span>\n }\n const orgLabel = row.organizationName ?? row.organizationId ?? '-'\n return (\n <div className=\"space-y-1 whitespace-normal break-words\">\n <div>{tenantLabel}</div>\n <div className=\"text-muted-foreground\">{orgLabel}</div>\n </div>\n )\n}\n\nfunction SecuritySudoPageInner() {\n const t = useT()\n const { requireSudo } = useSudoChallenge()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'security-sudo-management',\n })\n\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [rows, setRows] = React.useState<SudoConfigRow[]>([])\n const [saving, setSaving] = React.useState(false)\n\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { retryLastMutation },\n })\n },\n [retryLastMutation, runMutation],\n )\n\n const loadRows = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<SudoConfigListResponse>('/api/security/sudo/configs')\n if (!response.ok || !response.result) {\n setRows([])\n setError(t('security.admin.sudo.errors.load', 'Failed to load sudo configuration.'))\n setLoading(false)\n return\n }\n setRows(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadRows()\n }, [loadRows])\n\n const requestToken = React.useCallback(async () => {\n const sudoToken = await requireSudo('security.sudo.manage')\n if (!sudoToken) {\n flash(t('security.admin.sudo.flash.cancelled', 'Sudo challenge cancelled.'), 'error')\n return null\n }\n return sudoToken\n }, [requireSudo, t])\n\n const handleDelete = React.useCallback(async (row: SudoConfigRow) => {\n const accepted = await confirm({\n title: t('security.admin.sudo.delete.title', 'Delete sudo rule?'),\n text: t('security.admin.sudo.delete.text', 'This removes the selected sudo protection rule.'),\n variant: 'destructive',\n confirmText: t('ui.actions.delete', 'Delete'),\n cancelText: t('ui.actions.cancel', 'Cancel'),\n })\n if (!accepted) return\n\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSaving(true)\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/security/sudo/configs/${encodeURIComponent(row.id)}`, {\n method: 'DELETE',\n headers: { 'x-sudo-token': sudoToken },\n }),\n { id: row.id },\n )\n flash(t('security.admin.sudo.flash.deleted', 'Sudo configuration deleted.'), 'success')\n await loadRows()\n } catch {\n flash(t('security.admin.sudo.flash.deleteError', 'Failed to delete sudo configuration.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [confirm, loadRows, requestToken, runMutationWithContext, t])\n\n const columns = React.useMemo<ColumnDef<SudoConfigRow>[]>(() => [\n {\n accessorKey: 'label',\n header: t('security.admin.sudo.table.label', 'Label'),\n cell: ({ row }) => row.original.label ?? '\u2014',\n },\n {\n accessorKey: 'targetIdentifier',\n header: t('security.admin.sudo.table.targetIdentifier', 'Target'),\n },\n {\n id: 'scope',\n header: t('security.admin.sudo.table.scope', 'Scope'),\n cell: ({ row }) => renderScopeLabel(row.original, t('security.admin.sudo.scope.platform', 'Platform')),\n },\n {\n accessorKey: 'challengeMethod',\n header: t('security.admin.sudo.table.challengeMethod', 'Method'),\n cell: ({ row }) => t(`security.admin.sudo.challengeMethod.${row.original.challengeMethod}`, row.original.challengeMethod),\n },\n {\n accessorKey: 'ttlSeconds',\n header: t('security.admin.sudo.table.ttl', 'TTL'),\n cell: ({ row }) => t('security.admin.sudo.table.ttlValue', '{value}s', { value: String(row.original.ttlSeconds) }),\n },\n {\n id: 'source',\n header: t('security.admin.sudo.table.source', 'Source'),\n cell: ({ row }) => row.original.isDeveloperDefault\n ? t('security.admin.sudo.source.developer', 'Developer default')\n : t('security.admin.sudo.source.admin', 'Admin override'),\n },\n {\n accessorKey: 'isEnabled',\n header: t('security.admin.sudo.table.status', 'Status'),\n cell: ({ row }) => row.original.isEnabled\n ? t('security.admin.sudo.status.enabled', 'Enabled')\n : t('security.admin.sudo.status.disabled', 'Disabled'),\n },\n {\n id: 'actions',\n header: t('security.admin.sudo.table.actions', 'Actions'),\n cell: ({ row }) => {\n const isDeveloperDefault = row.original.isDeveloperDefault\n return (\n <div className=\"flex items-center gap-2\">\n {isDeveloperDefault ? (\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n disabled\n >\n <Pencil className=\"size-4\" />\n </IconButton>\n ) : (\n <IconButton\n asChild\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n >\n <Link href={`/backend/security/sudo/${row.original.id}/edit`}>\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n )}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.delete', 'Delete')}\n disabled={isDeveloperDefault}\n onClick={() => void handleDelete(row.original)}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n )\n },\n },\n ], [handleDelete, t])\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div className=\"rounded-xl border bg-muted/30 p-4\">\n <div className=\"flex items-start gap-3\">\n <ShieldAlert className=\"mt-0.5 size-5 text-amber-600\" />\n <div className=\"space-y-1\">\n <h2 className=\"text-sm font-semibold\">\n {t('security.admin.sudo.notice.title', 'Sensitive operations require re-authentication')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'security.admin.sudo.notice.description',\n 'Use sudo rules to require a fresh password or MFA challenge for selected features, routes, modules, or packages.',\n )}\n </p>\n </div>\n </div>\n </div>\n\n <DataTable<SudoConfigRow>\n title={t('security.admin.sudo.title', 'Sudo protection')}\n titleHeadingLevel={1}\n columns={columns}\n data={rows}\n actions={(\n <Button\n asChild\n variant=\"outline\"\n size=\"sm\"\n >\n <Link href=\"/backend/security/sudo/create\">\n <Plus className=\"mr-2 size-4\" />\n {t('security.admin.sudo.actions.add', 'Add rule')}\n </Link>\n </Button>\n )}\n perspective={{ tableId: extensionPoints.hosts.sudoTable.tableId }}\n isLoading={loading}\n error={error ? <span>{error}</span> : null}\n />\n\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n\nexport default function SecuritySudoPage() {\n return (\n <SudoProvider>\n <SecuritySudoPageInner />\n </SudoProvider>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/security/backend/security/users/page.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport { useRouter } from 'next/navigation.js'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport MfaComplianceBadge from '../../../components/MfaComplianceBadge'\nimport type { ComplianceItem, ComplianceResponse } from './_shared'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nconst PAGE_SIZE = 20\n\nfunction formatLastLogin(value: string | undefined, fallback: string): string {\n if (!value) return fallback\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return fallback\n return parsed.toLocaleString()\n}\n\nexport default function SecurityUsersPage() {\n const router = useRouter()\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'email', desc: false }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [items, setItems] = React.useState<ComplianceItem[]>([])\n\n const loadUsers = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<ComplianceResponse>('/api/security/users/mfa/compliance')\n if (!response.ok || !response.result) {\n setItems([])\n setError(t('security.admin.users.errors.load', 'Failed to load user compliance data.'))\n setLoading(false)\n return\n }\n\n setItems(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadUsers()\n }, [loadUsers])\n\n const filteredItems = React.useMemo(() => {\n const normalizedSearch = search.trim().toLowerCase()\n const enrolledFilter = typeof filterValues.enrolled === 'string' ? filterValues.enrolled : ''\n const compliantFilter = typeof filterValues.compliant === 'string' ? filterValues.compliant : ''\n\n return items.filter((item) => {\n if (enrolledFilter === 'true' && !item.enrolled) return false\n if (enrolledFilter === 'false' && item.enrolled) return false\n if (compliantFilter === 'true' && !item.compliant) return false\n if (compliantFilter === 'false' && item.compliant) return false\n\n if (!normalizedSearch) return true\n return (\n item.userId.toLowerCase().includes(normalizedSearch)\n || item.email.toLowerCase().includes(normalizedSearch)\n )\n })\n }, [filterValues.compliant, filterValues.enrolled, items, search])\n\n const total = filteredItems.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n\n React.useEffect(() => {\n if (page > totalPages) {\n setPage(totalPages)\n }\n }, [page, totalPages])\n\n const pagedItems = React.useMemo(() => {\n const start = (page - 1) * PAGE_SIZE\n return filteredItems.slice(start, start + PAGE_SIZE)\n }, [filteredItems, page])\n\n const columns = React.useMemo<ColumnDef<ComplianceItem>[]>(() => [\n {\n accessorKey: 'email',\n header: t('security.admin.users.table.email', 'Email'),\n },\n {\n id: 'mfaStatus',\n header: t('security.admin.users.table.mfaStatus', 'MFA status'),\n cell: ({ row }) => row.original.enrolled\n ? t('security.admin.users.mfa.enabled', 'Enabled')\n : t('security.admin.users.mfa.disabled', 'Not enrolled'),\n },\n {\n id: 'compliance',\n header: t('security.admin.users.table.compliance', 'Compliance'),\n cell: ({ row }) => (\n <MfaComplianceBadge enrolled={row.original.enrolled} compliant={row.original.compliant} />\n ),\n },\n {\n id: 'lastLogin',\n header: t('security.admin.users.table.lastLogin', 'Last login'),\n cell: ({ row }) => formatLastLogin(\n row.original.lastLoginAt,\n t('security.admin.users.lastLogin.unknown', 'Unknown'),\n ),\n },\n ], [t])\n\n const filterDefs = React.useMemo<FilterDef[]>(() => [\n {\n id: 'enrolled',\n label: t('security.admin.users.table.mfaStatus', 'MFA status'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'true', label: t('security.admin.users.mfa.enabled', 'Enabled') },\n { value: 'false', label: t('security.admin.users.mfa.disabled', 'Not enrolled') },\n ],\n },\n {\n id: 'compliant',\n label: t('security.admin.users.table.compliance', 'Compliance'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'true', label: t('ui.common.yes', 'Yes') },\n { value: 'false', label: t('ui.common.no', 'No') },\n ],\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<ComplianceItem>\n title={t('security.admin.users.table.title', 'Users')}\n columns={columns}\n data={pagedItems}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('security.admin.users.search', 'Search users...')}\n filters={filterDefs}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n onRowClick={(item) => {\n router.push(`/backend/security/users/${encodeURIComponent(item.userId)}`)\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={{\n label: t('ui.actions.refresh', 'Refresh'),\n onRefresh: () => void loadUsers(),\n isRefreshing: loading,\n }}\n perspective={{ tableId: extensionPoints.hosts.usersTable.tableId }}\n isLoading={loading}\n error={error ? (\n <div className=\"flex items-center justify-center gap-3\">\n <span>{error}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadUsers()}>\n {t('ui.actions.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n emptyState={t('security.admin.users.empty', 'No users found for this tenant.')}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AAwGQ,
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport { useRouter } from 'next/navigation.js'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport MfaComplianceBadge from '../../../components/MfaComplianceBadge'\nimport type { ComplianceItem, ComplianceResponse } from './_shared'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nconst PAGE_SIZE = 20\n\nfunction formatLastLogin(value: string | undefined, fallback: string): string {\n if (!value) return fallback\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return fallback\n return parsed.toLocaleString()\n}\n\nexport default function SecurityUsersPage() {\n const router = useRouter()\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'email', desc: false }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [items, setItems] = React.useState<ComplianceItem[]>([])\n\n const loadUsers = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<ComplianceResponse>('/api/security/users/mfa/compliance')\n if (!response.ok || !response.result) {\n setItems([])\n setError(t('security.admin.users.errors.load', 'Failed to load user compliance data.'))\n setLoading(false)\n return\n }\n\n setItems(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadUsers()\n }, [loadUsers])\n\n const filteredItems = React.useMemo(() => {\n const normalizedSearch = search.trim().toLowerCase()\n const enrolledFilter = typeof filterValues.enrolled === 'string' ? filterValues.enrolled : ''\n const compliantFilter = typeof filterValues.compliant === 'string' ? filterValues.compliant : ''\n\n return items.filter((item) => {\n if (enrolledFilter === 'true' && !item.enrolled) return false\n if (enrolledFilter === 'false' && item.enrolled) return false\n if (compliantFilter === 'true' && !item.compliant) return false\n if (compliantFilter === 'false' && item.compliant) return false\n\n if (!normalizedSearch) return true\n return (\n item.userId.toLowerCase().includes(normalizedSearch)\n || item.email.toLowerCase().includes(normalizedSearch)\n )\n })\n }, [filterValues.compliant, filterValues.enrolled, items, search])\n\n const total = filteredItems.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n\n React.useEffect(() => {\n if (page > totalPages) {\n setPage(totalPages)\n }\n }, [page, totalPages])\n\n const pagedItems = React.useMemo(() => {\n const start = (page - 1) * PAGE_SIZE\n return filteredItems.slice(start, start + PAGE_SIZE)\n }, [filteredItems, page])\n\n const columns = React.useMemo<ColumnDef<ComplianceItem>[]>(() => [\n {\n accessorKey: 'email',\n header: t('security.admin.users.table.email', 'Email'),\n },\n {\n id: 'mfaStatus',\n header: t('security.admin.users.table.mfaStatus', 'MFA status'),\n cell: ({ row }) => row.original.enrolled\n ? t('security.admin.users.mfa.enabled', 'Enabled')\n : t('security.admin.users.mfa.disabled', 'Not enrolled'),\n },\n {\n id: 'compliance',\n header: t('security.admin.users.table.compliance', 'Compliance'),\n cell: ({ row }) => (\n <MfaComplianceBadge enrolled={row.original.enrolled} compliant={row.original.compliant} />\n ),\n },\n {\n id: 'lastLogin',\n header: t('security.admin.users.table.lastLogin', 'Last login'),\n cell: ({ row }) => formatLastLogin(\n row.original.lastLoginAt,\n t('security.admin.users.lastLogin.unknown', 'Unknown'),\n ),\n },\n ], [t])\n\n const filterDefs = React.useMemo<FilterDef[]>(() => [\n {\n id: 'enrolled',\n label: t('security.admin.users.table.mfaStatus', 'MFA status'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'true', label: t('security.admin.users.mfa.enabled', 'Enabled') },\n { value: 'false', label: t('security.admin.users.mfa.disabled', 'Not enrolled') },\n ],\n },\n {\n id: 'compliant',\n label: t('security.admin.users.table.compliance', 'Compliance'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'true', label: t('ui.common.yes', 'Yes') },\n { value: 'false', label: t('ui.common.no', 'No') },\n ],\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<ComplianceItem>\n title={t('security.admin.users.table.title', 'Users')}\n titleHeadingLevel={1}\n columns={columns}\n data={pagedItems}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('security.admin.users.search', 'Search users...')}\n filters={filterDefs}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n onRowClick={(item) => {\n router.push(`/backend/security/users/${encodeURIComponent(item.userId)}`)\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={{\n label: t('ui.actions.refresh', 'Refresh'),\n onRefresh: () => void loadUsers(),\n isRefreshing: loading,\n }}\n perspective={{ tableId: extensionPoints.hosts.usersTable.tableId }}\n isLoading={loading}\n error={error ? (\n <div className=\"flex items-center justify-center gap-3\">\n <span>{error}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadUsers()}>\n {t('ui.actions.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n emptyState={t('security.admin.users.empty', 'No users found for this tenant.')}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -331,6 +331,7 @@ function EnforcementPolicyForm({
|
|
|
331
331
|
CrudForm,
|
|
332
332
|
{
|
|
333
333
|
title: value?.id ? t("security.admin.enforcement.form.titleEdit", "Edit enforcement policy") : t("security.admin.enforcement.form.titleCreate", "Create enforcement policy"),
|
|
334
|
+
titleHeadingLevel: 1,
|
|
334
335
|
backHref,
|
|
335
336
|
schema,
|
|
336
337
|
formId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/components/EnforcementPolicyForm.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { CrudForm, type CrudCustomFieldRenderProps, type CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\n\nexport type EnforcementScope = 'platform' | 'tenant' | 'organisation'\n\nexport type EnforcementPolicyFormValue = {\n id?: string\n scope: EnforcementScope\n tenantId: string\n organizationId: string\n isEnforced: boolean\n allowedMethods: string[]\n enforcementDeadline: string\n}\ntype EnforcementPolicyFormProps = {\n value?: Partial<EnforcementPolicyFormValue> | null\n submitting: boolean\n onSubmit: (value: EnforcementPolicyFormValue) => Promise<void> | void\n onCancel: () => void\n backHref: string\n}\n\nconst METHOD_OPTIONS = ['totp', 'passkey', 'otp_email'] as const\nconst METHOD_OPTION_VALUES = [...METHOD_OPTIONS] as [typeof METHOD_OPTIONS[number], ...typeof METHOD_OPTIONS[number][]]\nconst METHOD_DESCRIPTION_KEYS: Record<typeof METHOD_OPTIONS[number], { key: string; fallback: string }> = {\n totp: {\n key: 'security.admin.enforcement.methods.totpDescription',\n fallback: 'Authenticator app code (TOTP).',\n },\n passkey: {\n key: 'security.admin.enforcement.methods.passkeyDescription',\n fallback: 'Biometric or security key passkey.',\n },\n otp_email: {\n key: 'security.admin.enforcement.methods.otpEmailDescription',\n fallback: 'One-time verification code sent by email.',\n },\n}\n\nfunction toDatetimeLocal(value: string | null | undefined): string {\n if (!value) return ''\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return ''\n\n const year = parsed.getFullYear()\n const month = String(parsed.getMonth() + 1).padStart(2, '0')\n const day = String(parsed.getDate()).padStart(2, '0')\n const hours = String(parsed.getHours()).padStart(2, '0')\n const minutes = String(parsed.getMinutes()).padStart(2, '0')\n\n return `${year}-${month}-${day}T${hours}:${minutes}`\n}\n\nfunction ScopeTenantField({ value, setValue, values, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const scope = values?.scope\n if (scope !== 'tenant' && scope !== 'organisation') return null\n\n const normalizedValue = typeof value === 'string' && value.trim().length > 0 ? value : null\n\n return (\n <TenantSelect\n id=\"enforcement-tenant-id\"\n value={normalizedValue}\n onChange={(next) => setValue(next ?? '')}\n disabled={disabled}\n includeEmptyOption\n emptyOptionLabel={t('security.admin.enforcement.form.tenantPlaceholder', 'Select tenant')}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n />\n )\n}\n\nfunction ScopeOrganizationField({ value, setValue, values, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const scope = values?.scope\n const organizationValue = typeof value === 'string' && value.trim().length > 0 ? value : null\n const tenantId = typeof values?.tenantId === 'string' && values?.tenantId?.trim().length > 0 ? values?.tenantId : null\n const previousTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n previousTenantRef.current = tenantId\n return\n }\n if (previousTenantRef.current !== tenantId) {\n previousTenantRef.current = tenantId\n setValue('')\n }\n }, [setValue, tenantId])\n\n if (scope !== 'organisation') return null\n\n return (\n <OrganizationSelect\n id=\"enforcement-organization-id\"\n value={organizationValue}\n onChange={(next) => setValue(next ?? '')}\n disabled={disabled || !tenantId}\n includeEmptyOption\n emptyOptionLabel={t('security.admin.enforcement.form.organizationPlaceholder', 'Select organization')}\n tenantId={tenantId}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n />\n )\n}\n\nfunction AllowedMethodsField({ value, setValue, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const selectedMethods = React.useMemo(() => {\n if (!Array.isArray(value)) return []\n return value\n .filter((item): item is typeof METHOD_OPTIONS[number] => typeof item === 'string' && METHOD_OPTIONS.includes(item as typeof METHOD_OPTIONS[number]))\n }, [value])\n const [mode, setMode] = React.useState<'all' | 'restricted'>(() => (selectedMethods.length > 0 ? 'restricted' : 'all'))\n\n React.useEffect(() => {\n setMode(selectedMethods.length > 0 ? 'restricted' : 'all')\n }, [selectedMethods.length])\n\n const setAllMethods = React.useCallback(() => {\n if (disabled) return\n setMode('all')\n setValue([])\n }, [disabled, setValue])\n\n const setRestrictedMethods = React.useCallback(() => {\n if (disabled) return\n setMode('restricted')\n if (selectedMethods.length === 0) {\n setValue([METHOD_OPTIONS[0]])\n }\n }, [disabled, selectedMethods.length, setValue])\n\n const toggleMethod = React.useCallback((method: typeof METHOD_OPTIONS[number], checked: boolean) => {\n if (disabled) return\n if (!checked) {\n if (selectedMethods.length <= 1) return\n setValue(selectedMethods.filter((item) => item !== method))\n return\n }\n if (selectedMethods.includes(method)) return\n setValue([...selectedMethods, method])\n }, [disabled, selectedMethods, setValue])\n\n return (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <div className=\"flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant={mode === 'all' ? 'default' : 'outline'}\n onClick={setAllMethods}\n disabled={disabled}\n >\n {t('security.admin.enforcement.form.allowedMethodsAll', 'Allow all methods')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant={mode === 'restricted' ? 'default' : 'outline'}\n onClick={setRestrictedMethods}\n disabled={disabled}\n >\n {t('security.admin.enforcement.form.allowedMethodsRestricted', 'Restrict to selected methods')}\n </Button>\n </div>\n\n {mode === 'restricted' ? (\n <div className=\"space-y-2\">\n {METHOD_OPTIONS.map((method) => {\n const isChecked = selectedMethods.includes(method)\n const isLastSelected = isChecked && selectedMethods.length === 1\n return (\n <label\n key={method}\n className=\"flex items-start gap-3 rounded-md border bg-background px-3 py-2\"\n >\n <Checkbox\n checked={isChecked}\n onCheckedChange={(checked) => toggleMethod(method, checked === true)}\n disabled={disabled || isLastSelected}\n />\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">\n {t(`security.admin.enforcement.methods.${method}`, method)}\n </span>\n <span className=\"block text-xs text-muted-foreground\">\n {t(METHOD_DESCRIPTION_KEYS[method].key, METHOD_DESCRIPTION_KEYS[method].fallback)}\n </span>\n </span>\n </label>\n )\n })}\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'security.admin.enforcement.form.allowedMethodsHint',\n 'If no methods are selected, all available methods are allowed.',\n )}\n </p>\n )}\n </div>\n )\n}\n\nexport default function EnforcementPolicyForm({\n value,\n backHref,\n submitting = false,\n onSubmit,\n onCancel,\n}: EnforcementPolicyFormProps) {\n const t = useT()\n const formId = 'security-enforcement-policy-form'\n\n const initialValues = React.useMemo<EnforcementPolicyFormValue>(\n () => ({\n id: value?.id,\n scope: value?.scope === 'tenant' || value?.scope === 'organisation' ? value.scope : 'platform',\n tenantId: value?.tenantId ?? '',\n organizationId: value?.organizationId ?? '',\n isEnforced: value?.isEnforced ?? true,\n enforcementDeadline: toDatetimeLocal(value?.enforcementDeadline),\n allowedMethods: Array.isArray(value?.allowedMethods) ? value.allowedMethods : [],\n }),\n [value],\n )\n const [selectedScope, setSelectedScope] = React.useState<EnforcementScope>(initialValues.scope)\n\n React.useEffect(() => {\n setSelectedScope(initialValues.scope)\n }, [initialValues.scope])\n\n const schema = React.useMemo(\n () =>\n z\n .object({\n scope: z.enum(['platform', 'tenant', 'organisation']),\n tenantId: z.string().optional().default(''),\n organizationId: z.string().optional().default(''),\n isEnforced: z.boolean().default(true),\n allowedMethods: z.array(z.enum(METHOD_OPTION_VALUES)).default([]),\n enforcementDeadline: z.string().optional().default(''),\n })\n .superRefine((values, context) => {\n const tenantId = values.tenantId.trim()\n const organizationId = values.organizationId.trim()\n\n if (values.scope !== 'platform' && !tenantId) {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: t(\n 'security.admin.enforcement.form.errors.tenantRequired',\n 'Tenant ID is required for tenant and organisation scopes.',\n ),\n path: ['tenantId'],\n })\n }\n\n if (values.scope === 'organisation' && !organizationId) {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: t(\n 'security.admin.enforcement.form.errors.organizationRequired',\n 'Organization ID is required for organisation scope.',\n ),\n path: ['organizationId'],\n })\n }\n }),\n [t],\n )\n\n type EnforcementPolicyCrudValues = z.infer<typeof schema>\n\n const fields = React.useMemo<CrudField[]>(\n () => {\n const baseFields: CrudField[] = [\n {\n id: 'scope',\n label: t('security.admin.enforcement.form.scope', 'Scope'),\n type: 'custom',\n required: true,\n disabled: submitting,\n layout: 'half',\n component: ({ value, setValue, disabled, autoFocus }) => {\n const resolvedScope = value === 'tenant' || value === 'organisation' ? value : 'platform'\n return (\n <select\n className=\"w-full h-9 rounded border px-2 text-sm\"\n value={resolvedScope}\n onChange={(event) => {\n const next = event.target.value === 'tenant' || event.target.value === 'organisation'\n ? event.target.value\n : 'platform'\n setValue(next)\n setSelectedScope(next)\n }}\n autoFocus={autoFocus}\n disabled={disabled}\n data-crud-focus-target=\"\"\n >\n <option value=\"platform\">{t('security.admin.enforcement.scope.platform', 'Platform')}</option>\n <option value=\"tenant\">{t('security.admin.enforcement.scope.tenant', 'Tenant')}</option>\n <option value=\"organisation\">{t('security.admin.enforcement.scope.organisation', 'Organisation')}</option>\n </select>\n )\n },\n },\n {\n id: 'enforcementDeadline',\n label: t('security.admin.enforcement.form.deadline', 'Enforcement deadline'),\n type: 'datetime-local',\n disabled: submitting,\n layout: 'half',\n },\n ]\n\n if (selectedScope !== 'platform') {\n baseFields.push({\n id: 'tenantId',\n label: t('security.admin.enforcement.form.tenant', 'Tenant'),\n type: 'custom',\n component: ScopeTenantField,\n disabled: submitting,\n })\n }\n\n if (selectedScope === 'organisation') {\n baseFields.push({\n id: 'organizationId',\n label: t('security.admin.enforcement.form.organization', 'Organization'),\n type: 'custom',\n component: ScopeOrganizationField,\n disabled: submitting,\n })\n }\n\n baseFields.push(\n {\n id: 'isEnforced',\n label: t('security.admin.enforcement.form.enabled', 'Policy enforced'),\n type: 'checkbox',\n disabled: submitting,\n },\n {\n id: 'allowedMethods',\n label: t('security.admin.enforcement.form.allowedMethods', 'Allowed methods'),\n type: 'custom',\n component: AllowedMethodsField,\n disabled: submitting,\n },\n )\n\n return baseFields\n },\n [selectedScope, submitting, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: EnforcementPolicyCrudValues) => {\n const tenantId = values.tenantId.trim()\n const organizationId = values.organizationId.trim()\n\n await onSubmit({\n id: value?.id,\n scope: values.scope as EnforcementScope,\n tenantId,\n organizationId,\n isEnforced: values.isEnforced,\n enforcementDeadline: values.enforcementDeadline,\n allowedMethods: values.allowedMethods,\n })\n },\n [onSubmit, value?.id],\n )\n\n return (\n <CrudForm<EnforcementPolicyCrudValues>\n title={\n value?.id\n ? t('security.admin.enforcement.form.titleEdit', 'Edit enforcement policy')\n : t('security.admin.enforcement.form.titleCreate', 'Create enforcement policy')\n }\n backHref={backHref}\n schema={schema}\n formId={formId}\n fields={fields}\n initialValues={initialValues as Partial<EnforcementPolicyCrudValues>}\n hideFooterActions\n embedded={false}\n onSubmit={handleSubmit}\n extraActions={(\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={submitting}>\n {t('ui.actions.cancel', 'Cancel')}\n </Button>\n )}\n />\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { CrudForm, type CrudCustomFieldRenderProps, type CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\n\nexport type EnforcementScope = 'platform' | 'tenant' | 'organisation'\n\nexport type EnforcementPolicyFormValue = {\n id?: string\n scope: EnforcementScope\n tenantId: string\n organizationId: string\n isEnforced: boolean\n allowedMethods: string[]\n enforcementDeadline: string\n}\ntype EnforcementPolicyFormProps = {\n value?: Partial<EnforcementPolicyFormValue> | null\n submitting: boolean\n onSubmit: (value: EnforcementPolicyFormValue) => Promise<void> | void\n onCancel: () => void\n backHref: string\n}\n\nconst METHOD_OPTIONS = ['totp', 'passkey', 'otp_email'] as const\nconst METHOD_OPTION_VALUES = [...METHOD_OPTIONS] as [typeof METHOD_OPTIONS[number], ...typeof METHOD_OPTIONS[number][]]\nconst METHOD_DESCRIPTION_KEYS: Record<typeof METHOD_OPTIONS[number], { key: string; fallback: string }> = {\n totp: {\n key: 'security.admin.enforcement.methods.totpDescription',\n fallback: 'Authenticator app code (TOTP).',\n },\n passkey: {\n key: 'security.admin.enforcement.methods.passkeyDescription',\n fallback: 'Biometric or security key passkey.',\n },\n otp_email: {\n key: 'security.admin.enforcement.methods.otpEmailDescription',\n fallback: 'One-time verification code sent by email.',\n },\n}\n\nfunction toDatetimeLocal(value: string | null | undefined): string {\n if (!value) return ''\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return ''\n\n const year = parsed.getFullYear()\n const month = String(parsed.getMonth() + 1).padStart(2, '0')\n const day = String(parsed.getDate()).padStart(2, '0')\n const hours = String(parsed.getHours()).padStart(2, '0')\n const minutes = String(parsed.getMinutes()).padStart(2, '0')\n\n return `${year}-${month}-${day}T${hours}:${minutes}`\n}\n\nfunction ScopeTenantField({ value, setValue, values, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const scope = values?.scope\n if (scope !== 'tenant' && scope !== 'organisation') return null\n\n const normalizedValue = typeof value === 'string' && value.trim().length > 0 ? value : null\n\n return (\n <TenantSelect\n id=\"enforcement-tenant-id\"\n value={normalizedValue}\n onChange={(next) => setValue(next ?? '')}\n disabled={disabled}\n includeEmptyOption\n emptyOptionLabel={t('security.admin.enforcement.form.tenantPlaceholder', 'Select tenant')}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n />\n )\n}\n\nfunction ScopeOrganizationField({ value, setValue, values, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const scope = values?.scope\n const organizationValue = typeof value === 'string' && value.trim().length > 0 ? value : null\n const tenantId = typeof values?.tenantId === 'string' && values?.tenantId?.trim().length > 0 ? values?.tenantId : null\n const previousTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n previousTenantRef.current = tenantId\n return\n }\n if (previousTenantRef.current !== tenantId) {\n previousTenantRef.current = tenantId\n setValue('')\n }\n }, [setValue, tenantId])\n\n if (scope !== 'organisation') return null\n\n return (\n <OrganizationSelect\n id=\"enforcement-organization-id\"\n value={organizationValue}\n onChange={(next) => setValue(next ?? '')}\n disabled={disabled || !tenantId}\n includeEmptyOption\n emptyOptionLabel={t('security.admin.enforcement.form.organizationPlaceholder', 'Select organization')}\n tenantId={tenantId}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n />\n )\n}\n\nfunction AllowedMethodsField({ value, setValue, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const selectedMethods = React.useMemo(() => {\n if (!Array.isArray(value)) return []\n return value\n .filter((item): item is typeof METHOD_OPTIONS[number] => typeof item === 'string' && METHOD_OPTIONS.includes(item as typeof METHOD_OPTIONS[number]))\n }, [value])\n const [mode, setMode] = React.useState<'all' | 'restricted'>(() => (selectedMethods.length > 0 ? 'restricted' : 'all'))\n\n React.useEffect(() => {\n setMode(selectedMethods.length > 0 ? 'restricted' : 'all')\n }, [selectedMethods.length])\n\n const setAllMethods = React.useCallback(() => {\n if (disabled) return\n setMode('all')\n setValue([])\n }, [disabled, setValue])\n\n const setRestrictedMethods = React.useCallback(() => {\n if (disabled) return\n setMode('restricted')\n if (selectedMethods.length === 0) {\n setValue([METHOD_OPTIONS[0]])\n }\n }, [disabled, selectedMethods.length, setValue])\n\n const toggleMethod = React.useCallback((method: typeof METHOD_OPTIONS[number], checked: boolean) => {\n if (disabled) return\n if (!checked) {\n if (selectedMethods.length <= 1) return\n setValue(selectedMethods.filter((item) => item !== method))\n return\n }\n if (selectedMethods.includes(method)) return\n setValue([...selectedMethods, method])\n }, [disabled, selectedMethods, setValue])\n\n return (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <div className=\"flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant={mode === 'all' ? 'default' : 'outline'}\n onClick={setAllMethods}\n disabled={disabled}\n >\n {t('security.admin.enforcement.form.allowedMethodsAll', 'Allow all methods')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant={mode === 'restricted' ? 'default' : 'outline'}\n onClick={setRestrictedMethods}\n disabled={disabled}\n >\n {t('security.admin.enforcement.form.allowedMethodsRestricted', 'Restrict to selected methods')}\n </Button>\n </div>\n\n {mode === 'restricted' ? (\n <div className=\"space-y-2\">\n {METHOD_OPTIONS.map((method) => {\n const isChecked = selectedMethods.includes(method)\n const isLastSelected = isChecked && selectedMethods.length === 1\n return (\n <label\n key={method}\n className=\"flex items-start gap-3 rounded-md border bg-background px-3 py-2\"\n >\n <Checkbox\n checked={isChecked}\n onCheckedChange={(checked) => toggleMethod(method, checked === true)}\n disabled={disabled || isLastSelected}\n />\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">\n {t(`security.admin.enforcement.methods.${method}`, method)}\n </span>\n <span className=\"block text-xs text-muted-foreground\">\n {t(METHOD_DESCRIPTION_KEYS[method].key, METHOD_DESCRIPTION_KEYS[method].fallback)}\n </span>\n </span>\n </label>\n )\n })}\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'security.admin.enforcement.form.allowedMethodsHint',\n 'If no methods are selected, all available methods are allowed.',\n )}\n </p>\n )}\n </div>\n )\n}\n\nexport default function EnforcementPolicyForm({\n value,\n backHref,\n submitting = false,\n onSubmit,\n onCancel,\n}: EnforcementPolicyFormProps) {\n const t = useT()\n const formId = 'security-enforcement-policy-form'\n\n const initialValues = React.useMemo<EnforcementPolicyFormValue>(\n () => ({\n id: value?.id,\n scope: value?.scope === 'tenant' || value?.scope === 'organisation' ? value.scope : 'platform',\n tenantId: value?.tenantId ?? '',\n organizationId: value?.organizationId ?? '',\n isEnforced: value?.isEnforced ?? true,\n enforcementDeadline: toDatetimeLocal(value?.enforcementDeadline),\n allowedMethods: Array.isArray(value?.allowedMethods) ? value.allowedMethods : [],\n }),\n [value],\n )\n const [selectedScope, setSelectedScope] = React.useState<EnforcementScope>(initialValues.scope)\n\n React.useEffect(() => {\n setSelectedScope(initialValues.scope)\n }, [initialValues.scope])\n\n const schema = React.useMemo(\n () =>\n z\n .object({\n scope: z.enum(['platform', 'tenant', 'organisation']),\n tenantId: z.string().optional().default(''),\n organizationId: z.string().optional().default(''),\n isEnforced: z.boolean().default(true),\n allowedMethods: z.array(z.enum(METHOD_OPTION_VALUES)).default([]),\n enforcementDeadline: z.string().optional().default(''),\n })\n .superRefine((values, context) => {\n const tenantId = values.tenantId.trim()\n const organizationId = values.organizationId.trim()\n\n if (values.scope !== 'platform' && !tenantId) {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: t(\n 'security.admin.enforcement.form.errors.tenantRequired',\n 'Tenant ID is required for tenant and organisation scopes.',\n ),\n path: ['tenantId'],\n })\n }\n\n if (values.scope === 'organisation' && !organizationId) {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: t(\n 'security.admin.enforcement.form.errors.organizationRequired',\n 'Organization ID is required for organisation scope.',\n ),\n path: ['organizationId'],\n })\n }\n }),\n [t],\n )\n\n type EnforcementPolicyCrudValues = z.infer<typeof schema>\n\n const fields = React.useMemo<CrudField[]>(\n () => {\n const baseFields: CrudField[] = [\n {\n id: 'scope',\n label: t('security.admin.enforcement.form.scope', 'Scope'),\n type: 'custom',\n required: true,\n disabled: submitting,\n layout: 'half',\n component: ({ value, setValue, disabled, autoFocus }) => {\n const resolvedScope = value === 'tenant' || value === 'organisation' ? value : 'platform'\n return (\n <select\n className=\"w-full h-9 rounded border px-2 text-sm\"\n value={resolvedScope}\n onChange={(event) => {\n const next = event.target.value === 'tenant' || event.target.value === 'organisation'\n ? event.target.value\n : 'platform'\n setValue(next)\n setSelectedScope(next)\n }}\n autoFocus={autoFocus}\n disabled={disabled}\n data-crud-focus-target=\"\"\n >\n <option value=\"platform\">{t('security.admin.enforcement.scope.platform', 'Platform')}</option>\n <option value=\"tenant\">{t('security.admin.enforcement.scope.tenant', 'Tenant')}</option>\n <option value=\"organisation\">{t('security.admin.enforcement.scope.organisation', 'Organisation')}</option>\n </select>\n )\n },\n },\n {\n id: 'enforcementDeadline',\n label: t('security.admin.enforcement.form.deadline', 'Enforcement deadline'),\n type: 'datetime-local',\n disabled: submitting,\n layout: 'half',\n },\n ]\n\n if (selectedScope !== 'platform') {\n baseFields.push({\n id: 'tenantId',\n label: t('security.admin.enforcement.form.tenant', 'Tenant'),\n type: 'custom',\n component: ScopeTenantField,\n disabled: submitting,\n })\n }\n\n if (selectedScope === 'organisation') {\n baseFields.push({\n id: 'organizationId',\n label: t('security.admin.enforcement.form.organization', 'Organization'),\n type: 'custom',\n component: ScopeOrganizationField,\n disabled: submitting,\n })\n }\n\n baseFields.push(\n {\n id: 'isEnforced',\n label: t('security.admin.enforcement.form.enabled', 'Policy enforced'),\n type: 'checkbox',\n disabled: submitting,\n },\n {\n id: 'allowedMethods',\n label: t('security.admin.enforcement.form.allowedMethods', 'Allowed methods'),\n type: 'custom',\n component: AllowedMethodsField,\n disabled: submitting,\n },\n )\n\n return baseFields\n },\n [selectedScope, submitting, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: EnforcementPolicyCrudValues) => {\n const tenantId = values.tenantId.trim()\n const organizationId = values.organizationId.trim()\n\n await onSubmit({\n id: value?.id,\n scope: values.scope as EnforcementScope,\n tenantId,\n organizationId,\n isEnforced: values.isEnforced,\n enforcementDeadline: values.enforcementDeadline,\n allowedMethods: values.allowedMethods,\n })\n },\n [onSubmit, value?.id],\n )\n\n return (\n <CrudForm<EnforcementPolicyCrudValues>\n title={\n value?.id\n ? t('security.admin.enforcement.form.titleEdit', 'Edit enforcement policy')\n : t('security.admin.enforcement.form.titleCreate', 'Create enforcement policy')\n }\n titleHeadingLevel={1}\n backHref={backHref}\n schema={schema}\n formId={formId}\n fields={fields}\n initialValues={initialValues as Partial<EnforcementPolicyCrudValues>}\n hideFooterActions\n embedded={false}\n onSubmit={handleSubmit}\n extraActions={(\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={submitting}>\n {t('ui.actions.cancel', 'Cancel')}\n </Button>\n )}\n />\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["value"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/components/PasswordChangeForm.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { CrudForm, type CrudField, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getPasswordPolicy, getPasswordRequirements, buildPasswordSchema, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { PasswordStrengthStatus } from './PasswordStrengthStatus'\n\ntype PasswordChangeValues = {\n currentPassword: string\n newPassword: string\n confirmPassword: string\n}\n\ntype ChangePasswordResponse = {\n ok: boolean\n}\n\nfunction PasswordInputField({\n value,\n setValue,\n disabled,\n autoFocus,\n}: CrudCustomFieldRenderProps) {\n return (\n <PasswordInput\n className=\"max-w-xl\"\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => setValue(event.target.value)}\n disabled={disabled}\n autoFocus={autoFocus}\n />\n )\n}\n\ntype TrackedPasswordInputProps = CrudCustomFieldRenderProps & {\n onValueChange: (value: string) => void\n}\n\nfunction TrackedPasswordInput({\n value,\n setValue,\n disabled,\n autoFocus,\n onValueChange,\n}: TrackedPasswordInputProps) {\n return (\n <PasswordInput\n className=\"max-w-xl\"\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => {\n const nextValue = event.target.value\n onValueChange(nextValue)\n setValue(nextValue)\n }}\n disabled={disabled}\n autoFocus={autoFocus}\n />\n )\n}\n\nexport default function PasswordChangeForm() {\n const t = useT()\n const [formKey, setFormKey] = React.useState(0)\n const [newPasswordValue, setNewPasswordValue] = React.useState('')\n const formId = 'security-password-change-form'\n\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(() => getPasswordRequirements(passwordPolicy), [passwordPolicy])\n\n const schema = React.useMemo(\n () =>\n z\n .object({\n currentPassword: z.string().min(1, t('security.profile.password.form.errors.currentRequired', 'Current password is required.')),\n newPassword: buildPasswordSchema({\n policy: passwordPolicy,\n message: t('security.profile.password.form.errors.passwordPolicy', 'Password does not meet the requirements.'),\n }),\n confirmPassword: z.string().min(1, t('security.profile.password.form.errors.confirmRequired', 'Please confirm the new password.')),\n })\n .superRefine((values, ctx) => {\n if (values.newPassword !== values.confirmPassword) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: t('security.profile.password.form.errors.passwordMismatch', 'Passwords do not match.'),\n path: ['confirmPassword'],\n })\n }\n }),\n [passwordPolicy, t],\n )\n\n const passwordValidation = React.useMemo(\n () => validatePassword(newPasswordValue, passwordPolicy),\n [newPasswordValue, passwordPolicy],\n )\n\n const hasNewPasswordInput = newPasswordValue.trim().length > 0\n const fields = React.useMemo<CrudField[]>(\n () => [\n {\n id: 'currentPassword',\n label: t('security.profile.password.form.currentPassword', 'Current password'),\n type: 'custom',\n required: true,\n component: PasswordInputField,\n },\n {\n id: 'newPassword',\n label: t('security.profile.password.form.newPassword', 'New password'),\n type: 'custom',\n required: true,\n component: (props) => <TrackedPasswordInput {...props} onValueChange={setNewPasswordValue} />,\n },\n {\n id: 'confirmPassword',\n label: t('security.profile.password.form.confirmPassword', 'Confirm new password'),\n type: 'custom',\n required: true,\n component: PasswordInputField,\n },\n {\n id: 'passwordRequirements',\n label: '',\n type: 'custom',\n component: () => (\n <PasswordStrengthStatus\n hasInput={hasNewPasswordInput}\n passwordPolicy={passwordPolicy}\n passwordRequirements={passwordRequirements}\n passwordValidation={passwordValidation}\n framed={false}\n />\n ),\n },\n ],\n [hasNewPasswordInput, passwordPolicy, passwordRequirements, passwordValidation, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: PasswordChangeValues) => {\n if (!values.currentPassword.trim() || !values.newPassword.trim()) {\n throw createCrudFormError(\n t('security.profile.password.form.errors.required', 'Both current and new password are required.'),\n )\n }\n\n await readApiResultOrThrow<ChangePasswordResponse>(\n '/api/security/profile/password',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n currentPassword: values.currentPassword,\n newPassword: values.newPassword,\n }),\n },\n { errorMessage: t('security.profile.password.form.errors.save', 'Failed to update password.') },\n )\n\n setFormKey((previous) => previous + 1)\n setNewPasswordValue('')\n flash(t('security.profile.password.form.success', 'Password updated.'), 'success')\n },\n [t],\n )\n\n return (\n <CrudForm<PasswordChangeValues>\n title={t('security.profile.password.form.title', 'Change password')}\n key={formKey}\n formId={formId}\n schema={schema}\n fields={fields}\n initialValues={{\n currentPassword: '',\n newPassword: '',\n confirmPassword: '',\n }}\n hideFooterActions\n onSubmit={handleSubmit}\n embedded={false}\n />\n )\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { CrudForm, type CrudField, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getPasswordPolicy, getPasswordRequirements, buildPasswordSchema, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { PasswordStrengthStatus } from './PasswordStrengthStatus'\n\ntype PasswordChangeValues = {\n currentPassword: string\n newPassword: string\n confirmPassword: string\n}\n\ntype ChangePasswordResponse = {\n ok: boolean\n}\n\nfunction PasswordInputField({\n value,\n setValue,\n disabled,\n autoFocus,\n}: CrudCustomFieldRenderProps) {\n return (\n <PasswordInput\n className=\"max-w-xl\"\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => setValue(event.target.value)}\n disabled={disabled}\n autoFocus={autoFocus}\n />\n )\n}\n\ntype TrackedPasswordInputProps = CrudCustomFieldRenderProps & {\n onValueChange: (value: string) => void\n}\n\nfunction TrackedPasswordInput({\n value,\n setValue,\n disabled,\n autoFocus,\n onValueChange,\n}: TrackedPasswordInputProps) {\n return (\n <PasswordInput\n className=\"max-w-xl\"\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => {\n const nextValue = event.target.value\n onValueChange(nextValue)\n setValue(nextValue)\n }}\n disabled={disabled}\n autoFocus={autoFocus}\n />\n )\n}\n\nexport default function PasswordChangeForm() {\n const t = useT()\n const [formKey, setFormKey] = React.useState(0)\n const [newPasswordValue, setNewPasswordValue] = React.useState('')\n const formId = 'security-password-change-form'\n\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(() => getPasswordRequirements(passwordPolicy), [passwordPolicy])\n\n const schema = React.useMemo(\n () =>\n z\n .object({\n currentPassword: z.string().min(1, t('security.profile.password.form.errors.currentRequired', 'Current password is required.')),\n newPassword: buildPasswordSchema({\n policy: passwordPolicy,\n message: t('security.profile.password.form.errors.passwordPolicy', 'Password does not meet the requirements.'),\n }),\n confirmPassword: z.string().min(1, t('security.profile.password.form.errors.confirmRequired', 'Please confirm the new password.')),\n })\n .superRefine((values, ctx) => {\n if (values.newPassword !== values.confirmPassword) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: t('security.profile.password.form.errors.passwordMismatch', 'Passwords do not match.'),\n path: ['confirmPassword'],\n })\n }\n }),\n [passwordPolicy, t],\n )\n\n const passwordValidation = React.useMemo(\n () => validatePassword(newPasswordValue, passwordPolicy),\n [newPasswordValue, passwordPolicy],\n )\n\n const hasNewPasswordInput = newPasswordValue.trim().length > 0\n const fields = React.useMemo<CrudField[]>(\n () => [\n {\n id: 'currentPassword',\n label: t('security.profile.password.form.currentPassword', 'Current password'),\n type: 'custom',\n required: true,\n component: PasswordInputField,\n },\n {\n id: 'newPassword',\n label: t('security.profile.password.form.newPassword', 'New password'),\n type: 'custom',\n required: true,\n component: (props) => <TrackedPasswordInput {...props} onValueChange={setNewPasswordValue} />,\n },\n {\n id: 'confirmPassword',\n label: t('security.profile.password.form.confirmPassword', 'Confirm new password'),\n type: 'custom',\n required: true,\n component: PasswordInputField,\n },\n {\n id: 'passwordRequirements',\n label: '',\n type: 'custom',\n component: () => (\n <PasswordStrengthStatus\n hasInput={hasNewPasswordInput}\n passwordPolicy={passwordPolicy}\n passwordRequirements={passwordRequirements}\n passwordValidation={passwordValidation}\n framed={false}\n />\n ),\n },\n ],\n [hasNewPasswordInput, passwordPolicy, passwordRequirements, passwordValidation, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: PasswordChangeValues) => {\n if (!values.currentPassword.trim() || !values.newPassword.trim()) {\n throw createCrudFormError(\n t('security.profile.password.form.errors.required', 'Both current and new password are required.'),\n )\n }\n\n await readApiResultOrThrow<ChangePasswordResponse>(\n '/api/security/profile/password',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n currentPassword: values.currentPassword,\n newPassword: values.newPassword,\n }),\n },\n { errorMessage: t('security.profile.password.form.errors.save', 'Failed to update password.') },\n )\n\n setFormKey((previous) => previous + 1)\n setNewPasswordValue('')\n flash(t('security.profile.password.form.success', 'Password updated.'), 'success')\n },\n [t],\n )\n\n return (\n <CrudForm<PasswordChangeValues>\n title={t('security.profile.password.form.title', 'Change password')}\n titleHeadingLevel={1}\n key={formKey}\n formId={formId}\n schema={schema}\n fields={fields}\n initialValues={{\n currentPassword: '',\n newPassword: '',\n confirmPassword: '',\n }}\n hideFooterActions\n onSubmit={handleSubmit}\n embedded={false}\n />\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/components/SecurityUserForm.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport MfaComplianceBadge from './MfaComplianceBadge'\n\nexport type SecurityUserMethod = {\n type: string\n label?: string\n lastUsed?: string\n}\n\nexport type SecurityUserFormValue = {\n userId: string\n email: string\n enrolled: boolean\n recoveryCodesRemaining: number\n compliant: boolean\n methods: SecurityUserMethod[]\n resetReason: string\n}\n\ntype SecurityUserFormProps = {\n value: SecurityUserFormValue\n submitting: boolean\n onSubmit: (value: SecurityUserFormValue) => Promise<void> | void\n onCancel: () => void\n}\n\nfunction formatMethodLastUsed(value: string | undefined, fallback: string): string {\n if (!value) return fallback\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return fallback\n return parsed.toLocaleString()\n}\n\nfunction UserSummarySection({ value }: { value: SecurityUserFormValue }) {\n const t = useT()\n return (\n <div className=\"grid gap-2 md:grid-cols-4\">\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.table.email', 'Email')}</p>\n <p className=\"text-sm font-medium break-all\">{value.email}</p>\n </div>\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.detail.recoveryCodes', 'Recovery codes remaining')}</p>\n <p className=\"text-sm font-medium\">{String(value.recoveryCodesRemaining)}</p>\n </div>\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.table.compliance', 'Compliance')}</p>\n <div className=\"pt-1\">\n <MfaComplianceBadge enrolled={value.enrolled} compliant={value.compliant} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction MethodsSection({ methods }: { methods: SecurityUserMethod[] }) {\n const t = useT()\n if (!methods.length) {\n return (\n <p className=\"text-sm text-muted-foreground\">\n {t('security.admin.users.detail.noMethods', 'No active methods.')}\n </p>\n )\n }\n\n return (\n <div className=\"space-y-2\">\n {methods.map((method, index) => (\n <div key={`${method.type}-${index}`} className=\"rounded border bg-background px-3 py-2\">\n <div className=\"grid gap-2 md:grid-cols-3\">\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodType', 'Type')}\n </p>\n <p className=\"text-sm font-medium\">{method.type}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodLabel', 'Label')}\n </p>\n <p className=\"text-sm font-medium\">{method.label ?? '-'}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodLastUsed', 'Last used')}\n </p>\n <p className=\"text-sm font-medium\">\n {formatMethodLastUsed(\n method.lastUsed,\n t('security.admin.users.lastLogin.unknown', 'Unknown'),\n )}\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n )\n}\n\nexport default function SecurityUserForm({\n value,\n submitting,\n onSubmit,\n onCancel,\n}: SecurityUserFormProps) {\n const t = useT()\n const formId = 'security-user-form'\n\n const schema = React.useMemo(\n () => z.object({\n resetReason: z\n .string()\n .trim()\n .min(1, t('security.admin.users.errors.reasonRequired', 'Reset reason is required.')),\n }),\n [t],\n )\n\n type SecurityUserCrudValues = z.infer<typeof schema>\n\n const fields = React.useMemo<CrudField[]>(() => [\n {\n id: 'resetReason',\n label: t('security.admin.users.reset.reason', 'Reset reason'),\n type: 'textarea',\n required: true,\n disabled: submitting,\n description: t('security.admin.users.reset.reasonPlaceholder', 'Enter reason for audit trail'),\n },\n ], [submitting, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'summary',\n column: 1,\n component: () => <UserSummarySection value={value} />,\n },\n {\n id: 'methods',\n column: 1,\n component: () => <MethodsSection methods={value.methods} />,\n },\n {\n id: 'reset',\n column: 1,\n fields: ['resetReason'],\n },\n ], [value])\n\n const handleSubmit = React.useCallback(async (values: SecurityUserCrudValues) => {\n await onSubmit({\n ...value,\n resetReason: values.resetReason.trim(),\n })\n }, [onSubmit, value])\n\n return (\n <CrudForm<SecurityUserCrudValues>\n title={t('security.admin.users.detail.title', 'User MFA detail')}\n backHref=\"/backend/security/users\"\n schema={schema}\n formId={formId}\n fields={fields}\n groups={groups}\n initialValues={{ resetReason: '' }}\n embedded={false}\n submitLabel={t('security.admin.users.actions.resetMfa', 'Reset MFA')}\n onSubmit={handleSubmit}\n extraActions={(\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={submitting}>\n {t('ui.actions.cancel', 'Cancel')}\n </Button>\n )}\n />\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport MfaComplianceBadge from './MfaComplianceBadge'\n\nexport type SecurityUserMethod = {\n type: string\n label?: string\n lastUsed?: string\n}\n\nexport type SecurityUserFormValue = {\n userId: string\n email: string\n enrolled: boolean\n recoveryCodesRemaining: number\n compliant: boolean\n methods: SecurityUserMethod[]\n resetReason: string\n}\n\ntype SecurityUserFormProps = {\n value: SecurityUserFormValue\n submitting: boolean\n onSubmit: (value: SecurityUserFormValue) => Promise<void> | void\n onCancel: () => void\n}\n\nfunction formatMethodLastUsed(value: string | undefined, fallback: string): string {\n if (!value) return fallback\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return fallback\n return parsed.toLocaleString()\n}\n\nfunction UserSummarySection({ value }: { value: SecurityUserFormValue }) {\n const t = useT()\n return (\n <div className=\"grid gap-2 md:grid-cols-4\">\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.table.email', 'Email')}</p>\n <p className=\"text-sm font-medium break-all\">{value.email}</p>\n </div>\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.detail.recoveryCodes', 'Recovery codes remaining')}</p>\n <p className=\"text-sm font-medium\">{String(value.recoveryCodesRemaining)}</p>\n </div>\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.table.compliance', 'Compliance')}</p>\n <div className=\"pt-1\">\n <MfaComplianceBadge enrolled={value.enrolled} compliant={value.compliant} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction MethodsSection({ methods }: { methods: SecurityUserMethod[] }) {\n const t = useT()\n if (!methods.length) {\n return (\n <p className=\"text-sm text-muted-foreground\">\n {t('security.admin.users.detail.noMethods', 'No active methods.')}\n </p>\n )\n }\n\n return (\n <div className=\"space-y-2\">\n {methods.map((method, index) => (\n <div key={`${method.type}-${index}`} className=\"rounded border bg-background px-3 py-2\">\n <div className=\"grid gap-2 md:grid-cols-3\">\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodType', 'Type')}\n </p>\n <p className=\"text-sm font-medium\">{method.type}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodLabel', 'Label')}\n </p>\n <p className=\"text-sm font-medium\">{method.label ?? '-'}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodLastUsed', 'Last used')}\n </p>\n <p className=\"text-sm font-medium\">\n {formatMethodLastUsed(\n method.lastUsed,\n t('security.admin.users.lastLogin.unknown', 'Unknown'),\n )}\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n )\n}\n\nexport default function SecurityUserForm({\n value,\n submitting,\n onSubmit,\n onCancel,\n}: SecurityUserFormProps) {\n const t = useT()\n const formId = 'security-user-form'\n\n const schema = React.useMemo(\n () => z.object({\n resetReason: z\n .string()\n .trim()\n .min(1, t('security.admin.users.errors.reasonRequired', 'Reset reason is required.')),\n }),\n [t],\n )\n\n type SecurityUserCrudValues = z.infer<typeof schema>\n\n const fields = React.useMemo<CrudField[]>(() => [\n {\n id: 'resetReason',\n label: t('security.admin.users.reset.reason', 'Reset reason'),\n type: 'textarea',\n required: true,\n disabled: submitting,\n description: t('security.admin.users.reset.reasonPlaceholder', 'Enter reason for audit trail'),\n },\n ], [submitting, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'summary',\n column: 1,\n component: () => <UserSummarySection value={value} />,\n },\n {\n id: 'methods',\n column: 1,\n component: () => <MethodsSection methods={value.methods} />,\n },\n {\n id: 'reset',\n column: 1,\n fields: ['resetReason'],\n },\n ], [value])\n\n const handleSubmit = React.useCallback(async (values: SecurityUserCrudValues) => {\n await onSubmit({\n ...value,\n resetReason: values.resetReason.trim(),\n })\n }, [onSubmit, value])\n\n return (\n <CrudForm<SecurityUserCrudValues>\n title={t('security.admin.users.detail.title', 'User MFA detail')}\n titleHeadingLevel={1}\n backHref=\"/backend/security/users\"\n schema={schema}\n formId={formId}\n fields={fields}\n groups={groups}\n initialValues={{ resetReason: '' }}\n embedded={false}\n submitLabel={t('security.admin.users.actions.resetMfa', 'Reset MFA')}\n onSubmit={handleSubmit}\n extraActions={(\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={submitting}>\n {t('ui.actions.cancel', 'Cancel')}\n </Button>\n )}\n />\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA2CM,SACE,KADF;AAzCN,YAAY,WAAW;AACvB,SAAS,SAAS;AAClB,SAAS,YAAY;AACrB,SAAS,gBAAoD;AAC7D,SAAS,cAAc;AACvB,OAAO,wBAAwB;AAyB/B,SAAS,qBAAqB,OAA2B,UAA0B;AACjF,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,IAAI,KAAK,KAAK;AAC7B,MAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,EAAG,QAAO;AAC3C,SAAO,OAAO,eAAe;AAC/B;AAEA,SAAS,mBAAmB,EAAE,MAAM,GAAqC;AACvE,QAAM,IAAI,KAAK;AACf,SACE,qBAAC,SAAI,WAAU,6BACb;AAAA,yBAAC,SAAI,WAAU,wCACb;AAAA,0BAAC,OAAE,WAAU,iCAAiC,YAAE,oCAAoC,OAAO,GAAE;AAAA,MAC7F,oBAAC,OAAE,WAAU,iCAAiC,gBAAM,OAAM;AAAA,OAC5D;AAAA,IACA,qBAAC,SAAI,WAAU,wCACb;AAAA,0BAAC,OAAE,WAAU,iCAAiC,YAAE,6CAA6C,0BAA0B,GAAE;AAAA,MACzH,oBAAC,OAAE,WAAU,uBAAuB,iBAAO,MAAM,sBAAsB,GAAE;AAAA,OAC3E;AAAA,IACA,qBAAC,SAAI,WAAU,wCACb;AAAA,0BAAC,OAAE,WAAU,iCAAiC,YAAE,yCAAyC,YAAY,GAAE;AAAA,MACvG,oBAAC,SAAI,WAAU,QACb,8BAAC,sBAAmB,UAAU,MAAM,UAAU,WAAW,MAAM,WAAW,GAC5E;AAAA,OACF;AAAA,KACF;AAEJ;AAEA,SAAS,eAAe,EAAE,QAAQ,GAAsC;AACtE,QAAM,IAAI,KAAK;AACf,MAAI,CAAC,QAAQ,QAAQ;AACnB,WACE,oBAAC,OAAE,WAAU,iCACV,YAAE,yCAAyC,oBAAoB,GAClE;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAU,aACZ,kBAAQ,IAAI,CAAC,QAAQ,UACpB,oBAAC,SAAoC,WAAU,0CAC7C,+BAAC,SAAI,WAAU,6BACb;AAAA,yBAAC,SACC;AAAA,0BAAC,OAAE,WAAU,iCACV,YAAE,0CAA0C,MAAM,GACrD;AAAA,MACA,oBAAC,OAAE,WAAU,uBAAuB,iBAAO,MAAK;AAAA,OAClD;AAAA,IACA,qBAAC,SACC;AAAA,0BAAC,OAAE,WAAU,iCACV,YAAE,2CAA2C,OAAO,GACvD;AAAA,MACA,oBAAC,OAAE,WAAU,uBAAuB,iBAAO,SAAS,KAAI;AAAA,OAC1D;AAAA,IACA,qBAAC,SACC;AAAA,0BAAC,OAAE,WAAU,iCACV,YAAE,8CAA8C,WAAW,GAC9D;AAAA,MACA,oBAAC,OAAE,WAAU,uBACV;AAAA,QACC,OAAO;AAAA,QACP,EAAE,0CAA0C,SAAS;AAAA,MACvD,GACF;AAAA,OACF;AAAA,KACF,KAzBQ,GAAG,OAAO,IAAI,IAAI,KAAK,EA0BjC,CACD,GACH;AAEJ;AAEe,SAAR,iBAAkC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0B;AACxB,QAAM,IAAI,KAAK;AACf,QAAM,SAAS;AAEf,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM,EAAE,OAAO;AAAA,MACb,aAAa,EACV,OAAO,EACP,KAAK,EACL,IAAI,GAAG,EAAE,8CAA8C,2BAA2B,CAAC;AAAA,IACxF,CAAC;AAAA,IACD,CAAC,CAAC;AAAA,EACJ;AAIA,QAAM,SAAS,MAAM,QAAqB,MAAM;AAAA,IAC9C;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,qCAAqC,cAAc;AAAA,MAC5D,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU;AAAA,MACV,aAAa,EAAE,gDAAgD,8BAA8B;AAAA,IAC/F;AAAA,EACF,GAAG,CAAC,YAAY,CAAC,CAAC;AAElB,QAAM,SAAS,MAAM,QAAyB,MAAM;AAAA,IAClD;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,WAAW,MAAM,oBAAC,sBAAmB,OAAc;AAAA,IACrD;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,WAAW,MAAM,oBAAC,kBAAe,SAAS,MAAM,SAAS;AAAA,IAC3D;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ,CAAC,aAAa;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,eAAe,MAAM,YAAY,OAAO,WAAmC;AAC/E,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,aAAa,OAAO,YAAY,KAAK;AAAA,IACvC,CAAC;AAAA,EACH,GAAG,CAAC,UAAU,KAAK,CAAC;AAEpB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,qCAAqC,iBAAiB;AAAA,MAC/D,mBAAmB;AAAA,MACnB,UAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,EAAE,aAAa,GAAG;AAAA,MACjC,UAAU;AAAA,MACV,aAAa,EAAE,yCAAyC,WAAW;AAAA,MACnE,UAAU;AAAA,MACV,cACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,UAAU,UAAU,YAClE,YAAE,qBAAqB,QAAQ,GAClC;AAAA;AAAA,EAEJ;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -135,6 +135,7 @@ function SudoConfigCrudPageInner({
|
|
|
135
135
|
CrudForm,
|
|
136
136
|
{
|
|
137
137
|
title: mode === "edit" ? t("security.admin.sudo.form.title.edit", "Edit sudo rule") : t("security.admin.sudo.form.title.create", "Create sudo rule"),
|
|
138
|
+
titleHeadingLevel: 1,
|
|
138
139
|
backHref: "/backend/security/sudo",
|
|
139
140
|
fields,
|
|
140
141
|
groups,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/components/SudoConfigCrudPage.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SudoProvider } from './SudoProvider'\nimport { useSudoChallenge } from './hooks/useSudoChallenge'\nimport {\n buildSudoConfigInitialValues,\n buildSudoConfigPayload,\n createSudoConfigFormFields,\n createSudoConfigFormSchema,\n type SudoConfigFormValues,\n} from './SudoConfigForm'\n\ntype SudoConfigRecord = {\n id: string\n tenantId: string | null\n organizationId: string | null\n label: string | null\n targetIdentifier: string\n isEnabled: boolean\n ttlSeconds: number\n challengeMethod: SudoConfigFormValues['challengeMethod']\n}\n\ntype SudoConfigCrudPageProps = {\n mode: 'create' | 'edit'\n id?: string\n defaultTtlSeconds?: number\n maxTtlSeconds?: number\n}\n\nfunction SudoConfigCrudPageInner({\n mode,\n id,\n defaultTtlSeconds,\n maxTtlSeconds,\n}: SudoConfigCrudPageProps) {\n const t = useT()\n const router = useRouter()\n const { requireSudo } = useSudoChallenge()\n const [initialValues, setInitialValues] = React.useState<SudoConfigFormValues>(() => buildSudoConfigInitialValues(\n undefined,\n { defaultTtlSeconds },\n ))\n const [loading, setLoading] = React.useState(mode === 'edit')\n const [submitting, setSubmitting] = React.useState(false)\n const schema = React.useMemo(\n () => createSudoConfigFormSchema(t, { maxTtlSeconds }),\n [maxTtlSeconds, t],\n )\n const fields = React.useMemo(() => createSudoConfigFormFields(t, { disabled: submitting }), [submitting, t])\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'target',\n title: t('security.admin.sudo.form.group.target', 'Sudo target'),\n column: 1,\n fields: ['targetIdentifier', 'label'],\n },\n {\n id: 'details',\n title: t('security.admin.sudo.form.group.details', 'Configuration'),\n column: 1,\n fields: ['tenantId', 'organizationId', 'ttlSeconds', 'challengeMethod', 'isEnabled'],\n },\n ], [t])\n const redirectWithFlash = React.useCallback((message: string, type: 'success' | 'error' = 'success') => {\n router.push(`/backend/security/sudo?flash=${encodeURIComponent(message)}&type=${type}`)\n }, [router])\n\n React.useEffect(() => {\n if (mode !== 'edit' || !id) return\n\n const configId = id\n let cancelled = false\n\n async function loadConfig() {\n setLoading(true)\n const response = await apiCall<SudoConfigRecord>(`/api/security/sudo/configs/${encodeURIComponent(configId)}`)\n if (!cancelled) {\n if (response.ok && response.result) {\n setInitialValues(buildSudoConfigInitialValues({\n id: response.result.id,\n tenantId: response.result.tenantId ?? '',\n organizationId: response.result.organizationId ?? '',\n label: response.result.label ?? '',\n targetIdentifier: response.result.targetIdentifier,\n isEnabled: response.result.isEnabled,\n ttlSeconds: response.result.ttlSeconds,\n challengeMethod: response.result.challengeMethod,\n }, { defaultTtlSeconds }))\n } else {\n redirectWithFlash(t('security.admin.sudo.errors.load', 'Failed to load sudo configuration.'), 'error')\n }\n setLoading(false)\n }\n }\n\n void loadConfig()\n\n return () => {\n cancelled = true\n }\n }, [defaultTtlSeconds, id, mode, redirectWithFlash, t])\n\n const requestToken = React.useCallback(async () => {\n const sudoToken = await requireSudo('security.sudo.manage')\n if (!sudoToken) {\n flash(t('security.admin.sudo.flash.cancelled', 'Sudo challenge cancelled.'), 'error')\n return null\n }\n return sudoToken\n }, [requireSudo, t])\n\n const handleSubmit = React.useCallback(async (values: SudoConfigFormValues) => {\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSubmitting(true)\n try {\n const payload = buildSudoConfigPayload(values)\n if (mode === 'edit' && id) {\n const configId = id\n await updateCrud(`security/sudo/configs/${encodeURIComponent(configId)}`, payload, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.saveError', 'Failed to save sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.updated', 'Sudo configuration updated.'))\n } else {\n await createCrud('security/sudo/configs', payload, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.saveError', 'Failed to save sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.created', 'Sudo configuration created.'))\n }\n } finally {\n setSubmitting(false)\n }\n }, [id, mode, redirectWithFlash, requestToken, t])\n\n const handleDelete = React.useCallback(async () => {\n if (mode !== 'edit' || !id) return\n\n const configId = id\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSubmitting(true)\n try {\n await deleteCrud(`security/sudo/configs/${encodeURIComponent(configId)}`, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.deleteError', 'Failed to delete sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.deleted', 'Sudo configuration deleted.'))\n } finally {\n setSubmitting(false)\n }\n }, [id, mode, redirectWithFlash, requestToken, t])\n\n return (\n <Page>\n <PageBody>\n <CrudForm<SudoConfigFormValues>\n title={mode === 'edit'\n ? t('security.admin.sudo.form.title.edit', 'Edit sudo rule')\n : t('security.admin.sudo.form.title.create', 'Create sudo rule')}\n backHref=\"/backend/security/sudo\"\n fields={fields}\n groups={groups}\n initialValues={initialValues}\n isLoading={loading}\n loadingMessage={t('security.admin.sudo.loading', 'Loading sudo configuration...')}\n cancelHref=\"/backend/security/sudo\"\n submitLabel={mode === 'edit' ? t('ui.actions.save', 'Save') : t('ui.actions.create', 'Create')}\n schema={schema}\n onSubmit={handleSubmit}\n onDelete={mode === 'edit' ? handleDelete : undefined}\n />\n </PageBody>\n </Page>\n )\n}\n\nexport default function SudoConfigCrudPage(props: SudoConfigCrudPageProps) {\n return (\n <SudoProvider>\n <SudoConfigCrudPageInner {...props} />\n </SudoProvider>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SudoProvider } from './SudoProvider'\nimport { useSudoChallenge } from './hooks/useSudoChallenge'\nimport {\n buildSudoConfigInitialValues,\n buildSudoConfigPayload,\n createSudoConfigFormFields,\n createSudoConfigFormSchema,\n type SudoConfigFormValues,\n} from './SudoConfigForm'\n\ntype SudoConfigRecord = {\n id: string\n tenantId: string | null\n organizationId: string | null\n label: string | null\n targetIdentifier: string\n isEnabled: boolean\n ttlSeconds: number\n challengeMethod: SudoConfigFormValues['challengeMethod']\n}\n\ntype SudoConfigCrudPageProps = {\n mode: 'create' | 'edit'\n id?: string\n defaultTtlSeconds?: number\n maxTtlSeconds?: number\n}\n\nfunction SudoConfigCrudPageInner({\n mode,\n id,\n defaultTtlSeconds,\n maxTtlSeconds,\n}: SudoConfigCrudPageProps) {\n const t = useT()\n const router = useRouter()\n const { requireSudo } = useSudoChallenge()\n const [initialValues, setInitialValues] = React.useState<SudoConfigFormValues>(() => buildSudoConfigInitialValues(\n undefined,\n { defaultTtlSeconds },\n ))\n const [loading, setLoading] = React.useState(mode === 'edit')\n const [submitting, setSubmitting] = React.useState(false)\n const schema = React.useMemo(\n () => createSudoConfigFormSchema(t, { maxTtlSeconds }),\n [maxTtlSeconds, t],\n )\n const fields = React.useMemo(() => createSudoConfigFormFields(t, { disabled: submitting }), [submitting, t])\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'target',\n title: t('security.admin.sudo.form.group.target', 'Sudo target'),\n column: 1,\n fields: ['targetIdentifier', 'label'],\n },\n {\n id: 'details',\n title: t('security.admin.sudo.form.group.details', 'Configuration'),\n column: 1,\n fields: ['tenantId', 'organizationId', 'ttlSeconds', 'challengeMethod', 'isEnabled'],\n },\n ], [t])\n const redirectWithFlash = React.useCallback((message: string, type: 'success' | 'error' = 'success') => {\n router.push(`/backend/security/sudo?flash=${encodeURIComponent(message)}&type=${type}`)\n }, [router])\n\n React.useEffect(() => {\n if (mode !== 'edit' || !id) return\n\n const configId = id\n let cancelled = false\n\n async function loadConfig() {\n setLoading(true)\n const response = await apiCall<SudoConfigRecord>(`/api/security/sudo/configs/${encodeURIComponent(configId)}`)\n if (!cancelled) {\n if (response.ok && response.result) {\n setInitialValues(buildSudoConfigInitialValues({\n id: response.result.id,\n tenantId: response.result.tenantId ?? '',\n organizationId: response.result.organizationId ?? '',\n label: response.result.label ?? '',\n targetIdentifier: response.result.targetIdentifier,\n isEnabled: response.result.isEnabled,\n ttlSeconds: response.result.ttlSeconds,\n challengeMethod: response.result.challengeMethod,\n }, { defaultTtlSeconds }))\n } else {\n redirectWithFlash(t('security.admin.sudo.errors.load', 'Failed to load sudo configuration.'), 'error')\n }\n setLoading(false)\n }\n }\n\n void loadConfig()\n\n return () => {\n cancelled = true\n }\n }, [defaultTtlSeconds, id, mode, redirectWithFlash, t])\n\n const requestToken = React.useCallback(async () => {\n const sudoToken = await requireSudo('security.sudo.manage')\n if (!sudoToken) {\n flash(t('security.admin.sudo.flash.cancelled', 'Sudo challenge cancelled.'), 'error')\n return null\n }\n return sudoToken\n }, [requireSudo, t])\n\n const handleSubmit = React.useCallback(async (values: SudoConfigFormValues) => {\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSubmitting(true)\n try {\n const payload = buildSudoConfigPayload(values)\n if (mode === 'edit' && id) {\n const configId = id\n await updateCrud(`security/sudo/configs/${encodeURIComponent(configId)}`, payload, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.saveError', 'Failed to save sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.updated', 'Sudo configuration updated.'))\n } else {\n await createCrud('security/sudo/configs', payload, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.saveError', 'Failed to save sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.created', 'Sudo configuration created.'))\n }\n } finally {\n setSubmitting(false)\n }\n }, [id, mode, redirectWithFlash, requestToken, t])\n\n const handleDelete = React.useCallback(async () => {\n if (mode !== 'edit' || !id) return\n\n const configId = id\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSubmitting(true)\n try {\n await deleteCrud(`security/sudo/configs/${encodeURIComponent(configId)}`, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.deleteError', 'Failed to delete sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.deleted', 'Sudo configuration deleted.'))\n } finally {\n setSubmitting(false)\n }\n }, [id, mode, redirectWithFlash, requestToken, t])\n\n return (\n <Page>\n <PageBody>\n <CrudForm<SudoConfigFormValues>\n title={mode === 'edit'\n ? t('security.admin.sudo.form.title.edit', 'Edit sudo rule')\n : t('security.admin.sudo.form.title.create', 'Create sudo rule')}\n titleHeadingLevel={1}\n backHref=\"/backend/security/sudo\"\n fields={fields}\n groups={groups}\n initialValues={initialValues}\n isLoading={loading}\n loadingMessage={t('security.admin.sudo.loading', 'Loading sudo configuration...')}\n cancelHref=\"/backend/security/sudo\"\n submitLabel={mode === 'edit' ? t('ui.actions.save', 'Save') : t('ui.actions.create', 'Create')}\n schema={schema}\n onSubmit={handleSubmit}\n onDelete={mode === 'edit' ? handleDelete : undefined}\n />\n </PageBody>\n </Page>\n )\n}\n\nexport default function SudoConfigCrudPage(props: SudoConfigCrudPageProps) {\n return (\n <SudoProvider>\n <SudoConfigCrudPageInner {...props} />\n </SudoProvider>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -129,6 +129,7 @@ function SsoConfigListPage() {
|
|
|
129
129
|
DataTable,
|
|
130
130
|
{
|
|
131
131
|
title: t("sso.admin.title", "Single Sign-On"),
|
|
132
|
+
titleHeadingLevel: 1,
|
|
132
133
|
actions: canCreateNew ? /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/sso/config/new", children: t("sso.admin.new", "New SSO Config") }) }) : void 0,
|
|
133
134
|
columns,
|
|
134
135
|
data: data.items,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/sso/backend/page.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport Link from 'next/link'\n\ninterface SsoConfigRow {\n id: string\n name: string | null\n protocol: string\n issuer: string | null\n allowedDomains: string[]\n isActive: boolean\n hasClientSecret: boolean\n organizationId: string\n tenantId: string | null\n createdAt: string\n}\n\ninterface ListResponse {\n items: SsoConfigRow[]\n total: number\n totalPages: number\n totalIsCapped?: boolean\n isSuperAdmin?: boolean\n}\n\nconst fallback: ListResponse = { items: [], total: 0, totalPages: 1 }\n\nexport default function SsoConfigListPage() {\n const router = useRouter()\n const t = useT()\n\n const [data, setData] = React.useState<ListResponse>(fallback)\n const [isLoading, setIsLoading] = React.useState(true)\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const isSuperAdmin = !!data.isSuperAdmin\n\n const fetchData = React.useCallback(async () => {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '50')\n if (search) params.set('search', search)\n\n const call = await apiCall<ListResponse>(`/api/sso/config?${params}`, undefined, { fallback })\n if (call.ok && call.result) {\n setData(call.result)\n }\n setIsLoading(false)\n }, [page, search])\n\n React.useEffect(() => { fetchData() }, [fetchData])\n\n const handleDelete = async (row: SsoConfigRow) => {\n if (row.isActive) {\n flash(t('sso.admin.error.deleteActive', 'Cannot delete an active SSO configuration \u2014 deactivate it first'), 'error')\n return\n }\n\n const confirmed = await confirm({\n title: t('sso.admin.delete.title', 'Delete SSO Configuration'),\n text: t('sso.admin.delete.confirm', 'Are you sure? This will remove the SSO configuration. Users with linked SSO identities will need to use password login.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n\n if (!confirmed) return\n\n await apiCallOrThrow(`/api/sso/config/${row.id}`, { method: 'DELETE' }, {\n errorMessage: t('sso.admin.error.deleteFailed', 'Failed to delete SSO configuration'),\n })\n flash(t('sso.admin.delete.success', 'SSO configuration deleted'), 'success')\n fetchData()\n }\n\n const handleToggleActivation = async (row: SsoConfigRow) => {\n try {\n await apiCallOrThrow(\n `/api/sso/config/${row.id}/activate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ active: !row.isActive }),\n },\n { errorMessage: t('sso.admin.error.activationFailed', 'Failed to update activation status') },\n )\n flash(\n row.isActive\n ? t('sso.admin.deactivated', 'SSO configuration deactivated')\n : t('sso.admin.activated', 'SSO configuration activated'),\n 'success',\n )\n fetchData()\n } catch {\n // apiCallOrThrow already flashes the error\n }\n }\n\n const handleTestConnection = async (row: SsoConfigRow) => {\n try {\n const call = await apiCallOrThrow<{ ok: boolean; error?: string }>(\n `/api/sso/config/${row.id}/test`,\n { method: 'POST' },\n { errorMessage: t('sso.admin.error.testFailed', 'Connection test failed') },\n )\n if (call.result?.ok) {\n flash(t('sso.admin.test.success', 'Discovery successful \u2014 issuer is reachable'), 'success')\n } else {\n flash(call.result?.error || t('sso.admin.test.failed', 'Discovery failed'), 'error')\n }\n } catch {\n // apiCallOrThrow already flashes the error\n }\n }\n\n const columns = React.useMemo<ColumnDef<SsoConfigRow>[]>(() => {\n const cols: ColumnDef<SsoConfigRow>[] = [\n {\n accessorKey: 'name',\n header: t('sso.admin.column.name', 'Name'),\n cell: ({ row }) => row.original.name || row.original.issuer || '\u2014',\n },\n {\n accessorKey: 'protocol',\n header: t('sso.admin.column.protocol', 'Protocol'),\n cell: ({ row }) => row.original.protocol.toUpperCase(),\n },\n {\n accessorKey: 'allowedDomains',\n header: t('sso.admin.column.domains', 'Domains'),\n cell: ({ row }) => row.original.allowedDomains.join(', ') || '\u2014',\n },\n {\n accessorKey: 'isActive',\n header: t('sso.admin.column.status', 'Status'),\n cell: ({ row }) => (\n <span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${row.original.isActive ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'}`}>\n {row.original.isActive\n ? t('sso.admin.status.active', 'Active')\n : t('sso.admin.status.inactive', 'Inactive')}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('sso.admin.column.created', 'Created'),\n cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(),\n },\n ]\n\n return cols\n }, [t, isSuperAdmin])\n\n const hasConfigs = data.items.length > 0 || search\n const canCreateNew = isSuperAdmin || data.items.length === 0\n\n return (\n <Page>\n <PageBody>\n {!hasConfigs && !isLoading ? (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <h3 className=\"text-lg font-semibold mb-2\">\n {t('sso.admin.empty.title', 'No SSO configured')}\n </h3>\n <p className=\"text-muted-foreground mb-4 max-w-md\">\n {t('sso.admin.empty.description', 'Configure Single Sign-On to let your users authenticate with your identity provider.')}\n </p>\n <Button asChild>\n <Link href=\"/backend/sso/config/new\">\n {t('sso.admin.empty.cta', 'Configure SSO')}\n </Link>\n </Button>\n </div>\n ) : (\n <DataTable<SsoConfigRow>\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport Link from 'next/link'\n\ninterface SsoConfigRow {\n id: string\n name: string | null\n protocol: string\n issuer: string | null\n allowedDomains: string[]\n isActive: boolean\n hasClientSecret: boolean\n organizationId: string\n tenantId: string | null\n createdAt: string\n}\n\ninterface ListResponse {\n items: SsoConfigRow[]\n total: number\n totalPages: number\n totalIsCapped?: boolean\n isSuperAdmin?: boolean\n}\n\nconst fallback: ListResponse = { items: [], total: 0, totalPages: 1 }\n\nexport default function SsoConfigListPage() {\n const router = useRouter()\n const t = useT()\n\n const [data, setData] = React.useState<ListResponse>(fallback)\n const [isLoading, setIsLoading] = React.useState(true)\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const isSuperAdmin = !!data.isSuperAdmin\n\n const fetchData = React.useCallback(async () => {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '50')\n if (search) params.set('search', search)\n\n const call = await apiCall<ListResponse>(`/api/sso/config?${params}`, undefined, { fallback })\n if (call.ok && call.result) {\n setData(call.result)\n }\n setIsLoading(false)\n }, [page, search])\n\n React.useEffect(() => { fetchData() }, [fetchData])\n\n const handleDelete = async (row: SsoConfigRow) => {\n if (row.isActive) {\n flash(t('sso.admin.error.deleteActive', 'Cannot delete an active SSO configuration \u2014 deactivate it first'), 'error')\n return\n }\n\n const confirmed = await confirm({\n title: t('sso.admin.delete.title', 'Delete SSO Configuration'),\n text: t('sso.admin.delete.confirm', 'Are you sure? This will remove the SSO configuration. Users with linked SSO identities will need to use password login.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n\n if (!confirmed) return\n\n await apiCallOrThrow(`/api/sso/config/${row.id}`, { method: 'DELETE' }, {\n errorMessage: t('sso.admin.error.deleteFailed', 'Failed to delete SSO configuration'),\n })\n flash(t('sso.admin.delete.success', 'SSO configuration deleted'), 'success')\n fetchData()\n }\n\n const handleToggleActivation = async (row: SsoConfigRow) => {\n try {\n await apiCallOrThrow(\n `/api/sso/config/${row.id}/activate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ active: !row.isActive }),\n },\n { errorMessage: t('sso.admin.error.activationFailed', 'Failed to update activation status') },\n )\n flash(\n row.isActive\n ? t('sso.admin.deactivated', 'SSO configuration deactivated')\n : t('sso.admin.activated', 'SSO configuration activated'),\n 'success',\n )\n fetchData()\n } catch {\n // apiCallOrThrow already flashes the error\n }\n }\n\n const handleTestConnection = async (row: SsoConfigRow) => {\n try {\n const call = await apiCallOrThrow<{ ok: boolean; error?: string }>(\n `/api/sso/config/${row.id}/test`,\n { method: 'POST' },\n { errorMessage: t('sso.admin.error.testFailed', 'Connection test failed') },\n )\n if (call.result?.ok) {\n flash(t('sso.admin.test.success', 'Discovery successful \u2014 issuer is reachable'), 'success')\n } else {\n flash(call.result?.error || t('sso.admin.test.failed', 'Discovery failed'), 'error')\n }\n } catch {\n // apiCallOrThrow already flashes the error\n }\n }\n\n const columns = React.useMemo<ColumnDef<SsoConfigRow>[]>(() => {\n const cols: ColumnDef<SsoConfigRow>[] = [\n {\n accessorKey: 'name',\n header: t('sso.admin.column.name', 'Name'),\n cell: ({ row }) => row.original.name || row.original.issuer || '\u2014',\n },\n {\n accessorKey: 'protocol',\n header: t('sso.admin.column.protocol', 'Protocol'),\n cell: ({ row }) => row.original.protocol.toUpperCase(),\n },\n {\n accessorKey: 'allowedDomains',\n header: t('sso.admin.column.domains', 'Domains'),\n cell: ({ row }) => row.original.allowedDomains.join(', ') || '\u2014',\n },\n {\n accessorKey: 'isActive',\n header: t('sso.admin.column.status', 'Status'),\n cell: ({ row }) => (\n <span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${row.original.isActive ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'}`}>\n {row.original.isActive\n ? t('sso.admin.status.active', 'Active')\n : t('sso.admin.status.inactive', 'Inactive')}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('sso.admin.column.created', 'Created'),\n cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(),\n },\n ]\n\n return cols\n }, [t, isSuperAdmin])\n\n const hasConfigs = data.items.length > 0 || search\n const canCreateNew = isSuperAdmin || data.items.length === 0\n\n return (\n <Page>\n <PageBody>\n {!hasConfigs && !isLoading ? (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <h3 className=\"text-lg font-semibold mb-2\">\n {t('sso.admin.empty.title', 'No SSO configured')}\n </h3>\n <p className=\"text-muted-foreground mb-4 max-w-md\">\n {t('sso.admin.empty.description', 'Configure Single Sign-On to let your users authenticate with your identity provider.')}\n </p>\n <Button asChild>\n <Link href=\"/backend/sso/config/new\">\n {t('sso.admin.empty.cta', 'Configure SSO')}\n </Link>\n </Button>\n </div>\n ) : (\n <DataTable<SsoConfigRow>\n title={t('sso.admin.title', 'Single Sign-On')}\n titleHeadingLevel={1}\n actions={canCreateNew ? (\n <Button asChild size=\"sm\">\n <Link href=\"/backend/sso/config/new\">\n {t('sso.admin.new', 'New SSO Config')}\n </Link>\n </Button>\n ) : undefined}\n columns={columns}\n data={data.items}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('sso.admin.search', 'Search by name or issuer...')}\n onRowClick={(row) => router.push(`/backend/sso/config/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: t('common.edit', 'Edit'), onSelect: () => router.push(`/backend/sso/config/${row.id}`) },\n { id: 'test', label: t('sso.admin.action.test', 'Verify Discovery'), onSelect: () => handleTestConnection(row) },\n {\n id: 'toggle',\n label: row.isActive\n ? t('sso.admin.action.deactivate', 'Deactivate')\n : t('sso.admin.action.activate', 'Activate'),\n onSelect: () => handleToggleActivation(row),\n },\n { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => handleDelete(row) },\n ]}\n />\n )}\n pagination={{\n page,\n pageSize: 50,\n total: data.total,\n totalPages: data.totalPages,\n totalIsCapped: data?.totalIsCapped === true,\n onPageChange: setPage,\n }}\n isLoading={isLoading}\n />\n )}\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAqJU,cAwBA,YAxBA;AAnJV,OAAO,WAAW;AAClB,SAAS,iBAAiB;AAE1B,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,SAAS,sBAAsB;AACxC,SAAS,aAAa;AACtB,SAAS,wBAAwB;AACjC,SAAS,YAAY;AACrB,OAAO,UAAU;AAuBjB,MAAM,WAAyB,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,EAAE;AAErD,SAAR,oBAAqC;AAC1C,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AAEf,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAuB,QAAQ;AAC7D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAE3D,QAAM,eAAe,CAAC,CAAC,KAAK;AAE5B,QAAM,YAAY,MAAM,YAAY,YAAY;AAC9C,iBAAa,IAAI;AACjB,UAAM,SAAS,IAAI,gBAAgB;AACnC,WAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAC/B,WAAO,IAAI,YAAY,IAAI;AAC3B,QAAI,OAAQ,QAAO,IAAI,UAAU,MAAM;AAEvC,UAAM,OAAO,MAAM,QAAsB,mBAAmB,MAAM,IAAI,QAAW,EAAE,SAAS,CAAC;AAC7F,QAAI,KAAK,MAAM,KAAK,QAAQ;AAC1B,cAAQ,KAAK,MAAM;AAAA,IACrB;AACA,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,MAAM,MAAM,CAAC;AAEjB,QAAM,UAAU,MAAM;AAAE,cAAU;AAAA,EAAE,GAAG,CAAC,SAAS,CAAC;AAElD,QAAM,eAAe,OAAO,QAAsB;AAChD,QAAI,IAAI,UAAU;AAChB,YAAM,EAAE,gCAAgC,sEAAiE,GAAG,OAAO;AACnH;AAAA,IACF;AAEA,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,0BAA0B,0BAA0B;AAAA,MAC7D,MAAM,EAAE,4BAA4B,yHAAyH;AAAA,MAC7J,aAAa,EAAE,iBAAiB,QAAQ;AAAA,MACxC,SAAS;AAAA,IACX,CAAC;AAED,QAAI,CAAC,UAAW;AAEhB,UAAM,eAAe,mBAAmB,IAAI,EAAE,IAAI,EAAE,QAAQ,SAAS,GAAG;AAAA,MACtE,cAAc,EAAE,gCAAgC,oCAAoC;AAAA,IACtF,CAAC;AACD,UAAM,EAAE,4BAA4B,2BAA2B,GAAG,SAAS;AAC3E,cAAU;AAAA,EACZ;AAEA,QAAM,yBAAyB,OAAO,QAAsB;AAC1D,QAAI;AACF,YAAM;AAAA,QACJ,mBAAmB,IAAI,EAAE;AAAA,QACzB;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC,IAAI,SAAS,CAAC;AAAA,QAChD;AAAA,QACA,EAAE,cAAc,EAAE,oCAAoC,oCAAoC,EAAE;AAAA,MAC9F;AACA;AAAA,QACE,IAAI,WACA,EAAE,yBAAyB,+BAA+B,IAC1D,EAAE,uBAAuB,6BAA6B;AAAA,QAC1D;AAAA,MACF;AACA,gBAAU;AAAA,IACZ,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,uBAAuB,OAAO,QAAsB;AACxD,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB,mBAAmB,IAAI,EAAE;AAAA,QACzB,EAAE,QAAQ,OAAO;AAAA,QACjB,EAAE,cAAc,EAAE,8BAA8B,wBAAwB,EAAE;AAAA,MAC5E;AACA,UAAI,KAAK,QAAQ,IAAI;AACnB,cAAM,EAAE,0BAA0B,iDAA4C,GAAG,SAAS;AAAA,MAC5F,OAAO;AACL,cAAM,KAAK,QAAQ,SAAS,EAAE,yBAAyB,kBAAkB,GAAG,OAAO;AAAA,MACrF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,QAAmC,MAAM;AAC7D,UAAM,OAAkC;AAAA,MACtC;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,yBAAyB,MAAM;AAAA,QACzC,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,QAAQ,IAAI,SAAS,UAAU;AAAA,MACjE;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,6BAA6B,UAAU;AAAA,QACjD,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,SAAS,YAAY;AAAA,MACvD;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,4BAA4B,SAAS;AAAA,QAC/C,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,eAAe,KAAK,IAAI,KAAK;AAAA,MAC/D;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,2BAA2B,QAAQ;AAAA,QAC7C,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,UAAK,WAAW,uEAAuE,IAAI,SAAS,WAAW,+BAA+B,2BAA2B,IACvK,cAAI,SAAS,WACV,EAAE,2BAA2B,QAAQ,IACrC,EAAE,6BAA6B,UAAU,GAC/C;AAAA,MAEJ;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,4BAA4B,SAAS;AAAA,QAC/C,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,KAAK,IAAI,SAAS,SAAS,EAAE,mBAAmB;AAAA,MACzE;AAAA,IACF;AAEA,WAAO;AAAA,EACT,GAAG,CAAC,GAAG,YAAY,CAAC;AAEpB,QAAM,aAAa,KAAK,MAAM,SAAS,KAAK;AAC5C,QAAM,eAAe,gBAAgB,KAAK,MAAM,WAAW;AAE3D,SACE,oBAAC,QACC,+BAAC,YACE;AAAA,KAAC,cAAc,CAAC,YACf,qBAAC,SAAI,WAAU,+DACb;AAAA,0BAAC,QAAG,WAAU,8BACX,YAAE,yBAAyB,mBAAmB,GACjD;AAAA,MACA,oBAAC,OAAE,WAAU,uCACV,YAAE,+BAA+B,sFAAsF,GAC1H;AAAA,MACA,oBAAC,UAAO,SAAO,MACb,8BAAC,QAAK,MAAK,2BACR,YAAE,uBAAuB,eAAe,GAC3C,GACF;AAAA,OACF,IAEA;AAAA,MAAC;AAAA;AAAA,QACH,OAAO,EAAE,mBAAmB,gBAAgB;AAAA,QAC5C,mBAAmB;AAAA,QACf,SAAS,eACP,oBAAC,UAAO,SAAO,MAAC,MAAK,MACnB,8BAAC,QAAK,MAAK,2BACR,YAAE,iBAAiB,gBAAgB,GACtC,GACF,IACE;AAAA,QACJ;AAAA,QACA,MAAM,KAAK;AAAA,QACX,aAAa;AAAA,QACb,gBAAgB,CAAC,UAAU;AAAE,oBAAU,KAAK;AAAG,kBAAQ,CAAC;AAAA,QAAE;AAAA,QAC1D,mBAAmB,EAAE,oBAAoB,6BAA6B;AAAA,QACtE,YAAY,CAAC,QAAQ,OAAO,KAAK,uBAAuB,IAAI,EAAE,EAAE;AAAA,QAChE,YAAY,CAAC,QACX;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,EAAE,IAAI,QAAQ,OAAO,EAAE,eAAe,MAAM,GAAG,UAAU,MAAM,OAAO,KAAK,uBAAuB,IAAI,EAAE,EAAE,EAAE;AAAA,cAC5G,EAAE,IAAI,QAAQ,OAAO,EAAE,yBAAyB,kBAAkB,GAAG,UAAU,MAAM,qBAAqB,GAAG,EAAE;AAAA,cAC/G;AAAA,gBACE,IAAI;AAAA,gBACJ,OAAO,IAAI,WACP,EAAE,+BAA+B,YAAY,IAC7C,EAAE,6BAA6B,UAAU;AAAA,gBAC7C,UAAU,MAAM,uBAAuB,GAAG;AAAA,cAC5C;AAAA,cACA,EAAE,IAAI,UAAU,OAAO,EAAE,iBAAiB,QAAQ,GAAG,aAAa,MAAM,UAAU,MAAM,aAAa,GAAG,EAAE;AAAA,YAC5G;AAAA;AAAA,QACF;AAAA,QAEF,YAAY;AAAA,UACV;AAAA,UACA,UAAU;AAAA,UACV,OAAO,KAAK;AAAA,UACZ,YAAY,KAAK;AAAA,UACjB,eAAe,MAAM,kBAAkB;AAAA,UACvC,cAAc;AAAA,QAChB;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAED;AAAA,KACH,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/enterprise",
|
|
3
|
-
"version": "0.7.1-develop.
|
|
3
|
+
"version": "0.7.1-develop.7153.1.7145d295e6",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@open-mercato/core": "0.7.1-develop.
|
|
69
|
-
"@open-mercato/ui": "0.7.1-develop.
|
|
68
|
+
"@open-mercato/core": "0.7.1-develop.7153.1.7145d295e6",
|
|
69
|
+
"@open-mercato/ui": "0.7.1-develop.7153.1.7145d295e6",
|
|
70
70
|
"@simplewebauthn/browser": "^13.3.0",
|
|
71
71
|
"@simplewebauthn/server": "^13.3.2",
|
|
72
72
|
"@simplewebauthn/types": "^12.0.0",
|
|
@@ -76,12 +76,12 @@
|
|
|
76
76
|
"qrcode": "^1.5.4"
|
|
77
77
|
},
|
|
78
78
|
"peerDependencies": {
|
|
79
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
79
|
+
"@open-mercato/shared": "0.7.1-develop.7153.1.7145d295e6",
|
|
80
80
|
"react": "^19.0.0",
|
|
81
81
|
"react-dom": "^19.0.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
84
|
+
"@open-mercato/shared": "0.7.1-develop.7153.1.7145d295e6",
|
|
85
85
|
"@types/jest": "^30.0.0",
|
|
86
86
|
"@types/react": "^19.2.17",
|
|
87
87
|
"@types/react-dom": "^19.2.3",
|
|
@@ -233,6 +233,7 @@ export default function SecurityEnforcementPage() {
|
|
|
233
233
|
<PageBody>
|
|
234
234
|
<DataTable<EnforcementPolicyDto>
|
|
235
235
|
title={t('security.admin.enforcement.list.title', 'Existing policies')}
|
|
236
|
+
titleHeadingLevel={1}
|
|
236
237
|
actions={(
|
|
237
238
|
<Button type="button" variant="outline" size="sm" asChild>
|
|
238
239
|
<Link href="/backend/security/enforcement/create">
|
|
@@ -394,6 +394,7 @@ export default function EnforcementPolicyForm({
|
|
|
394
394
|
? t('security.admin.enforcement.form.titleEdit', 'Edit enforcement policy')
|
|
395
395
|
: t('security.admin.enforcement.form.titleCreate', 'Create enforcement policy')
|
|
396
396
|
}
|
|
397
|
+
titleHeadingLevel={1}
|
|
397
398
|
backHref={backHref}
|
|
398
399
|
schema={schema}
|
|
399
400
|
formId={formId}
|
|
@@ -164,6 +164,7 @@ export default function SecurityUserForm({
|
|
|
164
164
|
return (
|
|
165
165
|
<CrudForm<SecurityUserCrudValues>
|
|
166
166
|
title={t('security.admin.users.detail.title', 'User MFA detail')}
|
|
167
|
+
titleHeadingLevel={1}
|
|
167
168
|
backHref="/backend/security/users"
|
|
168
169
|
schema={schema}
|
|
169
170
|
formId={formId}
|
|
@@ -170,6 +170,7 @@ function SudoConfigCrudPageInner({
|
|
|
170
170
|
title={mode === 'edit'
|
|
171
171
|
? t('security.admin.sudo.form.title.edit', 'Edit sudo rule')
|
|
172
172
|
: t('security.admin.sudo.form.title.create', 'Create sudo rule')}
|
|
173
|
+
titleHeadingLevel={1}
|
|
173
174
|
backHref="/backend/security/sudo"
|
|
174
175
|
fields={fields}
|
|
175
176
|
groups={groups}
|
|
@@ -186,7 +186,8 @@ export default function SsoConfigListPage() {
|
|
|
186
186
|
</div>
|
|
187
187
|
) : (
|
|
188
188
|
<DataTable<SsoConfigRow>
|
|
189
|
-
|
|
189
|
+
title={t('sso.admin.title', 'Single Sign-On')}
|
|
190
|
+
titleHeadingLevel={1}
|
|
190
191
|
actions={canCreateNew ? (
|
|
191
192
|
<Button asChild size="sm">
|
|
192
193
|
<Link href="/backend/sso/config/new">
|