Skip to content

Creating a pre-auth key with expiry ≥ 1000 days returns 500 (RangeError: Invalid time value) #596

Description

@tpulatov

Description

Summary

Creating a pre-auth key from the web UI breaks whenever "Key Expiration" is >= 1000. Depending on the browser locale it either returns a 500, or — worse — silently creates the key with a completely wrong expiry and no error at all.

The Key Expiration field is a NumberInput (defaultValue: 90, min: 1, max: 365000). Values >= 1000 are formatted by react-aria via Intl.NumberFormat using the browser locale before the form is submitted, so the server receives a grouped string such as 365,000, 365 000 or 365.000 rather than 365000.

Root cause

The action handler parses the submitted value as:

const day = Number(expiry.toString().split(" ")[0]);
const date = new Date();
date.setDate(date.getDate() + day);

split(" ") exists to strip a "90 days"-style suffix, but it only splits on U+0020 and a grouped number may contain no plain space at all. The group separator therefore survives into Number().

Locale matrix

Formatting 365000 through Intl.NumberFormat, then parsing with the code above:

Locale Submitted Separator Parsed Result
en-US 365,000 U+002C , NaN 500
en-GB 365,000 U+002C , NaN 500
ru-RU 365 000 U+00A0 NBSP NaN 500
pl-PL 365 000 U+00A0 NBSP NaN 500
fr-FR 365 000 U+202F narrow NBSP NaN 500
de-DE 365.000 U+002E . 365 silent wrong expiry
es-ES 365.000 U+002E . 365 silent wrong expiry

Two distinct failure modes:

  1. Comma / non-breaking-space localesNaN reaches setDate(), producing an Invalid Date, and preAuthKeys.create() throws on expiration.toISOString() => 500, no key created. Note that NBSP locales look like they contain a space, but split(" ") does not match U+00A0 or U+202F.
  2. Dot-grouping locales (de-DE, es-ES)Number("365.000") reads the separator as a decimal point and returns 365. No error is raised: the key is created and appears valid, but expires in 365 days instead of 365000. This is the more dangerous case, since nothing surfaces the discrepancy to the user.

Client-side validation passes in every case (the value is within min/max), so the UI shows no field error.

Steps to reproduce

  1. Settings -> Auth Keys -> Generate auth key
  2. Select any user
  3. Set Key Expiration to 1000 (or type 999999999, which clamps to 365000)
  4. Submit

Expected: key is created with the requested expiry.

Actual: 500 and no key on en-US/en-GB/ru-RU/pl-PL/fr-FR; on de-DE/es-ES a key is created with a silently truncated expiry.

The threshold is exactly 1000 — the point at which grouping kicks in. Any value 1–999 works correctly in all locales.

Server log

RangeError: Invalid time value
    at Date.toISOString (<anonymous>)
    at Object.create (file:///app/build/server/index.js:165416:41)
    at authKeysAction (file:///app/build/server/index.js:192479:33)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async callRouteHandler (file:///app/build/server/index.js:13860:15)
    at async file:///app/build/server/index.js:8874:14
    at async callLoaderOrAction (file:///app/build/server/index.js:8918:19)

Workaround

Use an expiry below 1000 days, or create the key via CLI:

headscale preauthkeys create --user <id> --expiration 8760h --reusable

Suggested fix

Submit the NumberInput's raw numeric value rather than its formatted string — this avoids locale-dependent parsing entirely and is the more robust option. If the formatted string must be parsed server-side, strip all non-numeric characters first (Number(expiry.replace(/[^\d]/g, ""))); note that a naive replace(/,/g, "") would fix only the comma locales and leave both the NBSP and the silent dot-grouping cases broken.

Relation to #125

Distinct issue. #125 concerns a localized unit suffix ("90 Tage", "90 días"). Here the separator sits inside the number itself, the field carries no unit suffix, and the bug reproduces on en-US. Fixing #125 by normalizing the suffix would not address this.

Headplane Version

0.7.0

Headscale Version

v0.29.2

Metadata

Metadata

Assignees

Labels

AuthenticationAuthentication & PermissionsBugSomething isn't workingUI/UXRelated to the frontend UI

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions