chore(server): remove enable flag in mail config (#11680)

close #11625
This commit is contained in:
forehalo
2025-04-15 09:18:09 +00:00
parent b249939093
commit 7257f1b55b
15 changed files with 55 additions and 70 deletions

View File

@@ -200,11 +200,6 @@
"type": "object", "type": "object",
"description": "Configuration for mailer module", "description": "Configuration for mailer module",
"properties": { "properties": {
"enabled": {
"type": "boolean",
"description": "Whether enabled mail service.\n@default false",
"default": false
},
"SMTP.host": { "SMTP.host": {
"type": "string", "type": "string",
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"\n@environment `MAILER_HOST`", "description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"\n@environment `MAILER_HOST`",

View File

@@ -275,6 +275,10 @@ export const USER_FRIENDLY_ERRORS = {
args: { message: 'string' }, args: { message: 'string' },
message: ({ message }) => `HTTP request error, message: ${message}`, message: ({ message }) => `HTTP request error, message: ${message}`,
}, },
email_service_not_configured: {
type: 'internal_server_error',
message: 'Email service is not configured.',
},
// Input errors // Input errors
query_too_long: { query_too_long: {

View File

@@ -54,6 +54,12 @@ export class HttpRequestError extends UserFriendlyError {
super('bad_request', 'http_request_error', message, args); super('bad_request', 'http_request_error', message, args);
} }
} }
export class EmailServiceNotConfigured extends UserFriendlyError {
constructor(message?: string) {
super('internal_server_error', 'email_service_not_configured', message);
}
}
@ObjectType() @ObjectType()
class QueryTooLongDataType { class QueryTooLongDataType {
@Field() max!: number @Field() max!: number
@@ -943,6 +949,7 @@ export enum ErrorNames {
BAD_REQUEST, BAD_REQUEST,
GRAPHQL_BAD_REQUEST, GRAPHQL_BAD_REQUEST,
HTTP_REQUEST_ERROR, HTTP_REQUEST_ERROR,
EMAIL_SERVICE_NOT_CONFIGURED,
QUERY_TOO_LONG, QUERY_TOO_LONG,
VALIDATION_ERROR, VALIDATION_ERROR,
USER_NOT_FOUND, USER_NOT_FOUND,

View File

@@ -37,7 +37,6 @@ import { CurrentUser, Session } from './session';
interface PreflightResponse { interface PreflightResponse {
registered: boolean; registered: boolean;
hasPassword: boolean; hasPassword: boolean;
magicLink: boolean;
} }
interface SignInCredential { interface SignInCredential {
@@ -91,20 +90,16 @@ export class AuthController {
const user = await this.models.user.getUserByEmail(params.email); const user = await this.models.user.getUserByEmail(params.email);
const magicLinkAvailable = this.config.mailer.enabled;
if (!user) { if (!user) {
return { return {
registered: false, registered: false,
hasPassword: false, hasPassword: false,
magicLink: magicLinkAvailable,
}; };
} }
return { return {
registered: user.registered, registered: user.registered,
hasPassword: !!user.password, hasPassword: !!user.password,
magicLink: magicLinkAvailable,
}; };
} }

View File

@@ -3,7 +3,6 @@ import { defineModuleConfig } from '../../base';
declare global { declare global {
interface AppConfigSchema { interface AppConfigSchema {
mailer: { mailer: {
enabled: boolean;
SMTP: { SMTP: {
host: string; host: string;
port: number; port: number;
@@ -17,10 +16,6 @@ declare global {
} }
defineModuleConfig('mailer', { defineModuleConfig('mailer', {
enabled: {
desc: 'Whether enabled mail service.',
default: false,
},
'SMTP.host': { 'SMTP.host': {
desc: 'Host of the email server (e.g. smtp.gmail.com)', desc: 'Host of the email server (e.g. smtp.gmail.com)',
default: '', default: '',
@@ -49,6 +44,6 @@ defineModuleConfig('mailer', {
'SMTP.ignoreTLS': { 'SMTP.ignoreTLS': {
desc: "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.", desc: "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.",
default: false, default: false,
env: 'MAILER_IGNORE_TLS', env: ['MAILER_IGNORE_TLS', 'boolean'],
}, },
}); });

View File

@@ -1,12 +1,25 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { JobQueue } from '../../base'; import { EmailServiceNotConfigured, JobQueue } from '../../base';
import { MailSender } from './sender';
@Injectable() @Injectable()
export class Mailer { export class Mailer {
constructor(private readonly queue: JobQueue) {} constructor(
private readonly queue: JobQueue,
private readonly sender: MailSender
) {}
get enabled() {
// @ts-expect-error internal api
return this.sender.smtp !== null;
}
async send(command: Jobs['notification.sendMail']) { async send(command: Jobs['notification.sendMail']) {
if (!this.enabled) {
throw new EmailServiceNotConfigured();
}
try { try {
await this.queue.add('notification.sendMail', command); await this.queue.add('notification.sendMail', command);
return true; return true;

View File

@@ -56,13 +56,7 @@ export class MailSender {
} }
private setup() { private setup() {
const { SMTP, enabled } = this.config.mailer; const { SMTP } = this.config.mailer;
if (!enabled) {
this.smtp = null;
return;
}
const opts = configToSMTPOptions(SMTP); const opts = configToSMTPOptions(SMTP);
if (SMTP.host) { if (SMTP.host) {
@@ -83,6 +77,7 @@ export class MailSender {
}); });
} else { } else {
this.logger.warn('Mailer SMTP transport is not configured.'); this.logger.warn('Mailer SMTP transport is not configured.');
this.smtp = null;
} }
} }

View File

@@ -447,6 +447,7 @@ enum ErrorNames {
DOC_UPDATE_BLOCKED DOC_UPDATE_BLOCKED
EARLY_ACCESS_REQUIRED EARLY_ACCESS_REQUIRED
EMAIL_ALREADY_USED EMAIL_ALREADY_USED
EMAIL_SERVICE_NOT_CONFIGURED
EMAIL_TOKEN_NOT_FOUND EMAIL_TOKEN_NOT_FOUND
EMAIL_VERIFICATION_REQUIRED EMAIL_VERIFICATION_REQUIRED
EXPECT_TO_GRANT_DOC_USER_ROLES EXPECT_TO_GRANT_DOC_USER_ROLES

View File

@@ -592,6 +592,7 @@ export enum ErrorNames {
DOC_UPDATE_BLOCKED = 'DOC_UPDATE_BLOCKED', DOC_UPDATE_BLOCKED = 'DOC_UPDATE_BLOCKED',
EARLY_ACCESS_REQUIRED = 'EARLY_ACCESS_REQUIRED', EARLY_ACCESS_REQUIRED = 'EARLY_ACCESS_REQUIRED',
EMAIL_ALREADY_USED = 'EMAIL_ALREADY_USED', EMAIL_ALREADY_USED = 'EMAIL_ALREADY_USED',
EMAIL_SERVICE_NOT_CONFIGURED = 'EMAIL_SERVICE_NOT_CONFIGURED',
EMAIL_TOKEN_NOT_FOUND = 'EMAIL_TOKEN_NOT_FOUND', EMAIL_TOKEN_NOT_FOUND = 'EMAIL_TOKEN_NOT_FOUND',
EMAIL_VERIFICATION_REQUIRED = 'EMAIL_VERIFICATION_REQUIRED', EMAIL_VERIFICATION_REQUIRED = 'EMAIL_VERIFICATION_REQUIRED',
EXPECT_TO_GRANT_DOC_USER_ROLES = 'EXPECT_TO_GRANT_DOC_USER_ROLES', EXPECT_TO_GRANT_DOC_USER_ROLES = 'EXPECT_TO_GRANT_DOC_USER_ROLES',

View File

@@ -92,10 +92,6 @@
} }
}, },
"mailer": { "mailer": {
"enabled": {
"type": "Boolean",
"desc": "Whether enabled mail service."
},
"SMTP.host": { "SMTP.host": {
"type": "String", "type": "String",
"desc": "Host of the email server (e.g. smtp.gmail.com)", "desc": "Host of the email server (e.g. smtp.gmail.com)",

View File

@@ -76,7 +76,6 @@ export const KNOWN_CONFIG_GROUPS = [
name: 'Notification', name: 'Notification',
module: 'mailer', module: 'mailer',
fields: [ fields: [
'enabled',
'SMTP.host', 'SMTP.host',
'SMTP.port', 'SMTP.port',
'SMTP.username', 'SMTP.username',

View File

@@ -11,7 +11,7 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hoo
import { AuthService, CaptchaService } from '@affine/core/modules/cloud'; import { AuthService, CaptchaService } from '@affine/core/modules/cloud';
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session'; import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
import { Unreachable } from '@affine/env/constant'; import { Unreachable } from '@affine/env/constant';
import type { UserFriendlyError } from '@affine/error'; import { UserFriendlyError } from '@affine/error';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { import {
@@ -95,8 +95,10 @@ export const SignInWithEmailStep = ({
); );
} catch (err) { } catch (err) {
console.error(err); console.error(err);
const error = UserFriendlyError.fromAny(err);
notify.error({ notify.error({
title: 'Failed to send email, please try again.', title: 'Failed to sign in',
message: t[`error.${error.name}`](error.data),
}); });
} }
setIsSending(false); setIsSending(false);
@@ -109,6 +111,7 @@ export const SignInWithEmailStep = ({
needCaptcha, needCaptcha,
state.redirectUrl, state.redirectUrl,
verifyToken, verifyToken,
t,
]); ]);
useEffect(() => { useEffect(() => {

View File

@@ -93,46 +93,22 @@ export const SignInStep = ({
setIsMutating(true); setIsMutating(true);
try { try {
const { hasPassword, registered, magicLink } = const { hasPassword } = await authService.checkUserByEmail(email);
await authService.checkUserByEmail(email);
if (registered) { if (hasPassword) {
// provider password sign-in if user has by default changeState(prev => ({
// If with payment, onl support email sign in to avoid redirect to affine app ...prev,
if (hasPassword) { email,
changeState(prev => ({ step: 'signInWithPassword',
...prev, hasPassword: true,
email, }));
step: 'signInWithPassword',
hasPassword: true,
}));
} else {
if (magicLink) {
changeState(prev => ({
...prev,
email,
step: 'signInWithEmail',
hasPassword: false,
}));
} else {
notify.error({
title: 'Failed to send email. Please contact the administrator.',
});
}
}
} else { } else {
if (magicLink) { changeState(prev => ({
changeState(prev => ({ ...prev,
...prev, email,
email, step: 'signInWithEmail',
step: 'signInWithEmail', hasPassword: false,
hasPassword: false, }));
}));
} else {
notify.error({
title: 'Failed to send email. Please contact the administrator.',
});
}
} }
} catch (err: any) { } catch (err: any) {
console.error(err); console.error(err);

View File

@@ -7690,6 +7690,10 @@ export function useAFFiNEI18N(): {
["error.HTTP_REQUEST_ERROR"](options: { ["error.HTTP_REQUEST_ERROR"](options: {
readonly message: string; readonly message: string;
}): string; }): string;
/**
* `Email service is not configured.`
*/
["error.EMAIL_SERVICE_NOT_CONFIGURED"](): string;
/** /**
* `Query is too long, max length is {{max}}.` * `Query is too long, max length is {{max}}.`
*/ */

View File

@@ -1923,6 +1923,7 @@
"error.BAD_REQUEST": "Bad request.", "error.BAD_REQUEST": "Bad request.",
"error.GRAPHQL_BAD_REQUEST": "GraphQL bad request, code: {{code}}, {{message}}", "error.GRAPHQL_BAD_REQUEST": "GraphQL bad request, code: {{code}}, {{message}}",
"error.HTTP_REQUEST_ERROR": "HTTP request error, message: {{message}}", "error.HTTP_REQUEST_ERROR": "HTTP request error, message: {{message}}",
"error.EMAIL_SERVICE_NOT_CONFIGURED": "Email service is not configured.",
"error.QUERY_TOO_LONG": "Query is too long, max length is {{max}}.", "error.QUERY_TOO_LONG": "Query is too long, max length is {{max}}.",
"error.VALIDATION_ERROR": "Validation error, errors: {{errors}}", "error.VALIDATION_ERROR": "Validation error, errors: {{errors}}",
"error.USER_NOT_FOUND": "User not found.", "error.USER_NOT_FOUND": "User not found.",