59 lines
2.1 KiB
TypeScript
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;
|