* Allow admin access to user PATs * Tests for new screen in admin panel * Adding error for invalid token and way to parse error for OAuth 2 * Git bridge handles expired PAT * Script for alerting on close to expiry and expired git tokens * Refactoring and simplifying * Updating email templates to match agreed docs * tweak to email subject to include Overleaf * Allowing dry run in scripts and general tidy up * removing redundant tests and dry running script * Fixing CI errors * Adding new tab to admin test expectation * Address PR feedback on oauth2-server changes - Replace ad-hoc overleafErrorCode prop with a TokenExpiredError subclass - Collapse listTokens/listTokensForAdmin into a single hook Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Adding cron definitions for alerting on expiring git pat --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> GitOrigin-RevId: 69b9fd901a201592a580c69abe7bd7d603e85d3a
70 lines
1.8 KiB
JavaScript
70 lines
1.8 KiB
JavaScript
// One-off backfill: mark every personal access token that was already expired
|
|
// at the moment the expiry-notification feature shipped, so the recurring
|
|
// notifier (notify_expiring_tokens.mjs) does not blast a "your token has
|
|
// expired" email to users about long-dead tokens.
|
|
//
|
|
// `notificationsSuppressedAt` is set ONLY by this script. It is intentionally
|
|
// distinct from `lastNotifiedAt.expired` (which records actual sends) so the
|
|
// two cases remain unambiguous in the data forever.
|
|
//
|
|
// Idempotent: re-running does nothing further once the flag is set.
|
|
//
|
|
// Pass --dry-run to count matching tokens without writing.
|
|
|
|
import logger from '@overleaf/logger'
|
|
import {
|
|
db,
|
|
READ_PREFERENCE_SECONDARY,
|
|
} from '../../app/src/infrastructure/mongodb.mjs'
|
|
import { scriptRunner } from '../lib/ScriptRunner.mjs'
|
|
|
|
async function main() {
|
|
const dryRun = process.argv.includes('--dry-run')
|
|
const now = new Date()
|
|
const cursor = db.oauthAccessTokens.find(
|
|
{
|
|
type: 'pat',
|
|
accessTokenExpiresAt: { $lt: now },
|
|
'lastNotifiedAt.expired': { $exists: false },
|
|
notificationsSuppressedAt: { $exists: false },
|
|
},
|
|
{
|
|
projection: { _id: 1 },
|
|
readPreference: READ_PREFERENCE_SECONDARY,
|
|
}
|
|
)
|
|
|
|
let matched = 0
|
|
for await (const doc of cursor) {
|
|
if (!dryRun) {
|
|
await db.oauthAccessTokens.updateOne(
|
|
{ _id: doc._id },
|
|
{ $set: { notificationsSuppressedAt: now } }
|
|
)
|
|
}
|
|
matched++
|
|
}
|
|
if (dryRun) {
|
|
logger.info(
|
|
{ matched },
|
|
'dry run: expired-token notifications would be suppressed'
|
|
)
|
|
} else {
|
|
logger.info(
|
|
{ suppressed: matched },
|
|
'expired-token notifications suppressed'
|
|
)
|
|
}
|
|
}
|
|
|
|
try {
|
|
await scriptRunner(main)
|
|
process.exit(0)
|
|
} catch (error) {
|
|
logger.error(
|
|
{ err: error },
|
|
'backfill_suppress_expired_token_notifications failed'
|
|
)
|
|
process.exit(1)
|
|
}
|