diff --git a/backend/e2e-test/mocks/smtp.ts b/backend/e2e-test/mocks/smtp.ts index 9f83f7134a..4ba42e8389 100644 --- a/backend/e2e-test/mocks/smtp.ts +++ b/backend/e2e-test/mocks/smtp.ts @@ -5,6 +5,9 @@ export const mockSmtpServer = (): TSmtpService => { return { sendMail: async (data) => { storage.push(data); + }, + verify: async () => { + return true; } }; }; diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts index 17c1ddaaf2..45a58bd1ea 100644 --- a/backend/src/ee/services/oidc/oidc-config-service.ts +++ b/backend/src/ee/services/oidc/oidc-config-service.ts @@ -17,7 +17,7 @@ import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; -import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; @@ -56,7 +56,7 @@ type TOidcConfigServiceFactoryDep = { orgBotDAL: Pick; licenseService: Pick; tokenService: Pick; - smtpService: Pick; + smtpService: Pick; permissionService: Pick; oidcConfigDAL: Pick; }; @@ -223,6 +223,7 @@ export const oidcConfigServiceFactory = ({ let newUser: TUsers | undefined; if (serverCfg.trustOidcEmails) { + // we prioritize getting the most complete user to create the new alias under newUser = await userDAL.findOne( { email, @@ -230,6 +231,23 @@ export const oidcConfigServiceFactory = ({ }, tx ); + + if (!newUser) { + // this fetches user entries created via invites + newUser = await userDAL.findOne( + { + username: email + }, + tx + ); + + if (newUser && !newUser.isEmailVerified) { + // we automatically mark it as email-verified because we've configured trust for OIDC emails + newUser = await userDAL.updateById(newUser.id, { + isEmailVerified: true + }); + } + } } if (!newUser) { @@ -332,14 +350,20 @@ export const oidcConfigServiceFactory = ({ userId: user.id }); - await smtpService.sendMail({ - template: SmtpTemplates.EmailVerification, - subjectLine: "Infisical confirmation code", - recipients: [user.email], - substitutions: { - code: token - } - }); + await smtpService + .sendMail({ + template: SmtpTemplates.EmailVerification, + subjectLine: "Infisical confirmation code", + recipients: [user.email], + substitutions: { + code: token + } + }) + .catch((err: Error) => { + throw new OidcAuthError({ + message: `Error sending email confirmation code for user registration - contact the Infisical instance admin. ${err.message}` + }); + }); } return { isUserCompleted, providerAuthToken }; @@ -395,6 +419,18 @@ export const oidcConfigServiceFactory = ({ message: `Organization bot for organization with ID '${org.id}' not found`, name: "OrgBotNotFound" }); + + const serverCfg = await getServerCfg(); + if (isActive && !serverCfg.trustOidcEmails) { + const isSmtpConnected = await smtpService.verify(); + if (!isSmtpConnected) { + throw new BadRequestError({ + message: + "Cannot enable OIDC when there are issues with the instance's SMTP configuration. Bypass this by turning on trust for OIDC emails in the server admin console." + }); + } + } + const key = infisicalSymmetricDecrypt({ ciphertext: orgBot.encryptedSymmetricKey, iv: orgBot.symmetricKeyIV, diff --git a/backend/src/lib/errors/index.ts b/backend/src/lib/errors/index.ts index 0818cfe7d9..cc1c1d66b4 100644 --- a/backend/src/lib/errors/index.ts +++ b/backend/src/lib/errors/index.ts @@ -133,3 +133,15 @@ export class ScimRequestError extends Error { this.status = status; } } + +export class OidcAuthError extends Error { + name: string; + + error: unknown; + + constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) { + super(message || "Something went wrong"); + this.name = name || "OidcAuthError"; + this.error = error; + } +} diff --git a/backend/src/server/boot-strap-check.ts b/backend/src/server/boot-strap-check.ts index ceaef59e7f..7db2a71e82 100644 --- a/backend/src/server/boot-strap-check.ts +++ b/backend/src/server/boot-strap-check.ts @@ -46,10 +46,10 @@ export const bootstrapCheck = async ({ db }: BootstrapOpt) => { await createTransport(smtpCfg) .verify() .then(async () => { - console.info("SMTP successfully connected"); + console.info(`SMTP - Verified connection to ${appCfg.SMTP_HOST}:${appCfg.SMTP_PORT}`); }) - .catch((err) => { - console.error(`SMTP - Failed to connect to ${appCfg.SMTP_HOST}:${appCfg.SMTP_PORT}`); + .catch((err: Error) => { + console.error(`SMTP - Failed to connect to ${appCfg.SMTP_HOST}:${appCfg.SMTP_PORT} - ${err.message}`); logger.error(err); }); diff --git a/backend/src/server/plugins/error-handler.ts b/backend/src/server/plugins/error-handler.ts index 007902a176..c08b2fe4c7 100644 --- a/backend/src/server/plugins/error-handler.ts +++ b/backend/src/server/plugins/error-handler.ts @@ -10,6 +10,7 @@ import { GatewayTimeoutError, InternalServerError, NotFoundError, + OidcAuthError, RateLimitError, ScimRequestError, UnauthorizedError @@ -83,7 +84,10 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider status: error.status, detail: error.detail }); - // Handle JWT errors and make them more human-readable for the end-user. + } else if (error instanceof OidcAuthError) { + void res + .status(HttpStatusCodes.InternalServerError) + .send({ statusCode: HttpStatusCodes.InternalServerError, message: error.message, error: error.name }); } else if (error instanceof jwt.JsonWebTokenError) { const message = (() => { if (error.message === JWTErrors.JwtExpired) { diff --git a/backend/src/services/smtp/smtp-service.ts b/backend/src/services/smtp/smtp-service.ts index 1f38babb37..bdf2fe18c5 100644 --- a/backend/src/services/smtp/smtp-service.ts +++ b/backend/src/services/smtp/smtp-service.ts @@ -77,5 +77,21 @@ export const smtpServiceFactory = (cfg: TSmtpConfig) => { } }; - return { sendMail }; + const verify = async () => { + const isConnected = smtp + .verify() + .then(async () => { + logger.info("SMTP connected"); + return true; + }) + .catch((err: Error) => { + logger.error("SMTP error"); + logger.error(err); + return false; + }); + + return isConnected; + }; + + return { sendMail, verify }; };