mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
fix: use instance-specific emails for service accounts (#2502)
This commit is contained in:
@@ -12,6 +12,8 @@ import { API_V2_BETA_URL, API_V2_URL } from '@documenso/lib/constants/app';
|
||||
import { jobsClient } from '@documenso/lib/jobs/client';
|
||||
import { LicenseClient } from '@documenso/lib/server-only/license/license-client';
|
||||
import { TelemetryClient } from '@documenso/lib/server-only/telemetry/telemetry-client';
|
||||
import { migrateDeletedAccountServiceAccount } from '@documenso/lib/server-only/user/service-accounts/deleted-account';
|
||||
import { migrateLegacyServiceAccount } from '@documenso/lib/server-only/user/service-accounts/legacy-service-account';
|
||||
import { getIpAddress } from '@documenso/lib/universal/get-ip-address';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import { logger } from '@documenso/lib/utils/logger';
|
||||
@@ -144,4 +146,7 @@ if (env('NODE_ENV') !== 'development') {
|
||||
// Start license client to verify license on startup.
|
||||
void LicenseClient.start();
|
||||
|
||||
void migrateDeletedAccountServiceAccount();
|
||||
void migrateLegacyServiceAccount();
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -5,6 +5,8 @@ import { deleteCookie } from 'hono/cookie';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { onCreateUserHook } from '@documenso/lib/server-only/user/create-user';
|
||||
import { deletedServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/deleted-account';
|
||||
import { legacyServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/legacy-service-account';
|
||||
import { isValidReturnTo, normalizeReturnTo } from '@documenso/lib/utils/is-valid-return-to';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@@ -26,6 +28,13 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti
|
||||
const { email, name, sub, accessToken, accessTokenExpiresAt, idToken, redirectPath } =
|
||||
await validateOauth({ c, clientOptions });
|
||||
|
||||
if (
|
||||
email.toLowerCase() === legacyServiceAccountEmail() ||
|
||||
email.toLowerCase() === deletedServiceAccountEmail()
|
||||
) {
|
||||
return c.text('FORBIDDEN', 403);
|
||||
}
|
||||
|
||||
// Find the account if possible.
|
||||
const existingAccount = await prisma.account.findFirst({
|
||||
where: {
|
||||
|
||||
@@ -17,7 +17,10 @@ import { viewBackupCodes } from '@documenso/lib/server-only/2fa/view-backup-code
|
||||
import { createUser } from '@documenso/lib/server-only/user/create-user';
|
||||
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
|
||||
import { getMostRecentEmailVerificationToken } from '@documenso/lib/server-only/user/get-most-recent-email-verification-token';
|
||||
import { getUserByResetToken } from '@documenso/lib/server-only/user/get-user-by-reset-token';
|
||||
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
|
||||
import { deletedServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/deleted-account';
|
||||
import { legacyServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/legacy-service-account';
|
||||
import { updatePassword } from '@documenso/lib/server-only/user/update-password';
|
||||
import { verifyEmail } from '@documenso/lib/server-only/user/verify-email';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
@@ -57,6 +60,13 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
email.toLowerCase() === legacyServiceAccountEmail() ||
|
||||
email.toLowerCase() === deletedServiceAccountEmail()
|
||||
) {
|
||||
return c.text('FORBIDDEN', 403);
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: email.toLowerCase(),
|
||||
@@ -241,6 +251,13 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
.post('/forgot-password', sValidator('json', ZForgotPasswordSchema), async (c) => {
|
||||
const { email } = c.req.valid('json');
|
||||
|
||||
if (
|
||||
email.toLowerCase() === legacyServiceAccountEmail() ||
|
||||
email.toLowerCase() === deletedServiceAccountEmail()
|
||||
) {
|
||||
return c.text('FORBIDDEN', 403);
|
||||
}
|
||||
|
||||
await forgotPassword({
|
||||
email,
|
||||
});
|
||||
@@ -253,6 +270,15 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
.post('/reset-password', sValidator('json', ZResetPasswordSchema), async (c) => {
|
||||
const { token, password } = c.req.valid('json');
|
||||
|
||||
const user = await getUserByResetToken({ token });
|
||||
|
||||
if (
|
||||
user.email.toLowerCase() === legacyServiceAccountEmail() ||
|
||||
user.email.toLowerCase() === deletedServiceAccountEmail()
|
||||
) {
|
||||
return c.text('FORBIDDEN', 403);
|
||||
}
|
||||
|
||||
const requestMetadata = c.get('requestMetadata');
|
||||
|
||||
const { userId } = await resetPassword({
|
||||
|
||||
@@ -5,6 +5,8 @@ import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
import { Hono } from 'hono';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { deletedServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/deleted-account';
|
||||
import { legacyServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/legacy-service-account';
|
||||
import type { TAuthenticationResponseJSONSchema } from '@documenso/lib/types/webauthn';
|
||||
import { ZAuthenticationResponseJSONSchema } from '@documenso/lib/types/webauthn';
|
||||
import { getAuthenticatorOptions } from '@documenso/lib/utils/authenticator';
|
||||
@@ -74,6 +76,13 @@ export const passkeyRoute = new Hono<HonoAuthContext>()
|
||||
|
||||
const user = passkey.user;
|
||||
|
||||
if (
|
||||
user.email.toLowerCase() === legacyServiceAccountEmail() ||
|
||||
user.email.toLowerCase() === deletedServiceAccountEmail()
|
||||
) {
|
||||
return c.text('FORBIDDEN', 403);
|
||||
}
|
||||
|
||||
const { rpId, origin } = getAuthenticatorOptions();
|
||||
|
||||
const verification = await verifyAuthenticationResponse({
|
||||
|
||||
@@ -8,8 +8,6 @@ export const DOCUMENSO_INTERNAL_EMAIL = {
|
||||
address: FROM_ADDRESS,
|
||||
};
|
||||
|
||||
export const SERVICE_USER_EMAIL = 'serviceaccount@documenso.com';
|
||||
|
||||
export const EMAIL_VERIFICATION_STATE = {
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
VERIFIED: 'VERIFIED',
|
||||
|
||||
@@ -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,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,
|
||||
@@ -29,3 +47,20 @@ export const deletedAccountServiceAccount = async () => {
|
||||
|
||||
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(),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user