mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 08:54:20 +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.
120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import { createElement } from 'react';
|
|
|
|
import { msg } from '@lingui/core/macro';
|
|
import crypto from 'crypto';
|
|
import { DateTime } from 'luxon';
|
|
|
|
import { mailer } from '@documenso/email/mailer';
|
|
import { OrganisationAccountLinkConfirmationTemplate } from '@documenso/email/templates/organisation-account-link-confirmation';
|
|
import { getI18nInstance } from '@documenso/lib/client-only/providers/i18n-server';
|
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
|
import { DOCUMENSO_INTERNAL_EMAIL } from '@documenso/lib/constants/email';
|
|
import { ORGANISATION_ACCOUNT_LINK_VERIFICATION_TOKEN_IDENTIFIER } from '@documenso/lib/constants/organisations';
|
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|
import { getEmailContext } from '@documenso/lib/server-only/email/get-email-context';
|
|
import type { TOrganisationAccountLinkMetadata } from '@documenso/lib/types/organisation';
|
|
import { renderEmailWithI18N } from '@documenso/lib/utils/render-email-with-i18n';
|
|
import { prisma } from '@documenso/prisma';
|
|
|
|
export type SendOrganisationAccountLinkConfirmationEmailProps = TOrganisationAccountLinkMetadata & {
|
|
organisationName: string;
|
|
};
|
|
|
|
export const sendOrganisationAccountLinkConfirmationEmail = async ({
|
|
type,
|
|
userId,
|
|
organisationId,
|
|
organisationName,
|
|
oauthConfig,
|
|
}: SendOrganisationAccountLinkConfirmationEmailProps) => {
|
|
const user = await prisma.user.findFirst({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
include: {
|
|
verificationTokens: {
|
|
where: {
|
|
identifier: ORGANISATION_ACCOUNT_LINK_VERIFICATION_TOKEN_IDENTIFIER,
|
|
},
|
|
orderBy: {
|
|
createdAt: 'desc',
|
|
},
|
|
take: 1,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!user) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'User not found',
|
|
});
|
|
}
|
|
|
|
const [previousVerificationToken] = user.verificationTokens;
|
|
|
|
// If we've sent a token in the last 5 minutes, don't send another one
|
|
if (
|
|
previousVerificationToken?.createdAt &&
|
|
DateTime.fromJSDate(previousVerificationToken.createdAt).diffNow('minutes').minutes > -5
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const token = crypto.randomBytes(20).toString('hex');
|
|
|
|
const createdToken = await prisma.verificationToken.create({
|
|
data: {
|
|
identifier: ORGANISATION_ACCOUNT_LINK_VERIFICATION_TOKEN_IDENTIFIER,
|
|
token,
|
|
expires: DateTime.now().plus({ minutes: 30 }).toJSDate(),
|
|
metadata: {
|
|
type,
|
|
userId,
|
|
organisationId,
|
|
oauthConfig,
|
|
} satisfies TOrganisationAccountLinkMetadata,
|
|
userId,
|
|
},
|
|
});
|
|
|
|
const { emailLanguage } = await getEmailContext({
|
|
emailType: 'INTERNAL',
|
|
source: {
|
|
type: 'organisation',
|
|
organisationId,
|
|
},
|
|
meta: null,
|
|
});
|
|
|
|
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
|
const confirmationLink = `${assetBaseUrl}/organisation/sso/confirmation/${createdToken.token}`;
|
|
|
|
const confirmationTemplate = createElement(OrganisationAccountLinkConfirmationTemplate, {
|
|
type,
|
|
assetBaseUrl,
|
|
confirmationLink,
|
|
organisationName,
|
|
});
|
|
|
|
const [html, text] = await Promise.all([
|
|
renderEmailWithI18N(confirmationTemplate, { lang: emailLanguage }),
|
|
renderEmailWithI18N(confirmationTemplate, { lang: emailLanguage, plainText: true }),
|
|
]);
|
|
|
|
const i18n = await getI18nInstance(emailLanguage);
|
|
|
|
return mailer.sendMail({
|
|
to: {
|
|
address: user.email,
|
|
name: user.name || '',
|
|
},
|
|
from: DOCUMENSO_INTERNAL_EMAIL,
|
|
subject:
|
|
type === 'create'
|
|
? i18n._(msg`Account creation request`)
|
|
: i18n._(msg`Account linking request`),
|
|
html,
|
|
text,
|
|
});
|
|
};
|