@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.
- 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
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AA+BnE,eAAO,MAAM,MAAM,EAAE,yBAOpB,CAAC"}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Reference implementation of the manifest read by the SeedGrid CLI (Rust).
|
|
2
|
+
// See seedgrid-cli/docs/adr/0004-module-manifest-contract.md.
|
|
3
|
+
//
|
|
4
|
+
// `provider.from` is a scaffold-relative app alias (`@/modules/security`),
|
|
5
|
+
// not an npm import: `AuthProvider` runs the full login/session/redirect
|
|
6
|
+
// flow and only exists in scaffold (it hardcodes scaffold routes — see
|
|
7
|
+
// scaffold/README.md for the lib-vs-scaffold split found while extracting
|
|
8
|
+
// this module). The wiring mechanism is plain string substitution into a
|
|
9
|
+
// marker, so it doesn't care whether `from` resolves to an npm package or an
|
|
10
|
+
// app-local alias — this field just needs to be *some* valid import
|
|
11
|
+
// specifier for the generated app.
|
|
12
|
+
//
|
|
13
|
+
// `@seedgrid/fe-security/client` also exports `PermissionProvider`, a
|
|
14
|
+
// lighter permission-only context (ported from admin-web's
|
|
15
|
+
// `AdminAccessProvider`) for apps that already have their own login/session
|
|
16
|
+
// flow and only want permission checks. That's a different consumption
|
|
17
|
+
// pattern from CLI-scaffolded apps, so it is not referenced here.
|
|
18
|
+
//
|
|
19
|
+
// `navigation` uses the scaffold-local hook via the `@/modules/security` alias
|
|
20
|
+
// — same mechanism as `provider` above (plain string substitution into the
|
|
21
|
+
// app-shell-navigation markers, so a scaffold alias is fine even though the
|
|
22
|
+
// hook hardcodes scaffold routes). Without this the CLI never wires
|
|
23
|
+
// `...useSecurityAppShellSections()` into the app shell and the generated app
|
|
24
|
+
// ships with an EMPTY navigation menu.
|
|
25
|
+
//
|
|
26
|
+
// `env` lists only what is verified today (the service-token fallback read
|
|
27
|
+
// directly from `admin-web/lib/seedgrid-report-api-server.ts`). The base API
|
|
28
|
+
// URL is resolved per-tenant from the hostname, not a static env var, so it
|
|
29
|
+
// is not listed here.
|
|
30
|
+
export const module = {
|
|
31
|
+
id: "security",
|
|
32
|
+
configKey: "security",
|
|
33
|
+
env: ["SEEDGRID_REPORT_API_TOKEN"],
|
|
34
|
+
provider: [{ import: "AuthProvider", from: "@/modules/security" }],
|
|
35
|
+
navigation: { import: "useSecurityAppShellSections", from: "@/modules/security" },
|
|
36
|
+
messages: { import: "securityMessages", from: "@seedgrid/fe-security" },
|
|
37
|
+
};
|
package/dist/module.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "security",
|
|
3
|
+
"configKey": "security",
|
|
4
|
+
"env": [
|
|
5
|
+
"SEEDGRID_REPORT_API_TOKEN"
|
|
6
|
+
],
|
|
7
|
+
"provider": [
|
|
8
|
+
{
|
|
9
|
+
"import": "AuthProvider",
|
|
10
|
+
"from": "@/modules/security"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"navigation": {
|
|
14
|
+
"import": "useSecurityAppShellSections",
|
|
15
|
+
"from": "@/modules/security"
|
|
16
|
+
},
|
|
17
|
+
"messages": {
|
|
18
|
+
"import": "securityMessages",
|
|
19
|
+
"from": "@seedgrid/fe-security"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/paths.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export declare const SecurityPaths: {
|
|
|
40
40
|
/** ME (/me) — escopo do usuário corrente pelo token. */
|
|
41
41
|
readonly me: {
|
|
42
42
|
readonly permissions: "/me/permissions";
|
|
43
|
+
readonly companies: "/me/companies";
|
|
43
44
|
readonly selectCompany: "/me/select-company";
|
|
44
45
|
};
|
|
45
46
|
/** COMPANIES (/companies, /public/companies). */
|
package/dist/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,aAAa;IACxB,6CAA6C;;;;;;;;IAS7C,gEAAgE;;;;;;;;;;IAWhE,sBAAsB;;;;;wCAKG,MAAM;oCACV,MAAM;wCAEF,MAAM,mBAAmB,MAAM;;IAIxD,sBAAsB;;;;wCAIG,MAAM;;IAG/B,kCAAkC;;;wCAGT,MAAM;;IAG/B,wDAAwD
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,aAAa;IACxB,6CAA6C;;;;;;;;IAS7C,gEAAgE;;;;;;;;;;IAWhE,sBAAsB;;;;;wCAKG,MAAM;oCACV,MAAM;wCAEF,MAAM,mBAAmB,MAAM;;IAIxD,sBAAsB;;;;wCAIG,MAAM;;IAG/B,kCAAkC;;;wCAGT,MAAM;;IAG/B,wDAAwD;;;;;;IASxD,iDAAiD;;;;wCAIxB,MAAM;;IAG/B,gFAAgF;;;;;mCAK5D,MAAM;oCACL,MAAM;mCACP,MAAM;+BACV,MAAM;8BACP,MAAM;4CACQ,MAAM;6CACL,MAAM;wCAEX,MAAM;;CAEvB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC"}
|
package/dist/paths.js
CHANGED
|
@@ -48,6 +48,9 @@ export const SecurityPaths = {
|
|
|
48
48
|
/** ME (/me) — escopo do usuário corrente pelo token. */
|
|
49
49
|
me: {
|
|
50
50
|
permissions: "/me/permissions",
|
|
51
|
+
// Empresas (memberships) do usuário, para o seletor de empresa (login-tenancy).
|
|
52
|
+
companies: "/me/companies",
|
|
53
|
+
// Troca a empresa corrente e re-emite o par de tokens escopado (login-tenancy).
|
|
51
54
|
selectCompany: "/me/select-company",
|
|
52
55
|
},
|
|
53
56
|
/** COMPANIES (/companies, /public/companies). */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type PermissionInput = Iterable<string> | Set<string>;
|
|
2
|
+
export declare function normalizePermission(permission: string | null | undefined): string;
|
|
3
|
+
export declare function createPermissionSet(permissions: Iterable<string>): Set<string>;
|
|
4
|
+
export declare function hasPermission(permissions: PermissionInput, permission: string | null | undefined): boolean;
|
|
5
|
+
export declare function hasAnyPermission(permissions: PermissionInput, requiredPermissions: Array<string | null | undefined>): boolean;
|
|
6
|
+
export declare function hasAllPermissions(permissions: PermissionInput, requiredPermissions: Array<string | null | undefined>): boolean;
|
|
7
|
+
export declare function buildCrudPermissions(resource: string): {
|
|
8
|
+
read: string;
|
|
9
|
+
create: string;
|
|
10
|
+
update: string;
|
|
11
|
+
delete: string;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA,KAAK,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;AAEtD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,UAMxE;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,eAMhE;AAQD,wBAAgB,aAAa,CAC3B,WAAW,EAAE,eAAe,EAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,WAStC;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,eAAe,EAC5B,mBAAmB,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,WAKtD;AAED,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,eAAe,EAC5B,mBAAmB,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,WAKtD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM;;;;;EAYpD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function normalizePermission(permission) {
|
|
2
|
+
return String(permission ?? "")
|
|
3
|
+
.trim()
|
|
4
|
+
.replace(/[.\s-]+/g, "_")
|
|
5
|
+
.replace(/__+/g, "_")
|
|
6
|
+
.toUpperCase();
|
|
7
|
+
}
|
|
8
|
+
export function createPermissionSet(permissions) {
|
|
9
|
+
return new Set(Array.from(permissions)
|
|
10
|
+
.map((permission) => normalizePermission(permission))
|
|
11
|
+
.filter(Boolean));
|
|
12
|
+
}
|
|
13
|
+
function ensurePermissionSet(permissions) {
|
|
14
|
+
return permissions instanceof Set
|
|
15
|
+
? createPermissionSet(permissions)
|
|
16
|
+
: createPermissionSet(permissions);
|
|
17
|
+
}
|
|
18
|
+
export function hasPermission(permissions, permission) {
|
|
19
|
+
const normalizedPermission = normalizePermission(permission);
|
|
20
|
+
if (!normalizedPermission) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return ensurePermissionSet(permissions).has(normalizedPermission);
|
|
24
|
+
}
|
|
25
|
+
export function hasAnyPermission(permissions, requiredPermissions) {
|
|
26
|
+
return requiredPermissions.some((permission) => hasPermission(permissions, permission));
|
|
27
|
+
}
|
|
28
|
+
export function hasAllPermissions(permissions, requiredPermissions) {
|
|
29
|
+
return requiredPermissions.every((permission) => hasPermission(permissions, permission));
|
|
30
|
+
}
|
|
31
|
+
export function buildCrudPermissions(resource) {
|
|
32
|
+
const normalizedResource = normalizePermission(resource).replace(/_(READ|CREATE|UPDATE|DELETE)$/, "");
|
|
33
|
+
return {
|
|
34
|
+
read: `${normalizedResource}_READ`,
|
|
35
|
+
create: `${normalizedResource}_CREATE`,
|
|
36
|
+
update: `${normalizedResource}_UPDATE`,
|
|
37
|
+
delete: `${normalizedResource}_DELETE`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../src/server/action.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,wBAAsB,cAAc,CAAC,CAAC,EACpC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAM1B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Helper de Server Action (mutações). Roda o motor de sessão e devolve um ActionResult
|
|
2
|
+
// tipado — NUNCA lança pro client (evita a redação de erro do Next em produção).
|
|
3
|
+
//
|
|
4
|
+
// Uso na action:
|
|
5
|
+
// "use server";
|
|
6
|
+
// export async function updateUser(input) {
|
|
7
|
+
// return toActionResult(() =>
|
|
8
|
+
// security.apiFetchSecurity(SecurityPaths.users.byPublicId(input.id), {
|
|
9
|
+
// method: "PUT", body: JSON.stringify(input),
|
|
10
|
+
// }),
|
|
11
|
+
// );
|
|
12
|
+
// }
|
|
13
|
+
import { toProblem } from "./to-problem";
|
|
14
|
+
export async function toActionResult(run) {
|
|
15
|
+
try {
|
|
16
|
+
return { ok: true, data: await run() };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return { ok: false, problem: toProblem(error) };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-security-server.d.ts","sourceRoot":"","sources":["../../src/server/create-security-server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAMtD,OAAO,KAAK,EAEV,cAAc,EACf,MAAM,oBAAoB,CAAC;AAE5B,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,GAC3B,cAAc,CAsMhB"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Motor de sessão server-side do SeedGrid Security (portado do admin-web
|
|
2
|
+
// lib/seedgrid-report-api-server.ts, com os hardcodes parametrizados via
|
|
3
|
+
// SecurityServerConfig). SERVER-ONLY: usa next/headers.
|
|
4
|
+
//
|
|
5
|
+
// O app cria uma instância uma vez e importa os helpers ligados nos route handlers:
|
|
6
|
+
// export const security = createSecurityServer({ apiBaseUrl, cookies, ... });
|
|
7
|
+
import { cookies } from "next/headers";
|
|
8
|
+
import { ApiClientError } from "@seedgrid/fe-core";
|
|
9
|
+
import { SecurityPaths } from "../paths";
|
|
10
|
+
import { isLoginSuccess, } from "../session-contract";
|
|
11
|
+
import { ApiConnectionError, ApiRequestError } from "./errors";
|
|
12
|
+
export function createSecurityServer(config) {
|
|
13
|
+
const secure = config.secureCookies ?? process.env.NODE_ENV === "production";
|
|
14
|
+
async function buildHeaders(headers) {
|
|
15
|
+
const cookieStore = await cookies();
|
|
16
|
+
const sessionToken = cookieStore.get(config.cookies.token)?.value;
|
|
17
|
+
const tenancy = config.resolveTenancyHeaders
|
|
18
|
+
? await config.resolveTenancyHeaders()
|
|
19
|
+
: undefined;
|
|
20
|
+
return {
|
|
21
|
+
...(sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {}),
|
|
22
|
+
...(!sessionToken && config.serviceToken
|
|
23
|
+
? { Authorization: `Bearer ${config.serviceToken}` }
|
|
24
|
+
: {}),
|
|
25
|
+
...tenancy,
|
|
26
|
+
...headers,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function safeFetch(path, options = {}) {
|
|
30
|
+
try {
|
|
31
|
+
return await fetch(`${config.apiBaseUrl}${path}`, {
|
|
32
|
+
...options,
|
|
33
|
+
headers: await buildHeaders({
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
...options.headers,
|
|
36
|
+
}),
|
|
37
|
+
cache: "no-store",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new ApiConnectionError(path, error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function applySessionCookies(data) {
|
|
45
|
+
const cookieStore = await cookies();
|
|
46
|
+
const opts = { httpOnly: true, path: "/", sameSite: "lax", secure };
|
|
47
|
+
cookieStore.set(config.cookies.token, data.token, opts);
|
|
48
|
+
if (typeof data.refreshToken === "string") {
|
|
49
|
+
cookieStore.set(config.cookies.refresh, data.refreshToken, opts);
|
|
50
|
+
}
|
|
51
|
+
if (data.changePasswordRequired) {
|
|
52
|
+
cookieStore.set(config.cookies.passwordChangeRequired, "1", opts);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
cookieStore.delete(config.cookies.passwordChangeRequired);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function clearAuthCookies() {
|
|
59
|
+
const cookieStore = await cookies();
|
|
60
|
+
cookieStore.delete(config.cookies.token);
|
|
61
|
+
cookieStore.delete(config.cookies.refresh);
|
|
62
|
+
cookieStore.delete(config.cookies.passwordChangeRequired);
|
|
63
|
+
}
|
|
64
|
+
async function tryRefreshSession() {
|
|
65
|
+
const cookieStore = await cookies();
|
|
66
|
+
const refreshToken = cookieStore.get(config.cookies.refresh)?.value;
|
|
67
|
+
if (!refreshToken) {
|
|
68
|
+
await clearAuthCookies();
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
let response;
|
|
72
|
+
try {
|
|
73
|
+
response = await fetch(`${config.apiBaseUrl}${SecurityPaths.auth.refresh}`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({ refreshToken }),
|
|
77
|
+
cache: "no-store",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
await clearAuthCookies();
|
|
82
|
+
throw new ApiConnectionError(SecurityPaths.auth.refresh, error);
|
|
83
|
+
}
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
await clearAuthCookies();
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const payload = (await response.json());
|
|
89
|
+
if (!isLoginSuccess(payload)) {
|
|
90
|
+
await clearAuthCookies();
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
await applySessionCookies(payload);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
async function fetchWithSession(path, options = {}) {
|
|
97
|
+
let response = await safeFetch(path, options);
|
|
98
|
+
if (response.status !== 401) {
|
|
99
|
+
return response;
|
|
100
|
+
}
|
|
101
|
+
const refreshed = await tryRefreshSession();
|
|
102
|
+
if (!refreshed) {
|
|
103
|
+
return response;
|
|
104
|
+
}
|
|
105
|
+
response = await safeFetch(path, options);
|
|
106
|
+
return response;
|
|
107
|
+
}
|
|
108
|
+
function isInvalidSessionResponse(status, body) {
|
|
109
|
+
// Apenas 401 = sessão inválida/expirada (limpa cookies → login). 403 NÃO: é
|
|
110
|
+
// autenticado SEM permissão (sessão válida) — a tela só mostra "Acesso negado",
|
|
111
|
+
// nunca desloga. Tratar 403 como inválido fazia clicar num item proibido jogar o
|
|
112
|
+
// usuário pro /login.
|
|
113
|
+
if (status === 401) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
const normalized = body.toLocaleLowerCase("pt-BR");
|
|
117
|
+
return (normalized.includes("usuario nao encontrado") ||
|
|
118
|
+
normalized.includes("usuário não encontrado"));
|
|
119
|
+
}
|
|
120
|
+
function parseResponseBody(text) {
|
|
121
|
+
const trimmed = text.trim();
|
|
122
|
+
if (!trimmed) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(trimmed);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return text;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function apiFetchServer(path, options = {}) {
|
|
133
|
+
const response = await fetchWithSession(path, options);
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const text = await response.text();
|
|
136
|
+
if (isInvalidSessionResponse(response.status, text)) {
|
|
137
|
+
await clearAuthCookies();
|
|
138
|
+
}
|
|
139
|
+
throw new ApiRequestError(response.status, text);
|
|
140
|
+
}
|
|
141
|
+
return (await response.json());
|
|
142
|
+
}
|
|
143
|
+
async function apiFetchSecurity(path, options = {}) {
|
|
144
|
+
const response = await fetchWithSession(path, options);
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
const text = await response.text();
|
|
147
|
+
if (isInvalidSessionResponse(response.status, text)) {
|
|
148
|
+
await clearAuthCookies();
|
|
149
|
+
}
|
|
150
|
+
throw new ApiClientError(`API request failed with status ${response.status}`, response.status, parseResponseBody(text));
|
|
151
|
+
}
|
|
152
|
+
if (response.status === 204) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return (await response.json());
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
fetchWithSession,
|
|
159
|
+
apiFetchSecurity,
|
|
160
|
+
apiFetchServer,
|
|
161
|
+
buildHeaders,
|
|
162
|
+
applySessionCookies,
|
|
163
|
+
clearAuthCookies,
|
|
164
|
+
isInvalidSessionResponse,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Erro de request com corpo cru (usado pelo apiFetchServer, retrocompat). */
|
|
2
|
+
export declare class ApiRequestError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly body: string;
|
|
5
|
+
constructor(status: number, body: string);
|
|
6
|
+
}
|
|
7
|
+
/** Falha de conexão com o report-api (rede indisponível, DNS, etc.). */
|
|
8
|
+
export declare class ApiConnectionError extends Error {
|
|
9
|
+
readonly path: string;
|
|
10
|
+
constructor(path: string, cause?: unknown);
|
|
11
|
+
}
|
|
12
|
+
export declare function isApiConnectionError(error: unknown): error is ApiConnectionError;
|
|
13
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/server/errors.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAMzC;AAED,wEAAwE;AACxE,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM1C;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAEhF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Erros do motor de sessão server-side. `ApiClientError` (repasse do problem+json)
|
|
2
|
+
// vem do @seedgrid/fe-core e é usado pelo apiFetchSecurity/problemResponse.
|
|
3
|
+
/** Erro de request com corpo cru (usado pelo apiFetchServer, retrocompat). */
|
|
4
|
+
export class ApiRequestError extends Error {
|
|
5
|
+
status;
|
|
6
|
+
body;
|
|
7
|
+
constructor(status, body) {
|
|
8
|
+
super(body || `API request failed: ${status}`);
|
|
9
|
+
this.name = "ApiRequestError";
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.body = body;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Falha de conexão com o report-api (rede indisponível, DNS, etc.). */
|
|
15
|
+
export class ApiConnectionError extends Error {
|
|
16
|
+
path;
|
|
17
|
+
constructor(path, cause) {
|
|
18
|
+
super(`Não foi possível conectar à API em ${path}.`);
|
|
19
|
+
this.name = "ApiConnectionError";
|
|
20
|
+
this.path = path;
|
|
21
|
+
this.cause = cause;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function isApiConnectionError(error) {
|
|
25
|
+
return error instanceof ApiConnectionError;
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createSecurityServer } from "./create-security-server";
|
|
2
|
+
export type { SecurityServer, SecurityFetchOptions, } from "../server-contract";
|
|
3
|
+
export { ApiRequestError, ApiConnectionError, isApiConnectionError, } from "./errors";
|
|
4
|
+
export { problemResponse } from "./problem-response";
|
|
5
|
+
export { toProblem } from "./to-problem";
|
|
6
|
+
export { toActionResult } from "./action";
|
|
7
|
+
export { toRouteResponse } from "./route";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,YAAY,EACV,cAAc,EACd,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Barrel SERVER-ONLY (@seedgrid/fe-security/server). Usa next/headers e next/server —
|
|
2
|
+
// NÃO importe daqui em componentes client.
|
|
3
|
+
export { createSecurityServer } from "./create-security-server";
|
|
4
|
+
export { ApiRequestError, ApiConnectionError, isApiConnectionError, } from "./errors";
|
|
5
|
+
export { problemResponse } from "./problem-response";
|
|
6
|
+
export { toProblem } from "./to-problem";
|
|
7
|
+
export { toActionResult } from "./action";
|
|
8
|
+
export { toRouteResponse } from "./route";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"problem-response.d.ts","sourceRoot":"","sources":["../../src/server/problem-response.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAiC5D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Converte o erro lançado por uma chamada server-side (ApiClientError) na resposta que
|
|
2
|
+
// o route handler do Next devolve ao client. Repassa o `responseBody` (problem+json
|
|
3
|
+
// original do report-api) com o `status` upstream, como application/problem+json —
|
|
4
|
+
// assim o `extractApiErrorMessage` do lado client recupera a mensagem íntegra.
|
|
5
|
+
import { NextResponse } from "next/server";
|
|
6
|
+
import { ApiClientError } from "@seedgrid/fe-core";
|
|
7
|
+
export function problemResponse(error) {
|
|
8
|
+
if (error instanceof ApiClientError) {
|
|
9
|
+
const { responseBody, status } = error;
|
|
10
|
+
if (typeof responseBody === "string") {
|
|
11
|
+
return new NextResponse(responseBody, {
|
|
12
|
+
status,
|
|
13
|
+
headers: { "Content-Type": "application/problem+json" },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (responseBody != null) {
|
|
17
|
+
return NextResponse.json(responseBody, {
|
|
18
|
+
status,
|
|
19
|
+
headers: { "Content-Type": "application/problem+json" },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return NextResponse.json({ status, title: error.message }, { status, headers: { "Content-Type": "application/problem+json" } });
|
|
23
|
+
}
|
|
24
|
+
const detail = error instanceof Error
|
|
25
|
+
? error.message
|
|
26
|
+
: "Não foi possível conectar à API.";
|
|
27
|
+
return NextResponse.json({ status: 502, title: "Bad Gateway", detail }, { status: 502, headers: { "Content-Type": "application/problem+json" } });
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/server/route.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,wBAAsB,eAAe,CAAC,CAAC,EACrC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,YAAY,CAAC,CAUvB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Helper de route handler (leituras GET — cacheáveis). Roda o motor de sessão e
|
|
2
|
+
// devolve um NextResponse: sucesso → json; erro → application/problem+json (repassa o
|
|
3
|
+
// problem+json do report-api íntegro, pro extractApiErrorMessage do client ler).
|
|
4
|
+
//
|
|
5
|
+
// Uso no route handler:
|
|
6
|
+
// export async function GET() {
|
|
7
|
+
// return toRouteResponse(() => security.apiFetchSecurity(SecurityPaths.users.base));
|
|
8
|
+
// }
|
|
9
|
+
import { NextResponse } from "next/server";
|
|
10
|
+
import { problemResponse } from "./problem-response";
|
|
11
|
+
export async function toRouteResponse(run) {
|
|
12
|
+
try {
|
|
13
|
+
const data = await run();
|
|
14
|
+
if (data === null || data === undefined) {
|
|
15
|
+
return new NextResponse(null, { status: 204 });
|
|
16
|
+
}
|
|
17
|
+
return NextResponse.json(data);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return problemResponse(error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-problem.d.ts","sourceRoot":"","sources":["../../src/server/to-problem.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAoBvD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Converte um erro server-side (ApiClientError do motor de sessão, ou falha de rede)
|
|
2
|
+
// num ProblemDetail serializável. Base compartilhada por route handler e Server Action.
|
|
3
|
+
import { ApiClientError } from "@seedgrid/fe-core";
|
|
4
|
+
export function toProblem(error) {
|
|
5
|
+
if (error instanceof ApiClientError) {
|
|
6
|
+
const body = error.responseBody;
|
|
7
|
+
// problem+json do report-api: repassa o corpo inteiro (detail/violations/title),
|
|
8
|
+
// preservando o status do body quando presente.
|
|
9
|
+
if (body && typeof body === "object") {
|
|
10
|
+
return { status: error.status, ...body };
|
|
11
|
+
}
|
|
12
|
+
if (typeof body === "string") {
|
|
13
|
+
return { status: error.status, detail: body };
|
|
14
|
+
}
|
|
15
|
+
return { status: error.status, title: error.message };
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
status: 502,
|
|
19
|
+
title: "Bad Gateway",
|
|
20
|
+
detail: error instanceof Error ? error.message : "Erro de conexão.",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SecurityLoginResponse } from "./session-contract";
|
|
2
|
+
export type SecurityFetchOptions = Omit<RequestInit, "headers"> & {
|
|
3
|
+
headers?: HeadersInit;
|
|
4
|
+
};
|
|
5
|
+
export interface SecurityServer {
|
|
6
|
+
/** Fetch cru contra o report-api com sessão (cookie→Bearer) e refresh em 401. */
|
|
7
|
+
fetchWithSession(path: string, options?: SecurityFetchOptions): Promise<Response>;
|
|
8
|
+
/** Canônico do fluxo de segurança: em erro lança ApiClientError (p/ problemResponse). */
|
|
9
|
+
apiFetchSecurity<T>(path: string, options?: SecurityFetchOptions): Promise<T>;
|
|
10
|
+
/** Variante retrocompat: em erro lança ApiRequestError (corpo cru). */
|
|
11
|
+
apiFetchServer<T>(path: string, options?: SecurityFetchOptions): Promise<T>;
|
|
12
|
+
/** Monta os headers (Authorization da sessão/service + tenancy) para uma request. */
|
|
13
|
+
buildHeaders(headers?: HeadersInit): Promise<HeadersInit>;
|
|
14
|
+
/** Grava os cookies de sessão a partir de um login/select-company OK. */
|
|
15
|
+
applySessionCookies(data: Extract<SecurityLoginResponse, {
|
|
16
|
+
status: "OK";
|
|
17
|
+
}>): Promise<void>;
|
|
18
|
+
/** Apaga os cookies de sessão. */
|
|
19
|
+
clearAuthCookies(): Promise<void>;
|
|
20
|
+
/** 401 = sessão inválida (desloga). 403 = autenticado sem permissão (NÃO desloga). */
|
|
21
|
+
isInvalidSessionResponse(status: number, body: string): boolean;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=server-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-contract.d.ts","sourceRoot":"","sources":["../src/server-contract.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG;IAChE,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrB,yFAAyF;IACzF,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9E,uEAAuE;IACvE,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5E,qFAAqF;IACrF,YAAY,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1D,yEAAyE;IACzE,mBAAmB,CACjB,IAAI,EAAE,OAAO,CAAC,qBAAqB,EAAE;QAAE,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,kCAAkC;IAClC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,sFAAsF;IACtF,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACjE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type SecurityLoginResponse = {
|
|
2
|
+
status: "OK";
|
|
3
|
+
token: string;
|
|
4
|
+
tokenType?: string;
|
|
5
|
+
refreshToken?: string;
|
|
6
|
+
publicId?: string;
|
|
7
|
+
changePasswordRequired?: boolean;
|
|
8
|
+
twoFactorEnabled?: boolean;
|
|
9
|
+
} | {
|
|
10
|
+
status: "2F_REQUIRED";
|
|
11
|
+
tempToken: string;
|
|
12
|
+
method?: string;
|
|
13
|
+
} | {
|
|
14
|
+
status: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
};
|
|
17
|
+
export type SecurityResend2FAResponse = {
|
|
18
|
+
status: string;
|
|
19
|
+
method?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
tempToken?: string;
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
error?: string;
|
|
24
|
+
};
|
|
25
|
+
/** Escopo de permissões do usuário corrente (GET /me/permissions). */
|
|
26
|
+
export interface MePermissions {
|
|
27
|
+
userPublicId: string;
|
|
28
|
+
name: string;
|
|
29
|
+
roles: string[];
|
|
30
|
+
permissions: string[];
|
|
31
|
+
}
|
|
32
|
+
/** Narrowing: login concluído com token (status "OK"). */
|
|
33
|
+
export declare function isLoginSuccess(payload: SecurityLoginResponse): payload is Extract<SecurityLoginResponse, {
|
|
34
|
+
status: "OK";
|
|
35
|
+
}>;
|
|
36
|
+
//# sourceMappingURL=session-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-contract.d.ts","sourceRoot":"","sources":["../src/session-contract.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,qBAAqB,GAC7B;IACE,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GACD;IACE,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEN,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,sEAAsE;AACtE,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAC5B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IAAI,OAAO,CAAC,qBAAqB,EAAE;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,CAAC,CAM7D"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Contrato de resposta do login do seedgrid-security (espelha o backend). É genérico
|
|
2
|
+
// aos dois modos de tenancy — por isso mora no barrel client-safe, não no /server.
|
|
3
|
+
/** Narrowing: login concluído com token (status "OK"). */
|
|
4
|
+
export function isLoginSuccess(payload) {
|
|
5
|
+
return (payload.status === "OK" &&
|
|
6
|
+
"token" in payload &&
|
|
7
|
+
typeof payload.token === "string");
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Problem Details (RFC 7807) serializável — o corpo de erro do report-api. */
|
|
2
|
+
export interface ProblemDetail {
|
|
3
|
+
status: number;
|
|
4
|
+
title?: string;
|
|
5
|
+
detail?: string;
|
|
6
|
+
/** Violações de validação (se o backend enviar). */
|
|
7
|
+
violations?: Array<{
|
|
8
|
+
field?: string;
|
|
9
|
+
message?: string;
|
|
10
|
+
}>;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export type ActionResult<T> = {
|
|
14
|
+
ok: true;
|
|
15
|
+
data: T;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
problem: ProblemDetail;
|
|
19
|
+
};
|
|
20
|
+
/** Lê a mensagem exibível de um ProblemDetail (detail → 1ª violação → title → status). */
|
|
21
|
+
export declare function readProblemMessage(problem: ProblemDetail): string;
|
|
22
|
+
//# sourceMappingURL=session-result.d.ts.map
|