@mybuildex/pkg 0.6.1 → 0.8.0
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 +35 -0
- package/dist/acl/allows.d.ts +3 -0
- package/dist/acl/allows.js +53 -0
- package/dist/acl/allows.js.map +1 -0
- package/dist/acl/check.d.ts +32 -0
- package/dist/acl/check.js +114 -0
- package/dist/acl/check.js.map +1 -0
- package/dist/acl/map.generated.d.ts +1 -0
- package/dist/acl/map.generated.js +2367 -0
- package/dist/acl/map.generated.js.map +1 -0
- package/dist/acl/plugin.d.ts +55 -0
- package/dist/acl/plugin.js +261 -0
- package/dist/acl/plugin.js.map +1 -0
- package/dist/acl/roles.d.ts +23 -0
- package/dist/acl/roles.js +71 -0
- package/dist/acl/roles.js.map +1 -0
- package/dist/audit/audit.d.ts +165 -0
- package/dist/audit/audit.js +595 -0
- package/dist/audit/audit.js.map +1 -0
- package/dist/audit/serialize.d.ts +20 -0
- package/dist/audit/serialize.js +94 -0
- package/dist/audit/serialize.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +68 -1
- package/dist/index.js.map +1 -1
- package/dist/prisma/multitenant.d.ts +6 -0
- package/dist/prisma/multitenant.js +6 -2
- package/dist/prisma/multitenant.js.map +1 -1
- package/dist/redis/client.d.ts +18 -0
- package/dist/redis/client.js +73 -0
- package/dist/redis/client.js.map +1 -0
- package/dist/tenant/auth.d.ts +4 -2
- package/dist/tenant/auth.js +29 -2
- package/dist/tenant/auth.js.map +1 -1
- package/dist/tenant/permissions.d.ts +57 -0
- package/dist/tenant/permissions.js +187 -0
- package/dist/tenant/permissions.js.map +1 -0
- package/package.json +5 -3
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Papéis aceitos em inglês e português — espelho de
|
|
3
|
+
// src/utils/roles.js do front da plataforma. O array de
|
|
4
|
+
// permissões pode trazer o nome em inglês ('teacher'/'student') ou em pt-BR,
|
|
5
|
+
// igual ao profiles.identifier do banco ('professor'/'aluno'). Basta um deles.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.FUNCTION_ROLE_ALIASES = exports.FUNCTION_ROLES = exports.FUNCTION_ROLE_PAIRS = exports.ROLE_ALIASES = exports.RESPONSIBLE_ACLS = exports.STUDENT_ACLS = exports.TEACHER_ACLS = void 0;
|
|
8
|
+
exports.isRoleName = isRoleName;
|
|
9
|
+
exports.isSingleRoleList = isSingleRoleList;
|
|
10
|
+
exports.isFunctionRoleName = isFunctionRoleName;
|
|
11
|
+
exports.hasAclName = hasAclName;
|
|
12
|
+
exports.hasAnyOf = hasAnyOf;
|
|
13
|
+
exports.TEACHER_ACLS = ["teacher", "professor"];
|
|
14
|
+
exports.STUDENT_ACLS = ["student", "aluno"];
|
|
15
|
+
/** Responsável (front: store/active-child.js). Não entra na regra de ACL; só no resolver. */
|
|
16
|
+
exports.RESPONSIBLE_ACLS = ["responsible", "responsavel"];
|
|
17
|
+
/** nome (en ou pt) → todos os nomes equivalentes do mesmo papel */
|
|
18
|
+
exports.ROLE_ALIASES = {
|
|
19
|
+
teacher: exports.TEACHER_ACLS,
|
|
20
|
+
professor: exports.TEACHER_ACLS,
|
|
21
|
+
student: exports.STUDENT_ACLS,
|
|
22
|
+
aluno: exports.STUDENT_ACLS,
|
|
23
|
+
};
|
|
24
|
+
function isRoleName(name) {
|
|
25
|
+
return Object.prototype.hasOwnProperty.call(exports.ROLE_ALIASES, name);
|
|
26
|
+
}
|
|
27
|
+
/** true quando a lista só tem nomes de UM papel (ex.: ['teacher'] ou ['teacher', 'professor']). */
|
|
28
|
+
function isSingleRoleList(names) {
|
|
29
|
+
if (!names.length || !names.every(isRoleName))
|
|
30
|
+
return false;
|
|
31
|
+
return new Set(names.map(n => exports.ROLE_ALIASES[n][0])).size === 1;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Papéis de função da escola, en ↔ pt-BR — espelho de
|
|
35
|
+
* FUNCTION_ROLE_PAIRS do front (utils/roles.js). Diferente de professor/aluno,
|
|
36
|
+
* NÃO entram na regra "lista só com papel nega". O mapeamento papel → telas
|
|
37
|
+
* vive nas listas `acl` do mapa do front (ACL_MAP), com o nome em inglês.
|
|
38
|
+
*/
|
|
39
|
+
exports.FUNCTION_ROLE_PAIRS = [
|
|
40
|
+
["director", "diretor"],
|
|
41
|
+
["deputy_director", "diretor_auxiliar"],
|
|
42
|
+
["coordinator", "coordenador"],
|
|
43
|
+
["pedagogical", "pedagogico"],
|
|
44
|
+
["secretary", "secretaria"],
|
|
45
|
+
["assistant_secretary", "secretaria_auxiliar"],
|
|
46
|
+
["financial", "financeiro"],
|
|
47
|
+
["cashier", "financeiro_caixa"],
|
|
48
|
+
["doorman", "porteiro"],
|
|
49
|
+
["counselor", "orientador"],
|
|
50
|
+
["teaching_assistant", "monitor"],
|
|
51
|
+
["admissions", "comercial"],
|
|
52
|
+
["reception", "recepcao"],
|
|
53
|
+
];
|
|
54
|
+
exports.FUNCTION_ROLES = exports.FUNCTION_ROLE_PAIRS.map(([en]) => en);
|
|
55
|
+
exports.FUNCTION_ROLE_ALIASES = Object.freeze(Object.fromEntries(exports.FUNCTION_ROLE_PAIRS.flatMap(pair => pair.map(name => [name, pair]))));
|
|
56
|
+
function isFunctionRoleName(name) {
|
|
57
|
+
return Object.prototype.hasOwnProperty.call(exports.FUNCTION_ROLE_ALIASES, name);
|
|
58
|
+
}
|
|
59
|
+
/** As permissões do usuário têm `name` (papéis aceitam o nome en ou pt). */
|
|
60
|
+
function hasAclName(acl, name) {
|
|
61
|
+
if (isRoleName(name))
|
|
62
|
+
return exports.ROLE_ALIASES[name].some(alias => acl.includes(alias));
|
|
63
|
+
if (isFunctionRoleName(name))
|
|
64
|
+
return exports.FUNCTION_ROLE_ALIASES[name].some(alias => acl.includes(alias));
|
|
65
|
+
return acl.includes(name);
|
|
66
|
+
}
|
|
67
|
+
/** Alguma das permissões do usuário é um dos nomes. */
|
|
68
|
+
function hasAnyOf(acl, names) {
|
|
69
|
+
return names.some(n => acl.includes(n));
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=roles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roles.js","sourceRoot":"","sources":["../../src/acl/roles.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { SerializeOptions } from "./serialize";
|
|
2
|
+
export declare const AUDIT_WRITE_OPERATIONS: readonly ["create", "createMany", "createManyAndReturn", "update", "updateMany", "updateManyAndReturn", "upsert", "delete", "deleteMany"];
|
|
3
|
+
export type AuditOrigin = "USER" | "ANONYMOUS" | "SERVICE" | "CRON" | "WEBHOOK" | "SYSTEM";
|
|
4
|
+
export interface AuditOptions extends SerializeOptions {
|
|
5
|
+
/** Nome da API (ex.: v1_pedagogico). */
|
|
6
|
+
api: string;
|
|
7
|
+
/** Tabelas (models do Prisma) auditadas. "*" = TODAS: tabela nova
|
|
8
|
+
* entra sozinha, sem manter lista. Vazio = nada auditado. */
|
|
9
|
+
tables: string[] | "*";
|
|
10
|
+
/** Com tables "*": models que NÃO são auditados. */
|
|
11
|
+
exclude?: string[];
|
|
12
|
+
/** Tabela de destino (default audit_logs). */
|
|
13
|
+
table?: string;
|
|
14
|
+
/** Máximo de linhas lidas como "antes" num updateMany/deleteMany (default 200). */
|
|
15
|
+
maxManyRows?: number;
|
|
16
|
+
/** Log estruturado (default console.error/warn em JSON). */
|
|
17
|
+
logger?: (event: string, data: Record<string, unknown>) => void;
|
|
18
|
+
/** Resolve o usuário (users.id), a pessoa (users.id_people) e o nome
|
|
19
|
+
* (peoples.name) do autor pelo uid do token. Default true. */
|
|
20
|
+
actorLookup?: boolean;
|
|
21
|
+
/** TTL do cache do autor por escola+uid, ms (default 10 min). */
|
|
22
|
+
actorCacheTtlMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Cliente base (sem extensão) usado para ler o "antes" e gravar o log. */
|
|
25
|
+
export interface AuditBaseClient {
|
|
26
|
+
$schema: string;
|
|
27
|
+
$executeRawUnsafe(sql: string, ...params: unknown[]): Promise<unknown>;
|
|
28
|
+
$queryRawUnsafe?(sql: string, ...params: unknown[]): Promise<unknown>;
|
|
29
|
+
[model: string]: any;
|
|
30
|
+
}
|
|
31
|
+
export interface AuditEntry {
|
|
32
|
+
api: string;
|
|
33
|
+
schema: string;
|
|
34
|
+
auditTable: string;
|
|
35
|
+
client: AuditBaseClient;
|
|
36
|
+
createdAt: Date;
|
|
37
|
+
tableName: string;
|
|
38
|
+
recordId: string | null;
|
|
39
|
+
operation: string;
|
|
40
|
+
before: unknown;
|
|
41
|
+
after: unknown;
|
|
42
|
+
changed: string[] | null;
|
|
43
|
+
affectedRows: number | null;
|
|
44
|
+
serialize: SerializeOptions;
|
|
45
|
+
log: (event: string, data: Record<string, unknown>) => void;
|
|
46
|
+
/** Resolver o autor (users/peoples) na gravação. */
|
|
47
|
+
actorLookup?: boolean;
|
|
48
|
+
actorCacheTtlMs?: number;
|
|
49
|
+
}
|
|
50
|
+
interface AuditContext {
|
|
51
|
+
request: any;
|
|
52
|
+
requestId: string;
|
|
53
|
+
entries: AuditEntry[];
|
|
54
|
+
flushed: boolean;
|
|
55
|
+
startedAt?: number;
|
|
56
|
+
}
|
|
57
|
+
/** Contexto de auditoria da request corrente (undefined fora de request). */
|
|
58
|
+
export declare function getAuditContext(): AuditContext | undefined;
|
|
59
|
+
/** Abre um contexto fora do Fastify (testes, jobs que queiram agrupar escritas). */
|
|
60
|
+
export declare function runInAuditContext<T>(request: any, fn: () => T): T;
|
|
61
|
+
/**
|
|
62
|
+
* Handler `$allOperations` da extensão (exportado para teste). `base` é o
|
|
63
|
+
* client SEM extensão do mesmo schema: lê o "antes" e grava o log.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createAuditQueryHandler(base: AuditBaseClient, options: AuditOptions): ({ model, operation, args, query }: {
|
|
66
|
+
model?: string;
|
|
67
|
+
operation: string;
|
|
68
|
+
args: any;
|
|
69
|
+
query: (args: any) => Promise<any>;
|
|
70
|
+
}) => Promise<any>;
|
|
71
|
+
/** Extensão pronta para `client.$extends(...)`. */
|
|
72
|
+
export declare function createAuditExtension(base: AuditBaseClient, options: AuditOptions): {
|
|
73
|
+
name: string;
|
|
74
|
+
query: {
|
|
75
|
+
$allModels: {
|
|
76
|
+
$allOperations: ({ model, operation, args, query }: {
|
|
77
|
+
model?: string;
|
|
78
|
+
operation: string;
|
|
79
|
+
args: any;
|
|
80
|
+
query: (args: any) => Promise<any>;
|
|
81
|
+
}) => Promise<any>;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/** A auditoria está ligada? ("*" ou lista não vazia) */
|
|
86
|
+
export declare function auditEnabled(options: AuditOptions | undefined | null): boolean;
|
|
87
|
+
/** Aplica a extensão preservando o `$schema` do client base. */
|
|
88
|
+
export declare function withAudit<T extends AuditBaseClient>(base: T, options: AuditOptions): T;
|
|
89
|
+
/**
|
|
90
|
+
* Registra uma escrita feita por SQL cru (não passa pela extensão). Sem
|
|
91
|
+
* antes/depois: guarda o SQL e os parâmetros.
|
|
92
|
+
*/
|
|
93
|
+
export declare function auditRaw(prisma: AuditBaseClient, input: {
|
|
94
|
+
api: string;
|
|
95
|
+
table: string;
|
|
96
|
+
operation: string;
|
|
97
|
+
sql: string;
|
|
98
|
+
params?: unknown[];
|
|
99
|
+
recordId?: string | number | null;
|
|
100
|
+
affectedRows?: number | null;
|
|
101
|
+
auditTable?: string;
|
|
102
|
+
}, options?: SerializeOptions & {
|
|
103
|
+
logger?: AuditOptions["logger"];
|
|
104
|
+
}): Promise<void>;
|
|
105
|
+
export declare const AUDIT_COLUMNS: readonly ["created_at", "request_id", "api", "module", "action", "acl_entry", "http_method", "route", "url", "table_name", "record_id", "operation", "origin", "actor_uid", "actor_email", "actor_name", "actor_id_user", "actor_id_people", "actor_people_name", "ip", "user_agent", "data_before", "data_after", "changed_fields", "affected_rows", "status_code", "request_failed"];
|
|
106
|
+
/** DATETIME(3) em UTC — o mesmo relógio que o Prisma usa nas colunas de data. */
|
|
107
|
+
export declare function auditDate(d: Date): string;
|
|
108
|
+
export interface AuditRequestInfo {
|
|
109
|
+
requestId: string | null;
|
|
110
|
+
statusCode: number | null;
|
|
111
|
+
origin: AuditOrigin;
|
|
112
|
+
}
|
|
113
|
+
export interface AuditActor {
|
|
114
|
+
idUser: string | null;
|
|
115
|
+
idPeople: string | null;
|
|
116
|
+
peopleName: string | null;
|
|
117
|
+
}
|
|
118
|
+
/** Limpa o cache do autor (testes). */
|
|
119
|
+
export declare function clearAuditActorCache(): void;
|
|
120
|
+
/** Autor pelo uid, na base da escola. null quando não acha ou falha (nunca lança). */
|
|
121
|
+
export declare function resolveAuditActor(client: AuditBaseClient, schema: string, uid: string, options?: {
|
|
122
|
+
ttlMs?: number;
|
|
123
|
+
log?: (event: string, data: Record<string, unknown>) => void;
|
|
124
|
+
api?: string;
|
|
125
|
+
}): Promise<AuditActor | null>;
|
|
126
|
+
/** Linha (na ordem de AUDIT_COLUMNS) de uma entrada. Exportado para teste. */
|
|
127
|
+
export declare function auditRow(entry: AuditEntry, request: any, info: AuditRequestInfo, actor?: AuditActor | null): unknown[];
|
|
128
|
+
/** Grava as entradas: um INSERT por (schema, tabela de destino). Nunca lança. */
|
|
129
|
+
export declare function writeEntries(entries: AuditEntry[], ctx?: AuditContext, statusCode?: number | null): Promise<void>;
|
|
130
|
+
export interface AuditPluginOptions {
|
|
131
|
+
/** Origem da escrita a partir do request (ex.: conta de serviço => SERVICE). */
|
|
132
|
+
resolveOrigin?: (request: any) => AuditOrigin | undefined;
|
|
133
|
+
/** Nome da API nas linhas de 403 e no log de acesso (ex.: v1_pedagogico). */
|
|
134
|
+
api?: string;
|
|
135
|
+
/** Client da escola para gravar o 403 quando o request ainda não tem
|
|
136
|
+
* req.prisma (ex.: 403 do vínculo token↔escola, no onRequest). */
|
|
137
|
+
getTenantClient?: (idSchool: string) => any;
|
|
138
|
+
/** Tabela de destino (default audit_logs). */
|
|
139
|
+
table?: string;
|
|
140
|
+
/** Grava todo 403 em audit_logs (default true). */
|
|
141
|
+
auditForbidden?: boolean;
|
|
142
|
+
/** Máximo de linhas de 403 por usuário+rota por minuto (default 30). Acima
|
|
143
|
+
* disso: uma linha-resumo e o resto só no log. */
|
|
144
|
+
forbiddenLimitPerMinute?: number;
|
|
145
|
+
/** Uma linha estruturada por request no log da function (default true). */
|
|
146
|
+
accessLog?: boolean;
|
|
147
|
+
/** Log estruturado (default console em JSON). */
|
|
148
|
+
logger?: (event: string, data: Record<string, unknown>) => void;
|
|
149
|
+
/** Relógio (teste). */
|
|
150
|
+
now?: () => number;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Plugin (função aplicada direto na instância, como os demais do pkg): abre o
|
|
154
|
+
* contexto no onRequest e grava no onSend. Idempotente.
|
|
155
|
+
*/
|
|
156
|
+
export declare function createAuditPlugin(options?: AuditPluginOptions): (fastify: any) => Promise<void>;
|
|
157
|
+
export interface ForbiddenThrottle {
|
|
158
|
+
/** "record" grava normal; "summary" grava a linha-resumo; "skip" só loga. */
|
|
159
|
+
hit(key: string): {
|
|
160
|
+
decision: "record" | "summary" | "skip";
|
|
161
|
+
suppressedBefore: number;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export declare function createForbiddenThrottle(limit: number, now?: () => number): ForbiddenThrottle;
|
|
165
|
+
export {};
|