mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 00:43:40 +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:
@@ -17,6 +17,7 @@ export const AuthenticationErrorCode = {
|
||||
// TwoFactorMissingSecret: 'TWO_FACTOR_MISSING_SECRET',
|
||||
// TwoFactorMissingCredentials: 'TWO_FACTOR_MISSING_CREDENTIALS',
|
||||
InvalidTwoFactorCode: 'INVALID_TWO_FACTOR_CODE',
|
||||
SignupDisabled: 'SIGNUP_DISABLED',
|
||||
// IncorrectTwoFactorBackupCode: 'INCORRECT_TWO_FACTOR_BACKUP_CODE',
|
||||
// IncorrectIdentityProvider: 'INCORRECT_IDENTITY_PROVIDER',
|
||||
// IncorrectPassword: 'INCORRECT_PASSWORD',
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import type { Context } from 'hono';
|
||||
import { deleteCookie, getSignedCookie, setSignedCookie } from 'hono/cookie';
|
||||
|
||||
import {
|
||||
formatSecureCookieName,
|
||||
getCookieDomain,
|
||||
useSecureCookies,
|
||||
} from '@documenso/lib/constants/auth';
|
||||
import { formatSecureCookieName, getCookieDomain, useSecureCookies } from '@documenso/lib/constants/auth';
|
||||
import { appLog } from '@documenso/lib/utils/debugger';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import type { Context } from 'hono';
|
||||
import { deleteCookie, getSignedCookie, setSignedCookie } from 'hono/cookie';
|
||||
|
||||
import { AUTH_SESSION_LIFETIME } from '../../config';
|
||||
import { extractCookieFromHeaders } from '../utils/cookies';
|
||||
@@ -61,13 +56,7 @@ export const getSessionCookie = async (c: Context): Promise<string | null> => {
|
||||
* @param sessionToken - The session token to set.
|
||||
*/
|
||||
export const setSessionCookie = async (c: Context, sessionToken: string) => {
|
||||
await setSignedCookie(
|
||||
c,
|
||||
sessionCookieName,
|
||||
sessionToken,
|
||||
getAuthSecret(),
|
||||
sessionCookieOptions,
|
||||
).catch((err) => {
|
||||
await setSignedCookie(c, sessionCookieName, sessionToken, getAuthSecret(), sessionCookieOptions).catch((err) => {
|
||||
appLog('SetSessionCookie', `Error setting signed cookie: ${err}`);
|
||||
|
||||
throw err;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { sha256 } from '@oslojs/crypto/sha2';
|
||||
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
||||
import { type Session, type User, UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { sha256 } from '@oslojs/crypto/sha2';
|
||||
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
||||
import { type Session, type User, UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { AUTH_SESSION_LIFETIME } from '../../config';
|
||||
|
||||
@@ -15,14 +14,7 @@ import { AUTH_SESSION_LIFETIME } from '../../config';
|
||||
*/
|
||||
export type SessionUser = Pick<
|
||||
User,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'email'
|
||||
| 'emailVerified'
|
||||
| 'avatarImageId'
|
||||
| 'twoFactorEnabled'
|
||||
| 'roles'
|
||||
| 'signature'
|
||||
'id' | 'name' | 'email' | 'emailVerified' | 'avatarImageId' | 'twoFactorEnabled' | 'roles' | 'signature'
|
||||
>;
|
||||
|
||||
export type SessionValidationResult =
|
||||
@@ -43,11 +35,7 @@ export const generateSessionToken = (): string => {
|
||||
return token;
|
||||
};
|
||||
|
||||
export const createSession = async (
|
||||
token: string,
|
||||
userId: number,
|
||||
metadata: RequestMetadata,
|
||||
): Promise<Session> => {
|
||||
export const createSession = async (token: string, userId: number, metadata: RequestMetadata): Promise<Session> => {
|
||||
const hashedSessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||
|
||||
const session: Session = {
|
||||
@@ -166,9 +154,7 @@ export const invalidateSessions = async ({
|
||||
userId,
|
||||
ipAddress: metadata.ipAddress,
|
||||
userAgent: metadata.userAgent,
|
||||
type: isRevoke
|
||||
? UserSecurityAuditLogType.SESSION_REVOKED
|
||||
: UserSecurityAuditLogType.SIGN_OUT,
|
||||
type: isRevoke ? UserSecurityAuditLogType.SESSION_REVOKED : UserSecurityAuditLogType.SIGN_OUT,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import { ORGANISATION_USER_ACCOUNT_TYPE } from '@documenso/lib/constants/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import { getSession } from './get-session';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import { getSession } from './get-session';
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { Session } from '@prisma/client';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Session } from '@prisma/client';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import { AuthenticationErrorCode } from '../errors/error-codes';
|
||||
import type { SessionValidationResult } from '../session/session';
|
||||
@@ -23,9 +22,7 @@ export const getSession = async (c: Context | Request) => {
|
||||
throw new AppError(AuthenticationErrorCode.Unauthorized);
|
||||
};
|
||||
|
||||
export const getOptionalSession = async (
|
||||
c: Context | Request,
|
||||
): Promise<SessionValidationResult> => {
|
||||
export const getOptionalSession = async (c: Context | Request): Promise<SessionValidationResult> => {
|
||||
const sessionId = await getSessionCookie(mapRequestToContextForCookie(c));
|
||||
|
||||
if (!sessionId) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { CodeChallengeMethod, OAuth2Client, generateCodeVerifier, generateState } from 'arctic';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { CodeChallengeMethod, generateCodeVerifier, generateState, OAuth2Client } from 'arctic';
|
||||
import type { Context } from 'hono';
|
||||
import { setCookie } from 'hono/cookie';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
|
||||
import type { OAuthClientOptions } from '../../config';
|
||||
import { sessionCookieOptions } from '../session/session-cookies';
|
||||
import { getOpenIdConfiguration } from './open-id';
|
||||
@@ -49,11 +48,7 @@ export const handleOAuthAuthorizeUrl = async (options: HandleOAuthAuthorizeUrlOp
|
||||
requiredScopes: clientOptions.scope,
|
||||
});
|
||||
|
||||
const oAuthClient = new OAuth2Client(
|
||||
clientOptions.clientId,
|
||||
clientOptions.clientSecret,
|
||||
clientOptions.redirectUrl,
|
||||
);
|
||||
const oAuthClient = new OAuth2Client(clientOptions.clientId, clientOptions.clientSecret, clientOptions.redirectUrl);
|
||||
|
||||
const scopes = clientOptions.scope;
|
||||
const state = generateState();
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import { OAuth2Client, decodeIdToken } from 'arctic';
|
||||
import type { Context } from 'hono';
|
||||
import { deleteCookie } from 'hono/cookie';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { isEmailDomainAllowedForSignup, isSignupEnabledForProvider } from '@documenso/lib/constants/auth';
|
||||
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';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import { decodeIdToken, OAuth2Client } from 'arctic';
|
||||
import type { Context } from 'hono';
|
||||
import { deleteCookie } from 'hono/cookie';
|
||||
|
||||
import type { OAuthClientOptions } from '../../config';
|
||||
import { AuthenticationErrorCode } from '../errors/error-codes';
|
||||
@@ -23,8 +26,14 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti
|
||||
|
||||
const requestMeta = c.get('requestMetadata');
|
||||
|
||||
const { email, name, sub, accessToken, accessTokenExpiresAt, idToken, redirectPath } =
|
||||
await validateOauth({ c, clientOptions });
|
||||
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({
|
||||
@@ -105,6 +114,24 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti
|
||||
return c.redirect(redirectPath, 302);
|
||||
}
|
||||
|
||||
// Check if signups are disabled for this provider.
|
||||
if (!isSignupEnabledForProvider(clientOptions.id as 'google' | 'microsoft' | 'oidc')) {
|
||||
const errorUrl = new URL('/signin', NEXT_PUBLIC_WEBAPP_URL());
|
||||
|
||||
errorUrl.searchParams.set('error', AuthenticationErrorCode.SignupDisabled);
|
||||
|
||||
return c.redirect(errorUrl.toString(), 302);
|
||||
}
|
||||
|
||||
// Check domain restriction for new SSO users.
|
||||
if (!isEmailDomainAllowedForSignup(email)) {
|
||||
const errorUrl = new URL('/signin', NEXT_PUBLIC_WEBAPP_URL());
|
||||
|
||||
errorUrl.searchParams.set('error', AuthenticationErrorCode.SignupDisabled);
|
||||
|
||||
return c.redirect(errorUrl.toString(), 302);
|
||||
}
|
||||
|
||||
// Handle new user.
|
||||
const createdUser = await prisma.$transaction(async (tx) => {
|
||||
const user = await tx.user.create({
|
||||
@@ -152,11 +179,7 @@ export const validateOauth = async (options: HandleOAuthCallbackUrlOptions) => {
|
||||
requiredScopes: clientOptions.scope,
|
||||
});
|
||||
|
||||
const oAuthClient = new OAuth2Client(
|
||||
clientOptions.clientId,
|
||||
clientOptions.clientSecret,
|
||||
clientOptions.redirectUrl,
|
||||
);
|
||||
const oAuthClient = new OAuth2Client(clientOptions.clientId, clientOptions.clientSecret, clientOptions.redirectUrl);
|
||||
|
||||
const code = c.req.query('code');
|
||||
const state = c.req.query('state');
|
||||
@@ -184,11 +207,7 @@ export const validateOauth = async (options: HandleOAuthCallbackUrlOptions) => {
|
||||
|
||||
redirectPath = normalizeReturnTo(redirectPath) || '/';
|
||||
|
||||
const tokens = await oAuthClient.validateAuthorizationCode(
|
||||
token_endpoint,
|
||||
code,
|
||||
storedCodeVerifier,
|
||||
);
|
||||
const tokens = await oAuthClient.validateAuthorizationCode(token_endpoint, code, storedCodeVerifier);
|
||||
|
||||
const accessToken = tokens.accessToken();
|
||||
const accessTokenExpiresAt = tokens.accessTokenExpiresAt();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import { sendOrganisationAccountLinkConfirmationEmail } from '@documenso/ee/server-only/lib/send-organisation-account-link-confirmation-email';
|
||||
import { isSignupEnabledForProvider } from '@documenso/lib/constants/auth';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { onCreateUserHook } from '@documenso/lib/server-only/user/create-user';
|
||||
import { formatOrganisationLoginUrl } from '@documenso/lib/utils/organisation-authentication-portal';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
import { AuthenticationErrorCode } from '../errors/error-codes';
|
||||
import { onAuthorize } from './authorizer';
|
||||
@@ -16,9 +16,7 @@ type HandleOAuthOrganisationCallbackUrlOptions = {
|
||||
orgUrl: string;
|
||||
};
|
||||
|
||||
export const handleOAuthOrganisationCallbackUrl = async (
|
||||
options: HandleOAuthOrganisationCallbackUrlOptions,
|
||||
) => {
|
||||
export const handleOAuthOrganisationCallbackUrl = async (options: HandleOAuthOrganisationCallbackUrlOptions) => {
|
||||
const { c, orgUrl } = options;
|
||||
|
||||
const { organisation, clientOptions } = await getOrganisationAuthenticationPortalOptions({
|
||||
@@ -68,6 +66,14 @@ export const handleOAuthOrganisationCallbackUrl = async (
|
||||
|
||||
// Handle new user.
|
||||
if (!userToLink) {
|
||||
if (!isSignupEnabledForProvider('oidc')) {
|
||||
const errorUrl = new URL(formatOrganisationLoginUrl(orgUrl));
|
||||
|
||||
errorUrl.searchParams.set('error', AuthenticationErrorCode.SignupDisabled);
|
||||
|
||||
return c.redirect(errorUrl.toString(), 302);
|
||||
}
|
||||
|
||||
userToLink = await prisma.user.create({
|
||||
data: {
|
||||
email: email,
|
||||
@@ -76,7 +82,9 @@ export const handleOAuthOrganisationCallbackUrl = async (
|
||||
},
|
||||
});
|
||||
|
||||
await onCreateUserHook(userToLink).catch((err) => {
|
||||
await onCreateUserHook(userToLink, {
|
||||
skipPersonalOrganisation: !organisation.organisationAuthenticationPortal.allowPersonalOrganisations,
|
||||
}).catch((err) => {
|
||||
// Todo: (RR7) Add logging.
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
@@ -55,11 +55,7 @@ export const getOrganisationAuthenticationPortalOptions = async (
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
clientId,
|
||||
clientSecret: encryptedClientSecret,
|
||||
wellKnownUrl,
|
||||
} = organisation.organisationAuthenticationPortal;
|
||||
const { clientId, clientSecret: encryptedClientSecret, wellKnownUrl } = organisation.organisationAuthenticationPortal;
|
||||
|
||||
if (!clientId || !encryptedClientSecret || !wellKnownUrl) {
|
||||
throw new AppError(AppErrorCode.NOT_SETUP, {
|
||||
|
||||
Reference in New Issue
Block a user