@intelligo-dev/auth 1.0.0-beta.1 → 1.0.0-beta.14
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/NOTICE +6 -0
- package/README.md +59 -0
- package/dist/client.js +6 -16
- package/dist/client.js.map +1 -1
- package/dist/edge.js +22 -28
- package/dist/edge.js.map +1 -1
- package/dist/guard-error.js +23 -0
- package/dist/guard-error.js.map +1 -0
- package/dist/helpers.js +77 -97
- package/dist/helpers.js.map +1 -1
- package/dist/impersonation.js +46 -18
- package/dist/impersonation.js.map +1 -1
- package/dist/index.js +21 -27
- package/dist/index.js.map +1 -1
- package/dist/invitation-links.js +13 -0
- package/dist/invitation-links.js.map +1 -0
- package/dist/onboarding/errors.js +4 -14
- package/dist/onboarding/errors.js.map +1 -1
- package/dist/onboarding/schemas.js +3 -15
- package/dist/onboarding/schemas.js.map +1 -1
- package/dist/onboarding/service.js +14 -68
- package/dist/onboarding/service.js.map +1 -1
- package/dist/org-api.js +10 -67
- package/dist/org-api.js.map +1 -1
- package/dist/profile/errors.js +4 -14
- package/dist/profile/errors.js.map +1 -1
- package/dist/profile/schemas.js +9 -10
- package/dist/profile/schemas.js.map +1 -1
- package/dist/profile/service.js +33 -53
- package/dist/profile/service.js.map +1 -1
- package/dist/roles.js +28 -5
- package/dist/roles.js.map +1 -1
- package/dist/server.js +80 -98
- package/dist/server.js.map +1 -1
- package/dist/team/errors.js +4 -13
- package/dist/team/errors.js.map +1 -1
- package/dist/team/schemas.js +5 -18
- package/dist/team/schemas.js.map +1 -1
- package/dist/team/service.js +80 -157
- package/dist/team/service.js.map +1 -1
- package/dist/trusted-origins.js +25 -0
- package/dist/trusted-origins.js.map +1 -0
- package/dist/workspace/errors.js +4 -14
- package/dist/workspace/errors.js.map +1 -1
- package/dist/workspace/schemas.js +2 -18
- package/dist/workspace/schemas.js.map +1 -1
- package/dist/workspace/service.js +31 -91
- package/dist/workspace/service.js.map +1 -1
- package/dist/workspace-bootstrap.js +33 -0
- package/dist/workspace-bootstrap.js.map +1 -0
- package/dist/workspace-init.js +46 -51
- package/dist/workspace-init.js.map +1 -1
- package/dist/workspace-slug.js +29 -0
- package/dist/workspace-slug.js.map +1 -0
- package/package.json +36 -14
- package/src/client.ts +6 -16
- package/src/edge.ts +22 -28
- package/src/guard-error.ts +36 -0
- package/src/helpers.ts +99 -114
- package/src/impersonation.ts +52 -17
- package/src/index.ts +15 -10
- package/src/invitation-links.ts +15 -0
- package/src/onboarding/errors.ts +5 -17
- package/src/onboarding/schemas.ts +3 -15
- package/src/onboarding/service.ts +11 -65
- package/src/org-api.ts +9 -66
- package/src/profile/errors.ts +5 -17
- package/src/profile/schemas.ts +10 -10
- package/src/profile/service.ts +39 -50
- package/src/roles.ts +36 -5
- package/src/server.ts +86 -104
- package/src/team/errors.ts +4 -13
- package/src/team/schemas.ts +5 -18
- package/src/team/service.ts +100 -174
- package/src/trusted-origins.ts +26 -0
- package/src/workspace/errors.ts +4 -14
- package/src/workspace/schemas.ts +2 -18
- package/src/workspace/service.ts +40 -92
- package/src/workspace-bootstrap.ts +57 -0
- package/src/workspace-init.ts +55 -56
- package/src/workspace-slug.ts +32 -0
|
@@ -1,78 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workspace
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Mirrors `createTeamService(ports)` (./../team/service.ts) one
|
|
8
|
-
* directory over: a factory over optional ports, so this package's
|
|
9
|
-
* allowlisted dependency (`@intelligo-dev/core` only — see
|
|
10
|
-
* tests/architecture/dependency-direction.test.ts) never grows to
|
|
11
|
-
* include billing. A consumer binds that in at its composition root:
|
|
12
|
-
*
|
|
13
|
-
* const workspaceService = createWorkspaceService({
|
|
14
|
-
* checkWorkspaceLimit: ..., // adapts @intelligo-dev/billing's checkPlanLimit
|
|
15
|
-
* });
|
|
16
|
-
*
|
|
17
|
-
* Authorization (`requireAuth`/`requireWorkspace`/`requireRole`) lives
|
|
18
|
-
* INSIDE each method, not at the transport. Every recognized failure
|
|
19
|
-
* throws `WorkspaceServiceError` with a stable `code` — no
|
|
20
|
-
* revalidatePath/Sentry/next-intl/toast here; that shaping is the
|
|
21
|
-
* transport's job (a Server Action, a route handler).
|
|
22
|
-
*
|
|
23
|
-
* ---------------------------------------------------------------------
|
|
24
|
-
* Why `checkWorkspaceLimit` takes a `userId`, not a `workspaceId`
|
|
25
|
-
* ---------------------------------------------------------------------
|
|
26
|
-
* `createWorkspace` has no workspace to check the plan of yet — it is
|
|
27
|
-
* the thing being created. Ignite's original action worked around this
|
|
28
|
-
* by reading the caller's *existing* workspaces and using the first
|
|
29
|
-
* one's id to look up a plan via `@intelligo-dev/billing`'s
|
|
30
|
-
* `checkPlanLimit(workspaceId, "workspaces", currentCount)`, i.e. it
|
|
31
|
-
* borrowed an arbitrary existing workspace's subscription as a stand-in
|
|
32
|
-
* for "the caller's plan". That borrowing is a binding-layer concern,
|
|
33
|
-
* not a service-layer one: this service only knows the caller's
|
|
34
|
-
* `userId` and how many workspaces they already have. The composition
|
|
35
|
-
* root's binding (`checkWorkspaceLimit`) is where a consumer decides
|
|
36
|
-
* how to resolve "this user's plan" — by reading their first workspace
|
|
37
|
-
* the same way ignite did, or by a real per-user plan lookup if one
|
|
38
|
-
* exists.
|
|
39
|
-
*
|
|
40
|
-
* ---------------------------------------------------------------------
|
|
41
|
-
* The two Better-Auth pitfalls (already solved in ../team/service.ts —
|
|
42
|
-
* copied here rather than re-derived)
|
|
43
|
-
* ---------------------------------------------------------------------
|
|
44
|
-
* 1. Method-name mismatch: the typed `orgApi` wrapper (../org-api.ts)
|
|
45
|
-
* exists precisely because `auth.api`'s organization-plugin methods
|
|
46
|
-
* are keyed by their own camelCase *server* id, which does not
|
|
47
|
-
* always match the HTTP path or the client SDK name. This service
|
|
48
|
-
* uses `orgApi["/organization/list"]` and
|
|
49
|
-
* `orgApi["/organization/set-active"]` for the two calls the
|
|
50
|
-
* wrapper covers, and calls `auth.api.{createOrganization,
|
|
51
|
-
* updateOrganization, deleteOrganization, getFullOrganization}`
|
|
52
|
-
* directly for the base organization CRUD surface, which is not
|
|
53
|
-
* part of `orgApi`'s typed table (the same approach
|
|
54
|
-
* `../team/service.ts` takes for `getFullOrganization`). Ignite's
|
|
55
|
-
* original `actions/workspace.ts` already called these four by
|
|
56
|
-
* their correct `auth.api` names directly (it never went through a
|
|
57
|
-
* path-keyed cast), so there is no method-name bug to fix here.
|
|
58
|
-
* 2. `sessions.activeOrganizationId` does not exist on this repo's
|
|
59
|
-
* Drizzle schema. Ignite's original `getActiveWorkspace()` called
|
|
60
|
-
* `auth.api.getFullOrganization({ headers })` with no
|
|
61
|
-
* `organizationId` — the exact bug documented in
|
|
62
|
-
* `../team/service.ts`'s module comment: that resolves to *no*
|
|
63
|
-
* organization regardless of what the caller most recently
|
|
64
|
-
* activated. This service's `getActiveWorkspace` instead resolves
|
|
65
|
-
* the caller's workspace via `requireWorkspace()` first (which has
|
|
66
|
-
* its own explicit-id fallback) and passes that id explicitly to
|
|
67
|
-
* `getFullOrganization`.
|
|
2
|
+
* Workspace listing, creation, switching, editing and deletion. Each method
|
|
3
|
+
* authorizes itself and throws `WorkspaceServiceError`; the plan limit
|
|
4
|
+
* arrives as the `checkWorkspaceLimit` port bound at the composition root.
|
|
5
|
+
* `getFullOrganization` always gets an explicit `organizationId`, because a
|
|
6
|
+
* bare call resolves to nothing when the session has no active organization.
|
|
68
7
|
*/
|
|
69
|
-
import {
|
|
8
|
+
import { getRequestHeaders } from "@intelligo-dev/core/request-context";
|
|
70
9
|
import { createLogger } from "@intelligo-dev/core/logger";
|
|
71
|
-
import { auth } from "../server";
|
|
72
|
-
import { requireAuth, requireRole, requireWorkspace } from "../helpers";
|
|
73
|
-
import { orgApi } from "../org-api";
|
|
74
|
-
import { createWorkspaceSchema, updateWorkspaceSchema, } from "./schemas";
|
|
75
|
-
import { WorkspaceServiceError, isWorkspaceServiceError } from "./errors";
|
|
10
|
+
import { auth } from "../server.js";
|
|
11
|
+
import { requireAuth, requireRole, requireWorkspace } from "../helpers.js";
|
|
12
|
+
import { orgApi } from "../org-api.js";
|
|
13
|
+
import { createWorkspaceSchema, updateWorkspaceSchema, } from "./schemas.js";
|
|
14
|
+
import { WorkspaceServiceError, isWorkspaceServiceError } from "./errors.js";
|
|
76
15
|
const log = createLogger("WorkspaceService");
|
|
77
16
|
function errorMessage(error) {
|
|
78
17
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -138,12 +77,10 @@ export function createWorkspaceService(ports = {}) {
|
|
|
138
77
|
throw new WorkspaceServiceError("provider_error", `Better-Auth organization API call failed (${context})`, { cause: error });
|
|
139
78
|
}
|
|
140
79
|
}
|
|
141
|
-
/**
|
|
142
|
-
* List all workspaces for the current user.
|
|
143
|
-
*/
|
|
80
|
+
/** Every workspace the current user belongs to. */
|
|
144
81
|
async function listWorkspaces() {
|
|
145
82
|
await callRequireAuth();
|
|
146
|
-
const hdrs = await
|
|
83
|
+
const hdrs = await getRequestHeaders();
|
|
147
84
|
const orgs = await callOrgApi("list", () => orgApi["/organization/list"]({ headers: hdrs }));
|
|
148
85
|
return orgs ?? [];
|
|
149
86
|
}
|
|
@@ -157,7 +94,7 @@ export function createWorkspaceService(ports = {}) {
|
|
|
157
94
|
async function createWorkspace(input) {
|
|
158
95
|
const { user } = await callRequireAuth();
|
|
159
96
|
const validated = parseInput(createWorkspaceSchema, input);
|
|
160
|
-
const hdrs = await
|
|
97
|
+
const hdrs = await getRequestHeaders();
|
|
161
98
|
const existing = await callOrgApi("list", () => orgApi["/organization/list"]({ headers: hdrs }));
|
|
162
99
|
const existingCount = existing?.length ?? 0;
|
|
163
100
|
if (ports.checkWorkspaceLimit && existingCount > 0) {
|
|
@@ -182,27 +119,23 @@ export function createWorkspaceService(ports = {}) {
|
|
|
182
119
|
}));
|
|
183
120
|
return org;
|
|
184
121
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Switch the caller's active workspace.
|
|
187
|
-
*/
|
|
122
|
+
/** Switch the caller's active workspace. */
|
|
188
123
|
async function switchWorkspace(organizationId) {
|
|
189
124
|
await callRequireAuth();
|
|
190
125
|
if (typeof organizationId !== "string" || organizationId.length === 0) {
|
|
191
126
|
throw new WorkspaceServiceError("invalid_input", "Invalid workspace id");
|
|
192
127
|
}
|
|
193
|
-
const hdrs = await
|
|
128
|
+
const hdrs = await getRequestHeaders();
|
|
194
129
|
await callOrgApi("set-active", () => orgApi["/organization/set-active"]({
|
|
195
130
|
headers: hdrs,
|
|
196
131
|
body: { organizationId },
|
|
197
132
|
}));
|
|
198
133
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Update workspace settings (name/slug/logo). Owner/admin only.
|
|
201
|
-
*/
|
|
134
|
+
/** Update workspace settings (name/slug/logo). Owner/admin only. */
|
|
202
135
|
async function updateWorkspace(input) {
|
|
203
136
|
const { workspace } = await callRequireRole(["owner", "admin"]);
|
|
204
137
|
const validated = parseInput(updateWorkspaceSchema, input);
|
|
205
|
-
const hdrs = await
|
|
138
|
+
const hdrs = await getRequestHeaders();
|
|
206
139
|
const updateData = {};
|
|
207
140
|
if (validated.name)
|
|
208
141
|
updateData.name = validated.name;
|
|
@@ -225,7 +158,15 @@ export function createWorkspaceService(ports = {}) {
|
|
|
225
158
|
*/
|
|
226
159
|
async function deleteWorkspace() {
|
|
227
160
|
const { workspace } = await callRequireRole(["owner"]);
|
|
228
|
-
const hdrs = await
|
|
161
|
+
const hdrs = await getRequestHeaders();
|
|
162
|
+
if (ports.beforeDeleteWorkspace) {
|
|
163
|
+
try {
|
|
164
|
+
await ports.beforeDeleteWorkspace(workspace.id);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
throw new WorkspaceServiceError("provider_error", `Could not prepare the workspace for deletion: ${errorMessage(error)}`, { cause: error });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
229
170
|
await callOrgApi("delete", () => auth.api.deleteOrganization({
|
|
230
171
|
headers: hdrs,
|
|
231
172
|
body: { organizationId: workspace.id },
|
|
@@ -239,14 +180,13 @@ export function createWorkspaceService(ports = {}) {
|
|
|
239
180
|
}
|
|
240
181
|
}
|
|
241
182
|
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* relying on a non-existent `sessions.activeOrganizationId` fallback.
|
|
183
|
+
* The caller's active workspace, fully resolved. The id comes from
|
|
184
|
+
* `requireWorkspace()`, which falls back to the first workspace when the
|
|
185
|
+
* session has none active.
|
|
246
186
|
*/
|
|
247
187
|
async function getActiveWorkspace() {
|
|
248
188
|
const { workspace } = await callRequireWorkspace();
|
|
249
|
-
const hdrs = await
|
|
189
|
+
const hdrs = await getRequestHeaders();
|
|
250
190
|
const org = (await callOrgApi("getFullOrganization", () => auth.api.getFullOrganization({
|
|
251
191
|
headers: hdrs,
|
|
252
192
|
query: { organizationId: workspace.id },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/workspace/service.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/workspace/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What runs once a user's personal workspace exists: trial credits, a
|
|
3
|
+
* welcome note, a referral bonus. The product decides, so auth takes it
|
|
4
|
+
* as a handler set from the composition root and never imports billing.
|
|
5
|
+
*
|
|
6
|
+
* The workspace is created by the `user.create` hook at signup, or by
|
|
7
|
+
* `ensureUserWorkspace` when that hook did not get to it; both call the
|
|
8
|
+
* handler, for the one workspace each of them created.
|
|
9
|
+
*/
|
|
10
|
+
import { createLogger } from "@intelligo-dev/core/logger";
|
|
11
|
+
import { createRegistryRef } from "@intelligo-dev/core/registry";
|
|
12
|
+
const log = createLogger("WorkspaceBootstrap");
|
|
13
|
+
const handler = createRegistryRef("auth/workspace-created", null);
|
|
14
|
+
/** Set from the composition root; the last call wins. */
|
|
15
|
+
export function setWorkspaceCreatedHandler(next) {
|
|
16
|
+
handler.set(next);
|
|
17
|
+
}
|
|
18
|
+
export function clearWorkspaceCreatedHandler() {
|
|
19
|
+
handler.set(null);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs the handler without waiting for it: a failed bootstrap is
|
|
23
|
+
* logged and never fails the signup that created the workspace.
|
|
24
|
+
*/
|
|
25
|
+
export function workspaceCreated(created, run = handler.get()) {
|
|
26
|
+
if (!run)
|
|
27
|
+
return;
|
|
28
|
+
run(created).catch((error) => log.error("Workspace bootstrap failed", {
|
|
29
|
+
workspaceId: created.workspaceId,
|
|
30
|
+
error: error instanceof Error ? error.message : String(error),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=workspace-bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-bootstrap.js","sourceRoot":"","sources":["../src/workspace-bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,MAAM,GAAG,GAAG,YAAY,CAAC,oBAAoB,CAAC,CAAC;AAY/C,MAAM,OAAO,GAAG,iBAAiB,CAC/B,wBAAwB,EACxB,IAAI,CACL,CAAC;AAEF,yDAAyD;AACzD,MAAM,UAAU,0BAA0B,CACxC,IAA6B;IAE7B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAyB,EACzB,MAAsC,OAAO,CAAC,GAAG,EAAE;IAEnD,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,GAAG,CAAC,KAAK,CAAC,4BAA4B,EAAE;QACtC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KAC9D,CAAC,CACH,CAAC;AACJ,CAAC"}
|
package/dist/workspace-init.js
CHANGED
|
@@ -1,51 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Idempotent - safe to call on every page load.
|
|
7
|
-
*
|
|
8
|
-
* Pattern: "Belt-and-suspenders" approach consistent with Phase 9.
|
|
9
|
-
* Better-Auth doesn't provide a reliable hook for post-signup workspace creation,
|
|
10
|
-
* so we check + create on every authenticated page load.
|
|
11
|
-
*
|
|
12
|
-
* Dependency Inversion (Phase 42, PKG-01 preparation):
|
|
13
|
-
* Post-creation logic (e.g., trial provisioning) is injected via callback,
|
|
14
|
-
* so this module has ZERO billing imports. The actual billing call lives
|
|
15
|
-
* in the consumer application's authenticated layout, which already
|
|
16
|
-
* imports billing.
|
|
17
|
-
* This breaks the auth → billing circular dependency before Phase 43 extraction.
|
|
2
|
+
* Ensures every authenticated user has a workspace; called from the
|
|
3
|
+
* authenticated layout on every render. Post-creation work (trial
|
|
4
|
+
* provisioning) is the handler set with `setWorkspaceCreatedHandler`,
|
|
5
|
+
* so auth never imports billing.
|
|
18
6
|
*/
|
|
19
|
-
import { auth } from "./server";
|
|
7
|
+
import { auth } from "./server.js";
|
|
20
8
|
import { createLogger } from "@intelligo-dev/core/logger";
|
|
9
|
+
import { personalWorkspaceSlug } from "./workspace-slug.js";
|
|
10
|
+
import { workspaceCreated, } from "./workspace-bootstrap.js";
|
|
21
11
|
const log = createLogger("WorkspaceInit");
|
|
22
12
|
/**
|
|
23
|
-
* Ensure user has at least one workspace
|
|
13
|
+
* Ensure the user has at least one workspace.
|
|
24
14
|
*
|
|
25
|
-
* If
|
|
26
|
-
*
|
|
27
|
-
* user.create hook
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* Idempotent - safe to call repeatedly. Returns quickly if workspace exists.
|
|
15
|
+
* If the session already has a valid active workspace, returns it
|
|
16
|
+
* untouched; otherwise sets the first workspace as active. If none
|
|
17
|
+
* exist, creates a personal workspace, in case the user.create hook has not
|
|
18
|
+
* completed yet or the user was created outside Better-Auth. Idempotent.
|
|
31
19
|
*
|
|
32
20
|
* @param user - Authenticated user from session
|
|
33
21
|
* @param headers - Request headers (required for Better-Auth API)
|
|
34
|
-
* @param options.onWorkspaceCreated -
|
|
35
|
-
*
|
|
36
|
-
*
|
|
22
|
+
* @param options.onWorkspaceCreated - Runs after this call creates a workspace, in place of
|
|
23
|
+
* the handler set with `setWorkspaceCreatedHandler`. Fire-and-forget: a failure is logged,
|
|
24
|
+
* never thrown.
|
|
37
25
|
* @returns The organization ID that was set as active (useful for immediate access before session updates)
|
|
38
26
|
*/
|
|
39
27
|
export async function ensureUserWorkspace(user, headers, options) {
|
|
40
28
|
log.info("Starting workspace check", { email: user.email });
|
|
41
|
-
// Check if user has any organizations
|
|
42
29
|
const orgs = await auth.api.listOrganizations({
|
|
43
30
|
headers,
|
|
44
31
|
});
|
|
45
32
|
log.info("Found organizations", { count: orgs?.length ?? 0 });
|
|
46
33
|
if (orgs && orgs.length > 0 && orgs[0]) {
|
|
47
|
-
|
|
48
|
-
//
|
|
34
|
+
// Keep a workspace the user switched to while they are still a
|
|
35
|
+
// member; resetting to orgs[0] would undo every switch on re-render.
|
|
36
|
+
const current = await auth.api.getSession({ headers });
|
|
37
|
+
const active = current?.session?.activeOrganizationId;
|
|
38
|
+
if (active && orgs.some((org) => org.id === active)) {
|
|
39
|
+
log.debug("Active workspace still valid, keeping it", { orgId: active });
|
|
40
|
+
return active;
|
|
41
|
+
}
|
|
42
|
+
log.debug("No valid active workspace, setting first as active");
|
|
49
43
|
try {
|
|
50
44
|
await auth.api.setActiveOrganization({
|
|
51
45
|
headers,
|
|
@@ -61,25 +55,20 @@ export async function ensureUserWorkspace(user, headers, options) {
|
|
|
61
55
|
}
|
|
62
56
|
return orgs[0].id;
|
|
63
57
|
}
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// if both this fallback and the hook fire for the same signup.
|
|
58
|
+
// No workspace yet: create one. The unique index on organization.slug
|
|
59
|
+
// prevents duplicates when the hook or a concurrent request also creates
|
|
60
|
+
// it, because `personalWorkspaceSlug` is derived from the user; the loser
|
|
61
|
+
// takes the adopt-the-winner path below.
|
|
69
62
|
log.warn("No workspace found, creating one (hook may still fire)", {
|
|
70
63
|
email: user.email,
|
|
71
64
|
});
|
|
72
|
-
const slug = ((user.email || "user").split("@")[0] || "user")
|
|
73
|
-
.toLowerCase()
|
|
74
|
-
.replace(/[^a-z0-9-]/g, "-")
|
|
75
|
-
.slice(0, 30);
|
|
76
65
|
let workspaceId;
|
|
77
66
|
try {
|
|
78
67
|
const result = await auth.api.createOrganization({
|
|
79
68
|
headers,
|
|
80
69
|
body: {
|
|
81
70
|
name: `${user.name || "User"}'s Workspace`,
|
|
82
|
-
slug:
|
|
71
|
+
slug: personalWorkspaceSlug(user.email, user.id),
|
|
83
72
|
userId: user.id,
|
|
84
73
|
},
|
|
85
74
|
});
|
|
@@ -90,9 +79,8 @@ export async function ensureUserWorkspace(user, headers, options) {
|
|
|
90
79
|
log.info("Created fallback workspace", { workspaceId });
|
|
91
80
|
}
|
|
92
81
|
catch (error) {
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
// List orgs again — if one now exists, use it.
|
|
82
|
+
// A concurrent creator may have won the unique index; if a workspace
|
|
83
|
+
// now exists, use it.
|
|
96
84
|
const orgsAfter = await auth.api.listOrganizations({ headers });
|
|
97
85
|
if (orgsAfter && orgsAfter.length > 0 && orgsAfter[0]) {
|
|
98
86
|
log.info("Workspace created concurrently by hook, using it");
|
|
@@ -102,20 +90,27 @@ export async function ensureUserWorkspace(user, headers, options) {
|
|
|
102
90
|
});
|
|
103
91
|
return orgsAfter[0].id;
|
|
104
92
|
}
|
|
105
|
-
// Genuine failure
|
|
106
93
|
log.error("Failed to create fallback workspace", {
|
|
107
94
|
error: error instanceof Error ? error.message : String(error),
|
|
108
95
|
});
|
|
109
96
|
throw error;
|
|
110
97
|
}
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
98
|
+
// Point the session at the new workspace, or later requests that resolve
|
|
99
|
+
// one from the session alone find none. Not fatal (the next request sets
|
|
100
|
+
// it), so a failure is logged rather than thrown.
|
|
101
|
+
try {
|
|
102
|
+
await auth.api.setActiveOrganization({
|
|
103
|
+
headers,
|
|
104
|
+
body: { organizationId: workspaceId },
|
|
105
|
+
});
|
|
106
|
+
log.info("Set new workspace active", { workspaceId });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
log.error("Failed to set the new workspace active", {
|
|
110
|
+
error: error instanceof Error ? error.message : String(error),
|
|
111
|
+
});
|
|
118
112
|
}
|
|
113
|
+
workspaceCreated({ workspaceId, userId: user.id, email: user.email }, options?.onWorkspaceCreated);
|
|
119
114
|
return workspaceId;
|
|
120
115
|
}
|
|
121
116
|
//# sourceMappingURL=workspace-init.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-init.js","sourceRoot":"","sources":["../src/workspace-init.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"workspace-init.js","sourceRoot":"","sources":["../src/workspace-init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slug an auto-provisioned personal workspace gets.
|
|
3
|
+
*
|
|
4
|
+
* Deterministic on purpose. Two paths provision a first workspace —
|
|
5
|
+
* the `user.create` hook in `server.ts` and the layout's
|
|
6
|
+
* `ensureUserWorkspace` fallback — and several requests can reach the
|
|
7
|
+
* fallback at once, because the `(app)` layout re-renders for every
|
|
8
|
+
* request that enters the group (the document, each prefetched sibling
|
|
9
|
+
* route, an error-boundary retry).
|
|
10
|
+
*
|
|
11
|
+
* A slug derived from the user makes `organization.slug`'s unique index
|
|
12
|
+
* the arbiter: the first caller wins, every other one gets
|
|
13
|
+
* `ORGANIZATION_ALREADY_EXISTS` and adopts the winner's workspace. A
|
|
14
|
+
* per-attempt suffix would let each racer create a workspace of its own.
|
|
15
|
+
*
|
|
16
|
+
* Only the automatic personal workspace is named this way; a workspace
|
|
17
|
+
* someone creates by hand names itself.
|
|
18
|
+
*/
|
|
19
|
+
export function personalWorkspaceSlug(email, userId) {
|
|
20
|
+
const base = ((email || "user").split("@")[0] || "user").slice(0, 30);
|
|
21
|
+
// The id fragment is normalised with the rest: Better-Auth ids are
|
|
22
|
+
// mixed-case, and a slug the workspace settings form refuses
|
|
23
|
+
// (`[a-z0-9-]+`) makes the personal workspace impossible to rename.
|
|
24
|
+
return toSlug(`${base}-${userId.slice(0, 8)}`);
|
|
25
|
+
}
|
|
26
|
+
function toSlug(value) {
|
|
27
|
+
return value.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=workspace-slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-slug.js","sourceRoot":"","sources":["../src/workspace-slug.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAgC,EAChC,MAAc;IAEd,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtE,mEAAmE;IACnE,6DAA6D;IAC7D,oEAAoE;IACpE,OAAO,MAAM,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intelligo-dev/auth",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.14",
|
|
4
|
+
"description": "Multi-tenant authentication on Better-Auth: sign-up, workspaces, invitations and role-based access control.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"intelligo",
|
|
7
|
+
"ai-saas",
|
|
8
|
+
"saas",
|
|
9
|
+
"typescript",
|
|
10
|
+
"authentication",
|
|
11
|
+
"better-auth",
|
|
12
|
+
"multi-tenant",
|
|
13
|
+
"rbac",
|
|
14
|
+
"workspaces"
|
|
15
|
+
],
|
|
4
16
|
"license": "Apache-2.0",
|
|
17
|
+
"author": "Turtuvshin Byambaa <toroo.byamba@gmail.com>",
|
|
5
18
|
"repository": {
|
|
6
19
|
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/intelligo-
|
|
20
|
+
"url": "git+https://github.com/intelligo-dev/intelligo.git",
|
|
8
21
|
"directory": "packages/auth"
|
|
9
22
|
},
|
|
10
|
-
"homepage": "https://
|
|
23
|
+
"homepage": "https://intelligo.dev/docs/packages/auth",
|
|
11
24
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/intelligo-
|
|
25
|
+
"url": "https://github.com/intelligo-dev/intelligo/issues"
|
|
13
26
|
},
|
|
14
27
|
"type": "module",
|
|
15
28
|
"exports": {
|
|
@@ -27,32 +40,41 @@
|
|
|
27
40
|
}
|
|
28
41
|
},
|
|
29
42
|
"dependencies": {
|
|
30
|
-
"better-auth": "~1.6.30",
|
|
31
|
-
"drizzle-orm": "^0.45.1",
|
|
32
43
|
"server-only": "^0.0.1",
|
|
33
|
-
"
|
|
34
|
-
"@intelligo-dev/core": "1.0.0-beta.1"
|
|
44
|
+
"@intelligo-dev/core": "1.0.0-beta.14"
|
|
35
45
|
},
|
|
36
46
|
"peerDependencies": {
|
|
37
|
-
"
|
|
47
|
+
"better-auth": "~1.6.30",
|
|
48
|
+
"drizzle-orm": "^0.45.1",
|
|
49
|
+
"zod": "^3.25.76 || ^4.0.0"
|
|
38
50
|
},
|
|
39
51
|
"devDependencies": {
|
|
40
|
-
"@types/react": "
|
|
41
|
-
"
|
|
42
|
-
"
|
|
52
|
+
"@types/react": "19.2.18",
|
|
53
|
+
"better-auth": "~1.6.33",
|
|
54
|
+
"drizzle-orm": "^0.45.2",
|
|
55
|
+
"typescript": "^5.9.3",
|
|
56
|
+
"zod": "^4.6.5"
|
|
43
57
|
},
|
|
44
58
|
"files": [
|
|
45
59
|
"dist",
|
|
46
60
|
"src",
|
|
47
61
|
"!src/**/*.test.ts",
|
|
48
|
-
"!src/**/*.test.tsx"
|
|
62
|
+
"!src/**/*.test.tsx",
|
|
63
|
+
"!src/**/__tests__/**",
|
|
64
|
+
"LICENSE",
|
|
65
|
+
"NOTICE",
|
|
66
|
+
"README.md",
|
|
67
|
+
"!dist/**/*.tsbuildinfo"
|
|
49
68
|
],
|
|
50
69
|
"publishConfig": {
|
|
51
70
|
"access": "public"
|
|
52
71
|
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=22.14"
|
|
74
|
+
},
|
|
53
75
|
"scripts": {
|
|
54
76
|
"type-check": "tsc --noEmit",
|
|
55
77
|
"lint": "eslint .",
|
|
56
|
-
"build": "tsc -p tsconfig.build.json"
|
|
78
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/fix-esm-extensions.mjs dist"
|
|
57
79
|
}
|
|
58
80
|
}
|
package/src/client.ts
CHANGED
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Better-Auth
|
|
3
|
-
*
|
|
4
|
-
* Client-side authentication utilities for React components.
|
|
5
|
-
* This module uses "better-auth/react" which is a client-side module.
|
|
6
|
-
*
|
|
7
|
-
* IMPORTANT: Only import this in "use client" components.
|
|
8
|
-
* For server-side auth, use @intelligo-dev/auth (server.ts)
|
|
2
|
+
* The Better-Auth React client. Import only from "use client" components;
|
|
3
|
+
* server code uses `@intelligo-dev/auth`.
|
|
9
4
|
*/
|
|
10
5
|
|
|
11
6
|
import { createAuthClient } from "better-auth/react";
|
|
12
7
|
import { organizationClient } from "better-auth/client/plugins";
|
|
13
8
|
|
|
14
|
-
// No `baseURL`: the client and
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// baseURL here — the dev port has drifted before (3000 -> 3001 -> 4000)
|
|
19
|
-
// and an absolute cross-origin URL is blocked by the app's CSP
|
|
20
|
-
// (`connect-src 'self'`) whenever it doesn't match the port the app is
|
|
21
|
-
// actually served on.
|
|
9
|
+
// No `baseURL`: the client and server share an origin (`/api/auth/*`), so
|
|
10
|
+
// Better-Auth defaults to `window.location.origin`. An absolute URL is
|
|
11
|
+
// blocked by the app's CSP (`connect-src 'self'`) whenever its port differs
|
|
12
|
+
// from the one the app is served on.
|
|
22
13
|
export const authClient = createAuthClient({
|
|
23
14
|
plugins: [organizationClient()],
|
|
24
15
|
});
|
|
25
16
|
|
|
26
|
-
// Export commonly used hooks and methods for convenience
|
|
27
17
|
export const { useSession, signIn, signUp, signOut } = authClient;
|