feat(auth): enhance email validation when no workspace available + disable captcha on email validation (#11239)
Implemented fallback logic to associate a user with a workspace when none is found. Introduced new GraphQL types and mutations for roles and permissions management. Simplified and refactored URL-building logic for email verification, improving code maintainability and flexibility.
This commit is contained in:
@ -155,31 +155,25 @@ export class AuthResolver {
|
||||
return { loginToken };
|
||||
}
|
||||
|
||||
@UseGuards(CaptchaGuard)
|
||||
@Mutation(() => LoginToken)
|
||||
async getLoginTokenFromEmailVerificationToken(
|
||||
@Args()
|
||||
getLoginTokenFromEmailVerificationTokenInput: GetLoginTokenFromEmailVerificationTokenInput,
|
||||
@OriginHeader() origin: string,
|
||||
) {
|
||||
const workspace =
|
||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
);
|
||||
|
||||
workspaceValidator.assertIsDefinedOrThrow(
|
||||
workspace,
|
||||
new AuthException(
|
||||
'Workspace not found',
|
||||
AuthExceptionCode.WORKSPACE_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
const user =
|
||||
await this.emailVerificationTokenService.validateEmailVerificationTokenOrThrow(
|
||||
getLoginTokenFromEmailVerificationTokenInput.emailVerificationToken,
|
||||
);
|
||||
|
||||
const workspace =
|
||||
(await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
)) ??
|
||||
(await this.userWorkspaceService.findFirstRandomWorkspaceByUserId(
|
||||
user.id,
|
||||
));
|
||||
|
||||
await this.userService.markEmailAsVerified(user.id);
|
||||
|
||||
const loginToken = await this.loginTokenService.generateLoginToken(
|
||||
|
||||
@ -42,22 +42,37 @@ export class DomainManagerService {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
buildEmailVerificationURL({
|
||||
emailVerificationToken,
|
||||
email,
|
||||
workspace,
|
||||
}: {
|
||||
emailVerificationToken: string;
|
||||
email: string;
|
||||
workspace: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType;
|
||||
}) {
|
||||
return this.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname: 'verify-email',
|
||||
searchParams: { emailVerificationToken, email },
|
||||
private appendSearchParams(
|
||||
url: URL,
|
||||
searchParams: Record<string, string | number>,
|
||||
) {
|
||||
Object.entries(searchParams).forEach(([key, value]) => {
|
||||
if (isDefined(value)) {
|
||||
url.searchParams.set(key, value.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
buildBaseUrl({
|
||||
pathname,
|
||||
searchParams,
|
||||
}: {
|
||||
pathname?: string;
|
||||
searchParams?: Record<string, string | number>;
|
||||
}) {
|
||||
const url = this.getBaseUrl();
|
||||
|
||||
if (pathname) {
|
||||
url.pathname = pathname;
|
||||
}
|
||||
|
||||
if (searchParams) {
|
||||
this.appendSearchParams(url, searchParams);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname,
|
||||
@ -76,11 +91,7 @@ export class DomainManagerService {
|
||||
}
|
||||
|
||||
if (searchParams) {
|
||||
Object.entries(searchParams).forEach(([key, value]) => {
|
||||
if (isDefined(value)) {
|
||||
url.searchParams.set(key, value.toString());
|
||||
}
|
||||
});
|
||||
this.appendSearchParams(url, searchParams);
|
||||
}
|
||||
|
||||
return url;
|
||||
|
||||
@ -7,7 +7,6 @@ import { ResendEmailVerificationTokenInput } from 'src/engine/core-modules/email
|
||||
import { ResendEmailVerificationTokenOutput } from 'src/engine/core-modules/email-verification/dtos/resend-email-verification-token.output';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
||||
|
||||
@Resolver()
|
||||
@ -29,8 +28,6 @@ export class EmailVerificationResolver {
|
||||
origin,
|
||||
);
|
||||
|
||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
||||
|
||||
return await this.emailVerificationService.resendEmailVerificationToken(
|
||||
resendEmailVerificationTokenInput.email,
|
||||
workspace,
|
||||
|
||||
@ -41,7 +41,9 @@ export class EmailVerificationService {
|
||||
async sendVerificationEmail(
|
||||
userId: string,
|
||||
email: string,
|
||||
workspace: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType,
|
||||
workspace:
|
||||
| WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType
|
||||
| undefined,
|
||||
locale: keyof typeof APP_LOCALES,
|
||||
) {
|
||||
if (!this.environmentService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
||||
@ -51,12 +53,16 @@ export class EmailVerificationService {
|
||||
const { token: emailVerificationToken } =
|
||||
await this.emailVerificationTokenService.generateToken(userId, email);
|
||||
|
||||
const verificationLink =
|
||||
this.domainManagerService.buildEmailVerificationURL({
|
||||
emailVerificationToken,
|
||||
email,
|
||||
workspace,
|
||||
});
|
||||
const linkPathnameAndSearchParams = {
|
||||
pathname: 'verify-email',
|
||||
searchParams: { emailVerificationToken, email },
|
||||
};
|
||||
const verificationLink = workspace
|
||||
? this.domainManagerService.buildWorkspaceURL({
|
||||
workspace,
|
||||
...linkPathnameAndSearchParams,
|
||||
})
|
||||
: this.domainManagerService.buildBaseUrl(linkPathnameAndSearchParams);
|
||||
|
||||
const emailData = {
|
||||
link: verificationLink.toString(),
|
||||
@ -88,7 +94,9 @@ export class EmailVerificationService {
|
||||
|
||||
async resendEmailVerificationToken(
|
||||
email: string,
|
||||
workspace: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType,
|
||||
workspace:
|
||||
| WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType
|
||||
| undefined,
|
||||
locale: keyof typeof APP_LOCALES,
|
||||
) {
|
||||
if (!this.environmentService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
||||
|
||||
@ -186,6 +186,22 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
});
|
||||
}
|
||||
|
||||
async findFirstRandomWorkspaceByUserId(userId: string) {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
relations: ['workspaces', 'workspaces.workspace'],
|
||||
});
|
||||
|
||||
userValidator.assertIsDefinedOrThrow(
|
||||
user,
|
||||
new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND),
|
||||
);
|
||||
|
||||
return user.workspaces[0].workspace;
|
||||
}
|
||||
|
||||
async findAvailableWorkspacesByEmail(email: string) {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: {
|
||||
|
||||
Reference in New Issue
Block a user