From 965084cc0c28b0c9c54316b8be7b5b5dfe8708d5 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Wed, 16 Apr 2025 12:48:00 -0300 Subject: [PATCH] notifyExpiredTokens fixes --- .../resource-cleanup-queue.ts | 4 +- .../service-token/service-token-dal.ts | 67 +++++++------------ .../service-token/service-token-service.ts | 63 ++++++++++------- .../templates/serviceTokenExpired.handlebars | 4 +- 4 files changed, 70 insertions(+), 68 deletions(-) diff --git a/backend/src/services/resource-cleanup/resource-cleanup-queue.ts b/backend/src/services/resource-cleanup/resource-cleanup-queue.ts index edd684783..32f180636 100644 --- a/backend/src/services/resource-cleanup/resource-cleanup-queue.ts +++ b/backend/src/services/resource-cleanup/resource-cleanup-queue.ts @@ -22,7 +22,7 @@ type TDailyResourceCleanUpQueueServiceFactoryDep = { secretFolderVersionDAL: Pick; snapshotDAL: Pick; secretSharingDAL: Pick; - serviceTokenService: Pick; + serviceTokenService: Pick; queueService: TQueueServiceFactory; }; @@ -53,7 +53,7 @@ export const dailyResourceCleanUpQueueServiceFactory = ({ await secretVersionDAL.pruneExcessVersions(); await secretVersionV2DAL.pruneExcessVersions(); await secretFolderVersionDAL.pruneExcessVersions(); - await serviceTokenService.notifyExpiredTokens(); + await serviceTokenService.notifyExpiringTokens(); logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`); }); diff --git a/backend/src/services/service-token/service-token-dal.ts b/backend/src/services/service-token/service-token-dal.ts index 660d64a41..adb2f325a 100644 --- a/backend/src/services/service-token/service-token-dal.ts +++ b/backend/src/services/service-token/service-token-dal.ts @@ -28,51 +28,36 @@ export const serviceTokenDALFactory = (db: TDbClient) => { } }; - const findExpiredTokens = async (tx?: Knex, batchSize = 1000) => { + const findExpiringTokens = async (tx?: Knex, batchSize = 500, offset = 0) => { try { - const allDocs: { name: string; projectName: string; createdByEmail: string; id: string; projectId: string }[] = - []; - let offset = 0; - let hasMoreRecords = true; + const batch: { name: string; projectName: string; createdByEmail: string; id: string; projectId: string }[] = + await (tx || db.replicaNode())(TableName.ServiceToken) + .leftJoin( + TableName.Users, + `${TableName.Users}.id`, + db.raw(`${TableName.ServiceToken}."createdBy"::uuid`) + ) + .join(TableName.Project, `${TableName.Project}.id`, `${TableName.ServiceToken}.projectId`) + .whereRaw( + `${TableName.ServiceToken}."expiresAt" < NOW() + INTERVAL '1 day' AND ${TableName.ServiceToken}."expiryNotificationSent" = false` + ) + .whereNotNull(`${TableName.Users}.email`) + .select( + db.ref("id").withSchema(TableName.ServiceToken), + db.ref("name").withSchema(TableName.ServiceToken), + db.ref("projectId").withSchema(TableName.ServiceToken), + db.ref("createdBy").withSchema(TableName.ServiceToken), + db.ref("email").withSchema(TableName.Users).as("createdByEmail"), + db.ref("name").withSchema(TableName.Project).as("projectName") + ) + .limit(batchSize) + .offset(offset); - while (hasMoreRecords) { - // eslint-disable-next-line - const batch: { name: string; projectName: string; createdByEmail: string; id: string; projectId: string }[] = - // eslint-disable-next-line no-await-in-loop - await (tx || db.replicaNode())(TableName.ServiceToken) - .leftJoin( - TableName.Users, - `${TableName.Users}.id`, - db.raw(`${TableName.ServiceToken}."createdBy"::uuid`) - ) - .join(TableName.Project, `${TableName.Project}.id`, `${TableName.ServiceToken}.projectId`) - .whereRaw( - `${TableName.ServiceToken}."expiresAt" < NOW() + INTERVAL '1 day' AND ${TableName.ServiceToken}."expiryNotificationSent" = false` - ) - .whereNotNull(`${TableName.Users}.email`) - .select( - db.ref("id").withSchema(TableName.ServiceToken), - db.ref("name").withSchema(TableName.ServiceToken), - db.ref("projectId").withSchema(TableName.ServiceToken), - db.ref("createdBy").withSchema(TableName.ServiceToken), - db.ref("email").withSchema(TableName.Users).as("createdByEmail"), - db.ref("name").withSchema(TableName.Project).as("projectName") - ) - .limit(batchSize) - .offset(offset); - - if (batch.length === 0) { - hasMoreRecords = false; - } else { - allDocs.push(...batch); - offset += batchSize; - } - } - - return allDocs; + return batch; } catch (err) { throw new DatabaseError({ error: err, name: "FindExpiredTokens" }); } }; - return { ...stOrm, findById, findExpiredTokens }; + + return { ...stOrm, findById, findExpiringTokens }; }; diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 760ad4b79..bbd306bb5 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -189,31 +189,48 @@ export const serviceTokenServiceFactory = ({ return { ...serviceToken, lastUsed: new Date(), orgId: project.orgId }; }; - const notifyExpiredTokens = async () => { + const notifyExpiringTokens = async () => { const appCfg = getConfig(); + let processedCount = 0; + let hasMoreRecords = true; + let offset = 0; + const batchSize = 500; - const expiredTokens = await serviceTokenDAL.findExpiredTokens(); - if (expiredTokens.length === 0) return; + while (hasMoreRecords) { + // eslint-disable-next-line no-await-in-loop + const expiringTokens = await serviceTokenDAL.findExpiringTokens(undefined, batchSize, offset); - await Promise.all( - expiredTokens.map(async (token) => { - try { - await smtpService.sendMail({ - recipients: [token.createdByEmail], - subjectLine: "Your Service Token is about to expire", - template: SmtpTemplates.ServiceTokenExpired, - substitutions: { - tokenName: token.name, - projectName: token.projectName, - url: `${appCfg.SITE_URL}/secret-manager/${token.projectId}/access-management?selectedTab=service-tokens` - } - }); - await serviceTokenDAL.update({ id: token.id }, { expiryNotificationSent: true }); - } catch (error) { - logger.error(error, `Failed to send expiration notification for token ${token.id}:`); - } - }) - ); + if (expiringTokens.length === 0) { + hasMoreRecords = false; + break; + } + + // eslint-disable-next-line no-await-in-loop + await Promise.all( + expiringTokens.map(async (token) => { + try { + await smtpService.sendMail({ + recipients: [token.createdByEmail], + subjectLine: "Service Token Expiry Notice", + template: SmtpTemplates.ServiceTokenExpired, + substitutions: { + tokenName: token.name, + projectName: token.projectName, + url: `${appCfg.SITE_URL}/secret-manager/${token.projectId}/access-management?selectedTab=service-tokens` + } + }); + await serviceTokenDAL.update({ id: token.id }, { expiryNotificationSent: true }); + } catch (error) { + logger.error(error, `Failed to send expiration notification for token ${token.id}:`); + } + }) + ); + + processedCount += expiringTokens.length; + offset += batchSize; + } + + return processedCount; }; return { @@ -222,6 +239,6 @@ export const serviceTokenServiceFactory = ({ getServiceToken, getProjectServiceTokens, fnValidateServiceToken, - notifyExpiredTokens + notifyExpiringTokens }; }; diff --git a/backend/src/services/smtp/templates/serviceTokenExpired.handlebars b/backend/src/services/smtp/templates/serviceTokenExpired.handlebars index 0b3c63dfb..199150c05 100644 --- a/backend/src/services/smtp/templates/serviceTokenExpired.handlebars +++ b/backend/src/services/smtp/templates/serviceTokenExpired.handlebars @@ -7,10 +7,10 @@ -

Your Service Token is about to expire

+

Service Token Expiry Notice

Your service token "{{tokenName}}" will expire within 24 hours.

-

This token is currently being on project "{{projectName}}". If this token is still needed for your workflow, please create a new one before it expires.

+

This token is currently being used on project "{{projectName}}". If this token is still needed for your workflow, please create a new one before it expires.

Create New Token