@tatara/cli 0.13.0 → 0.13.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/bin.mjs
CHANGED
|
@@ -531,22 +531,6 @@ const admin = {
|
|
|
531
531
|
}
|
|
532
532
|
]
|
|
533
533
|
};
|
|
534
|
-
const MEMBERS = `/** The org's members, a page at a time. */
|
|
535
|
-
async function* members(orgId: string) {
|
|
536
|
-
const { adapter } = await auth.$context;
|
|
537
|
-
for (let offset = 0; ; offset += 100) {
|
|
538
|
-
const page = await adapter.findMany<{ userId: string; role: string }>({
|
|
539
|
-
model: "member",
|
|
540
|
-
where: [{ field: "organizationId", value: orgId }],
|
|
541
|
-
limit: 100,
|
|
542
|
-
offset,
|
|
543
|
-
sortBy: { field: "id", direction: "asc" },
|
|
544
|
-
});
|
|
545
|
-
yield* page;
|
|
546
|
-
if (page.length < 100) return;
|
|
547
|
-
}
|
|
548
|
-
}`;
|
|
549
|
-
const hasRole = "m.role.split(\",\").map((r) => r.trim()).includes(role)";
|
|
550
534
|
const notifications = {
|
|
551
535
|
id: "notifications",
|
|
552
536
|
kind: "addon",
|
|
@@ -621,7 +605,21 @@ const notifications = {
|
|
|
621
605
|
file: "src/modules/notifications/services/notifier.ts",
|
|
622
606
|
slot: "helpers",
|
|
623
607
|
when: ["auth-orgs"],
|
|
624
|
-
content:
|
|
608
|
+
content: `/** The org's members, a page at a time. */
|
|
609
|
+
async function* members(orgId: string) {
|
|
610
|
+
const { adapter } = await auth.$context;
|
|
611
|
+
for (let offset = 0; ; offset += 100) {
|
|
612
|
+
const page = await adapter.findMany<{ userId: string; role: string }>({
|
|
613
|
+
model: "member",
|
|
614
|
+
where: [{ field: "organizationId", value: orgId }],
|
|
615
|
+
limit: 100,
|
|
616
|
+
offset,
|
|
617
|
+
sortBy: { field: "id", direction: "asc" },
|
|
618
|
+
});
|
|
619
|
+
yield* page;
|
|
620
|
+
if (page.length < 100) return;
|
|
621
|
+
}
|
|
622
|
+
}`
|
|
625
623
|
},
|
|
626
624
|
{
|
|
627
625
|
file: "src/modules/notifications/services/notifier.ts",
|
|
@@ -631,7 +629,7 @@ const notifications = {
|
|
|
631
629
|
content: [
|
|
632
630
|
"recipients: {",
|
|
633
631
|
" async *role(orgId, role) {",
|
|
634
|
-
`\t\tfor await (const m of members(orgId)) if (
|
|
632
|
+
`\t\tfor await (const m of members(orgId)) if (m.role.split(",").map((r) => r.trim()).includes(role)) yield m.userId;`,
|
|
635
633
|
" },",
|
|
636
634
|
" permission() {",
|
|
637
635
|
" throw new Error(\"Notifying by permission needs the authz add-on\");",
|
|
@@ -645,8 +643,12 @@ const notifications = {
|
|
|
645
643
|
when: ["auth-orgs", "authz"],
|
|
646
644
|
content: [
|
|
647
645
|
"recipients: {",
|
|
646
|
+
" // authz roles (assigned, or held by every member): better-auth's member role is only owner or member",
|
|
648
647
|
" async *role(orgId, role) {",
|
|
649
|
-
|
|
648
|
+
" for await (const m of members(orgId)) {",
|
|
649
|
+
" const who = { userId: m.userId, orgId, orgRole: m.role, roles: [], kind: \"user\" as const, claims: {} };",
|
|
650
|
+
" if ((await authz.effectivePermissions(who)).roles.includes(role)) yield m.userId;",
|
|
651
|
+
" }",
|
|
650
652
|
" },",
|
|
651
653
|
" // the same check a request makes: built-in roles, assigned roles and overlays",
|
|
652
654
|
" async *permission(orgId, permission) {",
|
|
@@ -662,7 +664,7 @@ const notifications = {
|
|
|
662
664
|
file: "src/modules/notifications/services/notifier.ts",
|
|
663
665
|
slot: "imports",
|
|
664
666
|
when: ["auth-orgs", "authz"],
|
|
665
|
-
content: "import { checkPermission } from \"#core/authz/index\";\nimport type { PermissionKey } from \"#generated/permissions\";"
|
|
667
|
+
content: "import { authz, checkPermission } from \"#core/authz/index\";\nimport type { PermissionKey } from \"#generated/permissions\";"
|
|
666
668
|
},
|
|
667
669
|
{
|
|
668
670
|
file: "src/modules/notifications/services/notifier.ts",
|
|
@@ -3983,6 +3985,30 @@ const jobsDrivers = {
|
|
|
3983
3985
|
}
|
|
3984
3986
|
};
|
|
3985
3987
|
//#endregion
|
|
3988
|
+
//#region src/upgrade/codemods/notifier-roles.ts
|
|
3989
|
+
const NOTIFIER = "src/modules/notifications/services/notifier.ts";
|
|
3990
|
+
const TEST = "src/modules/notifications/tests/role-recipients.test.ts";
|
|
3991
|
+
/** `recipients.role` reads authz roles: better-auth's member role is only owner or member. */
|
|
3992
|
+
const notifierRoles = {
|
|
3993
|
+
version: "0.13.1",
|
|
3994
|
+
id: "notifier-roles",
|
|
3995
|
+
description: "Notifying by role reaches holders of the authz role",
|
|
3996
|
+
async run(ctx) {
|
|
3997
|
+
const { config } = ctx.project;
|
|
3998
|
+
const addons = config.addons;
|
|
3999
|
+
if (!addons.includes("notifications") || !addons.includes("authz")) return;
|
|
4000
|
+
if (!config.authMethods.includes("orgs")) return;
|
|
4001
|
+
const text = await ctx.read(NOTIFIER).catch(() => void 0);
|
|
4002
|
+
if (text === void 0) return;
|
|
4003
|
+
if (await ctx.baseline.isUnchanged(NOTIFIER, text)) await ctx.regenerate(NOTIFIER);
|
|
4004
|
+
else ctx.report.notes.push(`In ${NOTIFIER}, recipients.role should check authz roles: \`(await authz.effectivePermissions(who)).roles.includes(role)\` for each member, with who as in recipients.permission. Matching member.role finds nobody now that it is only owner or member.`);
|
|
4005
|
+
if (await ctx.read(TEST).catch(() => void 0) === void 0) {
|
|
4006
|
+
const test = await ctx.renderCurrent(TEST);
|
|
4007
|
+
if (test !== void 0) await ctx.write(TEST, test);
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
4011
|
+
//#endregion
|
|
3986
4012
|
//#region src/upgrade/codemods/package-commands.ts
|
|
3987
4013
|
/** Copies whose commands and seeds the packages contribute now (wired by `tatara sync`). */
|
|
3988
4014
|
const COPIES = [
|
|
@@ -4035,7 +4061,8 @@ const CODEMODS = [
|
|
|
4035
4061
|
jobsDrivers,
|
|
4036
4062
|
emailV2,
|
|
4037
4063
|
deployLedger,
|
|
4038
|
-
authzRoles
|
|
4064
|
+
authzRoles,
|
|
4065
|
+
notifierRoles
|
|
4039
4066
|
];
|
|
4040
4067
|
//#endregion
|
|
4041
4068
|
//#region src/upgrade/context.ts
|
package/package.json
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { InlineDriver } from "@tatara/jobs";
|
|
2
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { jobs } from "#core/jobs/index";
|
|
4
|
+
import { notify } from "#modules/notifications/services/notifier";
|
|
5
|
+
import { api } from "#tests/helpers/api";
|
|
6
|
+
import { authenticate } from "#tests/helpers/auth";
|
|
7
|
+
import { applyTestAccess } from "#tests/helpers/authz";
|
|
8
|
+
import { resetDatabase } from "#tests/helpers/db";
|
|
9
|
+
|
|
10
|
+
const drain = () => (jobs.driver as InlineDriver).drain();
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
await resetDatabase();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("notifying by role", () => {
|
|
17
|
+
it("reaches the members who hold the authz role, not better-auth's member role", async () => {
|
|
18
|
+
const admin = await authenticate();
|
|
19
|
+
await applyTestAccess(admin.userId, { systemRoles: ["admin"], orgId: admin.orgId });
|
|
20
|
+
const plain = await authenticate();
|
|
21
|
+
await notify({
|
|
22
|
+
orgId: admin.orgId as string,
|
|
23
|
+
to: { role: "admin" },
|
|
24
|
+
notification: "example.welcome",
|
|
25
|
+
data: { name: "Ada" },
|
|
26
|
+
});
|
|
27
|
+
await drain();
|
|
28
|
+
const count = async (headers: Record<string, string>) =>
|
|
29
|
+
(await api.get<{ data: { count: number } }>("/api/notifications/unread-count", { headers }))
|
|
30
|
+
.body.data.count;
|
|
31
|
+
expect(await count(admin.headers)).toBe(1);
|
|
32
|
+
expect(await count(plain.headers)).toBe(0);
|
|
33
|
+
});
|
|
34
|
+
});
|