@seedgrid/fe-security 2026.7.0 → 2026.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/client/index.d.ts +4 -0
  2. package/dist/client/index.d.ts.map +1 -0
  3. package/dist/client/index.js +5 -0
  4. package/dist/client/permission-provider.d.ts +18 -0
  5. package/dist/client/permission-provider.d.ts.map +1 -0
  6. package/dist/client/permission-provider.js +57 -0
  7. package/dist/client/pick-list-field.d.ts +15 -0
  8. package/dist/client/pick-list-field.d.ts.map +1 -0
  9. package/dist/client/pick-list-field.js +30 -0
  10. package/dist/client/status-badge.d.ts +6 -0
  11. package/dist/client/status-badge.d.ts.map +1 -0
  12. package/dist/client/status-badge.js +6 -0
  13. package/dist/config.d.ts +34 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +4 -0
  16. package/dist/dtos.d.ts +88 -0
  17. package/dist/dtos.d.ts.map +1 -0
  18. package/dist/dtos.js +3 -0
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +10 -0
  22. package/dist/menu.d.ts +19 -0
  23. package/dist/menu.d.ts.map +1 -0
  24. package/dist/menu.js +41 -0
  25. package/dist/messages.d.ts +1858 -0
  26. package/dist/messages.d.ts.map +1 -0
  27. package/dist/messages.js +990 -0
  28. package/dist/module.d.ts +3 -0
  29. package/dist/module.d.ts.map +1 -0
  30. package/dist/module.js +37 -0
  31. package/dist/module.json +21 -0
  32. package/dist/paths.d.ts +1 -0
  33. package/dist/paths.d.ts.map +1 -1
  34. package/dist/paths.js +3 -0
  35. package/dist/permissions.d.ts +14 -0
  36. package/dist/permissions.d.ts.map +1 -0
  37. package/dist/permissions.js +39 -0
  38. package/dist/server/action.d.ts +3 -0
  39. package/dist/server/action.d.ts.map +1 -0
  40. package/dist/server/action.js +21 -0
  41. package/dist/server/create-security-server.d.ts +4 -0
  42. package/dist/server/create-security-server.d.ts.map +1 -0
  43. package/dist/server/create-security-server.js +166 -0
  44. package/dist/server/errors.d.ts +13 -0
  45. package/dist/server/errors.d.ts.map +1 -0
  46. package/dist/server/errors.js +26 -0
  47. package/dist/server/index.d.ts +8 -0
  48. package/dist/server/index.d.ts.map +1 -0
  49. package/dist/server/index.js +8 -0
  50. package/dist/server/problem-response.d.ts +3 -0
  51. package/dist/server/problem-response.d.ts.map +1 -0
  52. package/dist/server/problem-response.js +28 -0
  53. package/dist/server/route.d.ts +3 -0
  54. package/dist/server/route.d.ts.map +1 -0
  55. package/dist/server/route.js +22 -0
  56. package/dist/server/to-problem.d.ts +3 -0
  57. package/dist/server/to-problem.d.ts.map +1 -0
  58. package/dist/server/to-problem.js +22 -0
  59. package/dist/server-contract.d.ts +23 -0
  60. package/dist/server-contract.d.ts.map +1 -0
  61. package/dist/server-contract.js +4 -0
  62. package/dist/session-contract.d.ts +36 -0
  63. package/dist/session-contract.d.ts.map +1 -0
  64. package/dist/session-contract.js +8 -0
  65. package/dist/session-result.d.ts +22 -0
  66. package/dist/session-result.d.ts.map +1 -0
  67. package/dist/session-result.js +14 -0
  68. package/package.json +38 -3
  69. package/scaffold/README.md +97 -0
  70. package/scaffold/src/app/api/security/session/2fa/resend/route.ts +8 -0
  71. package/scaffold/src/app/api/security/session/2fa/verify/route.ts +8 -0
  72. package/scaffold/src/app/api/security/session/login/route.ts +7 -0
  73. package/scaffold/src/app/api/security/session/logout/route.ts +5 -0
  74. package/scaffold/src/app/api/security/session/refresh/route.ts +7 -0
  75. package/scaffold/src/app/api/security/users/[publicId]/avatar/route.ts +15 -0
  76. package/scaffold/src/app/auth/2fa/verify/page.tsx +286 -0
  77. package/scaffold/src/app/auth/forgot-password/confirm/page.tsx +96 -0
  78. package/scaffold/src/app/auth/forgot-password/page.tsx +165 -0
  79. package/scaffold/src/app/auth/forgot-password/success/page.tsx +31 -0
  80. package/scaffold/src/app/auth/login/page.tsx +386 -0
  81. package/scaffold/src/app/auth/welcome/page.tsx +90 -0
  82. package/scaffold/src/app/login/page.tsx +7 -0
  83. package/scaffold/src/app/page.tsx +12 -0
  84. package/scaffold/src/app/security/2fa/page.tsx +370 -0
  85. package/scaffold/src/app/security/companies/(create)/new/page.tsx +12 -0
  86. package/scaffold/src/app/security/companies/(edit)/[publicId]/page.tsx +18 -0
  87. package/scaffold/src/app/security/companies/(list)/page.tsx +298 -0
  88. package/scaffold/src/app/security/companies/editors/CompanyEditor.tsx +754 -0
  89. package/scaffold/src/app/security/profile/page.tsx +492 -0
  90. package/scaffold/src/app/security/roles/(create)/new/page.tsx +11 -0
  91. package/scaffold/src/app/security/roles/(edit)/[publicId]/page.tsx +17 -0
  92. package/scaffold/src/app/security/roles/(list)/page.tsx +269 -0
  93. package/scaffold/src/app/security/roles/editors/RoleEditor.tsx +328 -0
  94. package/scaffold/src/app/security/users/(create)/new/page.tsx +10 -0
  95. package/scaffold/src/app/security/users/(edit)/[publicId]/page.tsx +16 -0
  96. package/scaffold/src/app/security/users/(list)/page.tsx +289 -0
  97. package/scaffold/src/app/security/users/editors/UserEditor.tsx +478 -0
  98. package/scaffold/src/app/users/change-password/page.tsx +172 -0
  99. package/scaffold/src/modules/security/AuthProvider.tsx +405 -0
  100. package/scaffold/src/modules/security/components/AppShell.tsx.hbs +889 -0
  101. package/scaffold/src/modules/security/components/CompanyManagementGuard.tsx +59 -0
  102. package/scaffold/src/modules/security/components/PageFrame.tsx +105 -0
  103. package/scaffold/src/modules/security/components/RouteGuard.tsx +114 -0
  104. package/scaffold/src/modules/security/components/SecurityDashboard.tsx.hbs +315 -0
  105. package/scaffold/src/modules/security/config.ts +55 -0
  106. package/scaffold/src/modules/security/hooks/useAuth.ts +7 -0
  107. package/scaffold/src/modules/security/hooks/useDebouncedSearch.ts +13 -0
  108. package/scaffold/src/modules/security/hooks/usePermission.ts +35 -0
  109. package/scaffold/src/modules/security/host-access.ts +59 -0
  110. package/scaffold/src/modules/security/index.ts +44 -0
  111. package/scaffold/src/modules/security/navigation.ts +41 -0
  112. package/scaffold/src/modules/security/server/avatar-routes.ts +86 -0
  113. package/scaffold/src/modules/security/server/public-api-base-url.ts +26 -0
  114. package/scaffold/src/modules/security/server/session-routes.ts.hbs +322 -0
  115. package/scaffold/src/modules/security/server/session-server.ts.hbs +30 -0
  116. package/scaffold/src/modules/security/services/auth-service.ts +332 -0
  117. package/scaffold/src/modules/security/services/security-api.test.ts +178 -0
  118. package/scaffold/src/modules/security/services/security-api.ts +281 -0
  119. package/scaffold/src/modules/security/services/security-service.test.ts +71 -0
  120. package/scaffold/src/modules/security/services/security-service.ts +727 -0
  121. package/scaffold/src/modules/security/services/session-storage.ts +80 -0
  122. package/scaffold/src/modules/security/types.ts +295 -0
  123. package/scaffold/src/modules/security/useSecurityAppShellSections.tsx.hbs +180 -0
  124. 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
+ }