hcengineering/platform · error · PlatformError

ExpiredLink

ExpiredLink

Error message

ExpiredLink

What it means

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.

Source

Thrown at server/account/src/utils.ts:1237

      ctx.error('Workspace record generation failed. Could not create a workspace record in 1000 attempts.', {
        workspaceName
      })
      throw new PlatformError(
        new Status(Severity.ERROR, platform.status.InternalServerError, { region, workspaceName, baseWorkspaceUrl })
      )
    }
  }
}

export async function checkInvite (ctx: MeasureContext, invite: WorkspaceInvite, email: string): Promise<WorkspaceUuid> {
  if (invite.remainingUses === 0) {
    ctx.warn('Invite limit exceeded', { email, ...invite })
    throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
  }

  if (invite.expiresOn > 0 && invite.expiresOn < Date.now()) {
    ctx.warn('Invite link expired', { email, ...invite })
    throw new PlatformError(new Status(Severity.ERROR, platform.status.ExpiredLink, {}))
  }

  // TODO: consider not using RegExp with user input as some regexes might
  // be slow or even cause catastrophic backtracking
  // if (
  //   invite.emailPattern != null &&
  //   invite.emailPattern.trim().length > 0 &&
  //   !new RegExp(invite.emailPattern).test(email)
  // ) {
  //   ctx.error("Invite doesn't allow this email address", { email, ...invite })
  //   Analytics.handleError(new Error(`Invite link email mask check failed ${invite.id} ${email} ${invite.emailPattern}`))
  //   throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
  // }

  if (invite.email != null && invite.email.trim().length > 0 && invite.email !== email) {
    ctx.warn("Invite doesn't allow this email address", { email, ...invite })
    throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
  }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Request a fresh invite link from the workspace admin.
  2. Have an admin extend or clear the invite's expiresOn field.
  3. If expiry is intentional, create a new invite with a longer validity period.
  4. If the date is only slightly in the past, verify server clocks are synchronized (NTP).

Example fix

// admin side: renew an expired invite
// before
invite.expiresOn < Date.now()
// after
await account.updateInvite(ctx, invite._id, { expiresOn: Date.now() + 7 * 24 * 3600 * 1000 })
Defensive patterns

Strategy: validation

Validate before calling

// check before attempting acceptance
if (invite.expiresOn > 0 && invite.expiresOn < Date.now()) {
  throw new Error('This invite link has expired; request a new one')
}

Type guard

function isInviteActive(invite: { expiresOn: number }): boolean {
  return invite.expiresOn === 0 || invite.expiresOn >= Date.now()
}

Try / catch

try {
  await account.acceptInvite(ctx, token, email)
} catch (err) {
  if (extractStatus(err, platform) === platform.status.ExpiredLink) {
    // show 'link expired' UI and offer to request a fresh invite
  } else throw err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/fac796999312c509. Report an issue: GitHub.