@selvajs/server 1.2.0 → 1.3.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.
Files changed (221) hide show
  1. package/dist/access/guards.d.ts +213 -0
  2. package/dist/access/guards.d.ts.map +1 -0
  3. package/dist/access/guards.js +417 -0
  4. package/dist/access/guards.js.map +1 -0
  5. package/dist/access/index.d.ts +2 -0
  6. package/dist/access/index.d.ts.map +1 -1
  7. package/dist/access/index.js +6 -0
  8. package/dist/access/index.js.map +1 -1
  9. package/dist/access/permissions-scope.d.ts +15 -0
  10. package/dist/access/permissions-scope.d.ts.map +1 -0
  11. package/dist/access/permissions-scope.js +23 -0
  12. package/dist/access/permissions-scope.js.map +1 -0
  13. package/dist/api/bodies.d.ts +104 -0
  14. package/dist/api/bodies.d.ts.map +1 -0
  15. package/dist/api/bodies.js +87 -0
  16. package/dist/api/bodies.js.map +1 -0
  17. package/dist/api/deps.d.ts +135 -0
  18. package/dist/api/deps.d.ts.map +1 -0
  19. package/dist/api/deps.js +55 -0
  20. package/dist/api/deps.js.map +1 -0
  21. package/dist/api/errors.d.ts +35 -0
  22. package/dist/api/errors.d.ts.map +1 -0
  23. package/dist/api/errors.js +63 -0
  24. package/dist/api/errors.js.map +1 -0
  25. package/dist/api/index.d.ts +12 -0
  26. package/dist/api/index.d.ts.map +1 -0
  27. package/dist/api/index.js +15 -0
  28. package/dist/api/index.js.map +1 -0
  29. package/dist/api/map-core-error.d.ts +19 -0
  30. package/dist/api/map-core-error.d.ts.map +1 -0
  31. package/dist/api/map-core-error.js +49 -0
  32. package/dist/api/map-core-error.js.map +1 -0
  33. package/dist/api/pagination.d.ts +19 -0
  34. package/dist/api/pagination.d.ts.map +1 -0
  35. package/dist/api/pagination.js +40 -0
  36. package/dist/api/pagination.js.map +1 -0
  37. package/dist/api/request.d.ts +93 -0
  38. package/dist/api/request.d.ts.map +1 -0
  39. package/dist/api/request.js +150 -0
  40. package/dist/api/request.js.map +1 -0
  41. package/dist/api/respond.d.ts +29 -0
  42. package/dist/api/respond.d.ts.map +1 -0
  43. package/dist/api/respond.js +59 -0
  44. package/dist/api/respond.js.map +1 -0
  45. package/dist/api/responses-schema.d.ts +158 -0
  46. package/dist/api/responses-schema.d.ts.map +1 -0
  47. package/dist/api/responses-schema.js +104 -0
  48. package/dist/api/responses-schema.js.map +1 -0
  49. package/dist/api/responses.d.ts +19 -0
  50. package/dist/api/responses.d.ts.map +1 -0
  51. package/dist/api/responses.js +21 -0
  52. package/dist/api/responses.js.map +1 -0
  53. package/dist/api/shaped.d.ts +23 -0
  54. package/dist/api/shaped.d.ts.map +1 -0
  55. package/dist/api/shaped.js +29 -0
  56. package/dist/api/shaped.js.map +1 -0
  57. package/dist/api/types.d.ts +41 -0
  58. package/dist/api/types.d.ts.map +1 -0
  59. package/dist/api/types.js +12 -0
  60. package/dist/api/types.js.map +1 -0
  61. package/dist/compute/errors.d.ts +18 -0
  62. package/dist/compute/errors.d.ts.map +1 -0
  63. package/dist/compute/errors.js +21 -0
  64. package/dist/compute/errors.js.map +1 -0
  65. package/dist/compute/evict-changed-servers.d.ts +22 -0
  66. package/dist/compute/evict-changed-servers.d.ts.map +1 -0
  67. package/dist/compute/evict-changed-servers.js +31 -0
  68. package/dist/compute/evict-changed-servers.js.map +1 -0
  69. package/dist/compute/idempotency-http.d.ts +47 -0
  70. package/dist/compute/idempotency-http.d.ts.map +1 -0
  71. package/dist/compute/idempotency-http.js +51 -0
  72. package/dist/compute/idempotency-http.js.map +1 -0
  73. package/dist/compute/index.d.ts +5 -0
  74. package/dist/compute/index.d.ts.map +1 -1
  75. package/dist/compute/index.js +5 -0
  76. package/dist/compute/index.js.map +1 -1
  77. package/dist/compute/resolve-server.d.ts +25 -0
  78. package/dist/compute/resolve-server.d.ts.map +1 -0
  79. package/dist/compute/resolve-server.js +35 -0
  80. package/dist/compute/resolve-server.js.map +1 -0
  81. package/dist/compute/server-config-write.d.ts +52 -0
  82. package/dist/compute/server-config-write.d.ts.map +1 -0
  83. package/dist/compute/server-config-write.js +100 -0
  84. package/dist/compute/server-config-write.js.map +1 -0
  85. package/dist/definitions/index.d.ts +1 -0
  86. package/dist/definitions/index.d.ts.map +1 -1
  87. package/dist/definitions/index.js +1 -0
  88. package/dist/definitions/index.js.map +1 -1
  89. package/dist/definitions/visibility.d.ts +74 -0
  90. package/dist/definitions/visibility.d.ts.map +1 -0
  91. package/dist/definitions/visibility.js +117 -0
  92. package/dist/definitions/visibility.js.map +1 -0
  93. package/dist/handlers/definitionVersions.d.ts +30 -0
  94. package/dist/handlers/definitionVersions.d.ts.map +1 -0
  95. package/dist/handlers/definitionVersions.js +93 -0
  96. package/dist/handlers/definitionVersions.js.map +1 -0
  97. package/dist/handlers/definitions.d.ts +35 -0
  98. package/dist/handlers/definitions.d.ts.map +1 -0
  99. package/dist/handlers/definitions.js +207 -0
  100. package/dist/handlers/definitions.js.map +1 -0
  101. package/dist/handlers/index.d.ts +27 -0
  102. package/dist/handlers/index.d.ts.map +1 -0
  103. package/dist/handlers/index.js +29 -0
  104. package/dist/handlers/index.js.map +1 -0
  105. package/dist/handlers/invite-lookup.d.ts +11 -0
  106. package/dist/handlers/invite-lookup.d.ts.map +1 -0
  107. package/dist/handlers/invite-lookup.js +20 -0
  108. package/dist/handlers/invite-lookup.js.map +1 -0
  109. package/dist/handlers/invites.d.ts +31 -0
  110. package/dist/handlers/invites.d.ts.map +1 -0
  111. package/dist/handlers/invites.js +148 -0
  112. package/dist/handlers/invites.js.map +1 -0
  113. package/dist/handlers/me.d.ts +8 -0
  114. package/dist/handlers/me.d.ts.map +1 -0
  115. package/dist/handlers/me.js +21 -0
  116. package/dist/handlers/me.js.map +1 -0
  117. package/dist/handlers/me.starred.d.ts +11 -0
  118. package/dist/handlers/me.starred.d.ts.map +1 -0
  119. package/dist/handlers/me.starred.js +21 -0
  120. package/dist/handlers/me.starred.js.map +1 -0
  121. package/dist/handlers/orgAssets.d.ts +13 -0
  122. package/dist/handlers/orgAssets.d.ts.map +1 -0
  123. package/dist/handlers/orgAssets.js +54 -0
  124. package/dist/handlers/orgAssets.js.map +1 -0
  125. package/dist/handlers/orgCompute.d.ts +16 -0
  126. package/dist/handlers/orgCompute.d.ts.map +1 -0
  127. package/dist/handlers/orgCompute.js +96 -0
  128. package/dist/handlers/orgCompute.js.map +1 -0
  129. package/dist/handlers/orgMembers.d.ts +29 -0
  130. package/dist/handlers/orgMembers.d.ts.map +1 -0
  131. package/dist/handlers/orgMembers.js +200 -0
  132. package/dist/handlers/orgMembers.js.map +1 -0
  133. package/dist/handlers/orgs.d.ts +16 -0
  134. package/dist/handlers/orgs.d.ts.map +1 -0
  135. package/dist/handlers/orgs.js +26 -0
  136. package/dist/handlers/orgs.js.map +1 -0
  137. package/dist/handlers/projectMembers.d.ts +15 -0
  138. package/dist/handlers/projectMembers.d.ts.map +1 -0
  139. package/dist/handlers/projectMembers.js +95 -0
  140. package/dist/handlers/projectMembers.js.map +1 -0
  141. package/dist/handlers/projects.d.ts +21 -0
  142. package/dist/handlers/projects.d.ts.map +1 -0
  143. package/dist/handlers/projects.js +164 -0
  144. package/dist/handlers/projects.js.map +1 -0
  145. package/dist/handlers/reclaim.d.ts +10 -0
  146. package/dist/handlers/reclaim.d.ts.map +1 -0
  147. package/dist/handlers/reclaim.js +38 -0
  148. package/dist/handlers/reclaim.js.map +1 -0
  149. package/dist/handlers/services.d.ts +22 -0
  150. package/dist/handlers/services.d.ts.map +1 -0
  151. package/dist/handlers/services.js +37 -0
  152. package/dist/handlers/services.js.map +1 -0
  153. package/dist/handlers/shareLinks.d.ts +23 -0
  154. package/dist/handlers/shareLinks.d.ts.map +1 -0
  155. package/dist/handlers/shareLinks.js +85 -0
  156. package/dist/handlers/shareLinks.js.map +1 -0
  157. package/dist/http/index.d.ts +1 -0
  158. package/dist/http/index.d.ts.map +1 -1
  159. package/dist/http/index.js +1 -0
  160. package/dist/http/index.js.map +1 -1
  161. package/dist/http/login-rate-limit.d.ts +72 -0
  162. package/dist/http/login-rate-limit.d.ts.map +1 -0
  163. package/dist/http/login-rate-limit.js +84 -0
  164. package/dist/http/login-rate-limit.js.map +1 -0
  165. package/dist/logging/requestId.d.ts +4 -0
  166. package/dist/logging/requestId.d.ts.map +1 -1
  167. package/dist/logging/requestId.js +15 -2
  168. package/dist/logging/requestId.js.map +1 -1
  169. package/dist/notifications/deliver-invite.d.ts +27 -0
  170. package/dist/notifications/deliver-invite.d.ts.map +1 -0
  171. package/dist/notifications/deliver-invite.js +32 -0
  172. package/dist/notifications/deliver-invite.js.map +1 -0
  173. package/dist/notifications/index.d.ts +1 -0
  174. package/dist/notifications/index.d.ts.map +1 -1
  175. package/dist/notifications/index.js +1 -0
  176. package/dist/notifications/index.js.map +1 -1
  177. package/dist/organizations/index.d.ts +3 -0
  178. package/dist/organizations/index.d.ts.map +1 -0
  179. package/dist/organizations/index.js +5 -0
  180. package/dist/organizations/index.js.map +1 -0
  181. package/dist/organizations/list-all-members.d.ts +10 -0
  182. package/dist/organizations/list-all-members.d.ts.map +1 -0
  183. package/dist/organizations/list-all-members.js +36 -0
  184. package/dist/organizations/list-all-members.js.map +1 -0
  185. package/dist/organizations/org-asset-service.d.ts +39 -0
  186. package/dist/organizations/org-asset-service.d.ts.map +1 -0
  187. package/dist/organizations/org-asset-service.js +71 -0
  188. package/dist/organizations/org-asset-service.js.map +1 -0
  189. package/dist/projects/create-project.d.ts +30 -0
  190. package/dist/projects/create-project.d.ts.map +1 -0
  191. package/dist/projects/create-project.js +56 -0
  192. package/dist/projects/create-project.js.map +1 -0
  193. package/dist/projects/index.d.ts +2 -0
  194. package/dist/projects/index.d.ts.map +1 -0
  195. package/dist/projects/index.js +3 -0
  196. package/dist/projects/index.js.map +1 -0
  197. package/dist/providers/auth-lookup.d.ts +14 -0
  198. package/dist/providers/auth-lookup.d.ts.map +1 -0
  199. package/dist/providers/auth-lookup.js +34 -0
  200. package/dist/providers/auth-lookup.js.map +1 -0
  201. package/dist/providers/index.d.ts +1 -0
  202. package/dist/providers/index.d.ts.map +1 -1
  203. package/dist/providers/index.js +1 -0
  204. package/dist/providers/index.js.map +1 -1
  205. package/dist/testing/harness.d.ts +168 -0
  206. package/dist/testing/harness.d.ts.map +1 -0
  207. package/dist/testing/harness.js +314 -0
  208. package/dist/testing/harness.js.map +1 -0
  209. package/dist/testing/index.d.ts +18 -0
  210. package/dist/testing/index.d.ts.map +1 -0
  211. package/dist/testing/index.js +15 -0
  212. package/dist/testing/index.js.map +1 -0
  213. package/dist/testing/scenarios.d.ts +67 -0
  214. package/dist/testing/scenarios.d.ts.map +1 -0
  215. package/dist/testing/scenarios.js +104 -0
  216. package/dist/testing/scenarios.js.map +1 -0
  217. package/dist/testing/seed-adapter.d.ts +31 -0
  218. package/dist/testing/seed-adapter.d.ts.map +1 -0
  219. package/dist/testing/seed-adapter.js +19 -0
  220. package/dist/testing/seed-adapter.js.map +1 -0
  221. package/package.json +32 -4
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Org invites: list, mint, revoke, resend.
3
+ *
4
+ * **The raw token exists only in the response.** The store sees an HMAC digest,
5
+ * so a leak of the `invites` table cannot be replayed without the instance
6
+ * secret — and `tokenHash` is dropped by the response schema, which is why
7
+ * every response here goes through `shaped`/`shapedCollection` rather than
8
+ * `{ body }`.
9
+ */
10
+ import { randomUUID } from 'node:crypto';
11
+ import { apiError, ApiErrorCode, CreatedInviteResponseSchema, CreateInviteBodySchema, InviteResponseSchema, noContent, parseBody, parseListOptions, requireCaller, requireParams, shaped, shapedCollection } from '../api/index.js';
12
+ import { DEFAULT_ORG_PERMISSIONS, MEMBER_ASSIGNABLE_PERMISSIONS } from '@selvajs/platform';
13
+ import { assertCanGrantPlatformPermissions, canActorChangeOrgRole, requireManageOrgMembers, requireActingOrg, splitFlatPermissions } from '../access/index.js';
14
+ import { deliverInvite } from '../notifications/index.js';
15
+ import { tokenCodec } from './services.js';
16
+ import { findPendingInviteInOrg } from './invite-lookup.js';
17
+ const INVITE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
18
+ /**
19
+ * Mail the accept link, or report why not.
20
+ *
21
+ * Mint and resend must send the identical message, so the deps threading lives
22
+ * here rather than at each call site. `not-configured` when the host wired no
23
+ * notification provider: the invite row is already committed and the caller
24
+ * still holds `acceptUrl`, so an instance without mail is a working instance,
25
+ * not a failed write.
26
+ */
27
+ async function mailInvite(req, invite, acceptUrl) {
28
+ const notifications = req.deps.notifications;
29
+ if (!notifications)
30
+ return 'not-configured';
31
+ return deliverInvite({
32
+ ctx: req.ctx,
33
+ log: req.log,
34
+ invite,
35
+ acceptUrl,
36
+ actor: { profile: req.profile, user: req.user },
37
+ orgs: req.deps.orgs,
38
+ notifications,
39
+ fallbackOrgName: req.deps.instanceName
40
+ });
41
+ }
42
+ /** Pending and recently accepted invites for the acting org. */
43
+ export const listInvites = async (req) => {
44
+ requireManageOrgMembers(req);
45
+ const { ctx, orgId } = requireActingOrg(req, req.params.orgId);
46
+ // `tokenHash` is the server-side lookup key; the response schema drops it.
47
+ return shapedCollection(InviteResponseSchema, await req.deps.invites.listByOrg(ctx, orgId, parseListOptions(req.url)));
48
+ };
49
+ /** Create an invite and return the accept URL. */
50
+ export const createInvite = async (req) => {
51
+ requireManageOrgMembers(req);
52
+ const { ctx, orgId } = requireActingOrg(req, req.params.orgId);
53
+ const { user } = requireCaller(req);
54
+ const input = await parseBody(req.request, CreateInviteBodySchema);
55
+ const { platform: submittedPlatformPerms, org: submittedOrgPerms } = splitFlatPermissions(input.permissions);
56
+ // An invite is a third write path into platform scope, so it shares the
57
+ // guard with both /api/admin/users handlers rather than restating it.
58
+ assertCanGrantPlatformPermissions(ctx, submittedPlatformPerms);
59
+ // An invite is a second door into `org_members`, so it carries the same
60
+ // owner-only role gate as PATCH /orgs/{orgId}/members/{userId}. Without
61
+ // it an admin mints themselves an `owner` invite, accepts it, and then
62
+ // passes the sole-owner check when removing the founder — `accept-invite`
63
+ // writes `invite.orgRole` verbatim and cannot re-verify the minter.
64
+ if (!(await canActorChangeOrgRole(ctx, orgId, input.orgRole, req))) {
65
+ apiError(403, ApiErrorCode.FORBIDDEN, 'Only the org owner can invite someone as owner or admin.');
66
+ }
67
+ // owner/admin always carry the full set — the checkbox array from the UI is
68
+ // ignored for those roles. `member` takes the caller's selection intersected
69
+ // with MEMBER_ASSIGNABLE_PERMISSIONS, so governance permissions
70
+ // (manage_org_members, manage_org_compute) cannot be granted to a member.
71
+ const orgPermissions = input.orgRole === 'member'
72
+ ? submittedOrgPerms.filter((p) => MEMBER_ASSIGNABLE_PERMISSIONS.includes(p))
73
+ : [...DEFAULT_ORG_PERMISSIONS[input.orgRole]];
74
+ const codec = tokenCodec(req.deps, 'invites');
75
+ const rawToken = codec.mintRawToken();
76
+ const now = new Date();
77
+ const invite = {
78
+ id: randomUUID(),
79
+ tokenHash: codec.hashToken(rawToken),
80
+ email: input.email,
81
+ orgId,
82
+ orgRole: input.orgRole,
83
+ orgPermissions,
84
+ platformPermissions: submittedPlatformPerms,
85
+ invitedBy: user.id,
86
+ createdAt: now.toISOString(),
87
+ expiresAt: new Date(now.getTime() + INVITE_TTL_MS).toISOString()
88
+ };
89
+ await req.deps.invites.create(ctx, invite);
90
+ // `acceptUrl` carries the raw token and exists only in this response — it
91
+ // is not a field of the stored invite.
92
+ const acceptUrl = `${req.url.origin}/accept-invite?token=${rawToken}`;
93
+ const delivery = await mailInvite(req, invite, acceptUrl);
94
+ return shaped(CreatedInviteResponseSchema, { ...invite, acceptUrl, delivery }, 201);
95
+ };
96
+ /** Revoke a pending invite. Consumed invites are preserved for audit. */
97
+ export const revokeInvite = async (req) => {
98
+ requireManageOrgMembers(req);
99
+ const { ctx, orgId } = requireActingOrg(req, req.params.orgId);
100
+ const { id } = requireParams(req.params, 'id');
101
+ if (!(await findPendingInviteInOrg(ctx, orgId, id, req.deps.invites))) {
102
+ apiError(404, ApiErrorCode.NOT_FOUND, 'Invite not found');
103
+ }
104
+ await req.deps.invites.revoke(ctx, id);
105
+ return noContent();
106
+ };
107
+ /**
108
+ * Re-send an invite: mint a replacement and revoke the original.
109
+ *
110
+ * A resend cannot reuse the original link. The raw token is never stored — only
111
+ * its HMAC digest is — so there is nothing to re-read, and rather than weaken
112
+ * that, this issues a fresh token and revokes the row it replaces. The old link
113
+ * stops working the moment this returns, which is also what an admin wants
114
+ * after mailing an invite to the wrong address.
115
+ *
116
+ * Grants are copied verbatim from the original. The role and permission gates
117
+ * ran when it was minted; re-deriving them here would let a since-demoted admin
118
+ * silently change what the invite carries.
119
+ */
120
+ export const resendInvite = async (req) => {
121
+ requireManageOrgMembers(req);
122
+ const { ctx, orgId } = requireActingOrg(req, req.params.orgId);
123
+ const { id } = requireParams(req.params, 'id');
124
+ const existing = await findPendingInviteInOrg(ctx, orgId, id, req.deps.invites);
125
+ if (!existing)
126
+ apiError(404, ApiErrorCode.NOT_FOUND, 'Invite not found');
127
+ if (existing.acceptedAt) {
128
+ apiError(409, ApiErrorCode.CONFLICT, 'This invite has already been accepted.');
129
+ }
130
+ const codec = tokenCodec(req.deps, 'invites');
131
+ const rawToken = codec.mintRawToken();
132
+ const now = new Date();
133
+ const replacement = {
134
+ ...existing,
135
+ id: randomUUID(),
136
+ tokenHash: codec.hashToken(rawToken),
137
+ createdAt: now.toISOString(),
138
+ expiresAt: new Date(now.getTime() + INVITE_TTL_MS).toISOString()
139
+ };
140
+ // Create before revoking: if the revoke fails the invitee holds a working
141
+ // link, whereas the reverse order can leave them with none at all.
142
+ await req.deps.invites.create(ctx, replacement);
143
+ await req.deps.invites.revoke(ctx, existing.id);
144
+ const acceptUrl = `${req.url.origin}/accept-invite?token=${rawToken}`;
145
+ const delivery = await mailInvite(req, replacement, acceptUrl);
146
+ return shaped(CreatedInviteResponseSchema, { ...replacement, acceptUrl, delivery }, 201);
147
+ };
148
+ //# sourceMappingURL=invites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invites.js","sourceRoot":"","sources":["../../src/handlers/invites.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Whoami. The first call a non-browser client makes, and the only way a token
3
+ * holder can see which org their credential resolved into — `actingOrgId` is
4
+ * derived per request from the caller's membership, not frozen at mint time.
5
+ */
6
+ import { type ApiHandler } from '../api/index.js';
7
+ export declare const getMe: ApiHandler;
8
+ //# sourceMappingURL=me.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"me.d.ts","sourceRoot":"","sources":["../../src/handlers/me.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEjE,eAAO,MAAM,KAAK,EAAE,UAcnB,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Whoami. The first call a non-browser client makes, and the only way a token
3
+ * holder can see which org their credential resolved into — `actingOrgId` is
4
+ * derived per request from the caller's membership, not frozen at mint time.
5
+ */
6
+ import { requireCaller } from '../api/index.js';
7
+ export const getMe = async (req) => {
8
+ const { ctx, user } = requireCaller(req);
9
+ return {
10
+ body: {
11
+ userId: user.id,
12
+ email: user.email,
13
+ displayName: req.profile?.displayName,
14
+ actingOrgId: ctx.actingOrgId,
15
+ platformPermissions: ctx.platformPermissions,
16
+ orgPermissions: ctx.orgPermissions,
17
+ starredDefinitions: req.profile?.starredDefinitions ?? []
18
+ }
19
+ };
20
+ };
21
+ //# sourceMappingURL=me.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"me.js","sourceRoot":"","sources":["../../src/handlers/me.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAmB,MAAM,iBAAiB,CAAC;AAEjE,MAAM,CAAC,MAAM,KAAK,GAAe,KAAK,EAAE,GAAG,EAAE,EAAE;IAC9C,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAEzC,OAAO;QACN,IAAI,EAAE;YACL,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,GAAG,CAAC,OAAO,EAAE,WAAW;YACrC,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;YAC5C,cAAc,EAAE,GAAG,CAAC,cAAc;YAClC,kBAAkB,EAAE,GAAG,CAAC,OAAO,EAAE,kBAAkB,IAAI,EAAE;SACzD;KACD,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Starring a definition for the calling user. Both directions are idempotent.
3
+ *
4
+ * No visibility check: a star is a row on the caller's own profile, keyed by
5
+ * guid, and reveals nothing about whether that guid exists. The read paths that
6
+ * expand stars into definitions filter by visibility there.
7
+ */
8
+ import { type ApiHandler } from '../api/index.js';
9
+ export declare const starDefinition: ApiHandler;
10
+ export declare const unstarDefinition: ApiHandler;
11
+ //# sourceMappingURL=me.starred.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"me.starred.d.ts","sourceRoot":"","sources":["../../src/handlers/me.starred.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA2C,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE3F,eAAO,MAAM,cAAc,EAAE,UAM5B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UAM9B,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Starring a definition for the calling user. Both directions are idempotent.
3
+ *
4
+ * No visibility check: a star is a row on the caller's own profile, keyed by
5
+ * guid, and reveals nothing about whether that guid exists. The read paths that
6
+ * expand stars into definitions filter by visibility there.
7
+ */
8
+ import { noContent, requireCaller, requireParams } from '../api/index.js';
9
+ export const starDefinition = async (req) => {
10
+ const { ctx, user } = requireCaller(req);
11
+ const { guid } = requireParams(req.params, 'guid');
12
+ await req.deps.userProfile.starDefinition(ctx, user.id, guid);
13
+ return noContent();
14
+ };
15
+ export const unstarDefinition = async (req) => {
16
+ const { ctx, user } = requireCaller(req);
17
+ const { guid } = requireParams(req.params, 'guid');
18
+ await req.deps.userProfile.unstarDefinition(ctx, user.id, guid);
19
+ return noContent();
20
+ };
21
+ //# sourceMappingURL=me.starred.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"me.starred.js","sourceRoot":"","sources":["../../src/handlers/me.starred.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAmB,MAAM,iBAAiB,CAAC;AAE3F,MAAM,CAAC,MAAM,cAAc,GAAe,KAAK,EAAE,GAAG,EAAE,EAAE;IACvD,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEnD,MAAM,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC9D,OAAO,SAAS,EAAE,CAAC;AACpB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAe,KAAK,EAAE,GAAG,EAAE,EAAE;IACzD,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEnD,MAAM,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAChE,OAAO,SAAS,EAAE,CAAC;AACpB,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Org branding assets, generic over kind (`logo`, `favicon`, …). Gated on
3
+ * `manage_org_members` — org branding is an org-admin action.
4
+ *
5
+ * Raster images and SVG are both accepted. Every input is rasterized to WebP by
6
+ * the shared transcoder inside the storage provider, so SVG carries no XSS
7
+ * surface: no vector blob is ever stored or served. The allowlist below is the
8
+ * HTTP gate; the rasterization is the security control.
9
+ */
10
+ import { type ApiHandler } from '../api/index.js';
11
+ export declare const uploadOrgAsset: ApiHandler;
12
+ export declare const removeOrgAsset: ApiHandler;
13
+ //# sourceMappingURL=orgAssets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgAssets.d.ts","sourceRoot":"","sources":["../../src/handlers/orgAssets.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAoD,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAmBpG,eAAO,MAAM,cAAc,EAAE,UAqB5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,UAO5B,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Org branding assets, generic over kind (`logo`, `favicon`, …). Gated on
3
+ * `manage_org_members` — org branding is an org-admin action.
4
+ *
5
+ * Raster images and SVG are both accepted. Every input is rasterized to WebP by
6
+ * the shared transcoder inside the storage provider, so SVG carries no XSS
7
+ * surface: no vector blob is ever stored or served. The allowlist below is the
8
+ * HTTP gate; the rasterization is the security control.
9
+ */
10
+ import { apiError, ApiErrorCode, noContent, requireUpload } from '../api/index.js';
11
+ import { OrgAssetKindSchema } from '@selvajs/platform';
12
+ import { requireManageOrgMembers, requireActingOrg } from '../access/index.js';
13
+ import { orgAssetService } from './services.js';
14
+ const ALLOWED_CONTENT_TYPES = new Set([
15
+ 'image/png',
16
+ 'image/jpeg',
17
+ 'image/webp',
18
+ 'image/gif',
19
+ 'image/svg+xml'
20
+ ]);
21
+ function parseKind(params) {
22
+ const kind = OrgAssetKindSchema.safeParse(params.kind);
23
+ if (!kind.success)
24
+ apiError(404, ApiErrorCode.NOT_FOUND, `Unknown asset kind '${params.kind}'`);
25
+ return kind.data;
26
+ }
27
+ export const uploadOrgAsset = async (req) => {
28
+ requireManageOrgMembers(req);
29
+ const kind = parseKind(req.params);
30
+ const { ctx, orgId } = requireActingOrg(req, req.params.orgId);
31
+ // The org must exist — keeps a leaked id from seeding orphan blobs.
32
+ const org = await req.deps.orgs.getOrg(ctx, orgId);
33
+ if (!org)
34
+ apiError(404, ApiErrorCode.NOT_FOUND, 'Organization not found');
35
+ const { file } = requireUpload(await req.request.formData(), 'image', {
36
+ maxBytes: req.deps.uploadLimits.maxImageFileSize,
37
+ contentTypes: {
38
+ allowed: ALLOWED_CONTENT_TYPES,
39
+ message: 'Unsupported image type. Allowed: PNG, JPG, WebP, GIF, SVG'
40
+ },
41
+ label: 'Image'
42
+ });
43
+ const bytes = new Uint8Array(await file.arrayBuffer());
44
+ const url = await orgAssetService(req.deps).saveAsset(ctx, orgId, kind, bytes, file.type);
45
+ return { body: { kind, url } };
46
+ };
47
+ export const removeOrgAsset = async (req) => {
48
+ requireManageOrgMembers(req);
49
+ const kind = parseKind(req.params);
50
+ const { ctx, orgId } = requireActingOrg(req, req.params.orgId);
51
+ await orgAssetService(req.deps).removeAsset(ctx, orgId, kind);
52
+ return noContent();
53
+ };
54
+ //# sourceMappingURL=orgAssets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgAssets.js","sourceRoot":"","sources":["../../src/handlers/orgAssets.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAmB,MAAM,iBAAiB,CAAC;AACpG,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACrC,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,eAAe;CACf,CAAC,CAAC;AAEH,SAAS,SAAS,CAAC,MAAyB;IAC3C,MAAM,IAAI,GAAG,kBAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,SAAS,EAAE,uBAAuB,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;IAChG,OAAO,IAAI,CAAC,IAAI,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAe,KAAK,EAAE,GAAG,EAAE,EAAE;IACvD,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE/D,oEAAoE;IACpE,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACnD,IAAI,CAAC,GAAG;QAAE,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAC;IAE1E,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE;QACrE,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,gBAAgB;QAChD,YAAY,EAAE;YACb,OAAO,EAAE,qBAAqB;YAC9B,OAAO,EAAE,2DAA2D;SACpE;QACD,KAAK,EAAE,OAAO;KACd,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1F,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAe,KAAK,EAAE,GAAG,EAAE,EAAE;IACvD,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE/D,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9D,OAAO,SAAS,EAAE,CAAC;AACpB,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Per-org compute config: this org's own servers (`scope: 'org'`) and its
3
+ * default selection (`orgDefaults[orgId]`). Platform servers and the global
4
+ * default are read-only here — `manage_compute` owns those via `/api/admin/compute`.
5
+ *
6
+ * Authorization: org `owner`/`admin` with `manage_org_compute`, plus the acting-org
7
+ * tenancy check. Gated by `ALLOW_ORG_COMPUTE_OVERRIDE`; when off, both methods 403.
8
+ *
9
+ * **`apiKey` merge semantics are the trap here.** Omitted preserves the stored
10
+ * key, `null` clears it, a string replaces it — and `/api/admin/compute` must
11
+ * agree, which is why the merge lives in `@selvajs/server/compute`, not inline.
12
+ */
13
+ import type { ApiHandler } from '../api/index.js';
14
+ export declare const getOrgCompute: ApiHandler;
15
+ export declare const updateOrgCompute: ApiHandler;
16
+ //# sourceMappingURL=orgCompute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgCompute.d.ts","sourceRoot":"","sources":["../../src/handlers/orgCompute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,iBAAiB,CAAC;AA0B9D,eAAO,MAAM,aAAa,EAAE,UAiC3B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UA0D9B,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Per-org compute config: this org's own servers (`scope: 'org'`) and its
3
+ * default selection (`orgDefaults[orgId]`). Platform servers and the global
4
+ * default are read-only here — `manage_compute` owns those via `/api/admin/compute`.
5
+ *
6
+ * Authorization: org `owner`/`admin` with `manage_org_compute`, plus the acting-org
7
+ * tenancy check. Gated by `ALLOW_ORG_COMPUTE_OVERRIDE`; when off, both methods 403.
8
+ *
9
+ * **`apiKey` merge semantics are the trap here.** Omitted preserves the stored
10
+ * key, `null` clears it, a string replaces it — and `/api/admin/compute` must
11
+ * agree, which is why the merge lives in `@selvajs/server/compute`, not inline.
12
+ */
13
+ import { apiError, ApiErrorCode, noContent, OrgComputePatchBodySchema, OrgComputeResponseSchema, parseBody, shaped } from '../api/index.js';
14
+ import { isOrgServer, isPlatformServer, serversVisibleTo } from '@selvajs/platform';
15
+ import { requireManageOrgCompute, requireActingOrg } from '../access/index.js';
16
+ import { evictChangedServers, validateIncomingServers, resolveApiKey, storedKeysById } from '../compute/index.js';
17
+ function requireFlag(req) {
18
+ if (!req.deps.flag('ALLOW_ORG_COMPUTE_OVERRIDE')) {
19
+ apiError(403, ApiErrorCode.FORBIDDEN, 'Per-org compute override is disabled on this instance (ALLOW_ORG_COMPUTE_OVERRIDE).');
20
+ }
21
+ }
22
+ export const getOrgCompute = async (req) => {
23
+ requireFlag(req);
24
+ requireManageOrgCompute(req);
25
+ const { ctx, orgId } = requireActingOrg(req, req.params.orgId);
26
+ // Scoped in the store, so `config.servers` holds only what this org may
27
+ // see and `defaultServerId` is blank unless it is one of them.
28
+ const config = await req.deps.computeServer.getConfig(ctx, { scopeToOrgId: orgId });
29
+ // Servers this org owns and may edit. `apiKey` is dropped by the response
30
+ // schema; `hasApiKey` is what a picker needs to render "key set".
31
+ const owned = config.servers
32
+ .filter((s) => isOrgServer(s) && s.ownerOrgId === orgId)
33
+ .map((s) => ({ ...s, hasApiKey: !!s.hasApiKey }));
34
+ // Platform and own servers visible to this org — the read-only catalog
35
+ // behind the "default selection" dropdown.
36
+ const catalog = config.servers.map((s) => ({
37
+ id: s.id,
38
+ label: s.label,
39
+ serverUrl: s.serverUrl,
40
+ scope: s.scope,
41
+ source: isPlatformServer(s) ? 'platform' : 'org',
42
+ timeoutMs: s.timeoutMs,
43
+ retryCount: s.retryCount
44
+ }));
45
+ return shaped(OrgComputeResponseSchema, {
46
+ servers: owned,
47
+ defaultServerId: config.orgDefaults?.[orgId] ?? null,
48
+ globalDefaultServerId: config.defaultServerId ?? null,
49
+ catalog
50
+ });
51
+ };
52
+ export const updateOrgCompute = async (req) => {
53
+ requireFlag(req);
54
+ requireManageOrgCompute(req);
55
+ const { ctx, orgId } = requireActingOrg(req, req.params.orgId);
56
+ const incoming = await parseBody(req.request, OrgComputePatchBodySchema);
57
+ // Cross-field rules the schema can't express (URL shape, duplicate ids).
58
+ validateIncomingServers(incoming.servers);
59
+ const provider = req.deps.computeServer;
60
+ // Every key is needed: an unchanged server keeps its stored key across the write.
61
+ const existing = await provider.getConfig(ctx, { includeApiKeys: true });
62
+ const storedKeys = storedKeysById(existing.servers.filter((s) => isOrgServer(s) && s.ownerOrgId === orgId));
63
+ const next = incoming.servers.map((s) => ({
64
+ id: s.id,
65
+ scope: 'org',
66
+ ownerOrgId: orgId,
67
+ label: s.label,
68
+ serverUrl: s.serverUrl,
69
+ timeoutMs: s.timeoutMs,
70
+ retryCount: s.retryCount,
71
+ apiKey: resolveApiKey(s.apiKey, storedKeys.get(s.id))
72
+ }));
73
+ // The requested org default must be visible to this org. The check runs
74
+ // against the projected post-save config so it sees both the new
75
+ // org-private rows and the existing platform ones.
76
+ if (typeof incoming.defaultServerId === 'string') {
77
+ const projected = {
78
+ servers: [
79
+ ...existing.servers.filter((s) => !(isOrgServer(s) && s.ownerOrgId === orgId)),
80
+ ...next
81
+ ],
82
+ defaultServerId: existing.defaultServerId,
83
+ orgDefaults: existing.orgDefaults
84
+ };
85
+ const visibleIds = new Set(serversVisibleTo(projected, orgId).map((s) => s.id));
86
+ if (!visibleIds.has(incoming.defaultServerId)) {
87
+ apiError(400, ApiErrorCode.VALIDATION_FAILED, 'defaultServerId must reference a server visible to this organization');
88
+ }
89
+ }
90
+ await provider.saveOrgServers(ctx, orgId, next, incoming.defaultServerId);
91
+ // Drop warm clients for servers whose URL or key rotated, or that were
92
+ // removed — keyed on `id`, they would not age out on their own.
93
+ evictChangedServers(existing.servers.filter((s) => isOrgServer(s) && s.ownerOrgId === orgId), next, req.deps.evictComputeClient);
94
+ return noContent();
95
+ };
96
+ //# sourceMappingURL=orgCompute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgCompute.js","sourceRoot":"","sources":["../../src/handlers/orgCompute.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Org member role, permissions, and removal.
3
+ *
4
+ * Two gates run on every request: `requireManageOrgMembers` (owner+admin) and
5
+ * `requireActingOrg` (the URL `orgId` must equal `ctx.actingOrgId`, so
6
+ * cross-org member mutation is blocked even for an actor with permissions in
7
+ * another org).
8
+ *
9
+ * The sole-owner invariant applies to demotion and removal alike — an org with
10
+ * no owner is unadministrable, and removing an owner ends their role exactly as
11
+ * demoting them does.
12
+ */
13
+ import type { ApiHandler } from '../api/index.js';
14
+ /**
15
+ * Body accepts either or both:
16
+ * - `{ role: OrgRole }` — change the member's role. Owner-only.
17
+ * - `{ permissions: OrgPermission[] }` — replace the member's grantable
18
+ * permissions. Owner/admin via `manage_org_members`. For `member`-role
19
+ * targets, only `MEMBER_ASSIGNABLE_PERMISSIONS` are accepted; promoting a
20
+ * member's permissions does NOT change their role.
21
+ */
22
+ export declare const updateOrgMember: ApiHandler;
23
+ /**
24
+ * Remove a member from the org. Owner/admin via `manage_org_members`, same
25
+ * tenancy check as PATCH, and the same sole-owner invariant — removing the last
26
+ * owner would leave the org unadministrable.
27
+ */
28
+ export declare const removeOrgMember: ApiHandler;
29
+ //# sourceMappingURL=orgMembers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgMembers.d.ts","sourceRoot":"","sources":["../../src/handlers/orgMembers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,iBAAiB,CAAC;AA+F9D;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,UA8D7B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,UAqE7B,CAAC"}