@agent-native/core 0.10.0 → 0.11.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/a2a/caller-auth.d.ts +12 -0
- package/dist/a2a/caller-auth.d.ts.map +1 -0
- package/dist/a2a/caller-auth.js +54 -0
- package/dist/a2a/caller-auth.js.map +1 -0
- package/dist/action.d.ts +17 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +22 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +32 -19
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +9 -46
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +25 -19
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/builder-frame.d.ts.map +1 -1
- package/dist/client/builder-frame.js +10 -3
- package/dist/client/builder-frame.js.map +1 -1
- package/dist/client/components/ui/dropdown-menu.d.ts +28 -0
- package/dist/client/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/client/components/ui/dropdown-menu.js +34 -0
- package/dist/client/components/ui/dropdown-menu.js.map +1 -0
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +2 -0
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionSlot.js +14 -1
- package/dist/client/extensions/ExtensionSlot.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +2 -0
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +2 -2
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +6 -17
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/extensions/iframe-bridge.d.ts.map +1 -1
- package/dist/client/extensions/iframe-bridge.js +5 -8
- package/dist/client/extensions/iframe-bridge.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +7 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +8 -3
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +153 -20
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/org/hooks.d.ts +29 -1
- package/dist/client/org/hooks.d.ts.map +1 -1
- package/dist/client/org/hooks.js +39 -2
- package/dist/client/org/hooks.js.map +1 -1
- package/dist/client/org/index.d.ts +2 -1
- package/dist/client/org/index.d.ts.map +1 -1
- package/dist/client/org/index.js +1 -1
- package/dist/client/org/index.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +11 -3
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +21 -5
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +39 -3
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +58 -21
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +1 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +20 -49
- package/dist/deploy/build.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/org/accept-pending.d.ts.map +1 -1
- package/dist/org/accept-pending.js +5 -3
- package/dist/org/accept-pending.js.map +1 -1
- package/dist/org/free-email-providers.d.ts +18 -0
- package/dist/org/free-email-providers.d.ts.map +1 -0
- package/dist/org/free-email-providers.js +124 -0
- package/dist/org/free-email-providers.js.map +1 -0
- package/dist/org/handlers.d.ts +29 -5
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +178 -37
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/index.d.ts +2 -1
- package/dist/org/index.d.ts.map +1 -1
- package/dist/org/index.js +2 -1
- package/dist/org/index.js.map +1 -1
- package/dist/org/migrations.d.ts.map +1 -1
- package/dist/org/migrations.js +4 -0
- package/dist/org/migrations.js.map +1 -1
- package/dist/org/plugin.d.ts.map +1 -1
- package/dist/org/plugin.js +13 -4
- package/dist/org/plugin.js.map +1 -1
- package/dist/org/schema.d.ts +19 -0
- package/dist/org/schema.d.ts.map +1 -1
- package/dist/org/schema.js +1 -0
- package/dist/org/schema.js.map +1 -1
- package/dist/org/types.d.ts +1 -0
- package/dist/org/types.d.ts.map +1 -1
- package/dist/org/types.js.map +1 -1
- package/dist/resources/metadata.d.ts +1 -0
- package/dist/resources/metadata.d.ts.map +1 -1
- package/dist/resources/metadata.js +13 -3
- package/dist/resources/metadata.js.map +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +44 -6
- package/dist/resources/store.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +1 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +38 -11
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +10 -3
- package/dist/server/google-oauth.js.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accept-pending.js","sourceRoot":"","sources":["../../src/org/accept-pending.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,MAAM,MAAM,GAAG,GAAW,EAAE,CAC1B,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;IACnD,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAOhE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,QAAgB;IAEhB,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IAEvB,IAAI,IAAI,
|
|
1
|
+
{"version":3,"file":"accept-pending.js","sourceRoot":"","sources":["../../src/org/accept-pending.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDbExec } from \"../db/client.js\";\nimport { putUserSetting } from \"../settings/user-settings.js\";\n\nconst nanoid = (): string =>\n globalThis.crypto?.randomUUID?.().replace(/-/g, \"\") ??\n Math.random().toString(36).slice(2) + Date.now().toString(36);\n\nexport interface AcceptPendingResult {\n accepted: Array<{ invitationId: string; orgId: string }>;\n activeOrgId: string | null;\n}\n\n/**\n * Accept every pending `org_invitations` row for this email:\n * - insert a matching `org_members` row (role 'member') when one doesn't exist\n * - flip the invitation's status to 'accepted'\n * - set the user's `active-org-id` to the most-recently-created invite\n *\n * Called from the Better Auth `user.create.after` hook so that a user who signs\n * up with an email they were just invited to lands in the org immediately,\n * rather than seeing a blank-slate app until they navigate to /team.\n *\n * Safe to call when the org tables don't exist (some templates don't use the\n * org module) — it swallows the \"no such table\" error and returns empty.\n */\nexport async function acceptPendingInvitationsForEmail(\n rawEmail: string,\n): Promise<AcceptPendingResult> {\n const email = rawEmail.trim().toLowerCase();\n if (!email) {\n return { accepted: [], activeOrgId: null };\n }\n\n const db = getDbExec();\n\n let rows: Array<{ id: string; orgId: string; role: string | null }> = [];\n try {\n const res = await db.execute({\n sql: `SELECT id, org_id AS \"orgId\", role FROM org_invitations\n WHERE LOWER(email) = ? AND status = 'pending'\n ORDER BY created_at DESC`,\n args: [email],\n });\n rows = res.rows.map((r: any) => ({\n id: String(r.id),\n orgId: String(r.orgId ?? r.org_id),\n role: r.role == null ? null : String(r.role),\n }));\n } catch (err) {\n // Template doesn't use the org module / tables not migrated yet.\n return { accepted: [], activeOrgId: null };\n }\n\n if (rows.length === 0) {\n return { accepted: [], activeOrgId: null };\n }\n\n const accepted: AcceptPendingResult[\"accepted\"] = [];\n for (const inv of rows) {\n const existing = await db.execute({\n sql: `SELECT 1 FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,\n args: [inv.orgId, email],\n });\n if (existing.rows.length === 0) {\n const role = inv.role === \"admin\" ? \"admin\" : \"member\";\n await db.execute({\n sql: `INSERT INTO org_members (id, org_id, email, role, joined_at) VALUES (?, ?, ?, ?, ?)`,\n args: [nanoid(), inv.orgId, email, role, Date.now()],\n });\n }\n await db.execute({\n sql: `UPDATE org_invitations SET status = 'accepted' WHERE id = ?`,\n args: [inv.id],\n });\n accepted.push({ invitationId: inv.id, orgId: inv.orgId });\n }\n\n // Set active-org-id to the most recent invite so the user lands in a\n // populated workspace on first load.\n const activeOrgId = accepted[0]?.orgId ?? null;\n if (activeOrgId) {\n try {\n await putUserSetting(email, \"active-org-id\", { orgId: activeOrgId });\n } catch {\n // user_settings table might not exist in a minimal template — not fatal.\n }\n }\n\n return { accepted, activeOrgId };\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free / public mailbox providers that should NOT be allowed as an
|
|
3
|
+
* organization's auto-join domain.
|
|
4
|
+
*
|
|
5
|
+
* Why: the auto-join feature lets anyone who signs up with an email at the
|
|
6
|
+
* org's `allowed_domain` join the org without an invitation. That is safe
|
|
7
|
+
* for company-owned domains (`acme.com`) — the company controls who gets
|
|
8
|
+
* an `@acme.com` address. It is catastrophic for shared mailbox providers
|
|
9
|
+
* (`gmail.com`, `outlook.com`, etc.) — anyone in the world can create a
|
|
10
|
+
* matching address and would be auto-added to the org.
|
|
11
|
+
*
|
|
12
|
+
* The list intentionally errs on the side of well-known providers; if a
|
|
13
|
+
* future provider isn't here we'll learn from a bug report rather than
|
|
14
|
+
* pretend we have an exhaustive registry.
|
|
15
|
+
*/
|
|
16
|
+
export declare const FREE_EMAIL_PROVIDER_DOMAINS: ReadonlySet<string>;
|
|
17
|
+
export declare function isFreeEmailProvider(domain: string): boolean;
|
|
18
|
+
//# sourceMappingURL=free-email-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"free-email-providers.d.ts","sourceRoot":"","sources":["../../src/org/free-email-providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,MAAM,CAwG1D,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3D"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free / public mailbox providers that should NOT be allowed as an
|
|
3
|
+
* organization's auto-join domain.
|
|
4
|
+
*
|
|
5
|
+
* Why: the auto-join feature lets anyone who signs up with an email at the
|
|
6
|
+
* org's `allowed_domain` join the org without an invitation. That is safe
|
|
7
|
+
* for company-owned domains (`acme.com`) — the company controls who gets
|
|
8
|
+
* an `@acme.com` address. It is catastrophic for shared mailbox providers
|
|
9
|
+
* (`gmail.com`, `outlook.com`, etc.) — anyone in the world can create a
|
|
10
|
+
* matching address and would be auto-added to the org.
|
|
11
|
+
*
|
|
12
|
+
* The list intentionally errs on the side of well-known providers; if a
|
|
13
|
+
* future provider isn't here we'll learn from a bug report rather than
|
|
14
|
+
* pretend we have an exhaustive registry.
|
|
15
|
+
*/
|
|
16
|
+
export const FREE_EMAIL_PROVIDER_DOMAINS = new Set([
|
|
17
|
+
// Google
|
|
18
|
+
"gmail.com",
|
|
19
|
+
"googlemail.com",
|
|
20
|
+
// Microsoft
|
|
21
|
+
"outlook.com",
|
|
22
|
+
"hotmail.com",
|
|
23
|
+
"live.com",
|
|
24
|
+
"msn.com",
|
|
25
|
+
"outlook.co.uk",
|
|
26
|
+
"hotmail.co.uk",
|
|
27
|
+
"live.co.uk",
|
|
28
|
+
"outlook.de",
|
|
29
|
+
"hotmail.de",
|
|
30
|
+
"live.de",
|
|
31
|
+
"outlook.fr",
|
|
32
|
+
"hotmail.fr",
|
|
33
|
+
"live.fr",
|
|
34
|
+
// Yahoo
|
|
35
|
+
"yahoo.com",
|
|
36
|
+
"yahoo.co.uk",
|
|
37
|
+
"yahoo.co.jp",
|
|
38
|
+
"yahoo.fr",
|
|
39
|
+
"yahoo.de",
|
|
40
|
+
"yahoo.it",
|
|
41
|
+
"yahoo.es",
|
|
42
|
+
"yahoo.ca",
|
|
43
|
+
"yahoo.com.au",
|
|
44
|
+
"yahoo.com.br",
|
|
45
|
+
"ymail.com",
|
|
46
|
+
"rocketmail.com",
|
|
47
|
+
// Apple
|
|
48
|
+
"icloud.com",
|
|
49
|
+
"me.com",
|
|
50
|
+
"mac.com",
|
|
51
|
+
// AOL / Verizon
|
|
52
|
+
"aol.com",
|
|
53
|
+
"aim.com",
|
|
54
|
+
// Privacy / disposable / forwarding
|
|
55
|
+
"proton.me",
|
|
56
|
+
"protonmail.com",
|
|
57
|
+
"pm.me",
|
|
58
|
+
"tutanota.com",
|
|
59
|
+
"tutanota.de",
|
|
60
|
+
"tuta.io",
|
|
61
|
+
"fastmail.com",
|
|
62
|
+
"fastmail.fm",
|
|
63
|
+
"duck.com",
|
|
64
|
+
"hey.com",
|
|
65
|
+
// Russian / Chinese majors
|
|
66
|
+
"yandex.com",
|
|
67
|
+
"yandex.ru",
|
|
68
|
+
"mail.ru",
|
|
69
|
+
"list.ru",
|
|
70
|
+
"bk.ru",
|
|
71
|
+
"inbox.ru",
|
|
72
|
+
"qq.com",
|
|
73
|
+
"163.com",
|
|
74
|
+
"126.com",
|
|
75
|
+
"sina.com",
|
|
76
|
+
"sina.cn",
|
|
77
|
+
"sohu.com",
|
|
78
|
+
// ISP / legacy / misc
|
|
79
|
+
"gmx.com",
|
|
80
|
+
"gmx.de",
|
|
81
|
+
"gmx.net",
|
|
82
|
+
"web.de",
|
|
83
|
+
"t-online.de",
|
|
84
|
+
"freenet.de",
|
|
85
|
+
"zoho.com",
|
|
86
|
+
"zohomail.com",
|
|
87
|
+
"rediffmail.com",
|
|
88
|
+
"mail.com",
|
|
89
|
+
"att.net",
|
|
90
|
+
"comcast.net",
|
|
91
|
+
"verizon.net",
|
|
92
|
+
"sbcglobal.net",
|
|
93
|
+
"bellsouth.net",
|
|
94
|
+
"cox.net",
|
|
95
|
+
"earthlink.net",
|
|
96
|
+
"btinternet.com",
|
|
97
|
+
"btopenworld.com",
|
|
98
|
+
"talktalk.net",
|
|
99
|
+
"sky.com",
|
|
100
|
+
"ntlworld.com",
|
|
101
|
+
"virginmedia.com",
|
|
102
|
+
"free.fr",
|
|
103
|
+
"orange.fr",
|
|
104
|
+
"wanadoo.fr",
|
|
105
|
+
"laposte.net",
|
|
106
|
+
"libero.it",
|
|
107
|
+
"tiscali.it",
|
|
108
|
+
"uol.com.br",
|
|
109
|
+
"bol.com.br",
|
|
110
|
+
"terra.com.br",
|
|
111
|
+
// Disposable
|
|
112
|
+
"mailinator.com",
|
|
113
|
+
"guerrillamail.com",
|
|
114
|
+
"10minutemail.com",
|
|
115
|
+
"trashmail.com",
|
|
116
|
+
"yopmail.com",
|
|
117
|
+
"tempmail.com",
|
|
118
|
+
"throwawaymail.com",
|
|
119
|
+
"sharklasers.com",
|
|
120
|
+
]);
|
|
121
|
+
export function isFreeEmailProvider(domain) {
|
|
122
|
+
return FREE_EMAIL_PROVIDER_DOMAINS.has(domain.trim().toLowerCase());
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=free-email-providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"free-email-providers.js","sourceRoot":"","sources":["../../src/org/free-email-providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAwB,IAAI,GAAG,CAAC;IACtE,SAAS;IACT,WAAW;IACX,gBAAgB;IAChB,YAAY;IACZ,aAAa;IACb,aAAa;IACb,UAAU;IACV,SAAS;IACT,eAAe;IACf,eAAe;IACf,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,QAAQ;IACR,WAAW;IACX,aAAa;IACb,aAAa;IACb,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;IACV,cAAc;IACd,cAAc;IACd,WAAW;IACX,gBAAgB;IAChB,QAAQ;IACR,YAAY;IACZ,QAAQ;IACR,SAAS;IACT,gBAAgB;IAChB,SAAS;IACT,SAAS;IACT,oCAAoC;IACpC,WAAW;IACX,gBAAgB;IAChB,OAAO;IACP,cAAc;IACd,aAAa;IACb,SAAS;IACT,cAAc;IACd,aAAa;IACb,UAAU;IACV,SAAS;IACT,2BAA2B;IAC3B,YAAY;IACZ,WAAW;IACX,SAAS;IACT,SAAS;IACT,OAAO;IACP,UAAU;IACV,QAAQ;IACR,SAAS;IACT,SAAS;IACT,UAAU;IACV,SAAS;IACT,UAAU;IACV,sBAAsB;IACtB,SAAS;IACT,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,aAAa;IACb,YAAY;IACZ,UAAU;IACV,cAAc;IACd,gBAAgB;IAChB,UAAU;IACV,SAAS;IACT,aAAa;IACb,aAAa;IACb,eAAe;IACf,eAAe;IACf,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,iBAAiB;IACjB,cAAc;IACd,SAAS;IACT,cAAc;IACd,iBAAiB;IACjB,SAAS;IACT,WAAW;IACX,YAAY;IACZ,aAAa;IACb,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,aAAa;IACb,gBAAgB;IAChB,mBAAmB;IACnB,kBAAkB;IAClB,eAAe;IACf,aAAa;IACb,cAAc;IACd,mBAAmB;IACnB,iBAAiB;CAClB,CAAC,CAAC;AAEH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,2BAA2B,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AACtE,CAAC","sourcesContent":["/**\n * Free / public mailbox providers that should NOT be allowed as an\n * organization's auto-join domain.\n *\n * Why: the auto-join feature lets anyone who signs up with an email at the\n * org's `allowed_domain` join the org without an invitation. That is safe\n * for company-owned domains (`acme.com`) — the company controls who gets\n * an `@acme.com` address. It is catastrophic for shared mailbox providers\n * (`gmail.com`, `outlook.com`, etc.) — anyone in the world can create a\n * matching address and would be auto-added to the org.\n *\n * The list intentionally errs on the side of well-known providers; if a\n * future provider isn't here we'll learn from a bug report rather than\n * pretend we have an exhaustive registry.\n */\nexport const FREE_EMAIL_PROVIDER_DOMAINS: ReadonlySet<string> = new Set([\n // Google\n \"gmail.com\",\n \"googlemail.com\",\n // Microsoft\n \"outlook.com\",\n \"hotmail.com\",\n \"live.com\",\n \"msn.com\",\n \"outlook.co.uk\",\n \"hotmail.co.uk\",\n \"live.co.uk\",\n \"outlook.de\",\n \"hotmail.de\",\n \"live.de\",\n \"outlook.fr\",\n \"hotmail.fr\",\n \"live.fr\",\n // Yahoo\n \"yahoo.com\",\n \"yahoo.co.uk\",\n \"yahoo.co.jp\",\n \"yahoo.fr\",\n \"yahoo.de\",\n \"yahoo.it\",\n \"yahoo.es\",\n \"yahoo.ca\",\n \"yahoo.com.au\",\n \"yahoo.com.br\",\n \"ymail.com\",\n \"rocketmail.com\",\n // Apple\n \"icloud.com\",\n \"me.com\",\n \"mac.com\",\n // AOL / Verizon\n \"aol.com\",\n \"aim.com\",\n // Privacy / disposable / forwarding\n \"proton.me\",\n \"protonmail.com\",\n \"pm.me\",\n \"tutanota.com\",\n \"tutanota.de\",\n \"tuta.io\",\n \"fastmail.com\",\n \"fastmail.fm\",\n \"duck.com\",\n \"hey.com\",\n // Russian / Chinese majors\n \"yandex.com\",\n \"yandex.ru\",\n \"mail.ru\",\n \"list.ru\",\n \"bk.ru\",\n \"inbox.ru\",\n \"qq.com\",\n \"163.com\",\n \"126.com\",\n \"sina.com\",\n \"sina.cn\",\n \"sohu.com\",\n // ISP / legacy / misc\n \"gmx.com\",\n \"gmx.de\",\n \"gmx.net\",\n \"web.de\",\n \"t-online.de\",\n \"freenet.de\",\n \"zoho.com\",\n \"zohomail.com\",\n \"rediffmail.com\",\n \"mail.com\",\n \"att.net\",\n \"comcast.net\",\n \"verizon.net\",\n \"sbcglobal.net\",\n \"bellsouth.net\",\n \"cox.net\",\n \"earthlink.net\",\n \"btinternet.com\",\n \"btopenworld.com\",\n \"talktalk.net\",\n \"sky.com\",\n \"ntlworld.com\",\n \"virginmedia.com\",\n \"free.fr\",\n \"orange.fr\",\n \"wanadoo.fr\",\n \"laposte.net\",\n \"libero.it\",\n \"tiscali.it\",\n \"uol.com.br\",\n \"bol.com.br\",\n \"terra.com.br\",\n // Disposable\n \"mailinator.com\",\n \"guerrillamail.com\",\n \"10minutemail.com\",\n \"trashmail.com\",\n \"yopmail.com\",\n \"tempmail.com\",\n \"throwawaymail.com\",\n \"sharklasers.com\",\n]);\n\nexport function isFreeEmailProvider(domain: string): boolean {\n return FREE_EMAIL_PROVIDER_DOMAINS.has(domain.trim().toLowerCase());\n}\n"]}
|
package/dist/org/handlers.d.ts
CHANGED
|
@@ -37,13 +37,23 @@ export declare const listMembersHandler: import("h3").EventHandlerWithFetch<impo
|
|
|
37
37
|
joinedAt: number;
|
|
38
38
|
}[];
|
|
39
39
|
}>>;
|
|
40
|
-
|
|
41
|
-
export declare const createInvitationHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
40
|
+
interface SingleInviteResult {
|
|
42
41
|
id: string;
|
|
43
|
-
email:
|
|
44
|
-
|
|
42
|
+
email: string;
|
|
43
|
+
role: "member" | "admin";
|
|
44
|
+
status: "pending";
|
|
45
45
|
emailSent: boolean;
|
|
46
|
-
emailError
|
|
46
|
+
emailError?: string;
|
|
47
|
+
}
|
|
48
|
+
interface SingleInviteFailure {
|
|
49
|
+
email: string;
|
|
50
|
+
error: string;
|
|
51
|
+
}
|
|
52
|
+
/** POST /_agent-native/org/invitations — invite one or many users by email */
|
|
53
|
+
export declare const createInvitationHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<SingleInviteResult | {
|
|
54
|
+
succeeded: SingleInviteResult[];
|
|
55
|
+
failed: SingleInviteFailure[];
|
|
56
|
+
total: number;
|
|
47
57
|
}>>;
|
|
48
58
|
/** GET /_agent-native/org/invitations — list pending invitations for the org */
|
|
49
59
|
export declare const listInvitationsHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
@@ -53,6 +63,7 @@ export declare const listInvitationsHandler: import("h3").EventHandlerWithFetch<
|
|
|
53
63
|
invitedBy: string;
|
|
54
64
|
createdAt: number;
|
|
55
65
|
status: string;
|
|
66
|
+
role: string;
|
|
56
67
|
}[];
|
|
57
68
|
}>>;
|
|
58
69
|
/** POST /_agent-native/org/invitations/:id/accept — accept an invitation */
|
|
@@ -65,6 +76,18 @@ export declare const acceptInvitationHandler: import("h3").EventHandlerWithFetch
|
|
|
65
76
|
export declare const removeMemberHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
66
77
|
success: boolean;
|
|
67
78
|
}>>;
|
|
79
|
+
/**
|
|
80
|
+
* PUT /_agent-native/org/members/:email/role — change a member's role
|
|
81
|
+
* (owner/admin only). Body: { role: "admin" | "member" }.
|
|
82
|
+
*
|
|
83
|
+
* Only owners can promote/demote admins. (Admins can manage members but
|
|
84
|
+
* not other admins — otherwise an admin could escalate themselves to
|
|
85
|
+
* owner-equivalent control by promoting a confederate.)
|
|
86
|
+
*/
|
|
87
|
+
export declare const changeMemberRoleHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
88
|
+
email: string;
|
|
89
|
+
role: string;
|
|
90
|
+
}>>;
|
|
68
91
|
/** PATCH /_agent-native/org — rename the current organization (owner/admin only) */
|
|
69
92
|
export declare const updateOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
70
93
|
orgId: string;
|
|
@@ -141,4 +164,5 @@ export declare const receiveA2ASecretHandler: import("h3").EventHandlerWithFetch
|
|
|
141
164
|
ok: boolean;
|
|
142
165
|
orgId: string;
|
|
143
166
|
}>>;
|
|
167
|
+
export {};
|
|
144
168
|
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/org/handlers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/org/handlers.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AA2B1C,2FAA2F;AAC3F,eAAO,MAAM,eAAe;;;;;;;cAaA,OAAO;;;;;;;;;;eAIC,MAAM;iBAAW,MAAM;;;;GA0EzD,CAAC;AAEH,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB;;;;GAkC3B,CAAC;AAEH,wDAAwD;AACxD,eAAO,MAAM,kBAAkB;;;cAWH,OAAO;;;GAIjC,CAAC;AAMH,UAAU,kBAAkB;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IACzB,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAsED,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB;;;;GAuEnC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,sBAAsB;;;;;;;;;GAyBlC,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB;;;UAoD8B,OAAO;GAkBxE,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,mBAAmB;;GA2D/B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB;;;GAqEnC,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,gBAAgB;;;GA4B3B,CAAC;AAEH,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB;;;UAkCC,OAAO;GAEnC,CAAC;AAEH,mGAAmG;AACnG,eAAO,MAAM,mBAAmB;;;UAqDR,OAAO;GAG9B,CAAC;AAEF,+FAA+F;AAC/F,eAAO,MAAM,gBAAgB;;GAsE3B,CAAC;AAEH,oGAAoG;AACpG,eAAO,MAAM,mBAAmB;;;GA0C/B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,oBAAoB;;;;;YA2DvB,MAAM;cACJ,MAAM;aACP,MAAM;YACP,OAAO;iBACF,MAAM;gBACP,MAAM;;GAyDnB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB;;;GAgGnC,CAAC"}
|
package/dist/org/handlers.js
CHANGED
|
@@ -14,13 +14,15 @@ function extractInvitationId(event) {
|
|
|
14
14
|
path.match(/\/org\/invitations\/([^\/]+)\/accept\/?$/);
|
|
15
15
|
return match?.[1] ? decodeURIComponent(match[1]) : undefined;
|
|
16
16
|
}
|
|
17
|
-
/** Extract the :email from member-delete paths. Same prefix-stripping caveat. */
|
|
17
|
+
/** Extract the :email from member-delete and member-role paths. Same prefix-stripping caveat. */
|
|
18
18
|
function extractMemberEmail(event) {
|
|
19
19
|
const fromRouter = getRouterParam(event, "email");
|
|
20
20
|
if (fromRouter)
|
|
21
21
|
return fromRouter;
|
|
22
22
|
const path = getRequestURL(event).pathname;
|
|
23
|
-
const match = path.match(/^\/([^\/]+)\/?$/) ??
|
|
23
|
+
const match = path.match(/^\/([^\/]+)\/role\/?$/) ??
|
|
24
|
+
path.match(/^\/([^\/]+)\/?$/) ??
|
|
25
|
+
path.match(/\/org\/members\/([^\/]+)(?:\/role)?\/?$/);
|
|
24
26
|
return match?.[1] ? decodeURIComponent(match[1]) : undefined;
|
|
25
27
|
}
|
|
26
28
|
const nanoid = () => globalThis.crypto?.randomUUID?.().replace(/-/g, "") ??
|
|
@@ -33,6 +35,7 @@ import { sendEmail, isEmailConfigured } from "../server/email.js";
|
|
|
33
35
|
import { renderInviteEmail } from "../server/email-templates.js";
|
|
34
36
|
import { getAppProductionUrl } from "../server/app-url.js";
|
|
35
37
|
import { getOrgContext } from "./context.js";
|
|
38
|
+
import { isFreeEmailProvider } from "./free-email-providers.js";
|
|
36
39
|
function getInviteAppUrl(event) {
|
|
37
40
|
return getAppProductionUrl(event);
|
|
38
41
|
}
|
|
@@ -189,58 +192,45 @@ export const listMembersHandler = defineEventHandler(async (event) => {
|
|
|
189
192
|
}));
|
|
190
193
|
return { members };
|
|
191
194
|
});
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
195
|
+
function normalizeInviteRole(input) {
|
|
196
|
+
return input === "admin" ? "admin" : "member";
|
|
197
|
+
}
|
|
198
|
+
async function inviteOne(ctx, rawEmail, role, event) {
|
|
199
|
+
const email = rawEmail.trim().toLowerCase();
|
|
200
|
+
if (!email) {
|
|
201
|
+
throw createError({ statusCode: 400, message: "Email is required" });
|
|
200
202
|
}
|
|
201
|
-
if (
|
|
203
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
202
204
|
throw createError({
|
|
203
|
-
statusCode:
|
|
204
|
-
message:
|
|
205
|
+
statusCode: 400,
|
|
206
|
+
message: `Invalid email: ${rawEmail}`,
|
|
205
207
|
});
|
|
206
208
|
}
|
|
207
|
-
const body = await readBody(event);
|
|
208
|
-
const email = body?.email?.trim()?.toLowerCase();
|
|
209
|
-
if (!email) {
|
|
210
|
-
throw createError({ statusCode: 400, message: "Email is required" });
|
|
211
|
-
}
|
|
212
209
|
const e = await exec();
|
|
213
|
-
// Existing rows in org_members / org_invitations may have any case
|
|
214
|
-
// (writes haven't been normalized historically). Compare with LOWER
|
|
215
|
-
// on both sides so an "alice@..." invite check correctly recognizes
|
|
216
|
-
// an existing "Alice@..." membership. `email` is already lowercased
|
|
217
|
-
// at parse time above, but call .toLowerCase() explicitly here so
|
|
218
|
-
// the contract at the SQL boundary matches every other handler in
|
|
219
|
-
// this file.
|
|
220
210
|
const existingMember = await e.execute({
|
|
221
211
|
sql: `SELECT 1 FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,
|
|
222
|
-
args: [ctx.orgId, email
|
|
212
|
+
args: [ctx.orgId, email],
|
|
223
213
|
});
|
|
224
214
|
if (existingMember.rows.length > 0) {
|
|
225
215
|
throw createError({
|
|
226
216
|
statusCode: 409,
|
|
227
|
-
message:
|
|
217
|
+
message: `${email} is already a member`,
|
|
228
218
|
});
|
|
229
219
|
}
|
|
230
220
|
const existingInvite = await e.execute({
|
|
231
221
|
sql: `SELECT 1 FROM org_invitations WHERE org_id = ? AND LOWER(email) = ? AND status = 'pending' LIMIT 1`,
|
|
232
|
-
args: [ctx.orgId, email
|
|
222
|
+
args: [ctx.orgId, email],
|
|
233
223
|
});
|
|
234
224
|
if (existingInvite.rows.length > 0) {
|
|
235
225
|
throw createError({
|
|
236
226
|
statusCode: 409,
|
|
237
|
-
message:
|
|
227
|
+
message: `An invitation is already pending for ${email}`,
|
|
238
228
|
});
|
|
239
229
|
}
|
|
240
230
|
const id = nanoid();
|
|
241
231
|
await e.execute({
|
|
242
|
-
sql: `INSERT INTO org_invitations (id, org_id, email, invited_by, created_at, status) VALUES (?, ?, ?, ?, ?, 'pending')`,
|
|
243
|
-
args: [id, ctx.orgId, email, ctx.email, Date.now()],
|
|
232
|
+
sql: `INSERT INTO org_invitations (id, org_id, email, invited_by, created_at, status, role) VALUES (?, ?, ?, ?, ?, 'pending', ?)`,
|
|
233
|
+
args: [id, ctx.orgId, email, ctx.email, Date.now(), role],
|
|
244
234
|
});
|
|
245
235
|
let emailSent = false;
|
|
246
236
|
let emailError;
|
|
@@ -260,7 +250,63 @@ export const createInvitationHandler = defineEventHandler(async (event) => {
|
|
|
260
250
|
console.error("[org/invitations] failed to send invite email", err);
|
|
261
251
|
}
|
|
262
252
|
}
|
|
263
|
-
return { id, email, status: "pending", emailSent, emailError };
|
|
253
|
+
return { id, email, role, status: "pending", emailSent, emailError };
|
|
254
|
+
}
|
|
255
|
+
/** POST /_agent-native/org/invitations — invite one or many users by email */
|
|
256
|
+
export const createInvitationHandler = defineEventHandler(async (event) => {
|
|
257
|
+
const ctx = await getOrgContext(event);
|
|
258
|
+
if (!ctx.orgId) {
|
|
259
|
+
throw createError({
|
|
260
|
+
statusCode: 400,
|
|
261
|
+
message: "You must belong to an organization to invite members",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (ctx.role !== "owner" && ctx.role !== "admin") {
|
|
265
|
+
throw createError({
|
|
266
|
+
statusCode: 403,
|
|
267
|
+
message: "Only owners and admins can invite members",
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const body = await readBody(event);
|
|
271
|
+
// Bulk shape: { invites: [{ email, role }, ...] } — preferred for any
|
|
272
|
+
// multi-recipient flow (paste-many, CSV upload). Single shape:
|
|
273
|
+
// { email, role } — kept for backwards compatibility.
|
|
274
|
+
const invitesInput = Array.isArray(body?.invites)
|
|
275
|
+
? body.invites.map((inv) => ({
|
|
276
|
+
email: String(inv?.email ?? ""),
|
|
277
|
+
role: inv?.role,
|
|
278
|
+
}))
|
|
279
|
+
: null;
|
|
280
|
+
if (invitesInput) {
|
|
281
|
+
const succeeded = [];
|
|
282
|
+
const failed = [];
|
|
283
|
+
const seen = new Set();
|
|
284
|
+
for (const inv of invitesInput) {
|
|
285
|
+
const lower = inv.email.trim().toLowerCase();
|
|
286
|
+
if (!lower)
|
|
287
|
+
continue;
|
|
288
|
+
if (seen.has(lower))
|
|
289
|
+
continue;
|
|
290
|
+
seen.add(lower);
|
|
291
|
+
try {
|
|
292
|
+
const result = await inviteOne({ orgId: ctx.orgId, orgName: ctx.orgName, email: ctx.email }, inv.email, normalizeInviteRole(inv.role), event);
|
|
293
|
+
succeeded.push(result);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
297
|
+
failed.push({ email: lower, error: message });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
succeeded,
|
|
302
|
+
failed,
|
|
303
|
+
total: succeeded.length + failed.length,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
// Single-invite shape.
|
|
307
|
+
const role = normalizeInviteRole(body?.role);
|
|
308
|
+
const result = await inviteOne({ orgId: ctx.orgId, orgName: ctx.orgName, email: ctx.email }, body?.email ?? "", role, event);
|
|
309
|
+
return result;
|
|
264
310
|
});
|
|
265
311
|
/** GET /_agent-native/org/invitations — list pending invitations for the org */
|
|
266
312
|
export const listInvitationsHandler = defineEventHandler(async (event) => {
|
|
@@ -269,7 +315,7 @@ export const listInvitationsHandler = defineEventHandler(async (event) => {
|
|
|
269
315
|
return { invitations: [] };
|
|
270
316
|
const e = await exec();
|
|
271
317
|
const { rows } = await e.execute({
|
|
272
|
-
sql: `SELECT id, email, invited_by AS "invitedBy", created_at AS "createdAt", status
|
|
318
|
+
sql: `SELECT id, email, invited_by AS "invitedBy", created_at AS "createdAt", status, role
|
|
273
319
|
FROM org_invitations
|
|
274
320
|
WHERE org_id = ? AND status = 'pending'`,
|
|
275
321
|
args: [ctx.orgId],
|
|
@@ -280,6 +326,9 @@ export const listInvitationsHandler = defineEventHandler(async (event) => {
|
|
|
280
326
|
invitedBy: String(r.invitedBy ?? r.invited_by),
|
|
281
327
|
createdAt: Number(r.createdAt ?? r.created_at),
|
|
282
328
|
status: String(r.status),
|
|
329
|
+
role: String(r.role ?? "member") === "admin"
|
|
330
|
+
? "admin"
|
|
331
|
+
: "member",
|
|
283
332
|
}));
|
|
284
333
|
return { invitations };
|
|
285
334
|
});
|
|
@@ -298,7 +347,7 @@ export const acceptInvitationHandler = defineEventHandler(async (event) => {
|
|
|
298
347
|
const invRes = await e.execute({
|
|
299
348
|
// Case-insensitive on email — see comment on the analogous
|
|
300
349
|
// pending-invitations query in getMyOrgHandler.
|
|
301
|
-
sql: `SELECT id, org_id AS "orgId" FROM org_invitations
|
|
350
|
+
sql: `SELECT id, org_id AS "orgId", role FROM org_invitations
|
|
302
351
|
WHERE id = ? AND LOWER(email) = ? AND status = 'pending' LIMIT 1`,
|
|
303
352
|
args: [invitationId, email.toLowerCase()],
|
|
304
353
|
});
|
|
@@ -310,6 +359,7 @@ export const acceptInvitationHandler = defineEventHandler(async (event) => {
|
|
|
310
359
|
}
|
|
311
360
|
const inv = invRes.rows[0];
|
|
312
361
|
const invOrgId = String(inv.orgId ?? inv.org_id);
|
|
362
|
+
const inviteRole = inv.role === "admin" ? "admin" : "member";
|
|
313
363
|
const existingMembership = await e.execute({
|
|
314
364
|
sql: `SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,
|
|
315
365
|
args: [invOrgId, email.toLowerCase()],
|
|
@@ -332,15 +382,15 @@ export const acceptInvitationHandler = defineEventHandler(async (event) => {
|
|
|
332
382
|
};
|
|
333
383
|
}
|
|
334
384
|
await e.execute({
|
|
335
|
-
sql: `INSERT INTO org_members (id, org_id, email, role, joined_at) VALUES (?, ?, ?,
|
|
336
|
-
args: [nanoid(), invOrgId, email, Date.now()],
|
|
385
|
+
sql: `INSERT INTO org_members (id, org_id, email, role, joined_at) VALUES (?, ?, ?, ?, ?)`,
|
|
386
|
+
args: [nanoid(), invOrgId, email, inviteRole, Date.now()],
|
|
337
387
|
});
|
|
338
388
|
await e.execute({
|
|
339
389
|
sql: `UPDATE org_invitations SET status = 'accepted' WHERE id = ?`,
|
|
340
390
|
args: [invitationId],
|
|
341
391
|
});
|
|
342
392
|
await putUserSetting(email, "active-org-id", { orgId: invOrgId });
|
|
343
|
-
return { orgId: invOrgId, orgName, role:
|
|
393
|
+
return { orgId: invOrgId, orgName, role: inviteRole };
|
|
344
394
|
});
|
|
345
395
|
/** DELETE /_agent-native/org/members/:email — remove a member (owner/admin only) */
|
|
346
396
|
export const removeMemberHandler = defineEventHandler(async (event) => {
|
|
@@ -397,6 +447,73 @@ export const removeMemberHandler = defineEventHandler(async (event) => {
|
|
|
397
447
|
});
|
|
398
448
|
return { success: true };
|
|
399
449
|
});
|
|
450
|
+
/**
|
|
451
|
+
* PUT /_agent-native/org/members/:email/role — change a member's role
|
|
452
|
+
* (owner/admin only). Body: { role: "admin" | "member" }.
|
|
453
|
+
*
|
|
454
|
+
* Only owners can promote/demote admins. (Admins can manage members but
|
|
455
|
+
* not other admins — otherwise an admin could escalate themselves to
|
|
456
|
+
* owner-equivalent control by promoting a confederate.)
|
|
457
|
+
*/
|
|
458
|
+
export const changeMemberRoleHandler = defineEventHandler(async (event) => {
|
|
459
|
+
const ctx = await getOrgContext(event);
|
|
460
|
+
if (!ctx.orgId) {
|
|
461
|
+
throw createError({ statusCode: 400, message: "No organization found" });
|
|
462
|
+
}
|
|
463
|
+
if (ctx.role !== "owner" && ctx.role !== "admin") {
|
|
464
|
+
throw createError({
|
|
465
|
+
statusCode: 403,
|
|
466
|
+
message: "Only owners and admins can change member roles",
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
const memberEmail = extractMemberEmail(event);
|
|
470
|
+
if (!memberEmail) {
|
|
471
|
+
throw createError({ statusCode: 400, message: "Email is required" });
|
|
472
|
+
}
|
|
473
|
+
const memberEmailLower = memberEmail.toLowerCase();
|
|
474
|
+
const body = await readBody(event);
|
|
475
|
+
const role = body?.role === "admin" ? "admin" : "member";
|
|
476
|
+
const e = await exec();
|
|
477
|
+
// Look up the target member's current role to enforce sensible rules
|
|
478
|
+
// about what changes are allowed.
|
|
479
|
+
const current = await e.execute({
|
|
480
|
+
sql: `SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,
|
|
481
|
+
args: [ctx.orgId, memberEmailLower],
|
|
482
|
+
});
|
|
483
|
+
if (current.rows.length === 0) {
|
|
484
|
+
throw createError({ statusCode: 404, message: "Member not found" });
|
|
485
|
+
}
|
|
486
|
+
const currentRole = String(current.rows[0].role);
|
|
487
|
+
if (currentRole === "owner") {
|
|
488
|
+
throw createError({
|
|
489
|
+
statusCode: 400,
|
|
490
|
+
message: "Cannot change the organization owner's role",
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
// Admins are scoped to managing members. If they could promote
|
|
494
|
+
// members to admin, they could grant near-owner powers without owner
|
|
495
|
+
// approval. Restrict admin/admin role transitions to the owner.
|
|
496
|
+
if (ctx.role === "admin" && (currentRole === "admin" || role === "admin")) {
|
|
497
|
+
throw createError({
|
|
498
|
+
statusCode: 403,
|
|
499
|
+
message: "Only the organization owner can manage admins",
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
// Self-demotion guard: prevent the only admin from removing their own
|
|
503
|
+
// ability to manage things, and prevent the owner-self edge case
|
|
504
|
+
// (already filtered above by the currentRole check).
|
|
505
|
+
if (memberEmailLower === ctx.email.toLowerCase() && ctx.role === "admin") {
|
|
506
|
+
throw createError({
|
|
507
|
+
statusCode: 400,
|
|
508
|
+
message: "Use the owner account to change your own admin role",
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
await e.execute({
|
|
512
|
+
sql: `UPDATE org_members SET role = ? WHERE org_id = ? AND LOWER(email) = ?`,
|
|
513
|
+
args: [role, ctx.orgId, memberEmailLower],
|
|
514
|
+
});
|
|
515
|
+
return { email: memberEmailLower, role };
|
|
516
|
+
});
|
|
400
517
|
/** PATCH /_agent-native/org — rename the current organization (owner/admin only) */
|
|
401
518
|
export const updateOrgHandler = defineEventHandler(async (event) => {
|
|
402
519
|
const ctx = await getOrgContext(event);
|
|
@@ -523,6 +640,30 @@ export const setDomainHandler = defineEventHandler(async (event) => {
|
|
|
523
640
|
message: "Invalid domain format",
|
|
524
641
|
});
|
|
525
642
|
}
|
|
643
|
+
if (raw) {
|
|
644
|
+
// Auto-join is "anyone with this domain joins automatically". That is
|
|
645
|
+
// safe for company domains (the company controls who gets an address)
|
|
646
|
+
// and catastrophic for shared mailbox providers — anyone in the world
|
|
647
|
+
// could create a matching mailbox and silently join the org.
|
|
648
|
+
if (isFreeEmailProvider(raw)) {
|
|
649
|
+
throw createError({
|
|
650
|
+
statusCode: 400,
|
|
651
|
+
message: "Free email providers (gmail.com, outlook.com, etc.) cannot be used as an auto-join domain. Use your company's own domain.",
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
// Restrict to the admin's own email domain. Without this, an admin
|
|
655
|
+
// could set `allowed_domain` to a domain they don't control, and
|
|
656
|
+
// anyone signing up under that domain would join the org. Even with
|
|
657
|
+
// the free-provider blocklist above, that would still let an admin
|
|
658
|
+
// hijack a competitor's domain.
|
|
659
|
+
const ownDomain = ctx.email.split("@")[1]?.toLowerCase() ?? "";
|
|
660
|
+
if (raw !== ownDomain) {
|
|
661
|
+
throw createError({
|
|
662
|
+
statusCode: 400,
|
|
663
|
+
message: `You can only auto-join your own email domain (${ownDomain}).`,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
526
667
|
const e = await exec();
|
|
527
668
|
if (raw) {
|
|
528
669
|
const existing = await e.execute({
|