161 lines
4.1 KiB
TypeScript
161 lines
4.1 KiB
TypeScript
|
|
import * as plugins from '../plugins.js';
|
||
|
|
import type { StorageManager } from '../storage/classes.storagemanager.js';
|
||
|
|
import type { IRemoteIngress } from '../../ts_interfaces/data/remoteingress.js';
|
||
|
|
|
||
|
|
const STORAGE_PREFIX = '/remote-ingress/';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Manages CRUD for remote ingress edge registrations.
|
||
|
|
* Persists edge configs via StorageManager and provides
|
||
|
|
* the allowed edges list for the Rust hub.
|
||
|
|
*/
|
||
|
|
export class RemoteIngressManager {
|
||
|
|
private storageManager: StorageManager;
|
||
|
|
private edges: Map<string, IRemoteIngress> = new Map();
|
||
|
|
|
||
|
|
constructor(storageManager: StorageManager) {
|
||
|
|
this.storageManager = storageManager;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Load all edge registrations from storage into memory.
|
||
|
|
*/
|
||
|
|
public async initialize(): Promise<void> {
|
||
|
|
const keys = await this.storageManager.list(STORAGE_PREFIX);
|
||
|
|
for (const key of keys) {
|
||
|
|
const edge = await this.storageManager.getJSON<IRemoteIngress>(key);
|
||
|
|
if (edge) {
|
||
|
|
this.edges.set(edge.id, edge);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a new edge registration.
|
||
|
|
*/
|
||
|
|
public async createEdge(
|
||
|
|
name: string,
|
||
|
|
listenPorts: number[],
|
||
|
|
tags?: string[],
|
||
|
|
): Promise<IRemoteIngress> {
|
||
|
|
const id = plugins.uuid.v4();
|
||
|
|
const secret = plugins.crypto.randomBytes(32).toString('hex');
|
||
|
|
const now = Date.now();
|
||
|
|
|
||
|
|
const edge: IRemoteIngress = {
|
||
|
|
id,
|
||
|
|
name,
|
||
|
|
secret,
|
||
|
|
listenPorts,
|
||
|
|
enabled: true,
|
||
|
|
tags: tags || [],
|
||
|
|
createdAt: now,
|
||
|
|
updatedAt: now,
|
||
|
|
};
|
||
|
|
|
||
|
|
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||
|
|
this.edges.set(id, edge);
|
||
|
|
return edge;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get an edge by ID.
|
||
|
|
*/
|
||
|
|
public getEdge(id: string): IRemoteIngress | undefined {
|
||
|
|
return this.edges.get(id);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all edge registrations.
|
||
|
|
*/
|
||
|
|
public getAllEdges(): IRemoteIngress[] {
|
||
|
|
return Array.from(this.edges.values());
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update an edge registration.
|
||
|
|
*/
|
||
|
|
public async updateEdge(
|
||
|
|
id: string,
|
||
|
|
updates: {
|
||
|
|
name?: string;
|
||
|
|
listenPorts?: number[];
|
||
|
|
enabled?: boolean;
|
||
|
|
tags?: string[];
|
||
|
|
},
|
||
|
|
): Promise<IRemoteIngress | null> {
|
||
|
|
const edge = this.edges.get(id);
|
||
|
|
if (!edge) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (updates.name !== undefined) edge.name = updates.name;
|
||
|
|
if (updates.listenPorts !== undefined) edge.listenPorts = updates.listenPorts;
|
||
|
|
if (updates.enabled !== undefined) edge.enabled = updates.enabled;
|
||
|
|
if (updates.tags !== undefined) edge.tags = updates.tags;
|
||
|
|
edge.updatedAt = Date.now();
|
||
|
|
|
||
|
|
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||
|
|
this.edges.set(id, edge);
|
||
|
|
return edge;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Delete an edge registration.
|
||
|
|
*/
|
||
|
|
public async deleteEdge(id: string): Promise<boolean> {
|
||
|
|
if (!this.edges.has(id)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
await this.storageManager.delete(`${STORAGE_PREFIX}${id}`);
|
||
|
|
this.edges.delete(id);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Regenerate the secret for an edge.
|
||
|
|
*/
|
||
|
|
public async regenerateSecret(id: string): Promise<string | null> {
|
||
|
|
const edge = this.edges.get(id);
|
||
|
|
if (!edge) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
edge.secret = plugins.crypto.randomBytes(32).toString('hex');
|
||
|
|
edge.updatedAt = Date.now();
|
||
|
|
|
||
|
|
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||
|
|
this.edges.set(id, edge);
|
||
|
|
return edge.secret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Verify an edge's secret using constant-time comparison.
|
||
|
|
*/
|
||
|
|
public verifySecret(id: string, secret: string): boolean {
|
||
|
|
const edge = this.edges.get(id);
|
||
|
|
if (!edge) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const expected = Buffer.from(edge.secret);
|
||
|
|
const provided = Buffer.from(secret);
|
||
|
|
if (expected.length !== provided.length) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return plugins.crypto.timingSafeEqual(expected, provided);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the list of allowed edges (enabled only) for the Rust hub.
|
||
|
|
*/
|
||
|
|
public getAllowedEdges(): Array<{ id: string; secret: string }> {
|
||
|
|
const result: Array<{ id: string; secret: string }> = [];
|
||
|
|
for (const edge of this.edges.values()) {
|
||
|
|
if (edge.enabled) {
|
||
|
|
result.push({ id: edge.id, secret: edge.secret });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
}
|