Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/channels/email/smtp-imap/SmtpImapChannelHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { smtpImapSendBodySchema, smtpImapSendResponseSchema } from '../../../htt
import type { SmtpImapSendResponse } from '../../../http/contracts/smtp-imap-outgoing';
import { NotFoundError } from '../../../errors';
import { SYSTEM_CONTEXT } from '../../../services/RequestContext';
import { OAuth2TokenRefreshService } from '../../../services/OAuth2TokenRefreshService';
import { extractConversationIdFromMessageId, extractConversationIdFromReferences } from '../shared/MessageIdUtils';

const DEFAULT_SESSION_TIMEOUT_MS = 24 * 60 * 60 * 1000;
Expand All @@ -50,6 +51,7 @@ export class SmtpImapChannelHost {
@inject(ProjectService) private readonly projectService: ProjectService,
@inject(UserService) private readonly userService: UserService,
@inject(SecretRefUtils) private readonly secretRefUtils: SecretRefUtils,
@inject(OAuth2TokenRefreshService) private readonly oauth2TokenRefreshService: OAuth2TokenRefreshService,
) {}

static getOpenAPIPaths(): RouteConfig[] {
Expand Down Expand Up @@ -123,6 +125,18 @@ export class SmtpImapChannelHost {
}
const { fromAddress, smtp, threadingStrategy, oauth2 } = configResult.data;

if (oauth2?.accessToken && oauth2.accessTokenExpiry && Date.now() >= oauth2.accessTokenExpiry) {
logger.info({ channelProviderId }, 'SMTP/IMAP outgoing: OAuth2 token expired, refreshing inline');
await this.oauth2TokenRefreshService.refreshProvider(channelProviderId);
const refreshedRawConfig = await this.secretRefUtils.resolveObject(providerRecord.config as Record<string, unknown>);
const refreshedResult = smtpImapChannelProviderConfigSchema.safeParse(refreshedRawConfig);
if (refreshedResult.success) {
configResult.data = refreshedResult.data;
}
}

const { oauth2: refreshedOAuth2 } = configResult.data;

let resolvedStageId = body.stageId ?? queryStageId;
if (!resolvedStageId) {
const project = await this.projectService.getProjectById(projectId, SYSTEM_CONTEXT);
Expand Down Expand Up @@ -165,7 +179,7 @@ export class SmtpImapChannelHost {
smtp.secure,
smtp.auth.user,
smtp.auth.pass,
oauth2?.accessToken,
refreshedOAuth2?.accessToken,
);

try {
Expand Down
13 changes: 13 additions & 0 deletions src/services/ImapInboundService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { eq, and } from 'drizzle-orm';
import { db } from '../db';
import { providers, apiKeys } from '../db/schema';
import { SmtpImapChannelHost } from '../channels/email/smtp-imap/SmtpImapChannelHost';
import { OAuth2TokenRefreshService } from './OAuth2TokenRefreshService';
import { smtpImapChannelProviderConfigSchema } from './providers/channel/SmtpImapChannelProvider';
import { SecretRefUtils } from './secrets/SecretRefUtils';
import { logger } from '../utils/logger';
Expand Down Expand Up @@ -430,6 +431,12 @@ export class ImapInboundService {

const config = configResult.data;

if (config.oauth2?.accessToken && config.oauth2.accessTokenExpiry && Date.now() >= config.oauth2.accessTokenExpiry) {
logger.info({ providerId }, 'IMAP reload: OAuth2 token expired, refreshing inline');
await container.resolve(OAuth2TokenRefreshService).refreshProvider(providerId);
return;
}

const apiKeyRecord = await this.findProjectApiKey(config.projectId);
if (!apiKeyRecord) {
logger.warn({ providerId, projectId: config.projectId }, 'No API key found for project, skipping IMAP reload');
Expand Down Expand Up @@ -487,6 +494,12 @@ export class ImapInboundService {

const config = configResult.data;

if (config.oauth2?.accessToken && config.oauth2.accessTokenExpiry && Date.now() >= config.oauth2.accessTokenExpiry) {
logger.info({ providerId: provider.id }, 'IMAP discovery: OAuth2 token expired, refreshing inline');
await container.resolve(OAuth2TokenRefreshService).refreshProvider(provider.id);
continue;
}

const apiKeyRecord = await this.findProjectApiKey(config.projectId);
if (!apiKeyRecord) {
logger.warn({ providerId: provider.id, projectId: config.projectId }, 'No API key found for project, skipping IMAP inbound');
Expand Down
Loading