@seedgrid/fe-security 2026.7.0 → 2026.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +5 -0
- package/dist/client/permission-provider.d.ts +18 -0
- package/dist/client/permission-provider.d.ts.map +1 -0
- package/dist/client/permission-provider.js +57 -0
- package/dist/client/pick-list-field.d.ts +15 -0
- package/dist/client/pick-list-field.d.ts.map +1 -0
- package/dist/client/pick-list-field.js +30 -0
- package/dist/client/status-badge.d.ts +6 -0
- package/dist/client/status-badge.d.ts.map +1 -0
- package/dist/client/status-badge.js +6 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +4 -0
- package/dist/dtos.d.ts +88 -0
- package/dist/dtos.d.ts.map +1 -0
- package/dist/dtos.js +3 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/menu.d.ts +19 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +41 -0
- package/dist/messages.d.ts +1858 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +990 -0
- package/dist/module.d.ts +3 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +37 -0
- package/dist/module.json +21 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +3 -0
- package/dist/permissions.d.ts +14 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +39 -0
- package/dist/server/action.d.ts +3 -0
- package/dist/server/action.d.ts.map +1 -0
- package/dist/server/action.js +21 -0
- package/dist/server/create-security-server.d.ts +4 -0
- package/dist/server/create-security-server.d.ts.map +1 -0
- package/dist/server/create-security-server.js +166 -0
- package/dist/server/errors.d.ts +13 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +26 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +8 -0
- package/dist/server/problem-response.d.ts +3 -0
- package/dist/server/problem-response.d.ts.map +1 -0
- package/dist/server/problem-response.js +28 -0
- package/dist/server/route.d.ts +3 -0
- package/dist/server/route.d.ts.map +1 -0
- package/dist/server/route.js +22 -0
- package/dist/server/to-problem.d.ts +3 -0
- package/dist/server/to-problem.d.ts.map +1 -0
- package/dist/server/to-problem.js +22 -0
- package/dist/server-contract.d.ts +23 -0
- package/dist/server-contract.d.ts.map +1 -0
- package/dist/server-contract.js +4 -0
- package/dist/session-contract.d.ts +36 -0
- package/dist/session-contract.d.ts.map +1 -0
- package/dist/session-contract.js +8 -0
- package/dist/session-result.d.ts +22 -0
- package/dist/session-result.d.ts.map +1 -0
- package/dist/session-result.js +14 -0
- package/package.json +38 -3
- package/scaffold/README.md +97 -0
- package/scaffold/src/app/api/security/session/2fa/resend/route.ts +8 -0
- package/scaffold/src/app/api/security/session/2fa/verify/route.ts +8 -0
- package/scaffold/src/app/api/security/session/login/route.ts +7 -0
- package/scaffold/src/app/api/security/session/logout/route.ts +5 -0
- package/scaffold/src/app/api/security/session/refresh/route.ts +7 -0
- package/scaffold/src/app/api/security/users/[publicId]/avatar/route.ts +15 -0
- package/scaffold/src/app/auth/2fa/verify/page.tsx +286 -0
- package/scaffold/src/app/auth/forgot-password/confirm/page.tsx +96 -0
- package/scaffold/src/app/auth/forgot-password/page.tsx +165 -0
- package/scaffold/src/app/auth/forgot-password/success/page.tsx +31 -0
- package/scaffold/src/app/auth/login/page.tsx +386 -0
- package/scaffold/src/app/auth/welcome/page.tsx +90 -0
- package/scaffold/src/app/login/page.tsx +7 -0
- package/scaffold/src/app/page.tsx +12 -0
- package/scaffold/src/app/security/2fa/page.tsx +370 -0
- package/scaffold/src/app/security/companies/(create)/new/page.tsx +12 -0
- package/scaffold/src/app/security/companies/(edit)/[publicId]/page.tsx +18 -0
- package/scaffold/src/app/security/companies/(list)/page.tsx +298 -0
- package/scaffold/src/app/security/companies/editors/CompanyEditor.tsx +754 -0
- package/scaffold/src/app/security/profile/page.tsx +492 -0
- package/scaffold/src/app/security/roles/(create)/new/page.tsx +11 -0
- package/scaffold/src/app/security/roles/(edit)/[publicId]/page.tsx +17 -0
- package/scaffold/src/app/security/roles/(list)/page.tsx +269 -0
- package/scaffold/src/app/security/roles/editors/RoleEditor.tsx +328 -0
- package/scaffold/src/app/security/users/(create)/new/page.tsx +10 -0
- package/scaffold/src/app/security/users/(edit)/[publicId]/page.tsx +16 -0
- package/scaffold/src/app/security/users/(list)/page.tsx +289 -0
- package/scaffold/src/app/security/users/editors/UserEditor.tsx +478 -0
- package/scaffold/src/app/users/change-password/page.tsx +172 -0
- package/scaffold/src/modules/security/AuthProvider.tsx +405 -0
- package/scaffold/src/modules/security/components/AppShell.tsx.hbs +889 -0
- package/scaffold/src/modules/security/components/CompanyManagementGuard.tsx +59 -0
- package/scaffold/src/modules/security/components/PageFrame.tsx +105 -0
- package/scaffold/src/modules/security/components/RouteGuard.tsx +114 -0
- package/scaffold/src/modules/security/components/SecurityDashboard.tsx.hbs +315 -0
- package/scaffold/src/modules/security/config.ts +55 -0
- package/scaffold/src/modules/security/hooks/useAuth.ts +7 -0
- package/scaffold/src/modules/security/hooks/useDebouncedSearch.ts +13 -0
- package/scaffold/src/modules/security/hooks/usePermission.ts +35 -0
- package/scaffold/src/modules/security/host-access.ts +59 -0
- package/scaffold/src/modules/security/index.ts +44 -0
- package/scaffold/src/modules/security/navigation.ts +41 -0
- package/scaffold/src/modules/security/server/avatar-routes.ts +86 -0
- package/scaffold/src/modules/security/server/public-api-base-url.ts +26 -0
- package/scaffold/src/modules/security/server/session-routes.ts.hbs +322 -0
- package/scaffold/src/modules/security/server/session-server.ts.hbs +30 -0
- package/scaffold/src/modules/security/services/auth-service.ts +332 -0
- package/scaffold/src/modules/security/services/security-api.test.ts +178 -0
- package/scaffold/src/modules/security/services/security-api.ts +281 -0
- package/scaffold/src/modules/security/services/security-service.test.ts +71 -0
- package/scaffold/src/modules/security/services/security-service.ts +727 -0
- package/scaffold/src/modules/security/services/session-storage.ts +80 -0
- package/scaffold/src/modules/security/types.ts +295 -0
- package/scaffold/src/modules/security/useSecurityAppShellSections.tsx.hbs +180 -0
- package/scaffold/src/modules/security/utils.ts +194 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { sgWhistle } from "@seedgrid/fe-components";
|
|
2
|
+
|
|
3
|
+
import { securityConfig } from "./config";
|
|
4
|
+
import type { PickListOption, PagedResult } from "./types";
|
|
5
|
+
|
|
6
|
+
export function normalizeSearchTerm(value: string) {
|
|
7
|
+
return value.trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isSearchReady(value: string) {
|
|
11
|
+
const normalized = normalizeSearchTerm(value);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
normalized.length === 0 ||
|
|
15
|
+
normalized.length >= securityConfig.search.minLength
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function hasActiveSearch(value: string) {
|
|
20
|
+
return normalizeSearchTerm(value).length >= securityConfig.search.minLength;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatDateTime(
|
|
24
|
+
value: string | null | undefined,
|
|
25
|
+
locale: string
|
|
26
|
+
) {
|
|
27
|
+
if (!value) {
|
|
28
|
+
return "-";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const parsedDate = new Date(value);
|
|
32
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new Intl.DateTimeFormat(locale, {
|
|
37
|
+
dateStyle: "medium",
|
|
38
|
+
timeStyle: "short",
|
|
39
|
+
}).format(parsedDate);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function ensureArray<T>(value: T[] | null | undefined) {
|
|
43
|
+
return Array.isArray(value) ? value : [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createPagedResult<T>(
|
|
47
|
+
items: T[],
|
|
48
|
+
total: number,
|
|
49
|
+
page: number,
|
|
50
|
+
size: number,
|
|
51
|
+
fallback = false
|
|
52
|
+
): PagedResult<T> {
|
|
53
|
+
return {
|
|
54
|
+
items,
|
|
55
|
+
total,
|
|
56
|
+
page,
|
|
57
|
+
size,
|
|
58
|
+
fallback,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function paginateItems<T>(items: T[], page: number, size: number) {
|
|
63
|
+
const safePage = Math.max(0, page);
|
|
64
|
+
const safeSize = Math.max(1, size);
|
|
65
|
+
const start = safePage * safeSize;
|
|
66
|
+
|
|
67
|
+
return createPagedResult(
|
|
68
|
+
items.slice(start, start + safeSize),
|
|
69
|
+
items.length,
|
|
70
|
+
safePage,
|
|
71
|
+
safeSize,
|
|
72
|
+
true
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function toPickListOption(
|
|
77
|
+
value: string | number,
|
|
78
|
+
label: string,
|
|
79
|
+
data?: unknown
|
|
80
|
+
): PickListOption {
|
|
81
|
+
return {
|
|
82
|
+
value,
|
|
83
|
+
label,
|
|
84
|
+
data,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function resolveNumericId(
|
|
89
|
+
entity: { id?: unknown; publicId?: unknown } | null | undefined
|
|
90
|
+
) {
|
|
91
|
+
if (!entity) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof entity.id === "number" && Number.isFinite(entity.id)) {
|
|
96
|
+
return entity.id;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof entity.id === "string" && /^\d+$/.test(entity.id.trim())) {
|
|
100
|
+
return Number(entity.id);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
typeof entity.publicId === "string" &&
|
|
105
|
+
/^\d+$/.test(entity.publicId.trim())
|
|
106
|
+
) {
|
|
107
|
+
return Number(entity.publicId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function extractSelectionValues(
|
|
114
|
+
items: Array<{ id?: unknown; publicId?: unknown }> | null | undefined
|
|
115
|
+
) {
|
|
116
|
+
return ensureArray(items)
|
|
117
|
+
.map((item) => {
|
|
118
|
+
const numericId = resolveNumericId(item);
|
|
119
|
+
|
|
120
|
+
if (numericId !== null) {
|
|
121
|
+
return numericId;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return typeof item.publicId === "string" ? item.publicId : null;
|
|
125
|
+
})
|
|
126
|
+
.filter((value): value is string | number => value !== null);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function pickBySelection<T extends { id?: unknown; publicId?: unknown }>(
|
|
130
|
+
items: T[],
|
|
131
|
+
selectedValue: string | number
|
|
132
|
+
) {
|
|
133
|
+
return items.find((item) => {
|
|
134
|
+
if (resolveNumericId(item) === selectedValue) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return item.publicId === selectedValue;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function resolveSelectedNumericIds<
|
|
143
|
+
T extends { id?: unknown; publicId?: unknown }
|
|
144
|
+
>(items: T[], selectedValues: Array<string | number>) {
|
|
145
|
+
const numericIds = selectedValues.map((selectedValue) => {
|
|
146
|
+
const item = pickBySelection(items, selectedValue);
|
|
147
|
+
return resolveNumericId(item);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return numericIds.every((value): value is number => value !== null)
|
|
151
|
+
? numericIds
|
|
152
|
+
: null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function extractFirstFormErrorMessage(error: unknown): string | null {
|
|
156
|
+
if (!error) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (typeof error === "string" && error.trim()) {
|
|
161
|
+
return error.trim();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (typeof error !== "object") {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const record = error as Record<string, unknown>;
|
|
169
|
+
|
|
170
|
+
if (typeof record.message === "string" && record.message.trim()) {
|
|
171
|
+
return record.message.trim();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const value of Object.values(record)) {
|
|
175
|
+
const nestedMessage = extractFirstFormErrorMessage(value);
|
|
176
|
+
|
|
177
|
+
if (nestedMessage) {
|
|
178
|
+
return nestedMessage;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function showFormValidationWhistle(id: string, message: string) {
|
|
186
|
+
sgWhistle.show({
|
|
187
|
+
id,
|
|
188
|
+
severity: "error",
|
|
189
|
+
message,
|
|
190
|
+
duration: 5000,
|
|
191
|
+
dismissible: true,
|
|
192
|
+
borderStyle: "soft",
|
|
193
|
+
});
|
|
194
|
+
}
|