fix: sessions from inactive providers can still be used (#1458)
* fix: sessions from inactive providers can still be used * fix(lint): dependency keys not sorted * chore: address pull request feedback
This commit is contained in:
@@ -49,14 +49,17 @@ const createCallback = <TAllowedNames extends string, TName extends TAllowedName
|
|||||||
* We are not using the runOnInit method as we want to run the job only once we start the cron job schedule manually.
|
* We are not using the runOnInit method as we want to run the job only once we start the cron job schedule manually.
|
||||||
* This allows us to always run it once we start it. Additionally it will not run the callback if only the cron job file is imported.
|
* This allows us to always run it once we start it. Additionally it will not run the callback if only the cron job file is imported.
|
||||||
*/
|
*/
|
||||||
const scheduledTask = cron.schedule(cronExpression, () => void catchingCallbackAsync(), {
|
let scheduledTask: cron.ScheduledTask | null = null;
|
||||||
scheduled: false,
|
if (cronExpression !== "never") {
|
||||||
name,
|
scheduledTask = cron.schedule(cronExpression, () => void catchingCallbackAsync(), {
|
||||||
timezone: creatorOptions.timezone,
|
scheduled: false,
|
||||||
});
|
name,
|
||||||
creatorOptions.logger.logDebug(
|
timezone: creatorOptions.timezone,
|
||||||
`The cron job '${name}' was created with expression ${cronExpression} in timezone ${creatorOptions.timezone} and runOnStart ${options.runOnStart}`,
|
});
|
||||||
);
|
creatorOptions.logger.logDebug(
|
||||||
|
`The cron job '${name}' was created with expression ${cronExpression} in timezone ${creatorOptions.timezone} and runOnStart ${options.runOnStart}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -90,7 +93,7 @@ export const createCronJobCreator = <TAllowedNames extends string = string>(
|
|||||||
options: CreateCronJobOptions = { runOnStart: false },
|
options: CreateCronJobOptions = { runOnStart: false },
|
||||||
) => {
|
) => {
|
||||||
creatorOptions.logger.logDebug(`Validating cron expression '${cronExpression}' for job: ${name}`);
|
creatorOptions.logger.logDebug(`Validating cron expression '${cronExpression}' for job: ${name}`);
|
||||||
if (!cron.validate(cronExpression)) {
|
if (cronExpression !== "never" && !cron.validate(cronExpression)) {
|
||||||
throw new Error(`Invalid cron expression '${cronExpression}' for job '${name}'`);
|
throw new Error(`Invalid cron expression '${cronExpression}' for job '${name}'`);
|
||||||
}
|
}
|
||||||
creatorOptions.logger.logDebug(`Cron job expression '${cronExpression}' for job ${name} is valid`);
|
creatorOptions.logger.logDebug(`Cron job expression '${cronExpression}' for job ${name} is valid`);
|
||||||
@@ -102,6 +105,8 @@ export const createCronJobCreator = <TAllowedNames extends string = string>(
|
|||||||
// This is a type guard to check if the cron expression is valid and give the user a type hint
|
// This is a type guard to check if the cron expression is valid and give the user a type hint
|
||||||
return returnValue as unknown as ValidateCron<TExpression> extends true
|
return returnValue as unknown as ValidateCron<TExpression> extends true
|
||||||
? typeof returnValue
|
? typeof returnValue
|
||||||
: "Invalid cron expression";
|
: TExpression extends "never"
|
||||||
|
? typeof returnValue
|
||||||
|
: "Invalid cron expression";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ export const EVERY_10_MINUTES = checkCron("*/10 * * * *") satisfies string;
|
|||||||
export const EVERY_HOUR = checkCron("0 * * * *") satisfies string;
|
export const EVERY_HOUR = checkCron("0 * * * *") satisfies string;
|
||||||
export const EVERY_DAY = checkCron("0 0 * * */1") satisfies string;
|
export const EVERY_DAY = checkCron("0 0 * * */1") satisfies string;
|
||||||
export const EVERY_WEEK = checkCron("0 0 * * 1") satisfies string;
|
export const EVERY_WEEK = checkCron("0 0 * * 1") satisfies string;
|
||||||
|
export const NEVER = "never";
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
|
|||||||
|
|
||||||
options.logger.logInfo(`Starting schedule cron job ${job.name}.`);
|
options.logger.logInfo(`Starting schedule cron job ${job.name}.`);
|
||||||
await job.onStartAsync();
|
await job.onStartAsync();
|
||||||
job.scheduledTask.start();
|
job.scheduledTask?.start();
|
||||||
},
|
},
|
||||||
startAllAsync: async () => {
|
startAllAsync: async () => {
|
||||||
for (const job of jobRegistry.values()) {
|
for (const job of jobRegistry.values()) {
|
||||||
options.logger.logInfo(`Starting schedule of cron job ${job.name}.`);
|
options.logger.logInfo(`Starting schedule of cron job ${job.name}.`);
|
||||||
await job.onStartAsync();
|
await job.onStartAsync();
|
||||||
job.scheduledTask.start();
|
job.scheduledTask?.start();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
runManually: (name: keyof TJobs) => {
|
runManually: (name: keyof TJobs) => {
|
||||||
@@ -48,19 +48,19 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
|
|||||||
if (!job) return;
|
if (!job) return;
|
||||||
|
|
||||||
options.logger.logInfo(`Running schedule cron job ${job.name} manually.`);
|
options.logger.logInfo(`Running schedule cron job ${job.name} manually.`);
|
||||||
job.scheduledTask.now();
|
job.scheduledTask?.now();
|
||||||
},
|
},
|
||||||
stop: (name: keyof TJobs) => {
|
stop: (name: keyof TJobs) => {
|
||||||
const job = jobRegistry.get(name as string);
|
const job = jobRegistry.get(name as string);
|
||||||
if (!job) return;
|
if (!job) return;
|
||||||
|
|
||||||
options.logger.logInfo(`Stopping schedule cron job ${job.name}.`);
|
options.logger.logInfo(`Stopping schedule cron job ${job.name}.`);
|
||||||
job.scheduledTask.stop();
|
job.scheduledTask?.stop();
|
||||||
},
|
},
|
||||||
stopAll: () => {
|
stopAll: () => {
|
||||||
for (const job of jobRegistry.values()) {
|
for (const job of jobRegistry.values()) {
|
||||||
options.logger.logInfo(`Stopping schedule cron job ${job.name}.`);
|
options.logger.logInfo(`Stopping schedule cron job ${job.name}.`);
|
||||||
job.scheduledTask.stop();
|
job.scheduledTask?.stop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getJobRegistry() {
|
getJobRegistry() {
|
||||||
|
|||||||
@@ -24,10 +24,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@extractus/feed-extractor": "^7.1.3",
|
"@extractus/feed-extractor": "^7.1.3",
|
||||||
"@homarr/analytics": "workspace:^0.1.0",
|
"@homarr/analytics": "workspace:^0.1.0",
|
||||||
|
"@homarr/auth": "workspace:^0.1.0",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||||
"@homarr/cron-jobs-core": "workspace:^0.1.0",
|
"@homarr/cron-jobs-core": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/icons": "workspace:^0.1.0",
|
"@homarr/icons": "workspace:^0.1.0",
|
||||||
"@homarr/integrations": "workspace:^0.1.0",
|
"@homarr/integrations": "workspace:^0.1.0",
|
||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { mediaServerJob } from "./jobs/integrations/media-server";
|
|||||||
import { pingJob } from "./jobs/ping";
|
import { pingJob } from "./jobs/ping";
|
||||||
import type { RssFeed } from "./jobs/rss-feeds";
|
import type { RssFeed } from "./jobs/rss-feeds";
|
||||||
import { rssFeedsJob } from "./jobs/rss-feeds";
|
import { rssFeedsJob } from "./jobs/rss-feeds";
|
||||||
|
import { sessionCleanupJob } from "./jobs/session-cleanup";
|
||||||
import { createCronJobGroup } from "./lib";
|
import { createCronJobGroup } from "./lib";
|
||||||
|
|
||||||
export const jobGroup = createCronJobGroup({
|
export const jobGroup = createCronJobGroup({
|
||||||
@@ -26,6 +27,7 @@ export const jobGroup = createCronJobGroup({
|
|||||||
rssFeeds: rssFeedsJob,
|
rssFeeds: rssFeedsJob,
|
||||||
indexerManager: indexerManagerJob,
|
indexerManager: indexerManagerJob,
|
||||||
healthMonitoring: healthMonitoringJob,
|
healthMonitoring: healthMonitoringJob,
|
||||||
|
sessionCleanup: sessionCleanupJob,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
|
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
|
||||||
|
|||||||
38
packages/cron-jobs/src/jobs/session-cleanup.ts
Normal file
38
packages/cron-jobs/src/jobs/session-cleanup.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { env } from "@homarr/auth/env.mjs";
|
||||||
|
import { NEVER } from "@homarr/cron-jobs-core/expressions";
|
||||||
|
import { db, eq, inArray } from "@homarr/db";
|
||||||
|
import { sessions, users } from "@homarr/db/schema/sqlite";
|
||||||
|
import { supportedAuthProviders } from "@homarr/definitions";
|
||||||
|
import { logger } from "@homarr/log";
|
||||||
|
|
||||||
|
import { createCronJob } from "../lib";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes sessions for users that have inactive auth providers.
|
||||||
|
* Sessions from other providers are deleted so they can no longer be used.
|
||||||
|
*/
|
||||||
|
export const sessionCleanupJob = createCronJob("sessionCleanup", NEVER, {
|
||||||
|
runOnStart: true,
|
||||||
|
}).withCallback(async () => {
|
||||||
|
const currentAuthProviders = env.AUTH_PROVIDERS;
|
||||||
|
|
||||||
|
const inactiveAuthProviders = supportedAuthProviders.filter((provider) => !currentAuthProviders.includes(provider));
|
||||||
|
const subQuery = db
|
||||||
|
.select({ id: users.id })
|
||||||
|
.from(users)
|
||||||
|
.where(inArray(users.provider, inactiveAuthProviders))
|
||||||
|
.as("sq");
|
||||||
|
const sessionsWithInactiveProviders = await db
|
||||||
|
.select({ userId: sessions.userId })
|
||||||
|
.from(sessions)
|
||||||
|
.rightJoin(subQuery, eq(sessions.userId, subQuery.id));
|
||||||
|
|
||||||
|
const userIds = sessionsWithInactiveProviders.map(({ userId }) => userId).filter((value) => value !== null);
|
||||||
|
await db.delete(sessions).where(inArray(sessions.userId, userIds));
|
||||||
|
|
||||||
|
if (sessionsWithInactiveProviders.length > 0) {
|
||||||
|
logger.info(`Deleted sessions for inactive providers count=${userIds.length}`);
|
||||||
|
} else {
|
||||||
|
logger.debug("No sessions to delete");
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -2073,6 +2073,9 @@
|
|||||||
},
|
},
|
||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Hole Data"
|
"label": "DNS Hole Data"
|
||||||
|
},
|
||||||
|
"sessionCleanup": {
|
||||||
|
"label": "Session Cleanup"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -749,6 +749,9 @@ importers:
|
|||||||
'@homarr/analytics':
|
'@homarr/analytics':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../analytics
|
version: link:../analytics
|
||||||
|
'@homarr/auth':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../auth
|
||||||
'@homarr/common':
|
'@homarr/common':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../common
|
version: link:../common
|
||||||
@@ -761,6 +764,9 @@ importers:
|
|||||||
'@homarr/db':
|
'@homarr/db':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../db
|
version: link:../db
|
||||||
|
'@homarr/definitions':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../definitions
|
||||||
'@homarr/icons':
|
'@homarr/icons':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../icons
|
version: link:../icons
|
||||||
|
|||||||
Reference in New Issue
Block a user