mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 11:27:32 +00:00
notifyExpiredTokens fixes
This commit is contained in:
@@ -22,7 +22,7 @@ type TDailyResourceCleanUpQueueServiceFactoryDep = {
|
|||||||
secretFolderVersionDAL: Pick<TSecretFolderVersionDALFactory, "pruneExcessVersions">;
|
secretFolderVersionDAL: Pick<TSecretFolderVersionDALFactory, "pruneExcessVersions">;
|
||||||
snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">;
|
snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">;
|
||||||
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets" | "pruneExpiredSecretRequests">;
|
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets" | "pruneExpiredSecretRequests">;
|
||||||
serviceTokenService: Pick<TServiceTokenServiceFactory, "notifyExpiredTokens">;
|
serviceTokenService: Pick<TServiceTokenServiceFactory, "notifyExpiringTokens">;
|
||||||
queueService: TQueueServiceFactory;
|
queueService: TQueueServiceFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ export const dailyResourceCleanUpQueueServiceFactory = ({
|
|||||||
await secretVersionDAL.pruneExcessVersions();
|
await secretVersionDAL.pruneExcessVersions();
|
||||||
await secretVersionV2DAL.pruneExcessVersions();
|
await secretVersionV2DAL.pruneExcessVersions();
|
||||||
await secretFolderVersionDAL.pruneExcessVersions();
|
await secretFolderVersionDAL.pruneExcessVersions();
|
||||||
await serviceTokenService.notifyExpiredTokens();
|
await serviceTokenService.notifyExpiringTokens();
|
||||||
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
|
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
try {
|
||||||
const allDocs: { name: string; projectName: string; createdByEmail: string; id: string; projectId: string }[] =
|
const batch: { name: string; projectName: string; createdByEmail: string; id: string; projectId: string }[] =
|
||||||
[];
|
await (tx || db.replicaNode())(TableName.ServiceToken)
|
||||||
let offset = 0;
|
.leftJoin<TUsers>(
|
||||||
let hasMoreRecords = true;
|
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) {
|
return batch;
|
||||||
// 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<TUsers>(
|
|
||||||
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;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new DatabaseError({ error: err, name: "FindExpiredTokens" });
|
throw new DatabaseError({ error: err, name: "FindExpiredTokens" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return { ...stOrm, findById, findExpiredTokens };
|
|
||||||
|
return { ...stOrm, findById, findExpiringTokens };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -189,31 +189,48 @@ export const serviceTokenServiceFactory = ({
|
|||||||
return { ...serviceToken, lastUsed: new Date(), orgId: project.orgId };
|
return { ...serviceToken, lastUsed: new Date(), orgId: project.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const notifyExpiredTokens = async () => {
|
const notifyExpiringTokens = async () => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
let processedCount = 0;
|
||||||
|
let hasMoreRecords = true;
|
||||||
|
let offset = 0;
|
||||||
|
const batchSize = 500;
|
||||||
|
|
||||||
const expiredTokens = await serviceTokenDAL.findExpiredTokens();
|
while (hasMoreRecords) {
|
||||||
if (expiredTokens.length === 0) return;
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const expiringTokens = await serviceTokenDAL.findExpiringTokens(undefined, batchSize, offset);
|
||||||
|
|
||||||
await Promise.all(
|
if (expiringTokens.length === 0) {
|
||||||
expiredTokens.map(async (token) => {
|
hasMoreRecords = false;
|
||||||
try {
|
break;
|
||||||
await smtpService.sendMail({
|
}
|
||||||
recipients: [token.createdByEmail],
|
|
||||||
subjectLine: "Your Service Token is about to expire",
|
// eslint-disable-next-line no-await-in-loop
|
||||||
template: SmtpTemplates.ServiceTokenExpired,
|
await Promise.all(
|
||||||
substitutions: {
|
expiringTokens.map(async (token) => {
|
||||||
tokenName: token.name,
|
try {
|
||||||
projectName: token.projectName,
|
await smtpService.sendMail({
|
||||||
url: `${appCfg.SITE_URL}/secret-manager/${token.projectId}/access-management?selectedTab=service-tokens`
|
recipients: [token.createdByEmail],
|
||||||
}
|
subjectLine: "Service Token Expiry Notice",
|
||||||
});
|
template: SmtpTemplates.ServiceTokenExpired,
|
||||||
await serviceTokenDAL.update({ id: token.id }, { expiryNotificationSent: true });
|
substitutions: {
|
||||||
} catch (error) {
|
tokenName: token.name,
|
||||||
logger.error(error, `Failed to send expiration notification for token ${token.id}:`);
|
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 {
|
return {
|
||||||
@@ -222,6 +239,6 @@ export const serviceTokenServiceFactory = ({
|
|||||||
getServiceToken,
|
getServiceToken,
|
||||||
getProjectServiceTokens,
|
getProjectServiceTokens,
|
||||||
fnValidateServiceToken,
|
fnValidateServiceToken,
|
||||||
notifyExpiredTokens
|
notifyExpiringTokens
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h2>Your Service Token is about to expire</h2>
|
<h2>Service Token Expiry Notice</h2>
|
||||||
<p>Your service token <strong>"{{tokenName}}"</strong> will expire within 24 hours.</p>
|
<p>Your service token <strong>"{{tokenName}}"</strong> will expire within 24 hours.</p>
|
||||||
|
|
||||||
<p>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.</p>
|
<p>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.</p>
|
||||||
|
|
||||||
<a href="{{url}}">Create New Token</a>
|
<a href="{{url}}">Create New Token</a>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user