diff --git a/services/web/scripts/oauth/backdate_token_expiry.mjs b/services/web/scripts/oauth/backdate_token_expiry.mjs new file mode 100644 index 0000000000..c8ee4e91cd --- /dev/null +++ b/services/web/scripts/oauth/backdate_token_expiry.mjs @@ -0,0 +1,124 @@ +import minimist from 'minimist' +import { db, ObjectId } from '../../app/src/infrastructure/mongodb.mjs' +import { scriptRunner } from '../lib/ScriptRunner.mjs' + +// Test helper: move a personal access token's accessTokenExpiresAt EARLIER, to +// simulate an expiring-soon or already-expired token when manually testing the +// expiry-notification flow (notify_expiring_tokens.mjs / git-bridge messaging). +// +// This script can only ever back-date a token: it refuses to set an expiry that +// is later than the token's current one. Moving an expiry forward would extend +// the life of a token that should be dead, so that direction is disallowed. +// +// Pass --dry-run to report what would change without writing. + +async function main() { + const opts = parseArgs() + + const token = await db.oauthAccessTokens.findOne({ + _id: new ObjectId(opts.tokenId), + }) + if (token == null) { + console.error(`No oauthAccessToken found with _id ${opts.tokenId}`) + process.exit(1) + } + + const currentExpiry = token.accessTokenExpiresAt + if (!(currentExpiry instanceof Date) || isNaN(currentExpiry.getTime())) { + console.error( + `Token ${opts.tokenId} has no valid accessTokenExpiresAt ` + + `(found: ${JSON.stringify(currentExpiry)}). ` + + 'This script only operates on tokens with an expiry, such as PATs.' + ) + process.exit(1) + } + + if (opts.expiry >= currentExpiry) { + console.error( + `Refusing to move expiry forward: requested ${opts.expiry.toISOString()} ` + + `is not earlier than current ${currentExpiry.toISOString()}. ` + + 'This script only back-dates token expiry.' + ) + process.exit(1) + } + + if (opts.dryRun) { + console.warn( + `[dry run] would back-date token ${opts.tokenId} expiry from ` + + `${currentExpiry.toISOString()} to ${opts.expiry.toISOString()}` + ) + return + } + + await db.oauthAccessTokens.updateOne( + { _id: token._id }, + { $set: { accessTokenExpiresAt: opts.expiry } } + ) + console.warn( + `Back-dated token ${opts.tokenId} expiry from ` + + `${currentExpiry.toISOString()} to ${opts.expiry.toISOString()}` + ) +} + +function parseArgs() { + const args = minimist(process.argv.slice(2), { + boolean: ['help', 'dry-run'], + }) + if (args.help) { + usage() + process.exit(0) + } + if (args._.length !== 0) { + usage() + process.exit(1) + } + + const tokenId = args['token-id'] + if (tokenId == null) { + console.error('Missing --token-id option') + process.exit(1) + } + if (!ObjectId.isValid(tokenId)) { + console.error(`Invalid --token-id: ${tokenId}`) + process.exit(1) + } + + if (args['expiry-date'] == null) { + console.error('Missing --expiry-date option') + process.exit(1) + } + const expiry = new Date(args['expiry-date']) + if (isNaN(expiry.getTime())) { + console.error(`Invalid --expiry-date: ${args['expiry-date']}`) + process.exit(1) + } + + return { + tokenId, + expiry, + dryRun: args['dry-run'], + } +} + +function usage() { + console.error(`Usage: backdate_token_expiry.mjs [OPTS...] + +Moves a personal access token's expiry EARLIER, to simulate an expiring or +expired token when testing the expiry-notification flow. Only ever back-dates; +refuses to move an expiry forward. + +Options: + --token-id _id of the oauthAccessToken to back-date + --expiry-date New expiry, earlier than the current one (e.g. 2026-06-01T00:00:00Z) + --dry-run Report the change without writing + --help Show this message +`) +} + +try { + await scriptRunner(main) + process.exit(0) +} catch (error) { + console.error(error) + process.exit(1) +}