Skip to content

Commit 8ae8832

Browse files
committed
feat(auth): auto-join organizations for self-hosted mode
When running in self-hosted mode with autoJoinOrganizations enabled: - First user signup creates a 'default' team organization (user becomes owner) - Subsequent users are automatically added as members to all existing team orgs The auto-join logic is in a separate querier method (autoJoinTeamOrganizations) called from the auth handlers, keeping insertUser focused on user creation.
1 parent 14d35c7 commit 8ae8832

File tree

5 files changed

+56
-1
lines changed

5 files changed

+56
-1
lines changed

bun.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/api/src/routes/auth/auth.server.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ async function handleOAuthCallback(
302302
password: null,
303303
});
304304

305+
// Auto-join team organizations (self-hosted mode)
306+
if (c.env.autoJoinOrganizations) {
307+
await db.autoJoinTeamOrganizations(user.id);
308+
}
309+
305310
// consume single-use invite (mark accepted)
306311
if (usedInviteId) {
307312
try {
@@ -926,6 +931,11 @@ export default function mountAuth(server: APIServer) {
926931
email_verified: emailVerified,
927932
});
928933

934+
// Auto-join team organizations (self-hosted mode)
935+
if (c.env.autoJoinOrganizations) {
936+
await db.autoJoinTeamOrganizations(user.id);
937+
}
938+
929939
// Sync user to telemetry system (async, don't block)
930940
if (c.env.sendTelemetryEvent) {
931941
c.env

internal/api/src/server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ export interface Bindings {
236236

237237
readonly serverVersion: string;
238238

239+
/** When true, auto-add new users to all existing team organizations (self-hosted mode) */
240+
readonly autoJoinOrganizations?: boolean;
241+
239242
// Optional AWS credentials used by platform logging to CloudWatch
240243
readonly AWS_ACCESS_KEY_ID?: string;
241244
readonly AWS_SECRET_ACCESS_KEY?: string;

internal/database/src/querier.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,47 @@ export default class Querier {
200200
}
201201

202202
// selectUserByID fetches a user by their ID.
203+
204+
// autoJoinTeamOrganizations handles auto-join logic for self-hosted deployments.
205+
// If no team organizations exist, creates a "default" org with the user as owner.
206+
// Otherwise, adds the user as a member to all existing team organizations.
207+
public async autoJoinTeamOrganizations(userId: string): Promise<void> {
208+
await this.tx(async (tx) => {
209+
// Get all existing team organizations
210+
const teamOrgs = await tx.db
211+
.select({ id: organization.id })
212+
.from(organization)
213+
.where(eq(organization.kind, "organization"));
214+
215+
if (teamOrgs.length === 0) {
216+
// First user: create default org, make them owner
217+
const [defaultOrg] = await tx.db
218+
.insert(organization)
219+
.values({
220+
name: "default",
221+
kind: "organization",
222+
created_by: userId,
223+
})
224+
.returning();
225+
226+
await tx.db.insert(organization_membership).values({
227+
organization_id: defaultOrg!.id,
228+
user_id: userId,
229+
role: "owner",
230+
});
231+
} else {
232+
// Subsequent users: add as member to all existing team orgs
233+
await tx.db.insert(organization_membership).values(
234+
teamOrgs.map((org) => ({
235+
organization_id: org.id,
236+
user_id: userId,
237+
role: "member" as const,
238+
}))
239+
);
240+
}
241+
});
242+
}
243+
203244
public async selectUserByID(
204245
id: string
205246
): Promise<UserWithPersonalOrganization | undefined> {

packages/server/src/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export async function startServer(options: ServerOptions) {
130130
{
131131
AUTH_SECRET: authSecret,
132132
NODE_ENV: "development",
133+
autoJoinOrganizations: true,
133134
serverVersion: pkg.version,
134135
ONBOARDING_AGENT_BUNDLE_URL:
135136
"https://artifacts.blink.host/starter-agent/bundle.tar.gz",

0 commit comments

Comments
 (0)