mirror of
https://github.com/documenso/documenso.git
synced 2026-07-14 14:57:12 +10:00
9ac7b94d9a
Allow organisations to manage an SSO OIDC compliant portal. This method is intended to streamline the onboarding process and paves the way to allow organisations to manage their members in a more strict way.
216 lines
5.5 KiB
TypeScript
216 lines
5.5 KiB
TypeScript
import type { Prisma } from '@prisma/client';
|
|
import { OrganisationType } from '@prisma/client';
|
|
import { OrganisationMemberRole } from '@prisma/client';
|
|
|
|
import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer';
|
|
import { prisma } from '@documenso/prisma';
|
|
|
|
import { IS_BILLING_ENABLED } from '../../constants/app';
|
|
import { ORGANISATION_INTERNAL_GROUPS } from '../../constants/organisations';
|
|
import { AppErrorCode } from '../../errors/app-error';
|
|
import { AppError } from '../../errors/app-error';
|
|
import type { InternalClaim } from '../../types/subscription';
|
|
import { INTERNAL_CLAIM_ID, internalClaims } from '../../types/subscription';
|
|
import { generateDatabaseId, prefixedId } from '../../universal/id';
|
|
import { generateDefaultOrganisationSettings } from '../../utils/organisations';
|
|
import { createTeam } from '../team/create-team';
|
|
|
|
type CreateOrganisationOptions = {
|
|
userId: number;
|
|
name: string;
|
|
type: OrganisationType;
|
|
url?: string;
|
|
customerId?: string;
|
|
claim: InternalClaim;
|
|
};
|
|
|
|
export const createOrganisation = async ({
|
|
name,
|
|
url,
|
|
type,
|
|
userId,
|
|
customerId,
|
|
claim,
|
|
}: CreateOrganisationOptions) => {
|
|
let customerIdToUse = customerId;
|
|
|
|
if (!customerId && IS_BILLING_ENABLED()) {
|
|
const user = await prisma.user.findUnique({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
});
|
|
|
|
if (!user) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'User not found',
|
|
});
|
|
}
|
|
|
|
customerIdToUse = await createCustomer({
|
|
name: user.name || user.email,
|
|
email: user.email,
|
|
})
|
|
.then((customer) => customer.id)
|
|
.catch((err) => {
|
|
console.error(err);
|
|
|
|
return undefined;
|
|
});
|
|
}
|
|
|
|
return await prisma.$transaction(async (tx) => {
|
|
const organisationSetting = await tx.organisationGlobalSettings.create({
|
|
data: {
|
|
...generateDefaultOrganisationSettings(),
|
|
id: generateDatabaseId('org_setting'),
|
|
},
|
|
});
|
|
|
|
const organisationClaim = await tx.organisationClaim.create({
|
|
data: {
|
|
id: generateDatabaseId('org_claim'),
|
|
originalSubscriptionClaimId: claim.id,
|
|
...createOrganisationClaimUpsertData(claim),
|
|
},
|
|
});
|
|
|
|
const organisationAuthenticationPortal = await tx.organisationAuthenticationPortal.create({
|
|
data: {
|
|
id: generateDatabaseId('org_sso'),
|
|
enabled: false,
|
|
clientId: '',
|
|
clientSecret: '',
|
|
wellKnownUrl: '',
|
|
},
|
|
});
|
|
|
|
const orgIdAndUrl = prefixedId('org');
|
|
|
|
const organisation = await tx.organisation
|
|
.create({
|
|
data: {
|
|
id: orgIdAndUrl,
|
|
name,
|
|
type,
|
|
url: url || orgIdAndUrl,
|
|
ownerUserId: userId,
|
|
organisationGlobalSettingsId: organisationSetting.id,
|
|
organisationClaimId: organisationClaim.id,
|
|
organisationAuthenticationPortalId: organisationAuthenticationPortal.id,
|
|
groups: {
|
|
create: ORGANISATION_INTERNAL_GROUPS.map((group) => ({
|
|
...group,
|
|
id: generateDatabaseId('org_group'),
|
|
})),
|
|
},
|
|
customerId: customerIdToUse,
|
|
},
|
|
include: {
|
|
groups: true,
|
|
},
|
|
})
|
|
.catch((err) => {
|
|
if (err.code === 'P2002') {
|
|
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
|
message: 'Organisation URL already exists',
|
|
});
|
|
}
|
|
|
|
throw err;
|
|
});
|
|
|
|
const adminGroup = organisation.groups.find(
|
|
(group) => group.organisationRole === OrganisationMemberRole.ADMIN,
|
|
);
|
|
|
|
if (!adminGroup) {
|
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
|
message: 'Admin group not found',
|
|
});
|
|
}
|
|
|
|
await tx.organisationMember.create({
|
|
data: {
|
|
id: generateDatabaseId('member'),
|
|
userId,
|
|
organisationId: organisation.id,
|
|
organisationGroupMembers: {
|
|
create: {
|
|
id: generateDatabaseId('group_member'),
|
|
groupId: adminGroup.id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return organisation;
|
|
});
|
|
};
|
|
|
|
type CreatePersonalOrganisationOptions = {
|
|
userId: number;
|
|
orgUrl?: string;
|
|
throwErrorOnOrganisationCreationFailure?: boolean;
|
|
inheritMembers?: boolean;
|
|
type?: OrganisationType;
|
|
};
|
|
|
|
export const createPersonalOrganisation = async ({
|
|
userId,
|
|
orgUrl,
|
|
throwErrorOnOrganisationCreationFailure = false,
|
|
inheritMembers = true,
|
|
type = OrganisationType.PERSONAL,
|
|
}: CreatePersonalOrganisationOptions) => {
|
|
const organisation = await createOrganisation({
|
|
name: 'Personal Organisation',
|
|
userId,
|
|
url: orgUrl,
|
|
type,
|
|
claim: internalClaims[INTERNAL_CLAIM_ID.FREE],
|
|
}).catch((err) => {
|
|
console.error(err);
|
|
|
|
if (throwErrorOnOrganisationCreationFailure) {
|
|
throw err;
|
|
}
|
|
|
|
// Todo: (LOGS)
|
|
});
|
|
|
|
if (organisation) {
|
|
await createTeam({
|
|
userId,
|
|
teamName: 'Personal Team',
|
|
teamUrl: prefixedId('personal'),
|
|
organisationId: organisation.id,
|
|
inheritMembers,
|
|
}).catch((err) => {
|
|
console.error(err);
|
|
|
|
// Todo: (LOGS)
|
|
});
|
|
}
|
|
|
|
return organisation;
|
|
};
|
|
|
|
export const createOrganisationClaimUpsertData = (subscriptionClaim: InternalClaim) => {
|
|
// Done like this to ensure type errors are thrown if items are added.
|
|
const data: Omit<
|
|
Prisma.SubscriptionClaimCreateInput,
|
|
'id' | 'createdAt' | 'updatedAt' | 'locked' | 'name'
|
|
> = {
|
|
flags: {
|
|
...subscriptionClaim.flags,
|
|
},
|
|
teamCount: subscriptionClaim.teamCount,
|
|
memberCount: subscriptionClaim.memberCount,
|
|
};
|
|
|
|
return {
|
|
...data,
|
|
};
|
|
};
|