Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands - CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround - CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check - NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere - MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates - LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
3.9 KiB
TypeScript
147 lines
3.9 KiB
TypeScript
/**
|
|
* Private Controller API authentication using cookie-based session
|
|
*/
|
|
|
|
import https from 'https';
|
|
import { UnifiAuthenticationError, UnifiNetworkError } from '../errors/UnifiErrors.js';
|
|
|
|
export interface PrivateAuthConfig {
|
|
baseUrl: string;
|
|
username: string;
|
|
password: string;
|
|
verifySSL?: boolean;
|
|
}
|
|
|
|
interface SessionCache {
|
|
cookie: string;
|
|
csrfToken?: string;
|
|
expiresAt: number;
|
|
}
|
|
|
|
/**
|
|
* Authentication handler for Private Controller API (proxy/network endpoints)
|
|
* Uses cookie-based session authentication
|
|
*/
|
|
export class PrivateAuth {
|
|
private config: PrivateAuthConfig;
|
|
private httpsAgent: https.Agent;
|
|
private sessionCache: SessionCache | null = null;
|
|
|
|
constructor(config: PrivateAuthConfig) {
|
|
this.config = {
|
|
verifySSL: false,
|
|
...config,
|
|
};
|
|
|
|
this.httpsAgent = new https.Agent({
|
|
rejectUnauthorized: this.config.verifySSL ?? false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get authentication cookie for requests
|
|
*/
|
|
async getAuthCookie(): Promise<string> {
|
|
// Check if we have a valid cached session
|
|
if (this.sessionCache && this.sessionCache.expiresAt > Date.now() + 60000) {
|
|
// Session is still valid (with 1 minute buffer)
|
|
return this.sessionCache.cookie;
|
|
}
|
|
|
|
// Request new session
|
|
return this.authenticate();
|
|
}
|
|
|
|
/**
|
|
* Get CSRF token if available
|
|
*/
|
|
getCsrfToken(): string | undefined {
|
|
return this.sessionCache?.csrfToken;
|
|
}
|
|
|
|
/**
|
|
* Authenticate and establish session
|
|
*/
|
|
private async authenticate(): Promise<string> {
|
|
const url = `${this.config.baseUrl}/api/auth/login`;
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
username: this.config.username,
|
|
password: this.config.password,
|
|
}),
|
|
// @ts-expect-error - agent may not be supported by all fetch implementations
|
|
agent: this.httpsAgent,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new UnifiAuthenticationError(
|
|
`Failed to authenticate: ${response.status} ${response.statusText} - ${errorText}`
|
|
);
|
|
}
|
|
|
|
// Extract cookie from Set-Cookie header
|
|
const setCookieHeader = response.headers.get('set-cookie');
|
|
if (!setCookieHeader) {
|
|
throw new UnifiAuthenticationError('No session cookie received from server');
|
|
}
|
|
|
|
// Parse cookie (typically format: "csrf_token=...; unifises=...")
|
|
const cookies = setCookieHeader.split(';').map(c => c.trim());
|
|
const sessionCookie = cookies.find(c => c.startsWith('unifises=')) || cookies[0];
|
|
|
|
if (!sessionCookie) {
|
|
throw new UnifiAuthenticationError('Invalid session cookie format');
|
|
}
|
|
|
|
// Extract CSRF token if present
|
|
const csrfCookie = cookies.find(c => c.startsWith('csrf_token='));
|
|
const csrfToken = csrfCookie ? csrfCookie.split('=')[1] : undefined;
|
|
|
|
// Cache session (default expiration: 1 hour)
|
|
this.sessionCache = {
|
|
cookie: sessionCookie,
|
|
csrfToken,
|
|
expiresAt: Date.now() + 3600000, // 1 hour
|
|
};
|
|
|
|
return sessionCookie;
|
|
} catch (error) {
|
|
if (error instanceof UnifiAuthenticationError) {
|
|
throw error;
|
|
}
|
|
throw new UnifiNetworkError(
|
|
`Failed to connect to UniFi Controller: ${error instanceof Error ? error.message : String(error)}`,
|
|
error instanceof Error ? error : undefined
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear cached session (force re-authentication on next request)
|
|
*/
|
|
clearCredentials(): void {
|
|
this.sessionCache = null;
|
|
}
|
|
|
|
/**
|
|
* Check if we have a valid cached session
|
|
*/
|
|
hasValidSession(): boolean {
|
|
return this.sessionCache !== null && this.sessionCache.expiresAt > Date.now();
|
|
}
|
|
|
|
/**
|
|
* Get HTTPS agent for requests
|
|
*/
|
|
getHttpsAgent(): https.Agent {
|
|
return this.httpsAgent;
|
|
}
|
|
}
|