Files
linumiq_net-web_app/lib/device.ts
T

59 lines
2.1 KiB
TypeScript

import { createHash, randomBytes, randomInt } from 'node:crypto';
/**
* Server-only helpers for the OAuth2 device-authorization-grant style "device
* pairing" flow (Home Assistant add-on). Dependency-free (node:crypto only).
*
* Opaque secrets (device_code, device token) are high-entropy random values,
* so a fast SHA-256 hash is sufficient for at-rest storage — we never persist
* or log the plaintext. Mirrors the hashing style in lib/auth/recovery.ts.
*/
/** Stable hash used both when storing and when looking up an opaque secret. */
export function hashToken(raw: string): string {
return createHash('sha256').update(raw).digest('hex');
}
/** Opaque device_code (the secret the device polls with). */
export function generateDeviceCode(): string {
return randomBytes(32).toString('hex');
}
/** Opaque long-lived device bearer token. */
export function generateDeviceToken(): string {
return randomBytes(32).toString('hex');
}
// Unambiguous alphabet: no 0/O/1/I to avoid transcription errors.
const USER_CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
/** Short human code, formatted `XXXX-XXXX` (uppercase, unbiased selection). */
export function generateUserCode(): string {
let out = '';
for (let i = 0; i < 8; i++) {
out += USER_CODE_ALPHABET[randomInt(USER_CODE_ALPHABET.length)];
}
return `${out.slice(0, 4)}-${out.slice(4, 8)}`;
}
/**
* Normalise a user-typed code: uppercase, strip non-alphanumerics, re-insert
* the dash as `XXXX-XXXX`. Returns '' if the result is not exactly 8 alnum
* characters.
*/
export function normalizeUserCode(raw: string): string {
const cleaned = raw.toUpperCase().replace(/[^A-Z0-9]/g, '');
if (cleaned.length !== 8) return '';
return `${cleaned.slice(0, 4)}-${cleaned.slice(4, 8)}`;
}
/** Device-code lifetime before it must be re-requested. */
export const DEVICE_CODE_TTL_SECONDS = 600;
/** Recommended polling interval the device should respect. */
export const DEVICE_POLL_INTERVAL_SECONDS = 5;
/** frp server the add-on connects back to. */
export const FRP_SERVER_ADDR = 'linumiq.net';
export const FRP_SERVER_PORT = 7000;