@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.
Files changed (125) hide show
  1. package/README.md +113 -0
  2. package/dist/client/index.d.ts +4 -0
  3. package/dist/client/index.d.ts.map +1 -0
  4. package/dist/client/index.js +5 -0
  5. package/dist/client/permission-provider.d.ts +18 -0
  6. package/dist/client/permission-provider.d.ts.map +1 -0
  7. package/dist/client/permission-provider.js +57 -0
  8. package/dist/client/pick-list-field.d.ts +15 -0
  9. package/dist/client/pick-list-field.d.ts.map +1 -0
  10. package/dist/client/pick-list-field.js +30 -0
  11. package/dist/client/status-badge.d.ts +6 -0
  12. package/dist/client/status-badge.d.ts.map +1 -0
  13. package/dist/client/status-badge.js +6 -0
  14. package/dist/config.d.ts +34 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +4 -0
  17. package/dist/dtos.d.ts +88 -0
  18. package/dist/dtos.d.ts.map +1 -0
  19. package/dist/dtos.js +3 -0
  20. package/dist/index.d.ts +8 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +10 -0
  23. package/dist/menu.d.ts +19 -0
  24. package/dist/menu.d.ts.map +1 -0
  25. package/dist/menu.js +41 -0
  26. package/dist/messages.d.ts +1858 -0
  27. package/dist/messages.d.ts.map +1 -0
  28. package/dist/messages.js +990 -0
  29. package/dist/module.d.ts +3 -0
  30. package/dist/module.d.ts.map +1 -0
  31. package/dist/module.js +37 -0
  32. package/dist/module.json +21 -0
  33. package/dist/paths.d.ts +1 -0
  34. package/dist/paths.d.ts.map +1 -1
  35. package/dist/paths.js +3 -0
  36. package/dist/permissions.d.ts +14 -0
  37. package/dist/permissions.d.ts.map +1 -0
  38. package/dist/permissions.js +39 -0
  39. package/dist/server/action.d.ts +3 -0
  40. package/dist/server/action.d.ts.map +1 -0
  41. package/dist/server/action.js +21 -0
  42. package/dist/server/create-security-server.d.ts +4 -0
  43. package/dist/server/create-security-server.d.ts.map +1 -0
  44. package/dist/server/create-security-server.js +166 -0
  45. package/dist/server/errors.d.ts +13 -0
  46. package/dist/server/errors.d.ts.map +1 -0
  47. package/dist/server/errors.js +26 -0
  48. package/dist/server/index.d.ts +8 -0
  49. package/dist/server/index.d.ts.map +1 -0
  50. package/dist/server/index.js +8 -0
  51. package/dist/server/problem-response.d.ts +3 -0
  52. package/dist/server/problem-response.d.ts.map +1 -0
  53. package/dist/server/problem-response.js +28 -0
  54. package/dist/server/route.d.ts +3 -0
  55. package/dist/server/route.d.ts.map +1 -0
  56. package/dist/server/route.js +22 -0
  57. package/dist/server/to-problem.d.ts +3 -0
  58. package/dist/server/to-problem.d.ts.map +1 -0
  59. package/dist/server/to-problem.js +22 -0
  60. package/dist/server-contract.d.ts +23 -0
  61. package/dist/server-contract.d.ts.map +1 -0
  62. package/dist/server-contract.js +4 -0
  63. package/dist/session-contract.d.ts +36 -0
  64. package/dist/session-contract.d.ts.map +1 -0
  65. package/dist/session-contract.js +8 -0
  66. package/dist/session-result.d.ts +22 -0
  67. package/dist/session-result.d.ts.map +1 -0
  68. package/dist/session-result.js +14 -0
  69. package/package.json +38 -3
  70. package/scaffold/README.md +97 -0
  71. package/scaffold/src/app/api/security/session/2fa/resend/route.ts +8 -0
  72. package/scaffold/src/app/api/security/session/2fa/verify/route.ts +8 -0
  73. package/scaffold/src/app/api/security/session/login/route.ts +7 -0
  74. package/scaffold/src/app/api/security/session/logout/route.ts +5 -0
  75. package/scaffold/src/app/api/security/session/refresh/route.ts +7 -0
  76. package/scaffold/src/app/api/security/users/[publicId]/avatar/route.ts +15 -0
  77. package/scaffold/src/app/auth/2fa/verify/page.tsx +286 -0
  78. package/scaffold/src/app/auth/forgot-password/confirm/page.tsx +96 -0
  79. package/scaffold/src/app/auth/forgot-password/page.tsx +165 -0
  80. package/scaffold/src/app/auth/forgot-password/success/page.tsx +31 -0
  81. package/scaffold/src/app/auth/login/page.tsx +386 -0
  82. package/scaffold/src/app/auth/welcome/page.tsx +90 -0
  83. package/scaffold/src/app/login/page.tsx +7 -0
  84. package/scaffold/src/app/page.tsx +12 -0
  85. package/scaffold/src/app/security/2fa/page.tsx +370 -0
  86. package/scaffold/src/app/security/companies/(create)/new/page.tsx +12 -0
  87. package/scaffold/src/app/security/companies/(edit)/[publicId]/page.tsx +18 -0
  88. package/scaffold/src/app/security/companies/(list)/page.tsx +298 -0
  89. package/scaffold/src/app/security/companies/editors/CompanyEditor.tsx +754 -0
  90. package/scaffold/src/app/security/profile/page.tsx +492 -0
  91. package/scaffold/src/app/security/roles/(create)/new/page.tsx +11 -0
  92. package/scaffold/src/app/security/roles/(edit)/[publicId]/page.tsx +17 -0
  93. package/scaffold/src/app/security/roles/(list)/page.tsx +269 -0
  94. package/scaffold/src/app/security/roles/editors/RoleEditor.tsx +328 -0
  95. package/scaffold/src/app/security/users/(create)/new/page.tsx +10 -0
  96. package/scaffold/src/app/security/users/(edit)/[publicId]/page.tsx +16 -0
  97. package/scaffold/src/app/security/users/(list)/page.tsx +289 -0
  98. package/scaffold/src/app/security/users/editors/UserEditor.tsx +478 -0
  99. package/scaffold/src/app/users/change-password/page.tsx +172 -0
  100. package/scaffold/src/modules/security/AuthProvider.tsx +405 -0
  101. package/scaffold/src/modules/security/components/AppShell.tsx.hbs +889 -0
  102. package/scaffold/src/modules/security/components/CompanyManagementGuard.tsx +59 -0
  103. package/scaffold/src/modules/security/components/PageFrame.tsx +105 -0
  104. package/scaffold/src/modules/security/components/RouteGuard.tsx +114 -0
  105. package/scaffold/src/modules/security/components/SecurityDashboard.tsx.hbs +315 -0
  106. package/scaffold/src/modules/security/config.ts +55 -0
  107. package/scaffold/src/modules/security/hooks/useAuth.ts +7 -0
  108. package/scaffold/src/modules/security/hooks/useDebouncedSearch.ts +13 -0
  109. package/scaffold/src/modules/security/hooks/usePermission.ts +35 -0
  110. package/scaffold/src/modules/security/host-access.ts +59 -0
  111. package/scaffold/src/modules/security/index.ts +44 -0
  112. package/scaffold/src/modules/security/navigation.ts +41 -0
  113. package/scaffold/src/modules/security/server/avatar-routes.ts +86 -0
  114. package/scaffold/src/modules/security/server/public-api-base-url.ts +26 -0
  115. package/scaffold/src/modules/security/server/session-routes.ts.hbs +322 -0
  116. package/scaffold/src/modules/security/server/session-server.ts.hbs +30 -0
  117. package/scaffold/src/modules/security/services/auth-service.ts +332 -0
  118. package/scaffold/src/modules/security/services/security-api.test.ts +178 -0
  119. package/scaffold/src/modules/security/services/security-api.ts +281 -0
  120. package/scaffold/src/modules/security/services/security-service.test.ts +71 -0
  121. package/scaffold/src/modules/security/services/security-service.ts +727 -0
  122. package/scaffold/src/modules/security/services/session-storage.ts +80 -0
  123. package/scaffold/src/modules/security/types.ts +295 -0
  124. package/scaffold/src/modules/security/useSecurityAppShellSections.tsx.hbs +180 -0
  125. package/scaffold/src/modules/security/utils.ts +194 -0
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @seedgrid/fe-security
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40seedgrid%2Ffe-security)](https://www.npmjs.com/package/@seedgrid/fe-security)
4
+ [![npm downloads](https://img.shields.io/npm/dm/%40seedgrid%2Ffe-security)](https://www.npmjs.com/package/@seedgrid/fe-security)
5
+ [![TypeScript](https://img.shields.io/badge/types-TypeScript-blue)](https://www.typescriptlang.org/)
6
+
7
+ Módulo de segurança do front SeedGrid: autenticação com sessão server-side (Next.js), 2FA, gestão de usuários, roles, permissões e empresas. É o módulo-base dos demais módulos front (`fe-apikeys`, `fe-audit`, `fe-backup`, `fe-billing-*`, `fe-login-tenancy`, `fe-self-enter`, `fe-subdomain-tenancy` declaram `requires: ["security"]`).
8
+
9
+ ## Sumário
10
+
11
+ - [O que este pacote contém](#o-que-este-pacote-contém)
12
+ - [Telas entregues pelo scaffold](#telas-entregues-pelo-scaffold)
13
+ - [Instalação](#instalação)
14
+ - [Uso rápido](#uso-rápido)
15
+ - [Permissões](#permissões)
16
+ - [Backend correspondente](#backend-correspondente)
17
+ - [Suporte](#suporte)
18
+
19
+ ## O que este pacote contém
20
+
21
+ O pacote tem três camadas:
22
+
23
+ **Lib client-safe** (`@seedgrid/fe-security`) — sem imports de `next/*`, pode entrar em bundle client:
24
+
25
+ - `SecurityPaths` — contrato único dos paths REST (auth, 2FA, users, roles, permissions, companies, me, self-enter), espelho do `RestControllerPath.java` da extensão backend.
26
+ - Helpers de permissão: `normalizePermission`, `hasPermission`, `hasAnyPermission`, `hasAllPermissions`, `buildCrudPermissions`, `createPermissionSet`.
27
+ - DTOs de usuários, roles, permissões, empresas e sessão (`session-contract`, `session-result`, `server-contract`).
28
+ - `securityMessages` — bundle i18n (en, pt-BR, es, fr, pt-PT).
29
+
30
+ **Componentes client** (`@seedgrid/fe-security/client`):
31
+
32
+ - `PermissionProvider` — contexto de permissões do usuário logado.
33
+ - `PickListField`, `StatusBadge` — componentes usados nas telas de CRUD.
34
+
35
+ **Motor server-side** (`@seedgrid/fe-security/server`) — SERVER-ONLY:
36
+
37
+ - `createSecurityServer` — motor de sessão (login, refresh de token, logout, 2FA) com cookie httpOnly; é a fundação sobre a qual os outros módulos compõem os seus engines.
38
+ - Helpers de rota: `toRouteResponse`, respostas Problem+JSON (`toProblem`, `problemResponse`) e helpers de server action.
39
+
40
+ ## Telas entregues pelo scaffold
41
+
42
+ O diretório `scaffold/` é copiado para dentro do app gerado pelo SeedGrid CLI (o app é dono do código e pode editar à vontade):
43
+
44
+ | Tela | Rota | O que faz |
45
+ | --- | --- | --- |
46
+ | Login | `/auth/login` | Autenticação com e-mail/senha; redireciona pro 2FA quando exigido |
47
+ | Verificação 2FA | `/auth/2fa/verify` | Confirma o código do segundo fator (com reenvio) |
48
+ | Esqueci a senha | `/auth/forgot-password` (+ `confirm`, `success`) | Fluxo completo de recuperação de senha |
49
+ | Boas-vindas | `/auth/welcome` | Primeira entrada / confirmação de e-mail |
50
+ | Usuários | `/security/users` (lista, novo, edição) | CRUD de usuários com avatar, membership por empresa |
51
+ | Roles | `/security/roles` (lista, novo, edição) | CRUD de roles com atribuição de permissões |
52
+ | Empresas | `/security/companies` (lista, novo, edição) | CRUD de empresas (guardado por `CompanyManagementGuard`) |
53
+ | Perfil | `/security/profile` | Dados do próprio usuário |
54
+ | Configuração 2FA | `/security/2fa` | Habilitar/desabilitar segundo fator (TOTP) |
55
+ | Trocar senha | `/users/change-password` | Troca de senha do usuário logado |
56
+
57
+ O scaffold também traz o `AuthProvider`, `RouteGuard` (proteção de rotas), `AppShell` com menu por permissão (`useSecurityAppShellSections`), dashboard de segurança e as rotas-proxy de API (`/api/security/session/*`, avatar de usuário).
58
+
59
+ ## Instalação
60
+
61
+ ```bash
62
+ pnpm add @seedgrid/fe-security @seedgrid/fe-core @seedgrid/fe-components
63
+ ```
64
+
65
+ A forma recomendada de consumir o módulo é via **SeedGrid CLI**, que copia o scaffold, registra navegação/i18n e configura o `module.json` automaticamente.
66
+
67
+ ## Uso rápido
68
+
69
+ ```ts
70
+ // src/modules/security/server/server-api.ts (server-only)
71
+ import { createSecurityServer } from "@seedgrid/fe-security/server";
72
+
73
+ export const session = createSecurityServer({
74
+ apiBaseUrl: process.env.SEEDGRID_API_BASE_URL!,
75
+ cookies: { /* nomes dos cookies de sessão do app */ },
76
+ serviceToken: process.env.SEEDGRID_REPORT_API_TOKEN, // fallback server-to-server
77
+ });
78
+ ```
79
+
80
+ ```ts
81
+ // Qualquer módulo dependente compõe sobre a mesma sessão:
82
+ import { createApiKeysServer } from "@seedgrid/fe-apikeys/server";
83
+ export const apiKeys = createApiKeysServer(session);
84
+ ```
85
+
86
+ ```tsx
87
+ // Client: checagem de permissão
88
+ import { hasPermission, buildCrudPermissions } from "@seedgrid/fe-security";
89
+
90
+ const USER_PERMISSIONS = buildCrudPermissions("USER"); // USER_READ, USER_CREATE, ...
91
+ if (hasPermission(userPermissions, USER_PERMISSIONS.read)) {
92
+ // renderiza a lista de usuários
93
+ }
94
+ ```
95
+
96
+ ## Permissões
97
+
98
+ As telas de CRUD seguem a convenção `RECURSO_AÇÃO`: `USER_*`, `ROLE_*`, `COMPANY_*` (`READ`/`CREATE`/`UPDATE`/`DELETE`). O menu do `AppShell` esconde seções que o usuário não pode acessar e o `RouteGuard` bloqueia acesso direto por URL.
99
+
100
+ ## Backend correspondente
101
+
102
+ Este módulo conversa com a extensão Quarkus `seedgrid-quarkus-ext-security`. Os paths de `SecurityPaths` são espelho 1:1 do `RestControllerPath.java` — mudou um path no backend, atualize aqui também.
103
+
104
+ Variável de ambiente exigida pelo scaffold: `SEEDGRID_REPORT_API_TOKEN`.
105
+
106
+ ## Suporte
107
+
108
+ - Issues: https://github.com/SeedGrid/seedgrid-fe-components/issues
109
+ - Repositório: https://github.com/SeedGrid/seedgrid-fe-components
110
+
111
+ ## Licença
112
+
113
+ MIT
@@ -0,0 +1,4 @@
1
+ export { PermissionProvider, usePermissions, type PermissionContextValue, type PermissionProviderProps, } from "./permission-provider";
2
+ export { StatusBadge } from "./status-badge";
3
+ export { PickListField } from "./pick-list-field";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,5 @@
1
+ // Barrel CLIENT (@seedgrid/fe-security/client). React + next/navigation (hooks client).
2
+ // Não importe daqui em código server-only.
3
+ export { PermissionProvider, usePermissions, } from "./permission-provider";
4
+ export { StatusBadge } from "./status-badge";
5
+ export { PickListField } from "./pick-list-field";
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from "react";
2
+ import type { MePermissions } from "../session-contract";
3
+ export interface PermissionContextValue {
4
+ loading: boolean;
5
+ me: MePermissions | null;
6
+ hasRole: (role: string) => boolean;
7
+ hasPermission: (permission: string) => boolean;
8
+ }
9
+ export interface PermissionProviderProps {
10
+ children: ReactNode;
11
+ /** Endpoint que devolve MePermissions. Default: "/api/auth/me". */
12
+ meUrl?: string;
13
+ /** Role que implica TODAS as permissões (superuser). Default: "ADMIN". */
14
+ adminRole?: string;
15
+ }
16
+ export declare function PermissionProvider({ children, meUrl, adminRole, }: PermissionProviderProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function usePermissions(): PermissionContextValue;
18
+ //# sourceMappingURL=permission-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-provider.d.ts","sourceRoot":"","sources":["../../src/client/permission-provider.tsx"],"names":[],"mappings":"AAOA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,EAAE,EAAE,aAAa,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;CAChD;AASD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,SAAS,CAAC;IACpB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,KAAsB,EACtB,SAAmB,GACpB,EAAE,uBAAuB,2CAmDzB;AAED,wBAAgB,cAAc,IAAI,sBAAsB,CAEvD"}
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ // Contexto de permissões do usuário corrente (portado do admin-web AdminAccessProvider).
4
+ // Client-safe: usa next/navigation (hook client) e react. Endpoint e role-superuser são
5
+ // parametrizáveis — o "ADMIN implica tudo" e o "/api/auth/me" eram hardcode do admin.
6
+ import { usePathname } from "next/navigation";
7
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
8
+ const PermissionContext = createContext({
9
+ loading: true,
10
+ me: null,
11
+ hasRole: () => false,
12
+ hasPermission: () => false,
13
+ });
14
+ export function PermissionProvider({ children, meUrl = "/api/auth/me", adminRole = "ADMIN", }) {
15
+ const pathname = usePathname();
16
+ const [loading, setLoading] = useState(true);
17
+ const [me, setMe] = useState(null);
18
+ const loadPermissions = useCallback(async (signal) => {
19
+ try {
20
+ const response = await fetch(meUrl, { cache: "no-store", signal });
21
+ if (!response.ok) {
22
+ setMe(null);
23
+ return;
24
+ }
25
+ setMe((await response.json()));
26
+ }
27
+ catch {
28
+ // Aborts são ignorados; demais erros deixam me = null.
29
+ }
30
+ finally {
31
+ setLoading(false);
32
+ }
33
+ }, [meUrl]);
34
+ // Rebusca o `me` a CADA navegação — a sessão pode ter trocado de usuário (logout +
35
+ // login com outra conta). O cookie é httpOnly (não observável no client), então a
36
+ // navegação é o sinal de "sessão pode ter mudado". Refetch em background (não mexe em
37
+ // `loading` após a 1ª carga), então não pisca a UI.
38
+ useEffect(() => {
39
+ const controller = new AbortController();
40
+ void loadPermissions(controller.signal);
41
+ return () => controller.abort();
42
+ }, [pathname, loadPermissions]);
43
+ const value = useMemo(() => {
44
+ const roles = new Set(me?.roles ?? []);
45
+ const permissions = new Set(me?.permissions ?? []);
46
+ return {
47
+ loading,
48
+ me,
49
+ hasRole: (role) => roles.has(role),
50
+ hasPermission: (permission) => roles.has(adminRole) || permissions.has(permission),
51
+ };
52
+ }, [loading, me, adminRole]);
53
+ return (_jsx(PermissionContext.Provider, { value: value, children: children }));
54
+ }
55
+ export function usePermissions() {
56
+ return useContext(PermissionContext);
57
+ }
@@ -0,0 +1,15 @@
1
+ import type { PickListOption } from "../dtos";
2
+ type PickListFieldProps = {
3
+ id: string;
4
+ title: string;
5
+ sourceHeader: string;
6
+ targetHeader: string;
7
+ options: PickListOption[];
8
+ selectedValues: Array<string | number>;
9
+ onChange: (values: Array<string | number>) => void;
10
+ emptyMessage: string;
11
+ readOnly?: boolean;
12
+ };
13
+ export declare function PickListField({ id, title, sourceHeader, targetHeader, options, selectedValues, onChange, emptyMessage, readOnly, }: PickListFieldProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=pick-list-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pick-list-field.d.ts","sourceRoot":"","sources":["../../src/client/pick-list-field.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAM9C,KAAK,kBAAkB,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,cAAc,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,QAAgB,GACjB,EAAE,kBAAkB,2CAyCpB"}
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useMemo } from "react";
4
+ import { SgPickList } from "@seedgrid/fe-components";
5
+ const groupBoxProps = {
6
+ className: "w-full",
7
+ };
8
+ export function PickListField({ id, title, sourceHeader, targetHeader, options, selectedValues, onChange, emptyMessage, readOnly = false, }) {
9
+ const sourceItems = useMemo(() => options
10
+ .filter((option) => !selectedValues.some((value) => isSameValue(value, option.value)))
11
+ .map(toItem), [options, selectedValues]);
12
+ const targetItems = useMemo(() => selectedValues
13
+ .map((selectedValue) => options.find((option) => isSameValue(selectedValue, option.value)))
14
+ .filter((option) => Boolean(option))
15
+ .map(toItem), [options, selectedValues]);
16
+ return (_jsx(SgPickList, { id: id, title: title, source: sourceItems, target: targetItems, sourceHeader: sourceHeader, targetHeader: targetHeader, showSourceFilter: true, showTargetFilter: true, emptyMessage: emptyMessage, readOnly: readOnly, disabled: readOnly, onChange: (event) => {
17
+ onChange(event.target.map((item) => item.value));
18
+ }, groupBoxProps: groupBoxProps }));
19
+ }
20
+ function toItem(option) {
21
+ return {
22
+ label: option.label,
23
+ value: option.value,
24
+ disabled: option.disabled,
25
+ data: option.data,
26
+ };
27
+ }
28
+ function isSameValue(left, right) {
29
+ return String(left) === String(right);
30
+ }
@@ -0,0 +1,6 @@
1
+ export declare function StatusBadge({ active, activeLabel, inactiveLabel, }: {
2
+ active: boolean;
3
+ activeLabel: string;
4
+ inactiveLabel: string;
5
+ }): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=status-badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-badge.d.ts","sourceRoot":"","sources":["../../src/client/status-badge.tsx"],"names":[],"mappings":"AAIA,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,WAAW,EACX,aAAa,GACd,EAAE;IACD,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,2CASA"}
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { SgBadge } from "@seedgrid/fe-components";
4
+ export function StatusBadge({ active, activeLabel, inactiveLabel, }) {
5
+ return (_jsx(SgBadge, { value: active ? activeLabel : inactiveLabel, severity: active ? "success" : "neutral", badgeStyle: "solid", rounded: true }));
6
+ }
@@ -0,0 +1,34 @@
1
+ /** Nomes dos cookies httpOnly que guardam a sessão. */
2
+ export interface SecuritySessionCookieNames {
3
+ /** Access token. Ex.: "sg_admin_token". */
4
+ token: string;
5
+ /** Refresh token. Ex.: "sg_admin_refresh_token". */
6
+ refresh: string;
7
+ /** Flag de troca de senha obrigatória. Ex.: "sg_admin_password_change_required". */
8
+ passwordChangeRequired: string;
9
+ }
10
+ export interface SecurityServerConfig {
11
+ /** Base URL do report-api (ex.: "http://localhost:8080"). O app lê do env e passa aqui. */
12
+ apiBaseUrl: string;
13
+ /** Nomes dos cookies de sessão. */
14
+ cookies: SecuritySessionCookieNames;
15
+ /**
16
+ * Flag `secure` dos cookies. Default: `process.env.NODE_ENV === "production"`.
17
+ * Passe explicitamente para não depender do NODE_ENV.
18
+ */
19
+ secureCookies?: boolean;
20
+ /**
21
+ * Service token (server-to-server) usado como Authorization APENAS quando não há
22
+ * sessão de usuário no cookie. Corresponde ao antigo SEEDGRID_REPORT_API_TOKEN.
23
+ */
24
+ serviceToken?: string;
25
+ /**
26
+ * PONTO DE PLUG DA TENANCY. Headers extras resolvidos por request e mesclados em
27
+ * toda chamada ao report-api:
28
+ * - account-tenancy injeta `X-Company-Id` (empresa escopada);
29
+ * - multi-tenancy injeta o header de tenant resolvido por host/subdomínio;
30
+ * - security-core (sem tenancy) omite.
31
+ */
32
+ resolveTenancyHeaders?: () => Promise<HeadersInit> | HeadersInit;
33
+ }
34
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,uDAAuD;AACvD,MAAM,WAAW,0BAA0B;IACzC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IAEnB,mCAAmC;IACnC,OAAO,EAAE,0BAA0B,CAAC;IAEpC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;CAClE"}
package/dist/config.js ADDED
@@ -0,0 +1,4 @@
1
+ // Config injetável do motor de sessão server-side. Tudo que era hardcode do admin-web
2
+ // (nomes de cookie, base URL, service-token, X-Company-Id) entra por aqui. A lib NÃO
3
+ // lê process.env: o APP lê o env que quiser e passa os valores no config.
4
+ export {};
package/dist/dtos.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ /** Página genérica do backend (list endpoints). */
2
+ export interface PaginatedResult<T> {
3
+ items: T[];
4
+ totalRecords: number;
5
+ page: number;
6
+ size: number;
7
+ sortField: string;
8
+ sortOrder: "asc" | "desc";
9
+ query: string;
10
+ }
11
+ export interface PermissionOption {
12
+ publicId: string;
13
+ name: string;
14
+ description: string;
15
+ }
16
+ /** Resumo de role usado dentro de UserEntry. */
17
+ export interface RoleSummary {
18
+ publicId: string;
19
+ name: string;
20
+ }
21
+ export interface RoleEntry {
22
+ id: number;
23
+ publicId: string;
24
+ name: string;
25
+ description: string;
26
+ active: boolean;
27
+ forIntegration: boolean;
28
+ createdAt: string;
29
+ updatedAt: string;
30
+ permissionsCount: number;
31
+ permissions: PermissionOption[];
32
+ }
33
+ export interface UserEntry {
34
+ id: number;
35
+ publicId: string;
36
+ email: string;
37
+ name: string;
38
+ whatsApp: string;
39
+ active: boolean;
40
+ root: boolean;
41
+ changePasswordRequired: boolean;
42
+ passwordExpiresAt: string | null;
43
+ twoFactorEnabled: boolean;
44
+ emailConfirmed: boolean;
45
+ companiesCount: number;
46
+ createdAt: string;
47
+ updatedAt: string;
48
+ roles: RoleSummary[];
49
+ }
50
+ export type TwoFactorMethod = "TOTP" | "EMAIL" | "WHATSAPP";
51
+ export interface TwoFactorStatus {
52
+ enabled: boolean;
53
+ primaryMethod: TwoFactorMethod | null;
54
+ availableMethods: TwoFactorMethod[];
55
+ hasBackupCodes: boolean;
56
+ unusedBackupCodesCount: number;
57
+ }
58
+ /**
59
+ * Resposta de /2f/enable, /2f/totp/confirm e /2f/setup/confirm. Campos variam por
60
+ * etapa: enable TOTP traz qrCode/secret; confirm traz backupCodes; EMAIL/WHATSAPP
61
+ * enable traz apenas metadata de confirmação.
62
+ */
63
+ export interface TwoFactorEnableResponse {
64
+ status: string;
65
+ method?: TwoFactorMethod;
66
+ primaryMethod?: TwoFactorMethod;
67
+ secret?: string;
68
+ otpauthUrl?: string;
69
+ qrCode?: {
70
+ format: string;
71
+ width: number;
72
+ height: number;
73
+ base64: string;
74
+ } | null;
75
+ backupCodes?: string[];
76
+ metadata?: {
77
+ email?: string;
78
+ whatsApp?: string;
79
+ [key: string]: unknown;
80
+ };
81
+ }
82
+ export interface PickListOption {
83
+ label: string;
84
+ value: string | number;
85
+ disabled?: boolean;
86
+ data?: unknown;
87
+ }
88
+ //# sourceMappingURL=dtos.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dtos.d.ts","sourceRoot":"","sources":["../src/dtos.ts"],"names":[],"mappings":"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"}
package/dist/dtos.js ADDED
@@ -0,0 +1,3 @@
1
+ // DTOs do SeedGrid Security (espelham o report-api). Separados dos DTOs de domínio do
2
+ // admin — aqui só entra o que é security-core. Client-safe (tipos puros).
3
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,2 +1,10 @@
1
1
  export * from "./paths";
2
+ export * from "./config";
3
+ export * from "./session-contract";
4
+ export * from "./session-result";
5
+ export * from "./server-contract";
6
+ export * from "./dtos";
7
+ export * from "./menu";
8
+ export * from "./permissions";
9
+ export { securityMessages } from "./messages";
2
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1,11 @@
1
+ // Barrel client-safe (@seedgrid/fe-security). Sem imports de next/* — pode entrar em
2
+ // bundle client. O motor de sessão server-side fica em "@seedgrid/fe-security/server".
1
3
  export * from "./paths";
4
+ export * from "./config";
5
+ export * from "./session-contract";
6
+ export * from "./session-result";
7
+ export * from "./server-contract";
8
+ export * from "./dtos";
9
+ export * from "./menu";
10
+ export * from "./permissions";
11
+ export { securityMessages } from "./messages";
package/dist/menu.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export interface NavItem {
2
+ href: string;
3
+ label: string;
4
+ children?: NavItem[];
5
+ /**
6
+ * Permissão de READ exigida para ver o item. Regras (canShowItem):
7
+ * - grupo (com children): aparece se tiver >=1 filho visível;
8
+ * - leaf com `permission`: exige hasPermission(permission);
9
+ * - leaf SEM `permission` (feature de plataforma sem perm dedicada): só adminRole;
10
+ * - home "/" é sempre pública.
11
+ */
12
+ permission?: string;
13
+ }
14
+ /** Href efetivo de um item: o do 1º filho (se grupo) ou o próprio. */
15
+ export declare function resolveNavHref(item: NavItem): string;
16
+ export declare function canShowItem(item: NavItem, hasPermission: (permission: string) => boolean, hasRole: (role: string) => boolean, adminRole?: string): boolean;
17
+ /** Filtra recursivamente a árvore, dropando grupos que ficaram sem filhos visíveis. */
18
+ export declare function filterNavItems(items: NavItem[], hasPermission: (permission: string) => boolean, hasRole: (role: string) => boolean, adminRole?: string): NavItem[];
19
+ //# sourceMappingURL=menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../src/menu.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,OAAO,EACb,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,EAC9C,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,EAClC,SAAS,SAAU,GAClB,OAAO,CAiBT;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EAAE,EAChB,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,EAC9C,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,EAClC,SAAS,SAAU,GAClB,OAAO,EAAE,CAiBX"}
package/dist/menu.js ADDED
@@ -0,0 +1,41 @@
1
+ // Algoritmo de menu-por-permissão (portado do admin-web Shell.tsx). Funções PURAS —
2
+ // sem React, client-safe. A app fornece a árvore `NavItem[]` (conteúdo específico dela)
3
+ // e as funções hasPermission/hasRole (do usePermissions); estas funções filtram.
4
+ /** Href efetivo de um item: o do 1º filho (se grupo) ou o próprio. */
5
+ export function resolveNavHref(item) {
6
+ return item.children?.[0]?.href ?? item.href;
7
+ }
8
+ export function canShowItem(item, hasPermission, hasRole, adminRole = "ADMIN") {
9
+ // Grupo: aparece se tiver >=1 filho visível — quem decide são os filhos (o
10
+ // filterNavItems dropa o grupo se sobrar 0). NÃO gateie o grupo pelo próprio href,
11
+ // senão some um grupo cujo filho o usuário PODE ver.
12
+ if (item.children?.length) {
13
+ return true;
14
+ }
15
+ // Home sempre pública.
16
+ if (item.href === "/") {
17
+ return true;
18
+ }
19
+ // Leaf com permissão declarada: exige a permissão de read.
20
+ if (item.permission) {
21
+ return hasPermission(item.permission);
22
+ }
23
+ // Leaf de plataforma sem permissão dedicada no backend: só adminRole.
24
+ return hasRole(adminRole);
25
+ }
26
+ /** Filtra recursivamente a árvore, dropando grupos que ficaram sem filhos visíveis. */
27
+ export function filterNavItems(items, hasPermission, hasRole, adminRole = "ADMIN") {
28
+ return items.flatMap((item) => {
29
+ if (!canShowItem(item, hasPermission, hasRole, adminRole)) {
30
+ return [];
31
+ }
32
+ if (!item.children?.length) {
33
+ return [item];
34
+ }
35
+ const children = filterNavItems(item.children, hasPermission, hasRole, adminRole);
36
+ if (children.length === 0) {
37
+ return [];
38
+ }
39
+ return [{ ...item, children }];
40
+ });
41
+ }