File
Blob: src/worker/auth/host-labels.ts
| 1 | import { randomBytes } from "@/worker/auth/bytes"; |
| 2 | import { HOST_WORDS } from "@/worker/auth/host-wordlist"; |
| 3 | |
| 4 | const wordSet = new Set<string>(HOST_WORDS); |
| 5 | const hostLabelRe = /^[a-z]+(-[a-z]+){4}$/; |
| 6 | |
| 7 | function randomWordIndex(): number { |
| 8 | const length = HOST_WORDS.length; |
| 9 | const max = 65_536 - (65_536 % length); |
| 10 | |
| 11 | while (true) { |
| 12 | const bytes = randomBytes(2); |
| 13 | const value = (bytes[0] << 8) | bytes[1]; |
| 14 | if (value < max) return value % length; |
| 15 | } |
| 16 | } |
| 17 | |
| 18 | export function isValidHostLabel(label: string): boolean { |
| 19 | if (label.length > 63 || !hostLabelRe.test(label)) return false; |
| 20 | return label.split("-").every((word) => wordSet.has(word)); |
| 21 | } |
| 22 | |
| 23 | export function generateHostLabel(): string { |
| 24 | for (let attempt = 0; attempt < 100; attempt += 1) { |
| 25 | const words = Array.from({ length: 5 }, () => HOST_WORDS[randomWordIndex()]); |
| 26 | const label = words.join("-"); |
| 27 | if (label.length <= 63) return label; |
| 28 | } |
| 29 | |
| 30 | throw new Error("Unable to generate a host label under 64 characters"); |
| 31 | } |