mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 01:15:49 +10:00
chore: merge main, resolve biome formatting conflicts
Merge origin/main into feat/external-2fa-codes. Resolve formatting conflicts caused by biome rollout; preserve both feature streams: PR's external 2FA token + signing-session 2FA proof additions plus main's RateLimit/RecipientExpired/signingReminders/date-auto-insert. In complete-document-with-token.ts, drop the duplicate early field-fetching block introduced when main moved that logic later with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH check using derivedRecipientActionAuth.
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { hash } from '@node-rs/bcrypt';
|
||||
import type { User } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { SALT_ROUNDS } from '../../constants/auth';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { createPersonalOrganisation } from '../organisation/create-organisation';
|
||||
@@ -60,13 +59,27 @@ export const createUser = async ({ name, email, password, signature }: CreateUse
|
||||
return user;
|
||||
};
|
||||
|
||||
export type OnCreateUserHookOptions = {
|
||||
/**
|
||||
* When true, do not create a "Personal Organisation" for the new user.
|
||||
* Used by the Organisation SSO signup path, where the user is intended
|
||||
* to operate inside the SSO organisation rather than a personal space.
|
||||
*
|
||||
* Defaults to false — preserves the historical behaviour of creating a
|
||||
* personal organisation for every new user.
|
||||
*/
|
||||
skipPersonalOrganisation?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Should be run after a user is created, example during email password signup or google sign in.
|
||||
*
|
||||
* @returns User
|
||||
*/
|
||||
export const onCreateUserHook = async (user: User) => {
|
||||
await createPersonalOrganisation({ userId: user.id });
|
||||
export const onCreateUserHook = async (user: User, options: OnCreateUserHookOptions = {}) => {
|
||||
if (!options.skipPersonalOrganisation) {
|
||||
await createPersonalOrganisation({ userId: user.id });
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { UserSecurityAuditLog, UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { UserSecurityAuditLog, UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { ONE_DAY } from '../../constants/time';
|
||||
import { ONE_HOUR } from '../../constants/time';
|
||||
import { sendForgotPassword } from '../auth/send-forgot-password';
|
||||
|
||||
export const forgotPassword = async ({ email }: { email: string }) => {
|
||||
@@ -41,7 +40,7 @@ export const forgotPassword = async ({ email }: { email: string }) => {
|
||||
await prisma.passwordResetToken.create({
|
||||
data: {
|
||||
token,
|
||||
expiry: new Date(Date.now() + ONE_DAY),
|
||||
expiry: new Date(Date.now() + ONE_HOUR),
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
type GetAllUsersProps = {
|
||||
username: string;
|
||||
@@ -9,12 +8,7 @@ type GetAllUsersProps = {
|
||||
perPage: number;
|
||||
};
|
||||
|
||||
export const findUsers = async ({
|
||||
username = '',
|
||||
email = '',
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: GetAllUsersProps) => {
|
||||
export const findUsers = async ({ username = '', email = '', page = 1, perPage = 10 }: GetAllUsersProps) => {
|
||||
const whereClause = Prisma.validator<Prisma.UserWhereInput>()({
|
||||
OR: [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
|
||||
export const getCompletedDocumentsMonthly = async () => {
|
||||
const qb = kyselyPrisma.$kysely
|
||||
.selectFrom('Envelope')
|
||||
@@ -31,6 +30,4 @@ export const getCompletedDocumentsMonthly = async () => {
|
||||
}));
|
||||
};
|
||||
|
||||
export type GetCompletedDocumentsMonthlyResult = Awaited<
|
||||
ReturnType<typeof getCompletedDocumentsMonthly>
|
||||
>;
|
||||
export type GetCompletedDocumentsMonthlyResult = Awaited<ReturnType<typeof getCompletedDocumentsMonthly>>;
|
||||
|
||||
@@ -6,9 +6,7 @@ export type getMostRecentEmailVerificationTokenOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const getMostRecentEmailVerificationToken = async ({
|
||||
userId,
|
||||
}: getMostRecentEmailVerificationTokenOptions) => {
|
||||
export const getMostRecentEmailVerificationToken = async ({ userId }: getMostRecentEmailVerificationTokenOptions) => {
|
||||
return await prisma.verificationToken.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export const getSignerConversionMonthly = async () => {
|
||||
const qb = kyselyPrisma.$kysely
|
||||
@@ -29,6 +28,4 @@ export const getSignerConversionMonthly = async () => {
|
||||
}));
|
||||
};
|
||||
|
||||
export type GetSignerConversionMonthlyResult = Awaited<
|
||||
ReturnType<typeof getSignerConversionMonthly>
|
||||
>;
|
||||
export type GetSignerConversionMonthlyResult = Awaited<ReturnType<typeof getSignerConversionMonthly>>;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
export interface GetUserByResetTokenOptions {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export const getUserByResetToken = async ({ token }: GetUserByResetTokenOptions) => {
|
||||
const result = await prisma.passwordResetToken.findFirst({
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result || !result.user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
return result.user;
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export const getUserMonthlyGrowth = async () => {
|
||||
const qb = kyselyPrisma.$kysely
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { compare, hash } from '@node-rs/bcrypt';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { SALT_ROUNDS } from '../../constants/auth';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { jobsClient } from '../../jobs/client';
|
||||
@@ -77,13 +76,13 @@ export const resetPassword = async ({ token, password, requestMetadata }: ResetP
|
||||
ipAddress: requestMetadata?.ipAddress,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await jobsClient.triggerJob({
|
||||
name: 'send.password.reset.success.email',
|
||||
payload: {
|
||||
userId: foundToken.userId,
|
||||
},
|
||||
});
|
||||
await jobsClient.triggerJob({
|
||||
name: 'send.password.reset.success.email',
|
||||
payload: {
|
||||
userId: foundToken.userId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import crypto from 'crypto';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { USER_SIGNUP_VERIFICATION_TOKEN_IDENTIFIER } from '../../constants/email';
|
||||
import { ONE_HOUR } from '../../constants/time';
|
||||
import { sendConfirmationEmail } from '../auth/send-confirmation-email';
|
||||
@@ -10,10 +9,7 @@ import { getMostRecentEmailVerificationToken } from './get-most-recent-email-ver
|
||||
|
||||
type SendConfirmationTokenOptions = { email: string; force?: boolean };
|
||||
|
||||
export const sendConfirmationToken = async ({
|
||||
email,
|
||||
force = false,
|
||||
}: SendConfirmationTokenOptions) => {
|
||||
export const sendConfirmationToken = async ({ email, force = false }: SendConfirmationTokenOptions) => {
|
||||
const token = crypto.randomBytes(20).toString('hex');
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
const LEGACY_DELETED_ACCOUNT_EMAIL = 'deleted-account@documenso.com';
|
||||
|
||||
export const deletedServiceAccountEmail = () => {
|
||||
try {
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
if (process.env.NEXT_PRIVATE_DELETED_SERVICE_ACCOUNT_EMAIL) {
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
return process.env.NEXT_PRIVATE_DELETED_SERVICE_ACCOUNT_EMAIL;
|
||||
}
|
||||
|
||||
const { hostname } = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000');
|
||||
|
||||
return `deleted-account@${hostname}`;
|
||||
} catch (error) {
|
||||
return LEGACY_DELETED_ACCOUNT_EMAIL;
|
||||
}
|
||||
};
|
||||
|
||||
export const deletedAccountServiceAccount = async () => {
|
||||
const serviceAccount = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: 'deleted-account@documenso.com',
|
||||
email: deletedServiceAccountEmail(),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
@@ -22,10 +40,23 @@ export const deletedAccountServiceAccount = async () => {
|
||||
});
|
||||
|
||||
if (!serviceAccount) {
|
||||
throw new Error(
|
||||
'Deleted account service account not found, have you ran the appropriate migrations?',
|
||||
);
|
||||
throw new Error('Deleted account service account not found, have you ran the appropriate migrations?');
|
||||
}
|
||||
|
||||
return serviceAccount;
|
||||
};
|
||||
|
||||
export const migrateDeletedAccountServiceAccount = async () => {
|
||||
if (deletedServiceAccountEmail() !== LEGACY_DELETED_ACCOUNT_EMAIL) {
|
||||
console.log(`Migrating deleted account service account to new email: ${deletedServiceAccountEmail()}`);
|
||||
|
||||
await prisma.user.updateMany({
|
||||
where: {
|
||||
email: LEGACY_DELETED_ACCOUNT_EMAIL,
|
||||
},
|
||||
data: {
|
||||
email: deletedServiceAccountEmail(),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
const LEGACY_SERVICE_ACCOUNT_EMAIL = 'serviceaccount@documenso.com';
|
||||
|
||||
export const legacyServiceAccountEmail = () => {
|
||||
try {
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
if (process.env.NEXT_PRIVATE_LEGACY_SERVICE_ACCOUNT_EMAIL) {
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
return process.env.NEXT_PRIVATE_LEGACY_SERVICE_ACCOUNT_EMAIL;
|
||||
}
|
||||
|
||||
const { hostname } = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000');
|
||||
|
||||
return `serviceaccount@${hostname}`;
|
||||
} catch (error) {
|
||||
return LEGACY_SERVICE_ACCOUNT_EMAIL;
|
||||
}
|
||||
};
|
||||
|
||||
export const migrateLegacyServiceAccount = async () => {
|
||||
if (legacyServiceAccountEmail() !== LEGACY_SERVICE_ACCOUNT_EMAIL) {
|
||||
console.log(`Migrating legacy service account to new email: ${legacyServiceAccountEmail()}`);
|
||||
|
||||
await prisma.user.updateMany({
|
||||
where: {
|
||||
email: LEGACY_SERVICE_ACCOUNT_EMAIL,
|
||||
},
|
||||
data: {
|
||||
email: legacyServiceAccountEmail(),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,9 +1,8 @@
|
||||
import { compare, hash } from '@node-rs/bcrypt';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { SALT_ROUNDS } from '@documenso/lib/constants/auth';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { compare, hash } from '@node-rs/bcrypt';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { AppError } from '../../errors/app-error';
|
||||
|
||||
@@ -14,12 +13,7 @@ export type UpdatePasswordOptions = {
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const updatePassword = async ({
|
||||
userId,
|
||||
password,
|
||||
currentPassword,
|
||||
requestMetadata,
|
||||
}: UpdatePasswordOptions) => {
|
||||
export const updatePassword = async ({ userId, password, currentPassword, requestMetadata }: UpdatePasswordOptions) => {
|
||||
// Existence check
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
@@ -54,6 +48,12 @@ export const updatePassword = async ({
|
||||
},
|
||||
});
|
||||
|
||||
await tx.passwordResetToken.deleteMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
return await tx.user.update({
|
||||
where: {
|
||||
id: userId,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
@@ -11,12 +10,7 @@ export type UpdateProfileOptions = {
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const updateProfile = async ({
|
||||
userId,
|
||||
name,
|
||||
signature,
|
||||
requestMetadata,
|
||||
}: UpdateProfileOptions) => {
|
||||
export const updateProfile = async ({ userId, name, signature, requestMetadata }: UpdateProfileOptions) => {
|
||||
// Existence check
|
||||
await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import {
|
||||
EMAIL_VERIFICATION_STATE,
|
||||
USER_SIGNUP_VERIFICATION_TOKEN_IDENTIFIER,
|
||||
} from '../../constants/email';
|
||||
import { EMAIL_VERIFICATION_STATE, USER_SIGNUP_VERIFICATION_TOKEN_IDENTIFIER } from '../../constants/email';
|
||||
import { jobsClient } from '../../jobs/client';
|
||||
|
||||
export type VerifyEmailProps = {
|
||||
@@ -50,10 +46,7 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||
});
|
||||
|
||||
// If there isn't a recent token or it's older than 1 hour, send a new token
|
||||
if (
|
||||
!mostRecentToken ||
|
||||
DateTime.now().minus({ hours: 1 }).toJSDate() > mostRecentToken.createdAt
|
||||
) {
|
||||
if (!mostRecentToken || DateTime.now().minus({ hours: 1 }).toJSDate() > mostRecentToken.createdAt) {
|
||||
await jobsClient.triggerJob({
|
||||
name: 'send.signup.confirmation.email',
|
||||
payload: {
|
||||
|
||||
Reference in New Issue
Block a user