@mybuildex/pkg 0.6.1 → 0.7.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 +26 -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 +2297 -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 +13 -0
- package/dist/index.js +64 -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/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,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Serialização do antes/depois da auditoria: JSON seguro (BigInt, Date,
|
|
3
|
+
// Decimal, Buffer), campos sensíveis mascarados e tamanho limitado.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.MASK = exports.DEFAULT_MASK_KEYS = void 0;
|
|
6
|
+
exports.sanitizeForAudit = sanitizeForAudit;
|
|
7
|
+
exports.auditJson = auditJson;
|
|
8
|
+
exports.changedFields = changedFields;
|
|
9
|
+
exports.onlyChanged = onlyChanged;
|
|
10
|
+
/** Chaves mascaradas em qualquer nível (senha, token, segredo, chave privada...). */
|
|
11
|
+
exports.DEFAULT_MASK_KEYS = /pass(word)?|senha|token|secret|private_?key|api_?key|authorization|cookie|credential/i;
|
|
12
|
+
exports.MASK = "***";
|
|
13
|
+
const DEFAULT_MAX_STRING = 16 * 1024;
|
|
14
|
+
const DEFAULT_MAX_JSON = 512 * 1024;
|
|
15
|
+
function truncateString(s, max) {
|
|
16
|
+
const bytes = Buffer.byteLength(s, "utf8");
|
|
17
|
+
if (bytes <= max)
|
|
18
|
+
return s;
|
|
19
|
+
// corta pelo número de caracteres proporcional (aprox.) e marca
|
|
20
|
+
const keep = Math.max(0, Math.floor(s.length * (max / bytes)));
|
|
21
|
+
return `${s.slice(0, keep)}…[truncado ${bytes} bytes]`;
|
|
22
|
+
}
|
|
23
|
+
/** Valor pronto para JSON.stringify, com máscara e truncamento. */
|
|
24
|
+
function sanitizeForAudit(value, options = {}, depth = 0) {
|
|
25
|
+
const maskKeys = options.maskKeys ?? exports.DEFAULT_MASK_KEYS;
|
|
26
|
+
const maxString = options.maxStringBytes ?? DEFAULT_MAX_STRING;
|
|
27
|
+
if (value === null || value === undefined)
|
|
28
|
+
return null;
|
|
29
|
+
if (depth > 20)
|
|
30
|
+
return "[profundidade máxima]";
|
|
31
|
+
if (typeof value === "bigint")
|
|
32
|
+
return value.toString();
|
|
33
|
+
if (typeof value === "string")
|
|
34
|
+
return truncateString(value, maxString);
|
|
35
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
36
|
+
return value;
|
|
37
|
+
if (value instanceof Date)
|
|
38
|
+
return isNaN(value.getTime()) ? null : value.toISOString();
|
|
39
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array)
|
|
40
|
+
return `[binário ${value.byteLength} bytes]`;
|
|
41
|
+
if (typeof value === "function" || typeof value === "symbol")
|
|
42
|
+
return null;
|
|
43
|
+
// Decimal do Prisma (e similares) sabem se serializar
|
|
44
|
+
if (typeof value.toJSON === "function" && !Array.isArray(value)) {
|
|
45
|
+
return sanitizeForAudit(value.toJSON(), options, depth + 1);
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value))
|
|
48
|
+
return value.map(v => sanitizeForAudit(v, options, depth + 1));
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const [k, v] of Object.entries(value)) {
|
|
51
|
+
out[k] = maskKeys.test(k) && v !== null && v !== undefined ? exports.MASK : sanitizeForAudit(v, options, depth + 1);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
/** JSON da coluna (ou null). Acima do limite, grava só um marcador com o tamanho. */
|
|
56
|
+
function auditJson(value, options = {}) {
|
|
57
|
+
if (value === null || value === undefined)
|
|
58
|
+
return null;
|
|
59
|
+
const json = JSON.stringify(sanitizeForAudit(value, options));
|
|
60
|
+
if (json === undefined)
|
|
61
|
+
return null;
|
|
62
|
+
const max = options.maxJsonBytes ?? DEFAULT_MAX_JSON;
|
|
63
|
+
const bytes = Buffer.byteLength(json, "utf8");
|
|
64
|
+
if (bytes > max)
|
|
65
|
+
return JSON.stringify({ _truncado: true, bytes });
|
|
66
|
+
return json;
|
|
67
|
+
}
|
|
68
|
+
/** Campos que mudaram entre antes e depois (comparação por valor serializado). */
|
|
69
|
+
function changedFields(before, after) {
|
|
70
|
+
const b = before && typeof before === "object" && !Array.isArray(before) ? before : {};
|
|
71
|
+
const a = after && typeof after === "object" && !Array.isArray(after) ? after : {};
|
|
72
|
+
const keys = new Set([...Object.keys(b), ...Object.keys(a)]);
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const k of keys) {
|
|
75
|
+
if (JSON.stringify(sanitizeForAudit(b[k] ?? null)) !== JSON.stringify(sanitizeForAudit(a[k] ?? null)))
|
|
76
|
+
out.push(k);
|
|
77
|
+
}
|
|
78
|
+
return out.sort();
|
|
79
|
+
}
|
|
80
|
+
/** Só os campos alterados (mais o id) — o update grava o diff, não a linha
|
|
81
|
+
* inteira: corta a maior parte do tamanho do audit_logs. */
|
|
82
|
+
function onlyChanged(row, changed) {
|
|
83
|
+
if (!row || typeof row !== "object" || Array.isArray(row))
|
|
84
|
+
return row ?? null;
|
|
85
|
+
const src = row;
|
|
86
|
+
const out = {};
|
|
87
|
+
if ("id" in src)
|
|
88
|
+
out.id = src.id;
|
|
89
|
+
for (const k of changed)
|
|
90
|
+
if (k in src)
|
|
91
|
+
out[k] = src[k];
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../src/audit/serialize.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -33,3 +33,16 @@ export type { ErrorHandlerOptions } from "./http/error-handler";
|
|
|
33
33
|
export { createGcfHandler } from "./http/gcf-handler";
|
|
34
34
|
export type { GcfHandlerOptions } from "./http/gcf-handler";
|
|
35
35
|
export { GeminiHelp, EMBED_DIM, GEMINI_MODEL, EMBED_MODEL } from "./ai/gemini";
|
|
36
|
+
export { createPermissionsResolver, defaultPermissionsQuery, invalidatePermissionsCache, permissionsCacheKey, permissionsVersionKey, } from "./tenant/permissions";
|
|
37
|
+
export type { PermissionsResolverOptions, PermissionsRedisLike, PermissionsInvalidationRedisLike, InvalidatePermissionsOptions, InvalidatePermissionsResult, } from "./tenant/permissions";
|
|
38
|
+
export { createRedisClient, resolveRedisOptions, DEFAULT_REDIS_CONNECT_TIMEOUT_MS } from "./redis/client";
|
|
39
|
+
export type { RedisClient, RedisClientOptions } from "./redis/client";
|
|
40
|
+
export { ACL_MAP } from "./acl/map.generated";
|
|
41
|
+
export { checkAcl, aclEntryIds, resolveAclRule, evaluateAclRule, AclConfigError } from "./acl/check";
|
|
42
|
+
export { TEACHER_ACLS, STUDENT_ACLS, RESPONSIBLE_ACLS, ROLE_ALIASES, isRoleName, isSingleRoleList, hasAclName, hasAnyOf, FUNCTION_ROLE_PAIRS, FUNCTION_ROLES, FUNCTION_ROLE_ALIASES, isFunctionRoleName } from "./acl/roles";
|
|
43
|
+
export type { AclMap, AclRule, ResolvedAclRule } from "./acl/check";
|
|
44
|
+
export { createAclPlugin, useModuleAcl, getAclRouteReport, DEFAULT_ACL_ACTIONS } from "./acl/plugin";
|
|
45
|
+
export type { AclMode, AclPluginOptions, AclRouteInfo, UseModuleAclOptions } from "./acl/plugin";
|
|
46
|
+
export { createAuditPlugin, createAuditExtension, createAuditQueryHandler, withAudit, auditEnabled, auditRaw, runInAuditContext, getAuditContext, writeEntries, auditRow, auditDate, resolveAuditActor, clearAuditActorCache, createForbiddenThrottle, AUDIT_COLUMNS, AUDIT_WRITE_OPERATIONS, } from "./audit/audit";
|
|
47
|
+
export type { AuditOptions, AuditPluginOptions, AuditOrigin, AuditEntry, AuditBaseClient, AuditActor } from "./audit/audit";
|
|
48
|
+
export { sanitizeForAudit, auditJson, changedFields, onlyChanged, DEFAULT_MASK_KEYS } from "./audit/serialize";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EMBED_MODEL = exports.GEMINI_MODEL = exports.EMBED_DIM = exports.GeminiHelp = exports.createGcfHandler = exports.isInternalError = exports.createErrorHandler = exports.getBucket = exports.getDefaultAuth = exports.getTenantAuth = exports.initFirebaseAdmin = exports.getFirebaseStorage = exports.getFirebaseClientApp = exports.createFirebaseClientPlugin = exports.createSwaggerPlugin = exports.DEFAULT_CORS = exports.createCorsPlugin = exports.withTenantSchema = exports.createMultitenantPrismaMariaDb = exports.resolveStatementTimeout = exports.createMultitenantPrisma = exports.createAsyncSendGuard = exports.createCompressPlugin = exports.createSharedSecretGuard = exports.safeEqual = exports.isPublicRoute = exports.publicPath = exports.publicRoute = exports.RequestScopeError = exports.runInRequestScope = exports.getRequestScope = exports.requestScoped = exports.createRequestScopePlugin = exports.createTenantMembershipAssert = exports.canonicalRequestPath = exports.createAuthMiddleware = exports.InvalidTenantError = exports.createTenantMiddleware = exports.readRawTenantId = exports.resolveTenantId = void 0;
|
|
3
|
+
exports.checkAcl = exports.ACL_MAP = exports.DEFAULT_REDIS_CONNECT_TIMEOUT_MS = exports.resolveRedisOptions = exports.createRedisClient = exports.permissionsVersionKey = exports.permissionsCacheKey = exports.invalidatePermissionsCache = exports.defaultPermissionsQuery = exports.createPermissionsResolver = exports.EMBED_MODEL = exports.GEMINI_MODEL = exports.EMBED_DIM = exports.GeminiHelp = exports.createGcfHandler = exports.isInternalError = exports.createErrorHandler = exports.getBucket = exports.getDefaultAuth = exports.getTenantAuth = exports.initFirebaseAdmin = exports.getFirebaseStorage = exports.getFirebaseClientApp = exports.createFirebaseClientPlugin = exports.createSwaggerPlugin = exports.DEFAULT_CORS = exports.createCorsPlugin = exports.withTenantSchema = exports.createMultitenantPrismaMariaDb = exports.resolveStatementTimeout = exports.createMultitenantPrisma = exports.createAsyncSendGuard = exports.createCompressPlugin = exports.createSharedSecretGuard = exports.safeEqual = exports.isPublicRoute = exports.publicPath = exports.publicRoute = exports.RequestScopeError = exports.runInRequestScope = exports.getRequestScope = exports.requestScoped = exports.createRequestScopePlugin = exports.createTenantMembershipAssert = exports.canonicalRequestPath = exports.createAuthMiddleware = exports.InvalidTenantError = exports.createTenantMiddleware = exports.readRawTenantId = exports.resolveTenantId = void 0;
|
|
4
|
+
exports.DEFAULT_MASK_KEYS = exports.onlyChanged = exports.changedFields = exports.auditJson = exports.sanitizeForAudit = exports.AUDIT_WRITE_OPERATIONS = exports.AUDIT_COLUMNS = exports.createForbiddenThrottle = exports.clearAuditActorCache = exports.resolveAuditActor = exports.auditDate = exports.auditRow = exports.writeEntries = exports.getAuditContext = exports.runInAuditContext = exports.auditRaw = exports.auditEnabled = exports.withAudit = exports.createAuditQueryHandler = exports.createAuditExtension = exports.createAuditPlugin = exports.DEFAULT_ACL_ACTIONS = exports.getAclRouteReport = exports.useModuleAcl = exports.createAclPlugin = exports.isFunctionRoleName = exports.FUNCTION_ROLE_ALIASES = exports.FUNCTION_ROLES = exports.FUNCTION_ROLE_PAIRS = exports.hasAnyOf = exports.hasAclName = exports.isSingleRoleList = exports.isRoleName = exports.ROLE_ALIASES = exports.RESPONSIBLE_ACLS = exports.STUDENT_ACLS = exports.TEACHER_ACLS = exports.AclConfigError = exports.evaluateAclRule = exports.resolveAclRule = exports.aclEntryIds = void 0;
|
|
4
5
|
var resolve_1 = require("./tenant/resolve");
|
|
5
6
|
Object.defineProperty(exports, "resolveTenantId", { enumerable: true, get: function () { return resolve_1.resolveTenantId; } });
|
|
6
7
|
Object.defineProperty(exports, "readRawTenantId", { enumerable: true, get: function () { return resolve_1.readRawTenantId; } });
|
|
@@ -59,4 +60,66 @@ Object.defineProperty(exports, "GeminiHelp", { enumerable: true, get: function (
|
|
|
59
60
|
Object.defineProperty(exports, "EMBED_DIM", { enumerable: true, get: function () { return gemini_1.EMBED_DIM; } });
|
|
60
61
|
Object.defineProperty(exports, "GEMINI_MODEL", { enumerable: true, get: function () { return gemini_1.GEMINI_MODEL; } });
|
|
61
62
|
Object.defineProperty(exports, "EMBED_MODEL", { enumerable: true, get: function () { return gemini_1.EMBED_MODEL; } });
|
|
63
|
+
// ---- permissões por usuário (cadeia users -> perfis -> políticas -> regras -> permissões) ----
|
|
64
|
+
var permissions_1 = require("./tenant/permissions");
|
|
65
|
+
Object.defineProperty(exports, "createPermissionsResolver", { enumerable: true, get: function () { return permissions_1.createPermissionsResolver; } });
|
|
66
|
+
Object.defineProperty(exports, "defaultPermissionsQuery", { enumerable: true, get: function () { return permissions_1.defaultPermissionsQuery; } });
|
|
67
|
+
Object.defineProperty(exports, "invalidatePermissionsCache", { enumerable: true, get: function () { return permissions_1.invalidatePermissionsCache; } });
|
|
68
|
+
Object.defineProperty(exports, "permissionsCacheKey", { enumerable: true, get: function () { return permissions_1.permissionsCacheKey; } });
|
|
69
|
+
Object.defineProperty(exports, "permissionsVersionKey", { enumerable: true, get: function () { return permissions_1.permissionsVersionKey; } });
|
|
70
|
+
// ---- cliente Redis padrão (best-effort, com timeout de conexão) ----
|
|
71
|
+
var client_1 = require("./redis/client");
|
|
72
|
+
Object.defineProperty(exports, "createRedisClient", { enumerable: true, get: function () { return client_1.createRedisClient; } });
|
|
73
|
+
Object.defineProperty(exports, "resolveRedisOptions", { enumerable: true, get: function () { return client_1.resolveRedisOptions; } });
|
|
74
|
+
Object.defineProperty(exports, "DEFAULT_REDIS_CONNECT_TIMEOUT_MS", { enumerable: true, get: function () { return client_1.DEFAULT_REDIS_CONNECT_TIMEOUT_MS; } });
|
|
75
|
+
// ---- ACL por endpoint (mapa do front) e auditoria de escrita ----
|
|
76
|
+
var map_generated_1 = require("./acl/map.generated");
|
|
77
|
+
Object.defineProperty(exports, "ACL_MAP", { enumerable: true, get: function () { return map_generated_1.ACL_MAP; } });
|
|
78
|
+
var check_1 = require("./acl/check");
|
|
79
|
+
Object.defineProperty(exports, "checkAcl", { enumerable: true, get: function () { return check_1.checkAcl; } });
|
|
80
|
+
Object.defineProperty(exports, "aclEntryIds", { enumerable: true, get: function () { return check_1.aclEntryIds; } });
|
|
81
|
+
Object.defineProperty(exports, "resolveAclRule", { enumerable: true, get: function () { return check_1.resolveAclRule; } });
|
|
82
|
+
Object.defineProperty(exports, "evaluateAclRule", { enumerable: true, get: function () { return check_1.evaluateAclRule; } });
|
|
83
|
+
Object.defineProperty(exports, "AclConfigError", { enumerable: true, get: function () { return check_1.AclConfigError; } });
|
|
84
|
+
var roles_1 = require("./acl/roles");
|
|
85
|
+
Object.defineProperty(exports, "TEACHER_ACLS", { enumerable: true, get: function () { return roles_1.TEACHER_ACLS; } });
|
|
86
|
+
Object.defineProperty(exports, "STUDENT_ACLS", { enumerable: true, get: function () { return roles_1.STUDENT_ACLS; } });
|
|
87
|
+
Object.defineProperty(exports, "RESPONSIBLE_ACLS", { enumerable: true, get: function () { return roles_1.RESPONSIBLE_ACLS; } });
|
|
88
|
+
Object.defineProperty(exports, "ROLE_ALIASES", { enumerable: true, get: function () { return roles_1.ROLE_ALIASES; } });
|
|
89
|
+
Object.defineProperty(exports, "isRoleName", { enumerable: true, get: function () { return roles_1.isRoleName; } });
|
|
90
|
+
Object.defineProperty(exports, "isSingleRoleList", { enumerable: true, get: function () { return roles_1.isSingleRoleList; } });
|
|
91
|
+
Object.defineProperty(exports, "hasAclName", { enumerable: true, get: function () { return roles_1.hasAclName; } });
|
|
92
|
+
Object.defineProperty(exports, "hasAnyOf", { enumerable: true, get: function () { return roles_1.hasAnyOf; } });
|
|
93
|
+
Object.defineProperty(exports, "FUNCTION_ROLE_PAIRS", { enumerable: true, get: function () { return roles_1.FUNCTION_ROLE_PAIRS; } });
|
|
94
|
+
Object.defineProperty(exports, "FUNCTION_ROLES", { enumerable: true, get: function () { return roles_1.FUNCTION_ROLES; } });
|
|
95
|
+
Object.defineProperty(exports, "FUNCTION_ROLE_ALIASES", { enumerable: true, get: function () { return roles_1.FUNCTION_ROLE_ALIASES; } });
|
|
96
|
+
Object.defineProperty(exports, "isFunctionRoleName", { enumerable: true, get: function () { return roles_1.isFunctionRoleName; } });
|
|
97
|
+
var plugin_3 = require("./acl/plugin");
|
|
98
|
+
Object.defineProperty(exports, "createAclPlugin", { enumerable: true, get: function () { return plugin_3.createAclPlugin; } });
|
|
99
|
+
Object.defineProperty(exports, "useModuleAcl", { enumerable: true, get: function () { return plugin_3.useModuleAcl; } });
|
|
100
|
+
Object.defineProperty(exports, "getAclRouteReport", { enumerable: true, get: function () { return plugin_3.getAclRouteReport; } });
|
|
101
|
+
Object.defineProperty(exports, "DEFAULT_ACL_ACTIONS", { enumerable: true, get: function () { return plugin_3.DEFAULT_ACL_ACTIONS; } });
|
|
102
|
+
var audit_1 = require("./audit/audit");
|
|
103
|
+
Object.defineProperty(exports, "createAuditPlugin", { enumerable: true, get: function () { return audit_1.createAuditPlugin; } });
|
|
104
|
+
Object.defineProperty(exports, "createAuditExtension", { enumerable: true, get: function () { return audit_1.createAuditExtension; } });
|
|
105
|
+
Object.defineProperty(exports, "createAuditQueryHandler", { enumerable: true, get: function () { return audit_1.createAuditQueryHandler; } });
|
|
106
|
+
Object.defineProperty(exports, "withAudit", { enumerable: true, get: function () { return audit_1.withAudit; } });
|
|
107
|
+
Object.defineProperty(exports, "auditEnabled", { enumerable: true, get: function () { return audit_1.auditEnabled; } });
|
|
108
|
+
Object.defineProperty(exports, "auditRaw", { enumerable: true, get: function () { return audit_1.auditRaw; } });
|
|
109
|
+
Object.defineProperty(exports, "runInAuditContext", { enumerable: true, get: function () { return audit_1.runInAuditContext; } });
|
|
110
|
+
Object.defineProperty(exports, "getAuditContext", { enumerable: true, get: function () { return audit_1.getAuditContext; } });
|
|
111
|
+
Object.defineProperty(exports, "writeEntries", { enumerable: true, get: function () { return audit_1.writeEntries; } });
|
|
112
|
+
Object.defineProperty(exports, "auditRow", { enumerable: true, get: function () { return audit_1.auditRow; } });
|
|
113
|
+
Object.defineProperty(exports, "auditDate", { enumerable: true, get: function () { return audit_1.auditDate; } });
|
|
114
|
+
Object.defineProperty(exports, "resolveAuditActor", { enumerable: true, get: function () { return audit_1.resolveAuditActor; } });
|
|
115
|
+
Object.defineProperty(exports, "clearAuditActorCache", { enumerable: true, get: function () { return audit_1.clearAuditActorCache; } });
|
|
116
|
+
Object.defineProperty(exports, "createForbiddenThrottle", { enumerable: true, get: function () { return audit_1.createForbiddenThrottle; } });
|
|
117
|
+
Object.defineProperty(exports, "AUDIT_COLUMNS", { enumerable: true, get: function () { return audit_1.AUDIT_COLUMNS; } });
|
|
118
|
+
Object.defineProperty(exports, "AUDIT_WRITE_OPERATIONS", { enumerable: true, get: function () { return audit_1.AUDIT_WRITE_OPERATIONS; } });
|
|
119
|
+
var serialize_1 = require("./audit/serialize");
|
|
120
|
+
Object.defineProperty(exports, "sanitizeForAudit", { enumerable: true, get: function () { return serialize_1.sanitizeForAudit; } });
|
|
121
|
+
Object.defineProperty(exports, "auditJson", { enumerable: true, get: function () { return serialize_1.auditJson; } });
|
|
122
|
+
Object.defineProperty(exports, "changedFields", { enumerable: true, get: function () { return serialize_1.changedFields; } });
|
|
123
|
+
Object.defineProperty(exports, "onlyChanged", { enumerable: true, get: function () { return serialize_1.onlyChanged; } });
|
|
124
|
+
Object.defineProperty(exports, "DEFAULT_MASK_KEYS", { enumerable: true, get: function () { return serialize_1.DEFAULT_MASK_KEYS; } });
|
|
62
125
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AuditOptions } from "../audit/audit";
|
|
1
2
|
export type TenantClient<T> = T & {
|
|
2
3
|
$schema: string;
|
|
3
4
|
};
|
|
@@ -58,6 +59,11 @@ export interface MultitenantOptions<TClient, TPool extends PoolLike> {
|
|
|
58
59
|
statementTimeoutMs?: number;
|
|
59
60
|
log?: unknown;
|
|
60
61
|
errorFormat?: string;
|
|
62
|
+
/** Auditoria de escrita: tabelas listadas (ou "*") ganham log de quem/onde/
|
|
63
|
+
* antes/depois em `audit_logs`. Ausente ou `tables` vazio = client
|
|
64
|
+
* original, sem extensão. Precisa do createAuditPlugin registrado para
|
|
65
|
+
* saber quem fez. */
|
|
66
|
+
audit?: AuditOptions;
|
|
61
67
|
}
|
|
62
68
|
export interface Multitenant<TClient> {
|
|
63
69
|
pool: PoolLike;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.resolveStatementTimeout = resolveStatementTimeout;
|
|
12
12
|
exports.createMultitenantPrisma = createMultitenantPrisma;
|
|
13
|
+
const audit_1 = require("../audit/audit");
|
|
13
14
|
/** Teto de execução: opção > env DB_STATEMENT_TIMEOUT_MS > 60000; inválido ou negativo cai no default; 0 desliga. */
|
|
14
15
|
function resolveStatementTimeout(value) {
|
|
15
16
|
const raw = value ?? process.env.DB_STATEMENT_TIMEOUT_MS ?? 60000;
|
|
@@ -66,12 +67,15 @@ function createMultitenantPrisma(options) {
|
|
|
66
67
|
let client = clients.get(dbName);
|
|
67
68
|
if (!client) {
|
|
68
69
|
const adapter = new options.PrismaMariaDb(pool, { database: dbName });
|
|
69
|
-
|
|
70
|
+
const base = new options.PrismaClient({
|
|
70
71
|
adapter,
|
|
71
72
|
log: options.log,
|
|
72
73
|
errorFormat: options.errorFormat,
|
|
73
74
|
});
|
|
74
|
-
|
|
75
|
+
base.$schema = dbName;
|
|
76
|
+
client = (0, audit_1.auditEnabled)(options.audit)
|
|
77
|
+
? (0, audit_1.withAudit)(base, options.audit)
|
|
78
|
+
: base;
|
|
75
79
|
clients.set(dbName, client);
|
|
76
80
|
}
|
|
77
81
|
return client;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multitenant.js","sourceRoot":"","sources":["../../src/prisma/multitenant.ts"],"names":[],"mappings":";AAAA,6EAA6E;AAC7E,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;;
|
|
1
|
+
{"version":3,"file":"multitenant.js","sourceRoot":"","sources":["../../src/prisma/multitenant.ts"],"names":[],"mappings":";AAAA,6EAA6E;AAC7E,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;;AAO3E,0DAKC;AAkFD,0DAiGC;AA7LD,0CAAqE;AAIrE,qHAAqH;AACrH,SAAgB,uBAAuB,CAAC,KAA8B;IAClE,MAAM,GAAG,GAAG,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,KAAK,CAAC;IAClE,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzB,CAAC;AA8ED,SAAS,mBAAmB,CAAC,UAAkB,EAAE,QAAgB;IAC7D,OAAO,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,QAAQ,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,SAAgB,uBAAuB,CACnC,OAA2C;IAE3C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC;IAClE,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,MAAM,SAAS,GACX,OAAO,CAAC,SAAS;QACjB,MAAM,CACF,OAAO,CAAC,GAAG,CAAC,iBAAiB;YAC7B,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,kBAAkB,CAAC;YAC3C,CAAC,CACJ,CAAC;IACN,MAAM,kBAAkB,GAAG,uBAAuB,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE/E,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;QAC5B,GAAG,CAAC,UAAU;YACV,CAAC,CAAC,EAAC,UAAU,EAAC;YACd,CAAC,CAAC,EAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,EAAC,CAAC;QACjE,IAAI,EAAE,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC;QACzC,QAAQ,EAAE,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC7C,eAAe,EAAE,SAAS;QAC1B,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,KAAK;QAC/C,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,KAAK;QAC/C,mEAAmE;QACnE,8DAA8D;QAC9D,mEAAmE;QACnE,+BAA+B;QAC/B,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;QAC7E,kEAAkE;QAClE,oEAAoE;QACpE,8DAA8D;QAC9D,kEAAkE;QAClE,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1F,6DAA6D;QAC7D,kDAAkD;QAClD,kBAAkB,EAAE,CAAC;QACrB,0EAA0E;QAC1E,0EAA0E;QAC1E,2EAA2E;QAC3E,4EAA4E;QAC5E,4EAA4E;QAC5E,aAAa,EAAE,IAAI;QACnB,GAAG,CAAC,kBAAkB,GAAG,CAAC,CAAC,CAAC,CAAC,EAAC,gBAAgB,EAAE,EAAC,kBAAkB,EAAE,kBAAkB,EAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAClG,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,mBAAmB,CAAC;IACjE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiC,CAAC;IAEzD,SAAS,SAAS,CAAC,MAAc;QAC7B,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,EAAC,QAAQ,EAAE,MAAM,EAAC,CAAC,CAAC;YACpE,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC;gBAClC,OAAO;gBACP,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,WAAW,EAAE,OAAO,CAAC,WAAW;aACnC,CAA0B,CAAC;YAC3B,IAAY,CAAC,OAAO,GAAG,MAAM,CAAC;YAC/B,MAAM,GAAG,IAAA,oBAAY,EAAC,OAAO,CAAC,KAAK,CAAC;gBAChC,CAAC,CAAC,IAAA,iBAAS,EAAC,IAAW,EAAE,OAAO,CAAC,KAAM,CAA0B;gBACjE,CAAC,CAAC,IAAI,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,MAAM,aAAa,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IAE5C,SAAS,SAAS,CAAC,QAAgB;QAC/B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,UAAU,CAAC;QACxE,OAAO,YAAY,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAED,SAAS,eAAe,CAAC,QAAgB;QACrC,mEAAmE;QACnE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,aAAa,CAAC;QAC3E,OAAO,SAAS,CAAC,YAAY,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,UAAU,aAAa;QACxB,MAAM,OAAO,CAAC,GAAG,CACb,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAChC,CAAS,CAAC,WAAW,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QACtC,CAAC,CAAC,CACL,CACJ,CAAC;QACF,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QAC5B,CAAC,CAAC,CAAC;IACP,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,EAAE,OAAY,EAAE,EAAE;QAClC,OAAO,CAAC,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;QACjD,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAC9C,CAAC,CAAC;IAEF,OAAO,EAAC,IAAI,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAC,CAAC;AACpF,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createClient } from "redis";
|
|
2
|
+
export type RedisClient = ReturnType<typeof createClient>;
|
|
3
|
+
export declare const DEFAULT_REDIS_CONNECT_TIMEOUT_MS = 3000;
|
|
4
|
+
export interface RedisClientOptions {
|
|
5
|
+
/** Opções do createClient. Default: JSON.parse(env.REDIS). */
|
|
6
|
+
options?: Record<string, any>;
|
|
7
|
+
/** Ambiente lido quando `options` não vem (default process.env). */
|
|
8
|
+
env?: Record<string, string | undefined>;
|
|
9
|
+
/** Tempo máximo para conectar, ms (default 3000). */
|
|
10
|
+
connectTimeoutMs?: number;
|
|
11
|
+
/** Fábrica do cliente (injeção para teste). Default: createClient do `redis`. */
|
|
12
|
+
createClient?: (options: any) => any;
|
|
13
|
+
/** Log de erro (default console.error). */
|
|
14
|
+
log?: (message: string, error?: unknown) => void;
|
|
15
|
+
}
|
|
16
|
+
/** Opções finais do createClient (exportado para teste). */
|
|
17
|
+
export declare function resolveRedisOptions(options?: RedisClientOptions): Record<string, any> | undefined;
|
|
18
|
+
export declare function createRedisClient(options?: RedisClientOptions): Promise<RedisClient | undefined>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_REDIS_CONNECT_TIMEOUT_MS = void 0;
|
|
4
|
+
exports.resolveRedisOptions = resolveRedisOptions;
|
|
5
|
+
exports.createRedisClient = createRedisClient;
|
|
6
|
+
// Cliente Redis padrão das APIs. Antes cada API criava o
|
|
7
|
+
// seu (v1_pedagogico: infra.ts; v3_peoples: config/redis.ts) — a conexão
|
|
8
|
+
// agora mora aqui, UMA vez.
|
|
9
|
+
//
|
|
10
|
+
// Contrato: a env `REDIS` traz o JSON das opções do `createClient` do
|
|
11
|
+
// node-redis (vem do Secret Manager pelo workflow deploy-firebase-api-with-redis).
|
|
12
|
+
//
|
|
13
|
+
// Best-effort — nunca derruba o boot:
|
|
14
|
+
// - sem `REDIS`, JSON inválido ou falha na conexão → `undefined` (a API
|
|
15
|
+
// segue sem cache; quem usa testa o cliente antes);
|
|
16
|
+
// - timeout na conexão (default 3 s): o reconnect padrão do node-redis tenta
|
|
17
|
+
// para sempre e deixaria o boot da function preso esperando;
|
|
18
|
+
// - `disableOfflineQueue: true` por padrão: com a conexão caída, o comando
|
|
19
|
+
// falha na hora em vez de ficar na fila segurando a request (quem usa já
|
|
20
|
+
// trata erro como "sem cache"). As opções da env podem sobrescrever.
|
|
21
|
+
//
|
|
22
|
+
// O cliente devolvido serve direto como `redis` do createTenantMembershipAssert
|
|
23
|
+
// e do createPermissionsResolver (get/setEx).
|
|
24
|
+
const redis_1 = require("redis");
|
|
25
|
+
exports.DEFAULT_REDIS_CONNECT_TIMEOUT_MS = 3000;
|
|
26
|
+
/** Opções finais do createClient (exportado para teste). */
|
|
27
|
+
function resolveRedisOptions(options) {
|
|
28
|
+
let base = options?.options;
|
|
29
|
+
if (!base) {
|
|
30
|
+
const raw = (options?.env ?? process.env).REDIS;
|
|
31
|
+
if (!raw)
|
|
32
|
+
return undefined;
|
|
33
|
+
base = JSON.parse(String(raw));
|
|
34
|
+
}
|
|
35
|
+
if (!base || typeof base !== "object" || Array.isArray(base))
|
|
36
|
+
throw new Error("REDIS precisa ser um objeto JSON com as opções do createClient");
|
|
37
|
+
return { disableOfflineQueue: true, ...base };
|
|
38
|
+
}
|
|
39
|
+
async function createRedisClient(options = {}) {
|
|
40
|
+
const log = options.log ?? ((message, error) => console.error(message, error));
|
|
41
|
+
const factory = options.createClient ?? redis_1.createClient;
|
|
42
|
+
const timeoutMs = options.connectTimeoutMs ?? exports.DEFAULT_REDIS_CONNECT_TIMEOUT_MS;
|
|
43
|
+
let client;
|
|
44
|
+
try {
|
|
45
|
+
const clientOptions = resolveRedisOptions(options);
|
|
46
|
+
if (!clientOptions)
|
|
47
|
+
return undefined;
|
|
48
|
+
client = factory(clientOptions);
|
|
49
|
+
client.on?.("error", (err) => log("Redis Client Error", err));
|
|
50
|
+
let timer;
|
|
51
|
+
const timeout = new Promise((_, reject) => {
|
|
52
|
+
timer = setTimeout(() => reject(new Error(`timeout de ${timeoutMs}ms na conexão`)), timeoutMs);
|
|
53
|
+
});
|
|
54
|
+
try {
|
|
55
|
+
await Promise.race([client.connect(), timeout]);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
}
|
|
60
|
+
return client;
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
log("Redis não iniciado", err);
|
|
64
|
+
try {
|
|
65
|
+
client?.destroy?.();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// cliente já descartado
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/redis/client.ts"],"names":[],"mappings":";;;AAsCA,kDASC;AAED,8CA8BC;AA/ED,yDAAyD;AACzD,yEAAyE;AACzE,4BAA4B;AAC5B,EAAE;AACF,sEAAsE;AACtE,mFAAmF;AACnF,EAAE;AACF,sCAAsC;AACtC,yEAAyE;AACzE,uDAAuD;AACvD,8EAA8E;AAC9E,gEAAgE;AAChE,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,EAAE;AACF,gFAAgF;AAChF,8CAA8C;AAC9C,iCAAmC;AAItB,QAAA,gCAAgC,GAAG,IAAI,CAAC;AAerD,4DAA4D;AAC5D,SAAgB,mBAAmB,CAAC,OAA4B;IAC5D,IAAI,IAAI,GAAG,OAAO,EAAE,OAAO,CAAC;IAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;QAChD,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IAChJ,OAAO,EAAC,mBAAmB,EAAE,IAAI,EAAE,GAAG,IAAI,EAAC,CAAC;AAChD,CAAC;AAEM,KAAK,UAAU,iBAAiB,CAAC,UAA8B,EAAE;IACpE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,OAAe,EAAE,KAAe,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IACjG,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,IAAI,oBAAY,CAAC;IACrD,MAAM,SAAS,GAAG,OAAO,CAAC,gBAAgB,IAAI,wCAAgC,CAAC;IAE/E,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACD,MAAM,aAAa,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,aAAa;YAAE,OAAO,SAAS,CAAC;QACrC,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;QAChC,MAAM,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC,GAAG,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC,CAAC;QACvE,IAAI,KAAgD,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;YACtC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,cAAc,SAAS,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QACnG,CAAC,CAAC,CAAC;QACH,IAAI,CAAC;YACD,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QACpD,CAAC;gBAAS,CAAC;YACP,YAAY,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,MAAqB,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,GAAG,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC;YACD,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACL,wBAAwB;QAC5B,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface PermissionsRedisLike {
|
|
2
|
+
get(key: string): Promise<string | null>;
|
|
3
|
+
setEx(key: string, ttlSeconds: number, value: string): Promise<unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface PermissionsResolverOptions {
|
|
6
|
+
/** Client Redis opcional (get/setEx). Sem ele, consulta o banco a cada request. */
|
|
7
|
+
redis?: PermissionsRedisLike;
|
|
8
|
+
/** TTL do cache, segundos (default 3600). */
|
|
9
|
+
cacheTtlSeconds?: number;
|
|
10
|
+
/** Identificadores de cada papel (default en + pt: teacher/professor,
|
|
11
|
+
* student/aluno, responsible/responsavel). */
|
|
12
|
+
roles?: {
|
|
13
|
+
teacher?: string | string[];
|
|
14
|
+
student?: string | string[];
|
|
15
|
+
responsible?: string | string[];
|
|
16
|
+
};
|
|
17
|
+
/** Consulta customizada: recebe o prisma da escola e o uid, devolve os identifiers. */
|
|
18
|
+
query?: (prisma: any, uid: string) => Promise<string[]>;
|
|
19
|
+
/** Log de erro (default console.error). */
|
|
20
|
+
logger?: (message: string, error?: unknown) => void;
|
|
21
|
+
}
|
|
22
|
+
/** A mesma consulta que as APIs usavam, com o uid como parâmetro. */
|
|
23
|
+
export declare function defaultPermissionsQuery(prisma: any, uid: string): Promise<string[]>;
|
|
24
|
+
export declare function createPermissionsResolver(options?: PermissionsResolverOptions): (req: any, idSchool: string | null) => Promise<void>;
|
|
25
|
+
/** Chave da versão do cache de permissões de uma escola. */
|
|
26
|
+
export declare function permissionsVersionKey(idSchool: string | number): string;
|
|
27
|
+
/** Chave do cache de permissões de um usuário numa versão. */
|
|
28
|
+
export declare function permissionsCacheKey(idSchool: string | number, version: string, uid: string): string;
|
|
29
|
+
export interface PermissionsInvalidationRedisLike {
|
|
30
|
+
incr(key: string): Promise<number>;
|
|
31
|
+
/** node-redis v4 devolve uma chave por iteração; v5, um lote (string[]). */
|
|
32
|
+
scanIterator?(options: {
|
|
33
|
+
MATCH: string;
|
|
34
|
+
COUNT?: number;
|
|
35
|
+
}): AsyncIterable<string | string[]>;
|
|
36
|
+
del?(keys: string | string[]): Promise<unknown>;
|
|
37
|
+
}
|
|
38
|
+
export interface InvalidatePermissionsOptions {
|
|
39
|
+
/** Também apaga as chaves do formato antigo `perms:<id>:<uid>` (cache sem
|
|
40
|
+
* versão), via SCAN — nunca KEYS. Ligar enquanto houver API no formato
|
|
41
|
+
* antigo lendo o cache. Default false. */
|
|
42
|
+
legacy?: boolean;
|
|
43
|
+
/** Log de erro (default console.error). */
|
|
44
|
+
logger?: (message: string, error?: unknown) => void;
|
|
45
|
+
}
|
|
46
|
+
export interface InvalidatePermissionsResult {
|
|
47
|
+
/** Nova versão da escola (null quando falhou). */
|
|
48
|
+
version: number | null;
|
|
49
|
+
/** Chaves antigas apagadas no modo legacy. */
|
|
50
|
+
legacyDeleted: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Invalida o cache de permissões de UMA escola: INCR em `perms_ver:<id>`.
|
|
54
|
+
* Chamar depois de qualquer escrita em permissões, regras, políticas,
|
|
55
|
+
* perfis ou vínculo usuário↔perfil (v1_acls). Nunca lança.
|
|
56
|
+
*/
|
|
57
|
+
export declare function invalidatePermissionsCache(redis: PermissionsInvalidationRedisLike | undefined | null, idSchool: string | number | null | undefined, options?: InvalidatePermissionsOptions): Promise<InvalidatePermissionsResult>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Resolver de permissões por papel (is_full / is_teacher / is_student),
|
|
3
|
+
// fonte única para atendimento e pedagogico, que tinham cópias idênticas do
|
|
4
|
+
// hook `onTenantResolved` com um defeito comum: começavam com
|
|
5
|
+
// `is_full = true` e mantinham isso quando o usuário não existia na escola,
|
|
6
|
+
// quando a lista vinha vazia ou quando a consulta falhava (fail-open, erro
|
|
7
|
+
// só no console).
|
|
8
|
+
//
|
|
9
|
+
// Regra:
|
|
10
|
+
// - sem id_school: não mexe no request (igual ao legado);
|
|
11
|
+
// - tudo começa false; sem user_id ou lista vazia continua tudo false;
|
|
12
|
+
// - is_full = a lista tem a permissão `full` — e SÓ isso. Antes qualquer
|
|
13
|
+
// perfil sem teacher/student virava is_full (inclusive responsável e
|
|
14
|
+
// aluno com o identificador `aluno`) e via tudo nos services;
|
|
15
|
+
// - is_teacher = teacher|professor; is_student = student|aluno;
|
|
16
|
+
// is_responsible = responsible|responsavel (nomes en ou pt, como o front);
|
|
17
|
+
// - erro em qualquer etapa: tudo false, permissions=[],
|
|
18
|
+
// permissions_error=true e log.
|
|
19
|
+
//
|
|
20
|
+
// Cache Redis `perms:<idSchool>:v<versão>:<uid>` (TTL 3600s), best-effort.
|
|
21
|
+
// A versão vem de `perms_ver:<idSchool>`: o v1_acls invalida a escola
|
|
22
|
+
// inteira com um INCR (invalidatePermissionsCache) — atômico e O(1), sem o
|
|
23
|
+
// `KEYS perms:<id>:*` que varria o Redis todo. Chaves de versões antigas
|
|
24
|
+
// morrem pelo TTL.
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.defaultPermissionsQuery = defaultPermissionsQuery;
|
|
27
|
+
exports.createPermissionsResolver = createPermissionsResolver;
|
|
28
|
+
exports.permissionsVersionKey = permissionsVersionKey;
|
|
29
|
+
exports.permissionsCacheKey = permissionsCacheKey;
|
|
30
|
+
exports.invalidatePermissionsCache = invalidatePermissionsCache;
|
|
31
|
+
const roles_1 = require("../acl/roles");
|
|
32
|
+
const SCHEMA_RE = /^[A-Za-z0-9_]+$/;
|
|
33
|
+
/** A mesma consulta que as APIs usavam, com o uid como parâmetro. */
|
|
34
|
+
async function defaultPermissionsQuery(prisma, uid) {
|
|
35
|
+
const schema = String(prisma?.$schema ?? "");
|
|
36
|
+
if (!SCHEMA_RE.test(schema))
|
|
37
|
+
throw new Error(`schema inválido: ${schema}`);
|
|
38
|
+
const db = "`" + schema + "`";
|
|
39
|
+
const sql = `select distinct identifier
|
|
40
|
+
from ${db}.permissions
|
|
41
|
+
where id in (select id_permission from ${db}.rules_permissions
|
|
42
|
+
where id_rule in (select id_rule from ${db}.policies_rules
|
|
43
|
+
where id_policie in (select id_policie from ${db}.profiles_policies
|
|
44
|
+
where id_profile in (select id_profile from ${db}.users_profiles
|
|
45
|
+
where id_user in (select id from ${db}.users where firebase_id = ?)))))`;
|
|
46
|
+
const rows = await prisma.$queryRawUnsafe(sql, uid);
|
|
47
|
+
return (rows ?? []).map((r) => String(r.identifier));
|
|
48
|
+
}
|
|
49
|
+
function createPermissionsResolver(options = {}) {
|
|
50
|
+
const ttl = options.cacheTtlSeconds ?? 3600;
|
|
51
|
+
const asList = (v, fallback) => v === undefined ? [...fallback] : Array.isArray(v) ? v : [v];
|
|
52
|
+
const teacher = asList(options.roles?.teacher, roles_1.TEACHER_ACLS);
|
|
53
|
+
const student = asList(options.roles?.student, roles_1.STUDENT_ACLS);
|
|
54
|
+
const responsible = asList(options.roles?.responsible, roles_1.RESPONSIBLE_ACLS);
|
|
55
|
+
const query = options.query ?? defaultPermissionsQuery;
|
|
56
|
+
const log = options.logger ?? ((m, e) => console.error(m, e));
|
|
57
|
+
return async function resolvePermissions(req, idSchool) {
|
|
58
|
+
if (!idSchool)
|
|
59
|
+
return;
|
|
60
|
+
req.is_full = false;
|
|
61
|
+
req.is_teacher = false;
|
|
62
|
+
req.is_student = false;
|
|
63
|
+
req.is_responsible = false;
|
|
64
|
+
req.permissions = [];
|
|
65
|
+
req.permissions_error = false;
|
|
66
|
+
const uid = req.user_id;
|
|
67
|
+
if (!uid)
|
|
68
|
+
return;
|
|
69
|
+
// serviço interno (auth internalAuth): não é usuário da escola — não consulta
|
|
70
|
+
if (req.internal === true)
|
|
71
|
+
return;
|
|
72
|
+
let cacheKey = null;
|
|
73
|
+
try {
|
|
74
|
+
let permissions = null;
|
|
75
|
+
if (options.redis) {
|
|
76
|
+
try {
|
|
77
|
+
const version = await readPermissionsVersion(options.redis, idSchool);
|
|
78
|
+
cacheKey = permissionsCacheKey(idSchool, version, uid);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// sem a versão não dá para saber se o cache vale: segue sem cache
|
|
82
|
+
log("permissions: redis get da versão falhou, consultando o banco", e);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (options.redis && cacheKey) {
|
|
86
|
+
try {
|
|
87
|
+
const cached = await options.redis.get(cacheKey);
|
|
88
|
+
if (cached) {
|
|
89
|
+
const parsed = JSON.parse(cached);
|
|
90
|
+
if (Array.isArray(parsed))
|
|
91
|
+
permissions = parsed.map(String);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
log("permissions: redis get falhou, consultando o banco", e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!permissions) {
|
|
99
|
+
permissions = await query(req.prisma, uid);
|
|
100
|
+
if (options.redis && cacheKey) {
|
|
101
|
+
try {
|
|
102
|
+
await options.redis.setEx(cacheKey, ttl, JSON.stringify(permissions));
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
log("permissions: redis setEx falhou", e);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
req.permissions = permissions;
|
|
110
|
+
if (permissions.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
req.is_full = permissions.includes("full");
|
|
113
|
+
req.is_teacher = teacher.some(r => permissions.includes(r));
|
|
114
|
+
req.is_student = student.some(r => permissions.includes(r));
|
|
115
|
+
req.is_responsible = responsible.some(r => permissions.includes(r));
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
log(`permissions: falha ao resolver permissões de ${uid} na escola ${idSchool}`, e);
|
|
119
|
+
req.is_full = false;
|
|
120
|
+
req.is_teacher = false;
|
|
121
|
+
req.is_student = false;
|
|
122
|
+
req.is_responsible = false;
|
|
123
|
+
req.permissions = [];
|
|
124
|
+
req.permissions_error = true;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// --------------------------------------------------- versão do cache ----
|
|
129
|
+
const SCHOOL_RE = /^\d+$/;
|
|
130
|
+
/** Chave da versão do cache de permissões de uma escola. */
|
|
131
|
+
function permissionsVersionKey(idSchool) {
|
|
132
|
+
return `perms_ver:${idSchool}`;
|
|
133
|
+
}
|
|
134
|
+
/** Chave do cache de permissões de um usuário numa versão. */
|
|
135
|
+
function permissionsCacheKey(idSchool, version, uid) {
|
|
136
|
+
return `perms:${idSchool}:v${version}:${uid}`;
|
|
137
|
+
}
|
|
138
|
+
async function readPermissionsVersion(redis, idSchool) {
|
|
139
|
+
const raw = await redis.get(permissionsVersionKey(idSchool));
|
|
140
|
+
// só inteiro: qualquer lixo na chave vira versão 0 (nunca entra na chave de cache)
|
|
141
|
+
return raw && /^\d+$/.test(raw) ? raw : "0";
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Invalida o cache de permissões de UMA escola: INCR em `perms_ver:<id>`.
|
|
145
|
+
* Chamar depois de qualquer escrita em permissões, regras, políticas,
|
|
146
|
+
* perfis ou vínculo usuário↔perfil (v1_acls). Nunca lança.
|
|
147
|
+
*/
|
|
148
|
+
async function invalidatePermissionsCache(redis, idSchool, options = {}) {
|
|
149
|
+
const log = options.logger ?? ((m, e) => console.error(m, e));
|
|
150
|
+
const result = { version: null, legacyDeleted: 0 };
|
|
151
|
+
const id = idSchool === null || idSchool === undefined ? "" : String(idSchool);
|
|
152
|
+
if (!redis)
|
|
153
|
+
return result;
|
|
154
|
+
if (!SCHOOL_RE.test(id) || Number(id) <= 0) {
|
|
155
|
+
log(`permissions: invalidação sem id_school válido (${id || "vazio"}) ignorada`);
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
result.version = await redis.incr(permissionsVersionKey(id));
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
log(`permissions: INCR da versão falhou na escola ${id}`, e);
|
|
163
|
+
}
|
|
164
|
+
if (options.legacy && typeof redis.scanIterator === "function" && typeof redis.del === "function") {
|
|
165
|
+
try {
|
|
166
|
+
const batch = [];
|
|
167
|
+
for await (const item of redis.scanIterator({ MATCH: `perms:${id}:*`, COUNT: 200 })) {
|
|
168
|
+
for (const key of Array.isArray(item) ? item : [item])
|
|
169
|
+
batch.push(key);
|
|
170
|
+
if (batch.length >= 200) {
|
|
171
|
+
const keys = batch.splice(0);
|
|
172
|
+
await redis.del(keys);
|
|
173
|
+
result.legacyDeleted += keys.length;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (batch.length) {
|
|
177
|
+
await redis.del(batch);
|
|
178
|
+
result.legacyDeleted += batch.length;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
log(`permissions: limpeza das chaves antigas falhou na escola ${id}`, e);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=permissions.js.map
|