@12-apps/audit
动作审计作为套件: 仅是附件的"谁做了什么"线索, 它的交易 编剧,背后的每个请求的演员背景,两个Prisma扩展 执行它, 保留扫描, 租户范围上市 终点和 观者读之.
词汇表是你的. 这个包船没有动作,没有资源类型, 没有字段许可列表,也没有产品语言——你宣布并传入. 见 采用.md 用于迁移表,如果您来自 1.x (中文(简体) ).
两半,各一厂.
// backend
import { createApiAudit } from '@12-apps/audit/server';
import { auditRouter } from '@12-apps/audit/hono';
// frontend
import { createWebAudit } from '@12-apps/audit/react';
它是为了什么
审计线索只有在事后无法编辑才值得,而且只值得 如果写得对的话,是站不住脚的 这个包是这两个属性, 机械制造:
- 不可思议. 该条目被写入 INSID 呼叫者的交易, 所以"它
"它被记录下来" 承诺或滚回一起——从来没有
火相与忘却. 后来,模型拒绝了
update编号 :upsert编号 :delete在客户端. - 正确归属. 入口带有一个身份的AIR:真正的人类 其全权证书授权写作,而且——当某届会议正在冒充时—— 谁是屏幕声称的。 一个领域无法回答两个问题,而且 拆解它们不能在附件表格中恢复.
词汇
一种价值描述的是可能审计的内容,一行可能说的话,以及人类 读取。 两者都取同物.
import { defineAuditVocabulary } from '@12-apps/audit';
export const AUDIT = defineAuditVocabulary({
actions: {
'post.publish': { label: 'Post published' },
'comment.hide': { label: 'Comment hidden' },
},
resources: {
post: { label: 'Post', fields: ['title', 'state', 'publishedAt'] },
comment: { label: 'Comment', fields: ['state', 'reason'] },
},
});
fields 是一个 逐项拒绝授权列表: diff 携带但
词汇表不删除名称, 因此调用者会通过整个数据库行
无法将 PII 泄露到仅包含附件的表格中。 反面是,一个疏漏是
无法在写入站点中隐藏并显示为空洞的条目——列出每个字段
共享资源类型中的每个写入器都会发出.
工厂拒绝,在集会上,一切 使词汇
不安全的后端: 一个空轴, 一个空轴 fields,空白标签,空白或
白空加法 id 或 字段名, 重复字段, 整数式 id 。 每个
其中一个是故障打开,解释在 采用.md 第5节.
后端的一半
const audit = auditRouter({
db: () => getPrismaClient(), // the seam: one owned model
vocabulary: AUDIT, // what may be audited
trackedModels: ['Post', 'Comment'], // created_by / updated_by
retention: { floorDays: 365 },
directory: { getUsers, listActors }, // who a user id is (optional)
messages: { forbidden: 'No access.' }, // your product's copy
resolveActor: async (request) => { // WHO is calling
const session = await auth(request.raw);
if (!session) return null;
const { tenantId, permissions } = await resolveTenant(request.params.tenantSlug, session);
// Return null — the 401 — rather than an actor with no tenant. An `undefined`
// tenantId is a cross-tenant read waiting to happen, so the package refuses it
// with a 500 (ADOPTING.md rule 1).
if (!tenantId) return null;
return {
tenantId,
userId: session.user.id,
permissions,
role: session.role,
scope: tenantId,
onBehalfOfUserId: session.impersonating?.subjectUserId ?? null,
};
},
});
app.use('*', audit.actorContext); // stamp every request
app.route('/api/admin/:tenantSlug', audit.router);
你得到什么回来:
routes | 框架中性描述符,按挂起顺序排列(./hono 适应它们) |
write(tx, entry) | 交易写入器 —— 在您的交易中调用它 |
extendPrismaClient(client) | 两次扩展,一次在客户端构建时应用 |
withActorContext(req, run) | 演员文本中间软件( nameactorContext 内 ./hono(中文(简体) ) |
retention | purgeExpired() 财务报告和审定财务报表 purgeTenantWindow(tenant, since, cutoff) |
store | 租户范围改为: 显示相同行的宿主表面 |
vocabulary | 有效的词汇表——标签和验证 |
终点
| 方法 | 路径 | |
|---|---|---|
GET | /audit-logs | 房客的踪迹, 最新的第一—— { data, pagination } |
GET | /audit-logs/actors | 观众的演员-过滤选项—— { data } |
过滤器 : q (资源id包含, \\ 200个字符), action_in, (中文(简体) ), resourceType_in, (中文(简体) ),
actorUserId, (中文(简体) ), resourceId, (中文(简体) ), from编号 :to (含 YYYY-MM-DD, (中文(简体) ), sort, (中文(简体) ),
page, (中文(简体) ), pageSize。呼号是配置(pagination),默认为
20 / 100 / 100 / 100 000, 并且它们作为限定整数而不是作为
光线 未知的过滤器值, 不可保存 sort 且日期不正确
400 从未接受和忽视;否认就是 { error } 在顶层
401 编号 : 403。 。 。 .
名单的顺序是 共计数字 : created_at DESC, id DESC 默认,或
ASC, ASC 页:1 ?sort=createdAt:asc —— 说 created_at 是一条线索的一轴,
向前看 事情是如何重建的 领带断了
遵循方向,否则两种观点不会相互颠倒。 审计线索是:
连续写作 created_at 实值 timestamp(3),所以联系是普通的——和
SQL 保证某类无法区分的行顺序
与 skip编号 :take 意思是读者看到一个条目两次,从不看到另一个.
接合断是接合的一部分AUDIT_LOG_ORDER_BY因此,一个非Prisma
而不是重新塑造它.
有一个 无写入结束点条目是由变异体自己写的, 所以这里的POST只是一种 伪造历史的方法.
允许它打开
audit:read 导出为 AUDIT_READ_PERMISSION,因为这个软件包拥有
端点 它守护。 把它写入目录,或者用不同的拼写
与 gatePermissions.read ——常数是默认数,而不是要求.
前端的半边
const { page: AuditLog } = createWebAudit({
apiBase: '/api/admin/my-store',
vocabulary: AUDIT,
labels: { title: 'History' }, // your product's words
});
整个SCREEN,关于共享 DataViews 网格:仪表板头
[i] 及其导出、 搜索框、 动作/ 资源/ 演员过滤器
药丸、 周期范围、 可排序的日期列、 列可见度、 已保存
视图,带有 diff 摘要和服务器-模式寻呼器的线索——以及
作为一行命名的假配对 两者 人类 词汇
与后端部分共享,所以存在的动作是动作
取景器可以标签.
作为 SAME 网格, 主机的其他列表使用是关键: 操作员移动
在Pedidos,Produtos和小道之间学习一个过滤棒. 两个东道方
要求随即提出 DataViewsCopyProvider 在您的根部( 网格本身)
语言是属于你的,它拒绝发明它们 table 包装,如果你
想要保存视图, 因为坚持是一个后端 。 见
通过.md§4b.
复制默认为英语, 日期为运行时的自有地段 。 两者都是 背弃而不是建议——一揽子方案无法了解你的市场,以及 1.x 默认为一个应用程序.
一个读者会说葡萄牙语的东道主 通过了运来的包而不是
重写它: PT_BR_AUDIT_LABELS (单位:千美元)./react改为: labels, (中文(简体) ), PT_BR_AUDIT_MESSAGES
(单位:千美元)./server改为: messages。每个都有一个 EN_US_* 双向和标签键地图
(单位:千美元)AUDIT_LABELS, (中文(简体) ), AUDIT_MESSAGES)——通过.md§4.
数据库
软件包拥有一个模型(prisma/audit.prisma及其移徙。 一个主机
复制部分与软件包自己的脚本:
node node_modules/@12-apps/audit/scripts/sync-audit-schema.mjs [<host-schema-dir>]
迁移是 重放安全 (单位:千美元)CREATE TABLE IF NOT EXISTS, (中文(简体) ),
ADD COLUMN IF NOT EXISTS, (中文(简体) ), CREATE INDEX IF NOT EXISTS),所以一个宿主已经
有一个 audit_logs 页:1
prisma migrate resolve --applied。 。 。 .
这不能防止
很明显,因为一个没有证件的警卫 会被信任过 他们:
- Raw SQL 绕过仅附加的守护.
$executeRaw*编号 :$queryRaw*永远 给一个模型代表,所以任何人 持有客户可以DELETE FROM audit_logs这是故意的——这是保留的唯一办法 扫描可以运行 - 这意味着警卫 停止AccIDENT,不是决心 操作员。 该级别的保证是数据库权限或触发,以及 两者都归东道主所有. - 另一个连接没有受到影响. psql, 一个迁移, 第二个客户端已建立 没有扩展.
- 主机仍然拥有授权. 许可标识上的包口 宿主决定了,它不计算它们,它不读任何物 演员没有带.
见 采用.md 完全合同,假冒对夫妇 设计、装配拒绝和核对表.