@intelligo-dev/auth 1.0.0-beta.2 → 1.0.0-beta.5
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 +39 -0
- package/dist/helpers.js +8 -8
- package/dist/helpers.js.map +1 -1
- package/dist/impersonation.js +4 -4
- package/dist/impersonation.js.map +1 -1
- package/dist/index.js +18 -18
- package/dist/onboarding/schemas.js +4 -5
- package/dist/onboarding/schemas.js.map +1 -1
- package/dist/onboarding/service.js +6 -6
- package/dist/org-api.js +10 -10
- package/dist/profile/schemas.js +2 -3
- package/dist/profile/schemas.js.map +1 -1
- package/dist/profile/service.js +9 -9
- package/dist/profile/service.js.map +1 -1
- package/dist/server.js +23 -7
- package/dist/server.js.map +1 -1
- package/dist/team/schemas.js +2 -2
- package/dist/team/service.js +24 -24
- package/dist/team/service.js.map +1 -1
- package/dist/trusted-origins.js +31 -0
- package/dist/trusted-origins.js.map +1 -0
- package/dist/workspace/schemas.js +2 -2
- package/dist/workspace/service.js +18 -18
- package/dist/workspace/service.js.map +1 -1
- package/dist/workspace-init.js +1 -1
- package/package.json +17 -10
- package/src/helpers.ts +6 -6
- package/src/impersonation.ts +3 -3
- package/src/onboarding/schemas.ts +4 -5
- package/src/onboarding/service.ts +3 -3
- package/src/org-api.ts +9 -9
- package/src/profile/schemas.ts +2 -3
- package/src/profile/service.ts +5 -5
- package/src/server.ts +24 -7
- package/src/team/schemas.ts +2 -2
- package/src/team/service.ts +20 -20
- package/src/trusted-origins.ts +32 -0
- package/src/workspace/schemas.ts +2 -2
- package/src/workspace/service.ts +13 -13
package/src/profile/schemas.ts
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* Zod schemas for user profile updates, shared by the profile service
|
|
5
5
|
* and its transports.
|
|
6
6
|
*
|
|
7
|
-
* (Ported from the product
|
|
8
|
-
* same semantics: optional name, optional-and-nullable image URL.
|
|
9
|
-
* Ignite's copy is retired at cutover.)
|
|
7
|
+
* (Ported from the first product's `lib/validations/profile.ts` —
|
|
8
|
+
* same semantics: optional name, optional-and-nullable image URL.)
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
import { z } from "zod";
|
package/src/profile/service.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Profile management service — the durable business rules behind
|
|
3
3
|
* reading the caller's own profile, updating it, and deleting the
|
|
4
|
-
* account,
|
|
4
|
+
* account, lifted out of the first product's profile actions
|
|
5
5
|
* (page/registry migration, `profile-settings` family, roadmap item 9).
|
|
6
6
|
*
|
|
7
7
|
* Mirrors `createTeamService(ports)` / `createWorkspaceService(ports)`
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
* direct calls: this service should not own *content* — copy, subject
|
|
39
39
|
* lines, template shape — for a side effect a consumer may want to
|
|
40
40
|
* localize, skip, or replace with a different provider. `onAccountDeleted`
|
|
41
|
-
* is fire-and-forget by design (matching
|
|
41
|
+
* is fire-and-forget by design (matching the original action's `.catch(console.error)`
|
|
42
42
|
* pattern): a failed confirmation email must never block the deletion
|
|
43
43
|
* that already succeeded.
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
|
-
import {
|
|
46
|
+
import { getRequestHeaders } from "@intelligo-dev/core/request-context";
|
|
47
47
|
import type { ZodType } from "zod";
|
|
48
48
|
import { eq } from "drizzle-orm";
|
|
49
49
|
import { createLogger } from "@intelligo-dev/core/logger";
|
|
@@ -134,12 +134,12 @@ export function createProfileService(ports: ProfileServicePorts = {}) {
|
|
|
134
134
|
* Only the fields present in `input` are sent — omitting a field
|
|
135
135
|
* leaves it unchanged; `image: null` is dropped rather than forwarded
|
|
136
136
|
* (Better-Auth's `updateUser` does not accept `null`), matching
|
|
137
|
-
*
|
|
137
|
+
* the original action.
|
|
138
138
|
*/
|
|
139
139
|
async function updateProfile(input: UpdateProfileInput): Promise<void> {
|
|
140
140
|
await callRequireAuth();
|
|
141
141
|
const validated = parseInput(updateProfileSchema, input);
|
|
142
|
-
const hdrs = await
|
|
142
|
+
const hdrs = await getRequestHeaders();
|
|
143
143
|
|
|
144
144
|
const updateData: { name?: string; image?: string } = {};
|
|
145
145
|
if (validated.name !== undefined) updateData.name = validated.name;
|
package/src/server.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
userAc,
|
|
21
21
|
} from "better-auth/plugins/admin/access";
|
|
22
22
|
import { PLATFORM_ADMIN_ROLE } from "./roles";
|
|
23
|
+
import { resolveTrustedOrigins } from "./trusted-origins";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Access control for the platform role.
|
|
@@ -52,6 +53,24 @@ import {
|
|
|
52
53
|
} from "@intelligo-dev/core/email";
|
|
53
54
|
import { eq } from "drizzle-orm";
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The origin this app is served from.
|
|
58
|
+
*
|
|
59
|
+
* `NEXT_PUBLIC_APP_URL` is a REQUIRED variable (`assertEnv` in
|
|
60
|
+
* `@intelligo-dev/core/env`), but auth is configured at module load,
|
|
61
|
+
* long before any composition root asserts it. This value therefore
|
|
62
|
+
* still needs a fallback for the absolute links that go into email —
|
|
63
|
+
* a verification or invitation URL has to name a concrete host.
|
|
64
|
+
*/
|
|
65
|
+
const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:4000";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Origins the CSRF check accepts — the one place the fallback above
|
|
69
|
+
* must NOT apply, because a guessed port there does not degrade
|
|
70
|
+
* gracefully. See `resolveTrustedOrigins` for the rule.
|
|
71
|
+
*/
|
|
72
|
+
const TRUSTED_ORIGINS = resolveTrustedOrigins(process.env);
|
|
73
|
+
|
|
55
74
|
export const auth = betterAuth({
|
|
56
75
|
// Explicit, so the name the framework documents (doctor, scaffold,
|
|
57
76
|
// env validation) is the one that is honoured; AUTH_SECRET stays a
|
|
@@ -72,7 +91,7 @@ export const auth = betterAuth({
|
|
|
72
91
|
},
|
|
73
92
|
}),
|
|
74
93
|
|
|
75
|
-
baseURL:
|
|
94
|
+
baseURL: APP_URL,
|
|
76
95
|
|
|
77
96
|
emailAndPassword: {
|
|
78
97
|
enabled: true,
|
|
@@ -136,7 +155,7 @@ export const auth = betterAuth({
|
|
|
136
155
|
updateAge: 60 * 60 * 24,
|
|
137
156
|
},
|
|
138
157
|
|
|
139
|
-
trustedOrigins:
|
|
158
|
+
trustedOrigins: TRUSTED_ORIGINS,
|
|
140
159
|
|
|
141
160
|
// Database hooks for automatic workspace setup (WORK-01)
|
|
142
161
|
databaseHooks: {
|
|
@@ -186,7 +205,7 @@ export const auth = betterAuth({
|
|
|
186
205
|
// Send welcome email (EMAIL-03, fire-and-forget).
|
|
187
206
|
// NOTE(DB-12): No retry or outbox — downstream failures silently ignored.
|
|
188
207
|
// Acceptable for v0.2; consider transactional outbox for Phase 14.
|
|
189
|
-
const dashboardUrl = `${
|
|
208
|
+
const dashboardUrl = `${APP_URL}/dashboard`;
|
|
190
209
|
sendWelcomeEmail({
|
|
191
210
|
to: user.email,
|
|
192
211
|
userName: user.name || user.email,
|
|
@@ -263,15 +282,13 @@ export const auth = betterAuth({
|
|
|
263
282
|
creatorRole: "owner",
|
|
264
283
|
// Invitation email sending via Resend (EMAIL-06, replaces Phase 10 placeholder)
|
|
265
284
|
sendInvitationEmail: async (data) => {
|
|
266
|
-
const appUrl =
|
|
267
|
-
process.env.NEXT_PUBLIC_APP_URL || "http://localhost:4000";
|
|
268
285
|
sendInvitationEmail({
|
|
269
286
|
to: data.email,
|
|
270
287
|
inviterName: data.inviter?.user?.name || "A team member",
|
|
271
288
|
workspaceName: data.organization?.name || "a workspace",
|
|
272
289
|
role: data.role || "member",
|
|
273
|
-
acceptUrl: `${
|
|
274
|
-
declineUrl: `${
|
|
290
|
+
acceptUrl: `${APP_URL}/accept-invitation/${data.id}`,
|
|
291
|
+
declineUrl: `${APP_URL}/invitation/decline?id=${data.id}`,
|
|
275
292
|
}).catch((err) =>
|
|
276
293
|
console.error("[Auth] Failed to send invitation email:", err)
|
|
277
294
|
);
|
package/src/team/schemas.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Zod schemas for team invite and member management inputs, shared by
|
|
5
5
|
* the team service and its transports.
|
|
6
6
|
*
|
|
7
|
-
* (Ported from the product
|
|
8
|
-
* semantics.
|
|
7
|
+
* (Ported from the first product's team validation module — same
|
|
8
|
+
* semantics.)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { z } from "zod";
|
package/src/team/service.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Team management service — the durable business rules behind
|
|
3
|
-
* workspace membership and invitations,
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* workspace membership and invitations, lifted out of the first
|
|
4
|
+
* product's team actions (first slice of the page/registry migration,
|
|
5
|
+
* section C1).
|
|
6
6
|
*
|
|
7
7
|
* Mirrors `createExecutions(ports)` (packages/executions/src/lifecycle.ts):
|
|
8
8
|
* a factory over optional ports, so this package's allowlisted
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* option, the hook fires on every `inviteMember()` call this service
|
|
39
39
|
* makes — there is no code path where it does not.
|
|
40
40
|
*
|
|
41
|
-
*
|
|
41
|
+
* The product's original `actions/team.ts` ALSO called
|
|
42
42
|
* `@intelligo-dev/core/email`'s `sendInvitationEmail` directly after the
|
|
43
43
|
* same `/organization/invite-member` call. That means **two** emails
|
|
44
44
|
* go out per invitation today. This is a live duplication bug, not a
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
* own `organizationId`).
|
|
73
73
|
*/
|
|
74
74
|
|
|
75
|
-
import {
|
|
75
|
+
import { getRequestHeaders } from "@intelligo-dev/core/request-context";
|
|
76
76
|
import type { ZodType } from "zod";
|
|
77
77
|
import { createLogger } from "@intelligo-dev/core/logger";
|
|
78
78
|
|
|
@@ -110,7 +110,7 @@ export type TeamServicePorts = {
|
|
|
110
110
|
/**
|
|
111
111
|
* Notify the workspace owner that a new member joined. `memberEmail`
|
|
112
112
|
* is included alongside the ports.md-listed fields because
|
|
113
|
-
* `triggerTeamMemberJoinedNotification` (
|
|
113
|
+
* `triggerTeamMemberJoinedNotification` (a consumer's binding) uses it
|
|
114
114
|
* to compose the notification message — dropping it would silently
|
|
115
115
|
* degrade the message text.
|
|
116
116
|
*/
|
|
@@ -198,7 +198,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
198
198
|
*/
|
|
199
199
|
async function listMembers(): Promise<OrgMember[]> {
|
|
200
200
|
const { workspace } = await callRequireWorkspace();
|
|
201
|
-
const hdrs = await
|
|
201
|
+
const hdrs = await getRequestHeaders();
|
|
202
202
|
|
|
203
203
|
const org = await callOrgApi("getFullOrganization", () =>
|
|
204
204
|
auth.api.getFullOrganization({
|
|
@@ -215,7 +215,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
215
215
|
*/
|
|
216
216
|
async function listInvitations(): Promise<OrgInvitation[]> {
|
|
217
217
|
const { workspace } = await callRequireWorkspace();
|
|
218
|
-
const hdrs = await
|
|
218
|
+
const hdrs = await getRequestHeaders();
|
|
219
219
|
|
|
220
220
|
const org = await callOrgApi("getFullOrganization", () =>
|
|
221
221
|
auth.api.getFullOrganization({
|
|
@@ -240,7 +240,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
240
240
|
}): Promise<OrgInvitation | null> {
|
|
241
241
|
const { workspace, user } = await callRequireRole(["owner", "admin"]);
|
|
242
242
|
const validated = parseInput(inviteMemberSchema, input);
|
|
243
|
-
const hdrs = await
|
|
243
|
+
const hdrs = await getRequestHeaders();
|
|
244
244
|
|
|
245
245
|
// Check team member limit (FLAG-05), via port only.
|
|
246
246
|
if (ports.checkMemberLimit) {
|
|
@@ -269,7 +269,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
269
269
|
|
|
270
270
|
// Better-Auth's own duplicate-pending-invite guard
|
|
271
271
|
// (USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION) runs inside this
|
|
272
|
-
// call and surfaces as a provider_error if tripped —
|
|
272
|
+
// call and surfaces as a provider_error if tripped — the original
|
|
273
273
|
// action never added a second check on top of it, so neither does
|
|
274
274
|
// this service.
|
|
275
275
|
const result = await callOrgApi("invite-member", () =>
|
|
@@ -317,7 +317,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
317
317
|
*/
|
|
318
318
|
async function acceptInvitation(invitationId: string): Promise<void> {
|
|
319
319
|
const { user } = await callRequireAuth();
|
|
320
|
-
const hdrs = await
|
|
320
|
+
const hdrs = await getRequestHeaders();
|
|
321
321
|
|
|
322
322
|
if (typeof invitationId !== "string" || invitationId.length === 0) {
|
|
323
323
|
throw new TeamServiceError("invalid_input", "Invalid invitation id");
|
|
@@ -376,7 +376,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
376
376
|
//
|
|
377
377
|
// Reuses `targetOrg` from the post-check above (scoped to
|
|
378
378
|
// `matched.organizationId`, the org the caller just joined) rather
|
|
379
|
-
// than re-fetching "the active org" the way
|
|
379
|
+
// than re-fetching "the active org" the way the original
|
|
380
380
|
// action did: Better-Auth's org plugin needs a
|
|
381
381
|
// `sessions.activeOrganizationId` column to resolve an org from
|
|
382
382
|
// headers alone, and this repo's Drizzle schema for `sessions`
|
|
@@ -427,7 +427,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
427
427
|
*/
|
|
428
428
|
async function rejectInvitation(invitationId: string): Promise<void> {
|
|
429
429
|
await callRequireAuth();
|
|
430
|
-
const hdrs = await
|
|
430
|
+
const hdrs = await getRequestHeaders();
|
|
431
431
|
|
|
432
432
|
if (typeof invitationId !== "string" || invitationId.length === 0) {
|
|
433
433
|
throw new TeamServiceError("invalid_input", "Invalid invitation id");
|
|
@@ -456,7 +456,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
456
456
|
*/
|
|
457
457
|
async function cancelInvitation(invitationId: string): Promise<void> {
|
|
458
458
|
await callRequireRole(["owner", "admin"]);
|
|
459
|
-
const hdrs = await
|
|
459
|
+
const hdrs = await getRequestHeaders();
|
|
460
460
|
|
|
461
461
|
await callOrgApi("cancel-invitation", () =>
|
|
462
462
|
orgApi["/organization/cancel-invitation"]({
|
|
@@ -471,7 +471,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
471
471
|
*/
|
|
472
472
|
async function removeMember(memberId: string): Promise<void> {
|
|
473
473
|
const { workspace } = await callRequireRole(["owner", "admin"]);
|
|
474
|
-
const hdrs = await
|
|
474
|
+
const hdrs = await getRequestHeaders();
|
|
475
475
|
|
|
476
476
|
await callOrgApi("remove-member", () =>
|
|
477
477
|
orgApi["/organization/remove-member"]({
|
|
@@ -490,7 +490,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
490
490
|
}): Promise<void> {
|
|
491
491
|
const { workspace } = await callRequireRole(["owner", "admin"]);
|
|
492
492
|
const validated = parseInput(updateRoleSchema, input);
|
|
493
|
-
const hdrs = await
|
|
493
|
+
const hdrs = await getRequestHeaders();
|
|
494
494
|
|
|
495
495
|
await callOrgApi("update-member-role", () =>
|
|
496
496
|
orgApi["/organization/update-member-role"]({
|
|
@@ -510,7 +510,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
510
510
|
*/
|
|
511
511
|
async function leaveWorkspace(): Promise<void> {
|
|
512
512
|
const { workspace, membership } = await callRequireWorkspace();
|
|
513
|
-
const hdrs = await
|
|
513
|
+
const hdrs = await getRequestHeaders();
|
|
514
514
|
|
|
515
515
|
if (membership.role === "owner") {
|
|
516
516
|
const org = await callOrgApi("getFullOrganization", () =>
|
|
@@ -555,7 +555,7 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
555
555
|
*/
|
|
556
556
|
async function transferOwnership(targetMemberId: string): Promise<void> {
|
|
557
557
|
const { workspace } = await callRequireRole(["owner"]);
|
|
558
|
-
const hdrs = await
|
|
558
|
+
const hdrs = await getRequestHeaders();
|
|
559
559
|
|
|
560
560
|
// Promote target to owner. Note: Better-Auth may handle demotion of
|
|
561
561
|
// the previous owner automatically. If not, the old owner remains
|
|
@@ -576,10 +576,10 @@ export function createTeamService(ports: TeamServicePorts = {}) {
|
|
|
576
576
|
* Get the caller's own pending invitations (for the invitation
|
|
577
577
|
* accept page). No explicit requireAuth here — Better-Auth's
|
|
578
578
|
* list-user-invitations endpoint reads the session off `headers`
|
|
579
|
-
* itself; ported as-is from
|
|
579
|
+
* itself; ported as-is from the original action.
|
|
580
580
|
*/
|
|
581
581
|
async function getUserInvitations(): Promise<OrgInvitation[]> {
|
|
582
|
-
const hdrs = await
|
|
582
|
+
const hdrs = await getRequestHeaders();
|
|
583
583
|
|
|
584
584
|
const invitations = await callOrgApi("list-user-invitations", () =>
|
|
585
585
|
orgApi["/organization/list-user-invitations"]({ headers: hdrs })
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which origins Better-Auth's CSRF check accepts.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `server.ts` so the rule can be tested without standing
|
|
5
|
+
* up the whole auth instance (which reaches the database and the email
|
|
6
|
+
* provider at module load).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the trusted-origin list from the environment.
|
|
11
|
+
*
|
|
12
|
+
* `NEXT_PUBLIC_APP_URL` is a required variable, and when it is set it
|
|
13
|
+
* is the only origin trusted — in every environment.
|
|
14
|
+
*
|
|
15
|
+
* When it is unset the answer depends on where we are. A guessed port
|
|
16
|
+
* is the worst possible default: the reference app serves on 4002, a
|
|
17
|
+
* scaffolded app on 3000, so a fixed guess rejects every sign-in with
|
|
18
|
+
* `403 INVALID_ORIGIN` while the log names only the rejected origin —
|
|
19
|
+
* nothing points back at the missing variable. Outside production we
|
|
20
|
+
* therefore trust any loopback port, so a fresh checkout works on
|
|
21
|
+
* whatever port Next picked. In production we trust nothing: an unset
|
|
22
|
+
* variable there is a configuration error, and failing closed is the
|
|
23
|
+
* only safe reading of it.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveTrustedOrigins(env: {
|
|
26
|
+
NEXT_PUBLIC_APP_URL?: string;
|
|
27
|
+
NODE_ENV?: string;
|
|
28
|
+
}): string[] {
|
|
29
|
+
if (env.NEXT_PUBLIC_APP_URL) return [env.NEXT_PUBLIC_APP_URL];
|
|
30
|
+
if (env.NODE_ENV === "production") return [];
|
|
31
|
+
return ["http://localhost:*", "http://127.0.0.1:*"];
|
|
32
|
+
}
|
package/src/workspace/schemas.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Zod schemas for workspace create/update inputs, shared by the
|
|
5
5
|
* workspace service and its transports.
|
|
6
6
|
*
|
|
7
|
-
* (Ported from the product
|
|
8
|
-
* same semantics.
|
|
7
|
+
* (Ported from the first product's workspace validation module —
|
|
8
|
+
* same semantics.)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { z } from "zod";
|
package/src/workspace/service.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workspace management service — the durable business rules behind
|
|
3
3
|
* workspace listing, creation, switching, editing and deletion,
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* lifted out of the first product's workspace actions (page/registry
|
|
5
|
+
* migration, `workspace-settings` family, roadmap item 8).
|
|
6
6
|
*
|
|
7
7
|
* Mirrors `createTeamService(ports)` (./../team/service.ts) one
|
|
8
8
|
* directory over: a factory over optional ports, so this package's
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Why `checkWorkspaceLimit` takes a `userId`, not a `workspaceId`
|
|
25
25
|
* ---------------------------------------------------------------------
|
|
26
26
|
* `createWorkspace` has no workspace to check the plan of yet — it is
|
|
27
|
-
* the thing being created.
|
|
27
|
+
* the thing being created. The product's original action worked around this
|
|
28
28
|
* by reading the caller's *existing* workspaces and using the first
|
|
29
29
|
* one's id to look up a plan via `@intelligo-dev/billing`'s
|
|
30
30
|
* `checkPlanLimit(workspaceId, "workspaces", currentCount)`, i.e. it
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* `userId` and how many workspaces they already have. The composition
|
|
35
35
|
* root's binding (`checkWorkspaceLimit`) is where a consumer decides
|
|
36
36
|
* how to resolve "this user's plan" — by reading their first workspace
|
|
37
|
-
* the same way
|
|
37
|
+
* the same way the first product did, or by a real per-user plan lookup if one
|
|
38
38
|
* exists.
|
|
39
39
|
*
|
|
40
40
|
* ---------------------------------------------------------------------
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* updateOrganization, deleteOrganization, getFullOrganization}`
|
|
52
52
|
* directly for the base organization CRUD surface, which is not
|
|
53
53
|
* part of `orgApi`'s typed table (the same approach
|
|
54
|
-
* `../team/service.ts` takes for `getFullOrganization`).
|
|
54
|
+
* `../team/service.ts` takes for `getFullOrganization`). The product's
|
|
55
55
|
* original `actions/workspace.ts` already called these four by
|
|
56
56
|
* their correct `auth.api` names directly (it never went through a
|
|
57
57
|
* path-keyed cast), so there is no method-name bug to fix here.
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
* fallback) and passes that id explicitly to `getFullOrganization`.
|
|
65
65
|
*/
|
|
66
66
|
|
|
67
|
-
import {
|
|
67
|
+
import { getRequestHeaders } from "@intelligo-dev/core/request-context";
|
|
68
68
|
import type { ZodType } from "zod";
|
|
69
69
|
import { createLogger } from "@intelligo-dev/core/logger";
|
|
70
70
|
|
|
@@ -189,7 +189,7 @@ export function createWorkspaceService(ports: WorkspaceServicePorts = {}) {
|
|
|
189
189
|
*/
|
|
190
190
|
async function listWorkspaces(): Promise<OrgListItem[]> {
|
|
191
191
|
await callRequireAuth();
|
|
192
|
-
const hdrs = await
|
|
192
|
+
const hdrs = await getRequestHeaders();
|
|
193
193
|
|
|
194
194
|
const orgs = await callOrgApi("list", () =>
|
|
195
195
|
orgApi["/organization/list"]({ headers: hdrs })
|
|
@@ -210,7 +210,7 @@ export function createWorkspaceService(ports: WorkspaceServicePorts = {}) {
|
|
|
210
210
|
): Promise<WorkspaceRecord> {
|
|
211
211
|
const { user } = await callRequireAuth();
|
|
212
212
|
const validated = parseInput(createWorkspaceSchema, input);
|
|
213
|
-
const hdrs = await
|
|
213
|
+
const hdrs = await getRequestHeaders();
|
|
214
214
|
|
|
215
215
|
const existing = await callOrgApi("list", () =>
|
|
216
216
|
orgApi["/organization/list"]({ headers: hdrs })
|
|
@@ -271,7 +271,7 @@ export function createWorkspaceService(ports: WorkspaceServicePorts = {}) {
|
|
|
271
271
|
throw new WorkspaceServiceError("invalid_input", "Invalid workspace id");
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
const hdrs = await
|
|
274
|
+
const hdrs = await getRequestHeaders();
|
|
275
275
|
|
|
276
276
|
await callOrgApi("set-active", () =>
|
|
277
277
|
orgApi["/organization/set-active"]({
|
|
@@ -289,7 +289,7 @@ export function createWorkspaceService(ports: WorkspaceServicePorts = {}) {
|
|
|
289
289
|
): Promise<WorkspaceRecord> {
|
|
290
290
|
const { workspace } = await callRequireRole(["owner", "admin"]);
|
|
291
291
|
const validated = parseInput(updateWorkspaceSchema, input);
|
|
292
|
-
const hdrs = await
|
|
292
|
+
const hdrs = await getRequestHeaders();
|
|
293
293
|
|
|
294
294
|
const updateData: { name?: string; slug?: string; logo?: string } = {};
|
|
295
295
|
if (validated.name) updateData.name = validated.name;
|
|
@@ -319,7 +319,7 @@ export function createWorkspaceService(ports: WorkspaceServicePorts = {}) {
|
|
|
319
319
|
*/
|
|
320
320
|
async function deleteWorkspace(): Promise<void> {
|
|
321
321
|
const { workspace } = await callRequireRole(["owner"]);
|
|
322
|
-
const hdrs = await
|
|
322
|
+
const hdrs = await getRequestHeaders();
|
|
323
323
|
|
|
324
324
|
await callOrgApi("delete", () =>
|
|
325
325
|
auth.api.deleteOrganization({
|
|
@@ -343,13 +343,13 @@ export function createWorkspaceService(ports: WorkspaceServicePorts = {}) {
|
|
|
343
343
|
|
|
344
344
|
/**
|
|
345
345
|
* Get the caller's active workspace, fully resolved. Unlike
|
|
346
|
-
*
|
|
346
|
+
* the original action (see the module doc comment), this resolves the
|
|
347
347
|
* workspace id explicitly via `requireWorkspace()` rather than
|
|
348
348
|
* relying on a non-existent `sessions.activeOrganizationId` fallback.
|
|
349
349
|
*/
|
|
350
350
|
async function getActiveWorkspace(): Promise<WorkspaceRecord> {
|
|
351
351
|
const { workspace } = await callRequireWorkspace();
|
|
352
|
-
const hdrs = await
|
|
352
|
+
const hdrs = await getRequestHeaders();
|
|
353
353
|
|
|
354
354
|
const org = (await callOrgApi("getFullOrganization", () =>
|
|
355
355
|
auth.api.getFullOrganization({
|