{"record":{"id":"fac796999312c509","repo":"hcengineering/platform","slug":"expiredlink","errorCode":"ExpiredLink","errorMessage":"ExpiredLink","messagePattern":"ExpiredLink","errorType":"error_code","errorClass":"PlatformError","httpStatus":410,"severity":"error","filePath":"server/account/src/utils.ts","lineNumber":1237,"sourceCode":"      ctx.error('Workspace record generation failed. Could not create a workspace record in 1000 attempts.', {\n        workspaceName\n      })\n      throw new PlatformError(\n        new Status(Severity.ERROR, platform.status.InternalServerError, { region, workspaceName, baseWorkspaceUrl })\n      )\n    }\n  }\n}\n\nexport async function checkInvite (ctx: MeasureContext, invite: WorkspaceInvite, email: string): Promise<WorkspaceUuid> {\n  if (invite.remainingUses === 0) {\n    ctx.warn('Invite limit exceeded', { email, ...invite })\n    throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))\n  }\n\n  if (invite.expiresOn > 0 && invite.expiresOn < Date.now()) {\n    ctx.warn('Invite link expired', { email, ...invite })\n    throw new PlatformError(new Status(Severity.ERROR, platform.status.ExpiredLink, {}))\n  }\n\n  // TODO: consider not using RegExp with user input as some regexes might\n  // be slow or even cause catastrophic backtracking\n  // if (\n  //   invite.emailPattern != null &&\n  //   invite.emailPattern.trim().length > 0 &&\n  //   !new RegExp(invite.emailPattern).test(email)\n  // ) {\n  //   ctx.error(\"Invite doesn't allow this email address\", { email, ...invite })\n  //   Analytics.handleError(new Error(`Invite link email mask check failed ${invite.id} ${email} ${invite.emailPattern}`))\n  //   throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))\n  // }\n\n  if (invite.email != null && invite.email.trim().length > 0 && invite.email !== email) {\n    ctx.warn(\"Invite doesn't allow this email address\", { email, ...invite })\n    throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))\n  }","sourceCodeStart":1219,"sourceCodeEnd":1255,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/server/account/src/utils.ts#L1219-L1255","documentation":"checkInvite throws a PlatformError with status ExpiredLink when the invite has a non-zero expiresOn timestamp that is earlier than the current time. The invite link was time-limited and its validity window has passed, so acceptance is refused.","triggerScenarios":"Accepting a workspace invite via checkInvite where invite.expiresOn > 0 && invite.expiresOn < Date.now(), i.e. any time after the invite's configured expiration date.","commonSituations":"Old invite links saved in emails or chat history and reused weeks later; invites created with short expiry windows in security-conscious workspaces; clock skew is rare but possible.","solutions":["Request a fresh invite link from the workspace admin.","Have an admin extend or clear the invite's expiresOn field.","If expiry is intentional, create a new invite with a longer validity period.","If the date is only slightly in the past, verify server clocks are synchronized (NTP)."],"exampleFix":"// admin side: renew an expired invite\n// before\ninvite.expiresOn < Date.now()\n// after\nawait account.updateInvite(ctx, invite._id, { expiresOn: Date.now() + 7 * 24 * 3600 * 1000 })","handlingStrategy":"validation","validationCode":"// check before attempting acceptance\nif (invite.expiresOn > 0 && invite.expiresOn < Date.now()) {\n  throw new Error('This invite link has expired; request a new one')\n}","typeGuard":"function isInviteActive(invite: { expiresOn: number }): boolean {\n  return invite.expiresOn === 0 || invite.expiresOn >= Date.now()\n}","tryCatchPattern":"try {\n  await account.acceptInvite(ctx, token, email)\n} catch (err) {\n  if (extractStatus(err, platform) === platform.status.ExpiredLink) {\n    // show 'link expired' UI and offer to request a fresh invite\n  } else throw err\n}","preventionTips":["Show expiry dates on invite links in the admin UI.","Send reminder emails before invites expire.","Use generous expiry windows unless security demands otherwise.","Validate expiresOn client-side before submitting the acceptance form."],"tags":["invite","expiration","time-based"],"backgroundTag":"invite-link-expired","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}