2025-06-04 02:28:38 +00:00
|
|
|
import type { User as AuthJsUser } from "next-auth";
|
|
|
|
|
import { env } from "@/env.mjs";
|
|
|
|
|
import { prisma } from "@/prisma";
|
|
|
|
|
import { OrgRole } from "@sourcebot/db";
|
|
|
|
|
import { SINGLE_TENANT_ORG_DOMAIN, SINGLE_TENANT_ORG_ID } from "@/lib/constants";
|
2025-06-17 21:04:25 +00:00
|
|
|
import { hasEntitlement } from "@sourcebot/shared";
|
2025-06-04 02:28:38 +00:00
|
|
|
import { isServiceError } from "@/lib/utils";
|
|
|
|
|
import { ServiceErrorException } from "@/lib/serviceError";
|
|
|
|
|
import { createAccountRequest } from "@/actions";
|
2025-06-18 17:50:36 +00:00
|
|
|
import { handleJITProvisioning } from "@/ee/features/sso/sso";
|
2025-06-04 02:28:38 +00:00
|
|
|
import { createLogger } from "@sourcebot/logger";
|
2025-06-18 17:50:36 +00:00
|
|
|
import { getAuditService } from "@/ee/features/audit/factory";
|
2025-06-04 02:28:38 +00:00
|
|
|
|
|
|
|
|
const logger = createLogger('web-auth-utils');
|
2025-06-18 17:50:36 +00:00
|
|
|
const auditService = getAuditService();
|
2025-06-04 02:28:38 +00:00
|
|
|
|
|
|
|
|
export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
|
2025-06-18 17:50:36 +00:00
|
|
|
if (!user.id) {
|
|
|
|
|
logger.error("User ID is undefined on user creation");
|
|
|
|
|
await auditService.createAudit({
|
|
|
|
|
action: "user.creation_failed",
|
|
|
|
|
actor: {
|
|
|
|
|
id: "undefined",
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
target: {
|
|
|
|
|
id: "undefined",
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
orgId: SINGLE_TENANT_ORG_ID, // TODO(mt)
|
|
|
|
|
metadata: {
|
|
|
|
|
message: "User ID is undefined on user creation"
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
throw new Error("User ID is undefined on user creation");
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-04 02:28:38 +00:00
|
|
|
// In single-tenant mode, we assign the first user to sign
|
|
|
|
|
// up as the owner of the default org.
|
2025-06-18 17:50:36 +00:00
|
|
|
if (env.SOURCEBOT_TENANCY_MODE === 'single') {
|
2025-06-04 02:28:38 +00:00
|
|
|
const defaultOrg = await prisma.org.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
id: SINGLE_TENANT_ORG_ID,
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
members: {
|
|
|
|
|
where: {
|
|
|
|
|
role: {
|
|
|
|
|
not: OrgRole.GUEST,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-18 17:50:36 +00:00
|
|
|
if (defaultOrg === null) {
|
|
|
|
|
await auditService.createAudit({
|
|
|
|
|
action: "user.creation_failed",
|
|
|
|
|
actor: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
target: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
orgId: SINGLE_TENANT_ORG_ID,
|
|
|
|
|
metadata: {
|
|
|
|
|
message: "Default org not found on single tenant user creation"
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-06-04 02:28:38 +00:00
|
|
|
throw new Error("Default org not found on single tenant user creation");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only the first user to sign up will be an owner of the default org.
|
|
|
|
|
const isFirstUser = defaultOrg.members.length === 0;
|
|
|
|
|
if (isFirstUser) {
|
|
|
|
|
await prisma.$transaction(async (tx) => {
|
|
|
|
|
await tx.org.update({
|
|
|
|
|
where: {
|
|
|
|
|
id: SINGLE_TENANT_ORG_ID,
|
|
|
|
|
},
|
|
|
|
|
data: {
|
|
|
|
|
members: {
|
|
|
|
|
create: {
|
|
|
|
|
role: OrgRole.OWNER,
|
|
|
|
|
user: {
|
|
|
|
|
connect: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await tx.user.update({
|
|
|
|
|
where: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
},
|
|
|
|
|
data: {
|
|
|
|
|
pendingApproval: false,
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-06-18 17:50:36 +00:00
|
|
|
|
|
|
|
|
await auditService.createAudit({
|
|
|
|
|
action: "user.owner_created",
|
|
|
|
|
actor: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
orgId: SINGLE_TENANT_ORG_ID,
|
|
|
|
|
target: {
|
|
|
|
|
id: SINGLE_TENANT_ORG_ID.toString(),
|
|
|
|
|
type: "org"
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-06-04 02:28:38 +00:00
|
|
|
} else {
|
|
|
|
|
// TODO(auth): handle multi tenant case
|
|
|
|
|
if (env.AUTH_EE_ENABLE_JIT_PROVISIONING === 'true' && hasEntitlement("sso")) {
|
2025-06-18 17:50:36 +00:00
|
|
|
const res = await handleJITProvisioning(user.id, SINGLE_TENANT_ORG_DOMAIN);
|
2025-06-04 02:28:38 +00:00
|
|
|
if (isServiceError(res)) {
|
|
|
|
|
logger.error(`Failed to provision user ${user.id} for org ${SINGLE_TENANT_ORG_DOMAIN}: ${res.message}`);
|
2025-06-18 17:50:36 +00:00
|
|
|
await auditService.createAudit({
|
|
|
|
|
action: "user.jit_provisioning_failed",
|
|
|
|
|
actor: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
target: {
|
|
|
|
|
id: SINGLE_TENANT_ORG_ID.toString(),
|
|
|
|
|
type: "org"
|
|
|
|
|
},
|
|
|
|
|
orgId: SINGLE_TENANT_ORG_ID,
|
|
|
|
|
metadata: {
|
|
|
|
|
message: `Failed to provision user ${user.id} for org ${SINGLE_TENANT_ORG_DOMAIN}: ${res.message}`
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-06-04 02:28:38 +00:00
|
|
|
throw new ServiceErrorException(res);
|
|
|
|
|
}
|
2025-06-18 17:50:36 +00:00
|
|
|
|
|
|
|
|
await auditService.createAudit({
|
|
|
|
|
action: "user.jit_provisioned",
|
|
|
|
|
actor: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
target: {
|
|
|
|
|
id: SINGLE_TENANT_ORG_ID.toString(),
|
|
|
|
|
type: "org"
|
|
|
|
|
},
|
|
|
|
|
orgId: SINGLE_TENANT_ORG_ID,
|
|
|
|
|
});
|
2025-06-04 02:28:38 +00:00
|
|
|
} else {
|
2025-06-18 17:50:36 +00:00
|
|
|
const res = await createAccountRequest(user.id, SINGLE_TENANT_ORG_DOMAIN);
|
2025-06-04 02:28:38 +00:00
|
|
|
if (isServiceError(res)) {
|
|
|
|
|
logger.error(`Failed to provision user ${user.id} for org ${SINGLE_TENANT_ORG_DOMAIN}: ${res.message}`);
|
2025-06-18 17:50:36 +00:00
|
|
|
await auditService.createAudit({
|
|
|
|
|
action: "user.join_request_creation_failed",
|
|
|
|
|
actor: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
target: {
|
|
|
|
|
id: SINGLE_TENANT_ORG_ID.toString(),
|
|
|
|
|
type: "org"
|
|
|
|
|
},
|
|
|
|
|
orgId: SINGLE_TENANT_ORG_ID,
|
|
|
|
|
metadata: {
|
|
|
|
|
message: res.message
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-06-04 02:28:38 +00:00
|
|
|
throw new ServiceErrorException(res);
|
|
|
|
|
}
|
2025-06-18 17:50:36 +00:00
|
|
|
|
|
|
|
|
await auditService.createAudit({
|
|
|
|
|
action: "user.join_requested",
|
|
|
|
|
actor: {
|
|
|
|
|
id: user.id,
|
|
|
|
|
type: "user"
|
|
|
|
|
},
|
|
|
|
|
orgId: SINGLE_TENANT_ORG_ID,
|
|
|
|
|
target: {
|
|
|
|
|
id: SINGLE_TENANT_ORG_ID.toString(),
|
|
|
|
|
type: "org"
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-06-04 02:28:38 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|