Files
proxmox/unifi-api/src/auth/PrivateAuth.ts
defiQUG fbda1b4beb
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
- 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>
2026-02-12 15:46:57 -08:00

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;
}
}