Add tests for authentication and security features
- Implement unit tests for password handling in `auth_test.ts`, covering bcrypt and legacy password hashes. - Create a fake database for user management to facilitate testing of the `AdminHandler`. - Validate JWT-based identity verification against database records. - Introduce tests for credential encryption and registry management in `security_test.ts`. - Ensure registry passwords are securely stored and can be decrypted correctly, including legacy support. - Add utility functions for password hashing and verification in `auth.ts`.
This commit is contained in:
@@ -0,0 +1,105 @@
|
|||||||
|
import { assert, assertEquals, fail } from '@std/assert';
|
||||||
|
|
||||||
|
import * as plugins from '../ts/plugins.ts';
|
||||||
|
import type { IUser as IDatabaseUser } from '../ts/types.ts';
|
||||||
|
import { AdminHandler } from '../ts/opsserver/handlers/admin.handler.ts';
|
||||||
|
import {
|
||||||
|
hashPassword,
|
||||||
|
isBcryptHash,
|
||||||
|
needsPasswordUpgrade,
|
||||||
|
verifyPassword,
|
||||||
|
} from '../ts/utils/auth.ts';
|
||||||
|
|
||||||
|
class FakeDatabase {
|
||||||
|
constructor(private users: Map<string, IDatabaseUser>) {}
|
||||||
|
|
||||||
|
getUserByUsername(username: string): IDatabaseUser | null {
|
||||||
|
return this.users.get(username) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserPassword(username: string, passwordHash: string): void {
|
||||||
|
const user = this.users.get(username);
|
||||||
|
if (!user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.users.set(username, {
|
||||||
|
...user,
|
||||||
|
passwordHash,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createAdminHandler(users: IDatabaseUser[]): Promise<AdminHandler> {
|
||||||
|
const userMap = new Map(users.map((user) => [user.username, user]));
|
||||||
|
const fakeOpsServer = {
|
||||||
|
typedrouter: new plugins.typedrequest.TypedRouter(),
|
||||||
|
oneboxRef: {
|
||||||
|
database: new FakeDatabase(userMap),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const adminHandler = new AdminHandler(fakeOpsServer as any);
|
||||||
|
await adminHandler.initialize();
|
||||||
|
return adminHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test('password helpers support bcrypt and legacy password hashes', async () => {
|
||||||
|
const password = 'correct horse battery staple';
|
||||||
|
const bcryptHash = await hashPassword(password);
|
||||||
|
|
||||||
|
assert(isBcryptHash(bcryptHash));
|
||||||
|
assert(await verifyPassword(password, bcryptHash));
|
||||||
|
assert(!(await verifyPassword('wrong password', bcryptHash)));
|
||||||
|
assert(!needsPasswordUpgrade(bcryptHash));
|
||||||
|
|
||||||
|
const legacyHash = btoa(password);
|
||||||
|
assert(await verifyPassword(password, legacyHash));
|
||||||
|
assert(needsPasswordUpgrade(legacyHash));
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('verified identity is derived from the signed JWT and database, not client fields', async () => {
|
||||||
|
const adminHandler = await createAdminHandler([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: 'alice',
|
||||||
|
passwordHash: await hashPassword('password123'),
|
||||||
|
role: 'user',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const expiresAt = Date.now() + 60_000;
|
||||||
|
const jwt = await adminHandler.smartjwtInstance.createJWT({
|
||||||
|
userId: '1',
|
||||||
|
username: 'alice',
|
||||||
|
role: 'user',
|
||||||
|
status: 'loggedIn',
|
||||||
|
expiresAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifiedIdentity = await adminHandler.getVerifiedIdentity({
|
||||||
|
jwt,
|
||||||
|
userId: '999',
|
||||||
|
username: 'mallory',
|
||||||
|
role: 'admin',
|
||||||
|
expiresAt: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(verifiedIdentity.userId, '1');
|
||||||
|
assertEquals(verifiedIdentity.username, 'alice');
|
||||||
|
assertEquals(verifiedIdentity.role, 'user');
|
||||||
|
assertEquals(verifiedIdentity.expiresAt, expiresAt);
|
||||||
|
|
||||||
|
let rejected = false;
|
||||||
|
try {
|
||||||
|
await adminHandler.getVerifiedAdminIdentity(verifiedIdentity);
|
||||||
|
fail('Expected admin-only identity verification to reject non-admin users');
|
||||||
|
} catch {
|
||||||
|
rejected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(rejected);
|
||||||
|
});
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { assert, assertEquals } from '@std/assert';
|
||||||
|
|
||||||
|
import type { IRegistry } from '../ts/types.ts';
|
||||||
|
import { credentialEncryption } from '../ts/classes/encryption.ts';
|
||||||
|
import { OneboxRegistriesManager } from '../ts/classes/registries.ts';
|
||||||
|
|
||||||
|
class FakeRegistryDatabase {
|
||||||
|
private registries = new Map<string, IRegistry>();
|
||||||
|
|
||||||
|
getRegistryByURL(url: string): IRegistry | null {
|
||||||
|
return this.registries.get(url) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRegistry(registry: Omit<IRegistry, 'id'>): Promise<IRegistry> {
|
||||||
|
const savedRegistry: IRegistry = {
|
||||||
|
id: this.registries.size + 1,
|
||||||
|
...registry,
|
||||||
|
};
|
||||||
|
this.registries.set(savedRegistry.url, savedRegistry);
|
||||||
|
return savedRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRegistry(url: string): void {
|
||||||
|
this.registries.delete(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllRegistries(): IRegistry[] {
|
||||||
|
return Array.from(this.registries.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test('credential encryption lazily initializes and roundtrips payloads', async () => {
|
||||||
|
const encrypted = await credentialEncryption.encrypt({ password: 'super-secret' });
|
||||||
|
const decrypted = await credentialEncryption.decrypt<{ password: string }>(encrypted);
|
||||||
|
|
||||||
|
assert(encrypted.length > 0);
|
||||||
|
assertEquals(decrypted.password, 'super-secret');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('registry passwords use encrypted storage with legacy decode fallback', async () => {
|
||||||
|
const fakeDatabase = new FakeRegistryDatabase();
|
||||||
|
const registriesManager = new OneboxRegistriesManager({ database: fakeDatabase } as any);
|
||||||
|
|
||||||
|
(registriesManager as any).loginToRegistry = async () => {};
|
||||||
|
|
||||||
|
const registry = await registriesManager.addRegistry(
|
||||||
|
'registry.example.com',
|
||||||
|
'ci-user',
|
||||||
|
'correct horse battery staple',
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(registry.passwordEncrypted.startsWith('enc:v1:'));
|
||||||
|
assertEquals(
|
||||||
|
await (registriesManager as any).decryptPassword(registry.passwordEncrypted),
|
||||||
|
'correct horse battery staple',
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
await (registriesManager as any).decryptPassword(btoa('legacy-password')),
|
||||||
|
'legacy-password',
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -43,6 +43,14 @@ const IV_LENGTH = 12;
|
|||||||
const SALT_LENGTH = 32;
|
const SALT_LENGTH = 32;
|
||||||
const PBKDF2_ITERATIONS = 100000;
|
const PBKDF2_ITERATIONS = 100000;
|
||||||
|
|
||||||
|
interface IS3ConnectionInfo {
|
||||||
|
endpoint: string;
|
||||||
|
accessKey: string;
|
||||||
|
secretKey: string;
|
||||||
|
bucket: string;
|
||||||
|
region: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class BackupManager {
|
export class BackupManager {
|
||||||
private oneboxRef: Onebox;
|
private oneboxRef: Onebox;
|
||||||
public archive: plugins.ContainerArchive | null = null;
|
public archive: plugins.ContainerArchive | null = null;
|
||||||
@@ -519,7 +527,8 @@ export class BackupManager {
|
|||||||
* Get backup password from settings
|
* Get backup password from settings
|
||||||
*/
|
*/
|
||||||
private getBackupPassword(): string | null {
|
private getBackupPassword(): string | null {
|
||||||
return this.oneboxRef.database.getSetting('backup_encryption_password');
|
return this.oneboxRef.database.getSetting('backup_encryption_password')
|
||||||
|
|| this.oneboxRef.database.getSetting('backupPassword');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -860,47 +869,48 @@ export class BackupManager {
|
|||||||
const bucketDir = `${dataDir}/${resource.resourceName}`;
|
const bucketDir = `${dataDir}/${resource.resourceName}`;
|
||||||
await Deno.mkdir(bucketDir, { recursive: true });
|
await Deno.mkdir(bucketDir, { recursive: true });
|
||||||
|
|
||||||
const endpoint = credentials.endpoint || credentials.S3_ENDPOINT;
|
const s3Info = this.getS3ConnectionInfo(credentials);
|
||||||
const accessKey = credentials.accessKey || credentials.S3_ACCESS_KEY;
|
const s3Client = this.createS3Client(s3Info);
|
||||||
const secretKey = credentials.secretKey || credentials.S3_SECRET_KEY;
|
let objectCount = 0;
|
||||||
const bucket = credentials.bucket || credentials.S3_BUCKET;
|
let continuationToken: string | undefined;
|
||||||
|
|
||||||
if (!endpoint || !accessKey || !secretKey || !bucket) {
|
do {
|
||||||
throw new Error('MinIO credentials incomplete');
|
const response = await s3Client.send(
|
||||||
}
|
new plugins.awsS3.ListObjectsV2Command({
|
||||||
|
Bucket: s3Info.bucket,
|
||||||
|
ContinuationToken: continuationToken,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const s3Client = new plugins.smartstorage.SmartStorage({
|
for (const object of response.Contents || []) {
|
||||||
endpoint,
|
const objectKey = object.Key;
|
||||||
accessKey,
|
|
||||||
secretKey,
|
|
||||||
bucket,
|
|
||||||
});
|
|
||||||
|
|
||||||
await s3Client.start();
|
|
||||||
|
|
||||||
const objects = await s3Client.listObjects();
|
|
||||||
|
|
||||||
for (const obj of objects) {
|
|
||||||
const objectKey = obj.Key;
|
|
||||||
if (!objectKey) continue;
|
if (!objectKey) continue;
|
||||||
|
|
||||||
const objectData = await s3Client.getObject(objectKey);
|
const objectResponse = await s3Client.send(
|
||||||
if (objectData) {
|
new plugins.awsS3.GetObjectCommand({
|
||||||
|
Bucket: s3Info.bucket,
|
||||||
|
Key: objectKey,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!objectResponse.Body) continue;
|
||||||
|
|
||||||
const objectPath = `${bucketDir}/${objectKey}`;
|
const objectPath = `${bucketDir}/${objectKey}`;
|
||||||
const parentDir = plugins.path.dirname(objectPath);
|
const parentDir = plugins.path.dirname(objectPath);
|
||||||
await Deno.mkdir(parentDir, { recursive: true });
|
await Deno.mkdir(parentDir, { recursive: true });
|
||||||
await Deno.writeFile(objectPath, objectData);
|
await Deno.writeFile(objectPath, await objectResponse.Body.transformToByteArray());
|
||||||
}
|
objectCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
await s3Client.stop();
|
continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;
|
||||||
|
} while (continuationToken);
|
||||||
|
|
||||||
await Deno.writeTextFile(
|
await Deno.writeTextFile(
|
||||||
`${bucketDir}/_metadata.json`,
|
`${bucketDir}/_metadata.json`,
|
||||||
JSON.stringify({ bucket, objectCount: objects.length }, null, 2)
|
JSON.stringify({ bucket: s3Info.bucket, objectCount }, null, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.success(`MinIO bucket exported: ${resource.resourceName} (${objects.length} objects)`);
|
logger.success(`MinIO bucket exported: ${resource.resourceName} (${objectCount} objects)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1279,39 +1289,25 @@ export class BackupManager {
|
|||||||
|
|
||||||
const bucketDir = `${dataDir}/${backupResourceName}`;
|
const bucketDir = `${dataDir}/${backupResourceName}`;
|
||||||
|
|
||||||
const endpoint = credentials.endpoint || credentials.S3_ENDPOINT;
|
const s3Info = this.getS3ConnectionInfo(credentials);
|
||||||
const accessKey = credentials.accessKey || credentials.S3_ACCESS_KEY;
|
const s3Client = this.createS3Client(s3Info);
|
||||||
const secretKey = credentials.secretKey || credentials.S3_SECRET_KEY;
|
|
||||||
const bucket = credentials.bucket || credentials.S3_BUCKET;
|
|
||||||
|
|
||||||
if (!endpoint || !accessKey || !secretKey || !bucket) {
|
|
||||||
throw new Error('MinIO credentials incomplete');
|
|
||||||
}
|
|
||||||
|
|
||||||
const s3Client = new plugins.smartstorage.SmartStorage({
|
|
||||||
endpoint,
|
|
||||||
accessKey,
|
|
||||||
secretKey,
|
|
||||||
bucket,
|
|
||||||
});
|
|
||||||
|
|
||||||
await s3Client.start();
|
|
||||||
|
|
||||||
let uploadedCount = 0;
|
let uploadedCount = 0;
|
||||||
|
|
||||||
for await (const entry of Deno.readDir(bucketDir)) {
|
for await (const filePath of this.walkFiles(bucketDir)) {
|
||||||
if (entry.name === '_metadata.json') continue;
|
if (plugins.path.basename(filePath) === '_metadata.json') continue;
|
||||||
|
|
||||||
const filePath = `${bucketDir}/${entry.name}`;
|
|
||||||
|
|
||||||
if (entry.isFile) {
|
|
||||||
const fileData = await Deno.readFile(filePath);
|
const fileData = await Deno.readFile(filePath);
|
||||||
await s3Client.putObject(entry.name, fileData);
|
const objectKey = plugins.path.relative(bucketDir, filePath).replaceAll('\\', '/');
|
||||||
|
await s3Client.send(
|
||||||
|
new plugins.awsS3.PutObjectCommand({
|
||||||
|
Bucket: s3Info.bucket,
|
||||||
|
Key: objectKey,
|
||||||
|
Body: fileData,
|
||||||
|
}),
|
||||||
|
);
|
||||||
uploadedCount++;
|
uploadedCount++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await s3Client.stop();
|
|
||||||
|
|
||||||
logger.success(`MinIO bucket imported: ${resource.resourceName} (${uploadedCount} objects)`);
|
logger.success(`MinIO bucket imported: ${resource.resourceName} (${uploadedCount} objects)`);
|
||||||
}
|
}
|
||||||
@@ -1585,7 +1581,7 @@ export class BackupManager {
|
|||||||
return await crypto.subtle.deriveKey(
|
return await crypto.subtle.deriveKey(
|
||||||
{
|
{
|
||||||
name: 'PBKDF2',
|
name: 'PBKDF2',
|
||||||
salt,
|
salt: this.toArrayBuffer(salt),
|
||||||
iterations: PBKDF2_ITERATIONS,
|
iterations: PBKDF2_ITERATIONS,
|
||||||
hash: 'SHA-256',
|
hash: 'SHA-256',
|
||||||
},
|
},
|
||||||
@@ -1600,8 +1596,54 @@ export class BackupManager {
|
|||||||
* Compute SHA-256 checksum
|
* Compute SHA-256 checksum
|
||||||
*/
|
*/
|
||||||
private async computeChecksum(data: Uint8Array): Promise<string> {
|
private async computeChecksum(data: Uint8Array): Promise<string> {
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
const hashBuffer = await crypto.subtle.digest('SHA-256', this.toArrayBuffer(data));
|
||||||
const hashArray = new Uint8Array(hashBuffer);
|
const hashArray = new Uint8Array(hashBuffer);
|
||||||
return 'sha256:' + Array.from(hashArray).map((b) => b.toString(16).padStart(2, '0')).join('');
|
return 'sha256:' + Array.from(hashArray).map((b) => b.toString(16).padStart(2, '0')).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getS3ConnectionInfo(credentials: Record<string, string>): IS3ConnectionInfo {
|
||||||
|
const endpoint = credentials.endpoint || credentials.S3_ENDPOINT;
|
||||||
|
const accessKey = credentials.accessKey || credentials.S3_ACCESS_KEY;
|
||||||
|
const secretKey = credentials.secretKey || credentials.S3_SECRET_KEY;
|
||||||
|
const bucket = credentials.bucket || credentials.S3_BUCKET;
|
||||||
|
|
||||||
|
if (!endpoint || !accessKey || !secretKey || !bucket) {
|
||||||
|
throw new Error('MinIO credentials incomplete');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
endpoint,
|
||||||
|
accessKey,
|
||||||
|
secretKey,
|
||||||
|
bucket,
|
||||||
|
region: credentials.region || credentials.AWS_REGION || 'us-east-1',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createS3Client(s3Info: IS3ConnectionInfo) {
|
||||||
|
return new plugins.awsS3.S3Client({
|
||||||
|
endpoint: s3Info.endpoint,
|
||||||
|
region: s3Info.region,
|
||||||
|
forcePathStyle: true,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: s3Info.accessKey,
|
||||||
|
secretAccessKey: s3Info.secretKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async *walkFiles(directory: string): AsyncGenerator<string> {
|
||||||
|
for await (const entry of Deno.readDir(directory)) {
|
||||||
|
const entryPath = plugins.path.join(directory, entry.name);
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
yield* this.walkFiles(entryPath);
|
||||||
|
} else if (entry.isFile) {
|
||||||
|
yield entryPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toArrayBuffer(data: Uint8Array): ArrayBuffer {
|
||||||
|
return data.slice().buffer as ArrayBuffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ export class CloudflareDomainSync {
|
|||||||
*/
|
*/
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const apiKey = this.database.getSetting('cloudflareAPIKey');
|
const apiKey = this.database.getSetting('cloudflareAPIKey')
|
||||||
|
|| this.database.getSetting('cloudflareToken');
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
logger.warn('Cloudflare API key not configured. Domain sync will be limited.');
|
logger.warn('Cloudflare API key not configured. Domain sync will be limited.');
|
||||||
|
|||||||
+2
-1
@@ -27,7 +27,8 @@ export class OneboxDnsManager {
|
|||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get Cloudflare credentials from settings
|
// Get Cloudflare credentials from settings
|
||||||
const apiKey = this.database.getSetting('cloudflareAPIKey');
|
const apiKey = this.database.getSetting('cloudflareAPIKey')
|
||||||
|
|| this.database.getSetting('cloudflareToken');
|
||||||
const serverIP = this.database.getSetting('serverIP');
|
const serverIP = this.database.getSetting('serverIP');
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
|
|||||||
@@ -97,7 +97,11 @@ export class CredentialEncryption {
|
|||||||
*/
|
*/
|
||||||
async encrypt(data: Record<string, string>): Promise<string> {
|
async encrypt(data: Record<string, string>): Promise<string> {
|
||||||
if (!this.key) {
|
if (!this.key) {
|
||||||
throw new Error('Encryption not initialized. Call init() first.');
|
await this.init();
|
||||||
|
}
|
||||||
|
const key = this.key;
|
||||||
|
if (!key) {
|
||||||
|
throw new Error('Encryption key initialization failed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(this.ivLength));
|
const iv = crypto.getRandomValues(new Uint8Array(this.ivLength));
|
||||||
@@ -105,7 +109,7 @@ export class CredentialEncryption {
|
|||||||
|
|
||||||
const ciphertext = await crypto.subtle.encrypt(
|
const ciphertext = await crypto.subtle.encrypt(
|
||||||
{ name: this.algorithm, iv },
|
{ name: this.algorithm, iv },
|
||||||
this.key,
|
key,
|
||||||
encoded
|
encoded
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -120,9 +124,15 @@ export class CredentialEncryption {
|
|||||||
/**
|
/**
|
||||||
* Decrypt a base64 string back to credentials object
|
* Decrypt a base64 string back to credentials object
|
||||||
*/
|
*/
|
||||||
async decrypt(encrypted: string): Promise<Record<string, string>> {
|
async decrypt<T extends Record<string, string> = Record<string, string>>(
|
||||||
|
encrypted: string,
|
||||||
|
): Promise<T> {
|
||||||
if (!this.key) {
|
if (!this.key) {
|
||||||
throw new Error('Encryption not initialized. Call init() first.');
|
await this.init();
|
||||||
|
}
|
||||||
|
const key = this.key;
|
||||||
|
if (!key) {
|
||||||
|
throw new Error('Encryption key initialization failed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const combined = this.base64ToBytes(encrypted);
|
const combined = this.base64ToBytes(encrypted);
|
||||||
@@ -133,12 +143,12 @@ export class CredentialEncryption {
|
|||||||
|
|
||||||
const decrypted = await crypto.subtle.decrypt(
|
const decrypted = await crypto.subtle.decrypt(
|
||||||
{ name: this.algorithm, iv },
|
{ name: this.algorithm, iv },
|
||||||
this.key,
|
key,
|
||||||
ciphertext
|
ciphertext
|
||||||
);
|
);
|
||||||
|
|
||||||
const decoded = new TextDecoder().decode(decrypted);
|
const decoded = new TextDecoder().decode(decrypted);
|
||||||
return JSON.parse(decoded);
|
return JSON.parse(decoded) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+14
-10
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import * as plugins from '../plugins.ts';
|
import * as plugins from '../plugins.ts';
|
||||||
import { logger } from '../logging.ts';
|
import { logger } from '../logging.ts';
|
||||||
|
import { hashPassword, needsPasswordUpgrade, verifyPassword } from '../utils/auth.ts';
|
||||||
import { getErrorMessage } from '../utils/error.ts';
|
import { getErrorMessage } from '../utils/error.ts';
|
||||||
import type { Onebox } from './onebox.ts';
|
import type { Onebox } from './onebox.ts';
|
||||||
import type {
|
import type {
|
||||||
@@ -404,15 +405,17 @@ export class OneboxHttpServer {
|
|||||||
|
|
||||||
logger.info(`User found: ${username}, checking password...`);
|
logger.info(`User found: ${username}, checking password...`);
|
||||||
|
|
||||||
// Verify password (simple base64 comparison for now)
|
const passwordMatches = await verifyPassword(password, user.passwordHash);
|
||||||
const passwordHash = btoa(password);
|
if (!passwordMatches) {
|
||||||
logger.info(`Password hash: ${passwordHash}, stored hash: ${user.passwordHash}`);
|
|
||||||
|
|
||||||
if (passwordHash !== user.passwordHash) {
|
|
||||||
logger.info(`Password mismatch for user: ${username}`);
|
logger.info(`Password mismatch for user: ${username}`);
|
||||||
return this.jsonResponse({ success: false, error: 'Invalid credentials' }, 401);
|
return this.jsonResponse({ success: false, error: 'Invalid credentials' }, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needsPasswordUpgrade(user.passwordHash)) {
|
||||||
|
const upgradedHash = await hashPassword(password);
|
||||||
|
this.oneboxRef.database.updateUserPassword(user.username, upgradedHash);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate simple token (in production, use proper JWT)
|
// Generate simple token (in production, use proper JWT)
|
||||||
const token = btoa(`${user.username}:${Date.now()}`);
|
const token = btoa(`${user.username}:${Date.now()}`);
|
||||||
|
|
||||||
@@ -1324,7 +1327,7 @@ export class OneboxHttpServer {
|
|||||||
type: 'service',
|
type: 'service',
|
||||||
name: service.name,
|
name: service.name,
|
||||||
domain: service.domain || null,
|
domain: service.domain || null,
|
||||||
targetHost: service.containerIP || 'unknown',
|
targetHost: service.containerID || 'unknown',
|
||||||
targetPort: service.port || 80,
|
targetPort: service.port || 80,
|
||||||
status: service.status,
|
status: service.status,
|
||||||
});
|
});
|
||||||
@@ -1380,6 +1383,7 @@ export class OneboxHttpServer {
|
|||||||
rabbitmq: 5672,
|
rabbitmq: 5672,
|
||||||
caddy: 80,
|
caddy: 80,
|
||||||
clickhouse: 8123,
|
clickhouse: 8123,
|
||||||
|
mariadb: 3306,
|
||||||
};
|
};
|
||||||
return ports[type] || 0;
|
return ports[type] || 0;
|
||||||
}
|
}
|
||||||
@@ -1396,11 +1400,11 @@ export class OneboxHttpServer {
|
|||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
proxy: {
|
proxy: {
|
||||||
running: proxyStatus.running,
|
running: proxyStatus.http.running || proxyStatus.https.running,
|
||||||
httpPort: proxyStatus.httpPort,
|
httpPort: proxyStatus.http.port,
|
||||||
httpsPort: proxyStatus.httpsPort,
|
httpsPort: proxyStatus.https.port,
|
||||||
routes: proxyStatus.routes,
|
routes: proxyStatus.routes,
|
||||||
certificates: proxyStatus.certificates,
|
certificates: proxyStatus.https.certificates,
|
||||||
},
|
},
|
||||||
logReceiver: {
|
logReceiver: {
|
||||||
running: logReceiverStats.running,
|
running: logReceiverStats.running,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import { logger } from '../logging.ts';
|
import { logger } from '../logging.ts';
|
||||||
import { getErrorMessage } from '../utils/error.ts';
|
import { getErrorMessage } from '../utils/error.ts';
|
||||||
|
import { hashPassword } from '../utils/auth.ts';
|
||||||
import { OneboxDatabase } from './database.ts';
|
import { OneboxDatabase } from './database.ts';
|
||||||
import { OneboxDockerManager } from './docker.ts';
|
import { OneboxDockerManager } from './docker.ts';
|
||||||
import { OneboxServicesManager } from './services.ts';
|
import { OneboxServicesManager } from './services.ts';
|
||||||
@@ -226,8 +227,7 @@ export class Onebox {
|
|||||||
if (!adminUser) {
|
if (!adminUser) {
|
||||||
logger.info('Creating default admin user...');
|
logger.info('Creating default admin user...');
|
||||||
|
|
||||||
// Simple base64 encoding for now - should use bcrypt in production
|
const passwordHash = await hashPassword('admin');
|
||||||
const passwordHash = btoa('admin');
|
|
||||||
|
|
||||||
await this.database.createUser({
|
await this.database.createUser({
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ export class ClickHouseProvider extends BasePlatformServiceProvider {
|
|||||||
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
||||||
// Reuse existing credentials from database
|
// Reuse existing credentials from database
|
||||||
logger.info('Reusing existing ClickHouse credentials (data directory already initialized)');
|
logger.info('Reusing existing ClickHouse credentials (data directory already initialized)');
|
||||||
adminCredentials = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
adminCredentials = await credentialEncryption.decrypt<{ username: string; password: string }>(
|
||||||
|
platformService.adminCredentialsEncrypted,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Generate new credentials for fresh deployment
|
// Generate new credentials for fresh deployment
|
||||||
logger.info('Generating new ClickHouse admin credentials');
|
logger.info('Generating new ClickHouse admin credentials');
|
||||||
@@ -191,7 +193,9 @@ export class ClickHouseProvider extends BasePlatformServiceProvider {
|
|||||||
throw new Error('ClickHouse platform service not found or not configured');
|
throw new Error('ClickHouse platform service not found or not configured');
|
||||||
}
|
}
|
||||||
|
|
||||||
const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
const adminCreds = await credentialEncryption.decrypt<{ username: string; password: string }>(
|
||||||
|
platformService.adminCredentialsEncrypted,
|
||||||
|
);
|
||||||
const containerName = this.getContainerName();
|
const containerName = this.getContainerName();
|
||||||
|
|
||||||
// Generate resource names and credentials
|
// Generate resource names and credentials
|
||||||
@@ -247,7 +251,9 @@ export class ClickHouseProvider extends BasePlatformServiceProvider {
|
|||||||
throw new Error('ClickHouse platform service not found or not configured');
|
throw new Error('ClickHouse platform service not found or not configured');
|
||||||
}
|
}
|
||||||
|
|
||||||
const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
const adminCreds = await credentialEncryption.decrypt<{ username: string; password: string }>(
|
||||||
|
platformService.adminCredentialsEncrypted,
|
||||||
|
);
|
||||||
|
|
||||||
logger.info(`Deprovisioning ClickHouse database '${resource.resourceName}'...`);
|
logger.info(`Deprovisioning ClickHouse database '${resource.resourceName}'...`);
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ export class MariaDBProvider extends BasePlatformServiceProvider {
|
|||||||
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
||||||
// Reuse existing credentials from database
|
// Reuse existing credentials from database
|
||||||
logger.info('Reusing existing MariaDB credentials (data directory already initialized)');
|
logger.info('Reusing existing MariaDB credentials (data directory already initialized)');
|
||||||
adminCredentials = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
adminCredentials = await credentialEncryption.decrypt<{ username: string; password: string }>(
|
||||||
|
platformService.adminCredentialsEncrypted,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Generate new credentials for fresh deployment
|
// Generate new credentials for fresh deployment
|
||||||
logger.info('Generating new MariaDB admin credentials');
|
logger.info('Generating new MariaDB admin credentials');
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ export class MinioProvider extends BasePlatformServiceProvider {
|
|||||||
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
||||||
// Reuse existing credentials from database
|
// Reuse existing credentials from database
|
||||||
logger.info('Reusing existing MinIO credentials (data directory already initialized)');
|
logger.info('Reusing existing MinIO credentials (data directory already initialized)');
|
||||||
adminCredentials = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
adminCredentials = await credentialEncryption.decrypt<{ username: string; password: string }>(
|
||||||
|
platformService.adminCredentialsEncrypted,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Generate new credentials for fresh deployment
|
// Generate new credentials for fresh deployment
|
||||||
logger.info('Generating new MinIO admin credentials');
|
logger.info('Generating new MinIO admin credentials');
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ export class MongoDBProvider extends BasePlatformServiceProvider {
|
|||||||
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
||||||
// Reuse existing credentials from database
|
// Reuse existing credentials from database
|
||||||
logger.info('Reusing existing MongoDB credentials (data directory already initialized)');
|
logger.info('Reusing existing MongoDB credentials (data directory already initialized)');
|
||||||
adminCredentials = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
adminCredentials = await credentialEncryption.decrypt<{ username: string; password: string }>(
|
||||||
|
platformService.adminCredentialsEncrypted,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Generate new credentials for fresh deployment
|
// Generate new credentials for fresh deployment
|
||||||
logger.info('Generating new MongoDB admin credentials');
|
logger.info('Generating new MongoDB admin credentials');
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ export class RedisProvider extends BasePlatformServiceProvider {
|
|||||||
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
if (dataExists && platformService?.adminCredentialsEncrypted) {
|
||||||
// Reuse existing credentials from database
|
// Reuse existing credentials from database
|
||||||
logger.info('Reusing existing Redis credentials (data directory already initialized)');
|
logger.info('Reusing existing Redis credentials (data directory already initialized)');
|
||||||
adminCredentials = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
adminCredentials = await credentialEncryption.decrypt<{ username: string; password: string }>(
|
||||||
|
platformService.adminCredentialsEncrypted,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Generate new credentials for fresh deployment
|
// Generate new credentials for fresh deployment
|
||||||
logger.info('Generating new Redis admin credentials');
|
logger.info('Generating new Redis admin credentials');
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import type { IRegistry } from '../types.ts';
|
|||||||
import { logger } from '../logging.ts';
|
import { logger } from '../logging.ts';
|
||||||
import { getErrorMessage } from '../utils/error.ts';
|
import { getErrorMessage } from '../utils/error.ts';
|
||||||
import { OneboxDatabase } from './database.ts';
|
import { OneboxDatabase } from './database.ts';
|
||||||
|
import { credentialEncryption } from './encryption.ts';
|
||||||
|
|
||||||
|
const encryptedPasswordPrefix = 'enc:v1:';
|
||||||
|
|
||||||
export class OneboxRegistriesManager {
|
export class OneboxRegistriesManager {
|
||||||
private oneboxRef: any; // Will be Onebox instance
|
private oneboxRef: any; // Will be Onebox instance
|
||||||
@@ -22,17 +25,23 @@ export class OneboxRegistriesManager {
|
|||||||
/**
|
/**
|
||||||
* Encrypt a password (simple base64 for now, should use proper encryption)
|
* Encrypt a password (simple base64 for now, should use proper encryption)
|
||||||
*/
|
*/
|
||||||
private encryptPassword(password: string): string {
|
private async encryptPassword(password: string): Promise<string> {
|
||||||
// TODO: Use proper encryption with a secret key
|
const encrypted = await credentialEncryption.encrypt({ password });
|
||||||
// For now, using base64 encoding (NOT SECURE, just for structure)
|
return `${encryptedPasswordPrefix}${encrypted}`;
|
||||||
return plugins.encoding.encodeBase64(new TextEncoder().encode(password));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt a password
|
* Decrypt a password
|
||||||
*/
|
*/
|
||||||
private decryptPassword(encrypted: string): string {
|
private async decryptPassword(encrypted: string): Promise<string> {
|
||||||
// TODO: Use proper decryption
|
if (encrypted.startsWith(encryptedPasswordPrefix)) {
|
||||||
|
const decrypted = await credentialEncryption.decrypt<{ password: string }>(
|
||||||
|
encrypted.slice(encryptedPasswordPrefix.length),
|
||||||
|
);
|
||||||
|
return decrypted.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy compatibility for older databases that stored base64-encoded passwords.
|
||||||
return new TextDecoder().decode(plugins.encoding.decodeBase64(encrypted));
|
return new TextDecoder().decode(plugins.encoding.decodeBase64(encrypted));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +57,7 @@ export class OneboxRegistriesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt password
|
// Encrypt password
|
||||||
const passwordEncrypted = this.encryptPassword(password);
|
const passwordEncrypted = await this.encryptPassword(password);
|
||||||
|
|
||||||
// Create registry in database
|
// Create registry in database
|
||||||
const registry = await this.database.createRegistry({
|
const registry = await this.database.createRegistry({
|
||||||
@@ -111,7 +120,7 @@ export class OneboxRegistriesManager {
|
|||||||
try {
|
try {
|
||||||
logger.info(`Logging into registry: ${registry.url}`);
|
logger.info(`Logging into registry: ${registry.url}`);
|
||||||
|
|
||||||
const password = this.decryptPassword(registry.passwordEncrypted);
|
const password = await this.decryptPassword(registry.passwordEncrypted);
|
||||||
|
|
||||||
// Use docker login command
|
// Use docker login command
|
||||||
const command = [
|
const command = [
|
||||||
|
|||||||
+2
-1
@@ -39,7 +39,8 @@ export class OneboxSslManager {
|
|||||||
this.acmeEmail = acmeEmail;
|
this.acmeEmail = acmeEmail;
|
||||||
|
|
||||||
// Get Cloudflare API key (reuse from DNS manager)
|
// Get Cloudflare API key (reuse from DNS manager)
|
||||||
const cfApiKey = this.database.getSetting('cloudflareAPIKey');
|
const cfApiKey = this.database.getSetting('cloudflareAPIKey')
|
||||||
|
|| this.database.getSetting('cloudflareToken');
|
||||||
|
|
||||||
if (!cfApiKey) {
|
if (!cfApiKey) {
|
||||||
logger.warn('Cloudflare API key not configured. SSL certificate management will be limited.');
|
logger.warn('Cloudflare API key not configured. SSL certificate management will be limited.');
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import * as plugins from '../../plugins.ts';
|
|||||||
import { logger } from '../../logging.ts';
|
import { logger } from '../../logging.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
|
import { hashPassword, needsPasswordUpgrade, verifyPassword } from '../../utils/auth.ts';
|
||||||
|
|
||||||
export interface IJwtData {
|
export interface IJwtData {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
role: 'admin' | 'user';
|
||||||
status: 'loggedIn' | 'loggedOut';
|
status: 'loggedIn' | 'loggedOut';
|
||||||
expiresAt: number;
|
expiresAt: number;
|
||||||
}
|
}
|
||||||
@@ -18,12 +21,80 @@ export class AdminHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async initialize(): Promise<void> {
|
public async initialize(): Promise<void> {
|
||||||
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
this.smartjwtInstance = new plugins.smartjwt.SmartJwt<IJwtData>();
|
||||||
await this.smartjwtInstance.init();
|
await this.smartjwtInstance.init();
|
||||||
await this.smartjwtInstance.createNewKeyPair();
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async createIdentityForUser(
|
||||||
|
user: interfaces.data.IUser & { id?: number },
|
||||||
|
expiresAt: number,
|
||||||
|
): Promise<interfaces.data.IIdentity> {
|
||||||
|
const userId = String(user.id || user.username);
|
||||||
|
const jwt = await this.smartjwtInstance.createJWT({
|
||||||
|
userId,
|
||||||
|
username: user.username,
|
||||||
|
role: user.role,
|
||||||
|
status: 'loggedIn',
|
||||||
|
expiresAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
jwt,
|
||||||
|
userId,
|
||||||
|
username: user.username,
|
||||||
|
expiresAt,
|
||||||
|
role: user.role,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getVerifiedIdentity(
|
||||||
|
identityArg: interfaces.data.IIdentity | null | undefined,
|
||||||
|
): Promise<interfaces.data.IIdentity> {
|
||||||
|
if (!identityArg?.jwt) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
let jwtData: IJwtData;
|
||||||
|
try {
|
||||||
|
jwtData = await this.smartjwtInstance.verifyJWTAndGetData(identityArg.jwt);
|
||||||
|
} catch {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jwtData.expiresAt < Date.now() || jwtData.status !== 'loggedIn') {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = this.opsServerRef.oneboxRef.database.getUserByUsername(jwtData.username);
|
||||||
|
if (!user) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = String(user.id || user.username);
|
||||||
|
if (jwtData.userId !== userId) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
jwt: identityArg.jwt,
|
||||||
|
userId,
|
||||||
|
username: user.username,
|
||||||
|
expiresAt: jwtData.expiresAt,
|
||||||
|
role: user.role,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getVerifiedAdminIdentity(
|
||||||
|
identityArg: interfaces.data.IIdentity | null | undefined,
|
||||||
|
): Promise<interfaces.data.IIdentity> {
|
||||||
|
const identity = await this.getVerifiedIdentity(identityArg);
|
||||||
|
if (identity.role !== 'admin') {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('Admin access required');
|
||||||
|
}
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
// Login
|
// Login
|
||||||
this.typedrouter.addTypedHandler(
|
this.typedrouter.addTypedHandler(
|
||||||
@@ -36,30 +107,24 @@ export class AdminHandler {
|
|||||||
throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
|
throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify password (base64 comparison to match existing DB scheme)
|
const passwordMatches = await verifyPassword(dataArg.password, user.passwordHash);
|
||||||
const passwordHash = btoa(dataArg.password);
|
if (!passwordMatches) {
|
||||||
if (passwordHash !== user.passwordHash) {
|
|
||||||
throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
|
throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needsPasswordUpgrade(user.passwordHash)) {
|
||||||
|
const upgradedHash = await hashPassword(dataArg.password);
|
||||||
|
this.opsServerRef.oneboxRef.database.updateUserPassword(user.username, upgradedHash);
|
||||||
|
}
|
||||||
|
|
||||||
const expiresAt = Date.now() + 24 * 3600 * 1000;
|
const expiresAt = Date.now() + 24 * 3600 * 1000;
|
||||||
const userId = String(user.id || user.username);
|
const freshUser = this.opsServerRef.oneboxRef.database.getUserByUsername(user.username) || user;
|
||||||
const jwt = await this.smartjwtInstance.createJWT({
|
const identity = await this.createIdentityForUser(freshUser, expiresAt);
|
||||||
userId,
|
|
||||||
status: 'loggedIn',
|
|
||||||
expiresAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(`User logged in: ${user.username}`);
|
logger.info(`User logged in: ${user.username}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
identity: {
|
identity,
|
||||||
jwt,
|
|
||||||
userId,
|
|
||||||
username: user.username,
|
|
||||||
expiresAt,
|
|
||||||
role: user.role,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||||
@@ -84,22 +149,11 @@ export class AdminHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_VerifyIdentity>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_VerifyIdentity>(
|
||||||
'verifyIdentity',
|
'verifyIdentity',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
if (!dataArg.identity?.jwt) {
|
|
||||||
return { valid: false };
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt);
|
const identity = await this.getVerifiedIdentity(dataArg.identity);
|
||||||
if (jwtData.expiresAt < Date.now()) return { valid: false };
|
|
||||||
if (jwtData.status !== 'loggedIn') return { valid: false };
|
|
||||||
return {
|
return {
|
||||||
valid: true,
|
valid: true,
|
||||||
identity: {
|
identity,
|
||||||
jwt: dataArg.identity.jwt,
|
|
||||||
userId: jwtData.userId,
|
|
||||||
username: dataArg.identity.username,
|
|
||||||
expiresAt: jwtData.expiresAt,
|
|
||||||
role: dataArg.identity.role,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return { valid: false };
|
return { valid: false };
|
||||||
@@ -113,18 +167,18 @@ export class AdminHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ChangePassword>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ChangePassword>(
|
||||||
'changePassword',
|
'changePassword',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await this.requireValidIdentity(dataArg);
|
const identity = await this.getVerifiedIdentity(dataArg.identity);
|
||||||
const user = this.opsServerRef.oneboxRef.database.getUserByUsername(dataArg.identity.username);
|
const user = this.opsServerRef.oneboxRef.database.getUserByUsername(identity.username);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('User not found');
|
throw new plugins.typedrequest.TypedResponseError('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentHash = btoa(dataArg.currentPassword);
|
const currentPasswordMatches = await verifyPassword(dataArg.currentPassword, user.passwordHash);
|
||||||
if (currentHash !== user.passwordHash) {
|
if (!currentPasswordMatches) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Current password is incorrect');
|
throw new plugins.typedrequest.TypedResponseError('Current password is incorrect');
|
||||||
}
|
}
|
||||||
|
|
||||||
const newHash = btoa(dataArg.newPassword);
|
const newHash = await hashPassword(dataArg.newPassword);
|
||||||
this.opsServerRef.oneboxRef.database.updateUserPassword(user.username, newHash);
|
this.opsServerRef.oneboxRef.database.updateUserPassword(user.username, newHash);
|
||||||
logger.info(`Password changed for user: ${user.username}`);
|
logger.info(`Password changed for user: ${user.username}`);
|
||||||
|
|
||||||
@@ -134,25 +188,13 @@ export class AdminHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async requireValidIdentity(dataArg: { identity: interfaces.data.IIdentity }): Promise<void> {
|
|
||||||
const passed = await this.validIdentityGuard.exec({ identity: dataArg.identity });
|
|
||||||
if (!passed) {
|
|
||||||
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guard for valid identity
|
// Guard for valid identity
|
||||||
public validIdentityGuard = new plugins.smartguard.Guard<{
|
public validIdentityGuard = new plugins.smartguard.Guard<{
|
||||||
identity: interfaces.data.IIdentity;
|
identity: interfaces.data.IIdentity;
|
||||||
}>(
|
}>(
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
if (!dataArg.identity?.jwt) return false;
|
|
||||||
try {
|
try {
|
||||||
const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt);
|
await this.getVerifiedIdentity(dataArg.identity);
|
||||||
if (jwtData.expiresAt < Date.now()) return false;
|
|
||||||
if (jwtData.status !== 'loggedIn') return false;
|
|
||||||
if (dataArg.identity.expiresAt !== jwtData.expiresAt) return false;
|
|
||||||
if (dataArg.identity.userId !== jwtData.userId) return false;
|
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
@@ -166,9 +208,12 @@ export class AdminHandler {
|
|||||||
identity: interfaces.data.IIdentity;
|
identity: interfaces.data.IIdentity;
|
||||||
}>(
|
}>(
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
const isValid = await this.validIdentityGuard.exec(dataArg);
|
try {
|
||||||
if (!isValid) return false;
|
const identity = await this.getVerifiedIdentity(dataArg.identity);
|
||||||
return dataArg.identity.role === 'admin';
|
return identity.role === 'admin';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ failedHint: 'user is not admin', name: 'adminIdentityGuard' },
|
{ failedHint: 'user is not admin', name: 'adminIdentityGuard' },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as plugins from '../../plugins.ts';
|
|||||||
import { logger } from '../../logging.ts';
|
import { logger } from '../../logging.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class AppStoreHandler {
|
export class AppStoreHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -18,7 +18,7 @@ export class AppStoreHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAppTemplates>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAppTemplates>(
|
||||||
'getAppTemplates',
|
'getAppTemplates',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const apps = await this.opsServerRef.oneboxRef.appStore.getApps();
|
const apps = await this.opsServerRef.oneboxRef.appStore.getApps();
|
||||||
return { apps };
|
return { apps };
|
||||||
},
|
},
|
||||||
@@ -30,7 +30,7 @@ export class AppStoreHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAppConfig>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAppConfig>(
|
||||||
'getAppConfig',
|
'getAppConfig',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const config = await this.opsServerRef.oneboxRef.appStore.getAppVersionConfig(
|
const config = await this.opsServerRef.oneboxRef.appStore.getAppVersionConfig(
|
||||||
dataArg.appId,
|
dataArg.appId,
|
||||||
dataArg.version,
|
dataArg.version,
|
||||||
@@ -46,7 +46,7 @@ export class AppStoreHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetUpgradeableServices>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetUpgradeableServices>(
|
||||||
'getUpgradeableServices',
|
'getUpgradeableServices',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const services = await this.opsServerRef.oneboxRef.appStore.getUpgradeableServices();
|
const services = await this.opsServerRef.oneboxRef.appStore.getUpgradeableServices();
|
||||||
return { services };
|
return { services };
|
||||||
},
|
},
|
||||||
@@ -58,7 +58,7 @@ export class AppStoreHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpgradeService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpgradeService>(
|
||||||
'upgradeService',
|
'upgradeService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
|
|
||||||
const existingService = this.opsServerRef.oneboxRef.database.getServiceByName(dataArg.serviceName);
|
const existingService = this.opsServerRef.oneboxRef.database.getServiceByName(dataArg.serviceName);
|
||||||
if (!existingService) {
|
if (!existingService) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class BackupsHandler {
|
export class BackupsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -16,7 +16,7 @@ export class BackupsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackups>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackups>(
|
||||||
'getBackups',
|
'getBackups',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const backups = this.opsServerRef.oneboxRef.backupManager.listBackups();
|
const backups = this.opsServerRef.oneboxRef.backupManager.listBackups();
|
||||||
return { backups };
|
return { backups };
|
||||||
},
|
},
|
||||||
@@ -27,7 +27,7 @@ export class BackupsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackup>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackup>(
|
||||||
'getBackup',
|
'getBackup',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const backup = this.opsServerRef.oneboxRef.database.getBackupById(dataArg.backupId);
|
const backup = this.opsServerRef.oneboxRef.database.getBackupById(dataArg.backupId);
|
||||||
if (!backup) {
|
if (!backup) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Backup not found');
|
throw new plugins.typedrequest.TypedResponseError('Backup not found');
|
||||||
@@ -41,7 +41,7 @@ export class BackupsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteBackup>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteBackup>(
|
||||||
'deleteBackup',
|
'deleteBackup',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.backupManager.deleteBackup(dataArg.backupId);
|
await this.opsServerRef.oneboxRef.backupManager.deleteBackup(dataArg.backupId);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
@@ -52,7 +52,7 @@ export class BackupsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RestoreBackup>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RestoreBackup>(
|
||||||
'restoreBackup',
|
'restoreBackup',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const rawResult = await this.opsServerRef.oneboxRef.backupManager.restoreBackup(
|
const rawResult = await this.opsServerRef.oneboxRef.backupManager.restoreBackup(
|
||||||
dataArg.backupId,
|
dataArg.backupId,
|
||||||
dataArg.options,
|
dataArg.options,
|
||||||
@@ -75,7 +75,7 @@ export class BackupsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DownloadBackup>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DownloadBackup>(
|
||||||
'downloadBackup',
|
'downloadBackup',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const backup = this.opsServerRef.oneboxRef.database.getBackupById(dataArg.backupId);
|
const backup = this.opsServerRef.oneboxRef.database.getBackupById(dataArg.backupId);
|
||||||
if (!backup) {
|
if (!backup) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Backup not found');
|
throw new plugins.typedrequest.TypedResponseError('Backup not found');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class DnsHandler {
|
export class DnsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -16,7 +16,7 @@ export class DnsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsRecords>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsRecords>(
|
||||||
'getDnsRecords',
|
'getDnsRecords',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const records = this.opsServerRef.oneboxRef.dns.listDNSRecords();
|
const records = this.opsServerRef.oneboxRef.dns.listDNSRecords();
|
||||||
return { records };
|
return { records };
|
||||||
},
|
},
|
||||||
@@ -27,7 +27,7 @@ export class DnsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateDnsRecord>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateDnsRecord>(
|
||||||
'createDnsRecord',
|
'createDnsRecord',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.dns.addDNSRecord(dataArg.domain, dataArg.value);
|
await this.opsServerRef.oneboxRef.dns.addDNSRecord(dataArg.domain, dataArg.value);
|
||||||
const records = this.opsServerRef.oneboxRef.dns.listDNSRecords();
|
const records = this.opsServerRef.oneboxRef.dns.listDNSRecords();
|
||||||
const record = records.find((r: any) => r.domain === dataArg.domain);
|
const record = records.find((r: any) => r.domain === dataArg.domain);
|
||||||
@@ -40,7 +40,7 @@ export class DnsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteDnsRecord>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteDnsRecord>(
|
||||||
'deleteDnsRecord',
|
'deleteDnsRecord',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.dns.removeDNSRecord(dataArg.domain);
|
await this.opsServerRef.oneboxRef.dns.removeDNSRecord(dataArg.domain);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
@@ -51,7 +51,7 @@ export class DnsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SyncDns>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SyncDns>(
|
||||||
'syncDns',
|
'syncDns',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
if (!this.opsServerRef.oneboxRef.dns.isConfigured()) {
|
if (!this.opsServerRef.oneboxRef.dns.isConfigured()) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('DNS manager not configured');
|
throw new plugins.typedrequest.TypedResponseError('DNS manager not configured');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class DomainsHandler {
|
export class DomainsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -57,7 +57,7 @@ export class DomainsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDomains>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDomains>(
|
||||||
'getDomains',
|
'getDomains',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const domains = this.buildDomainViews();
|
const domains = this.buildDomainViews();
|
||||||
return { domains };
|
return { domains };
|
||||||
},
|
},
|
||||||
@@ -68,7 +68,7 @@ export class DomainsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDomain>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDomain>(
|
||||||
'getDomain',
|
'getDomain',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const domain = this.opsServerRef.oneboxRef.database.getDomainByName(dataArg.domainName);
|
const domain = this.opsServerRef.oneboxRef.database.getDomainByName(dataArg.domainName);
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Domain not found');
|
throw new plugins.typedrequest.TypedResponseError('Domain not found');
|
||||||
@@ -87,7 +87,7 @@ export class DomainsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SyncDomains>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SyncDomains>(
|
||||||
'syncDomains',
|
'syncDomains',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
if (!this.opsServerRef.oneboxRef.cloudflareDomainSync) {
|
if (!this.opsServerRef.oneboxRef.cloudflareDomainSync) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Cloudflare domain sync not configured');
|
throw new plugins.typedrequest.TypedResponseError('Cloudflare domain sync not configured');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as plugins from '../../plugins.ts';
|
|||||||
import { logger } from '../../logging.ts';
|
import { logger } from '../../logging.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class LogsHandler {
|
export class LogsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -18,7 +18,7 @@ export class LogsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceLogStream>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceLogStream>(
|
||||||
'getServiceLogStream',
|
'getServiceLogStream',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
|
|
||||||
const service = this.opsServerRef.oneboxRef.database.getServiceByName(dataArg.serviceName);
|
const service = this.opsServerRef.oneboxRef.database.getServiceByName(dataArg.serviceName);
|
||||||
if (!service) {
|
if (!service) {
|
||||||
@@ -99,7 +99,7 @@ export class LogsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceLogStream>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceLogStream>(
|
||||||
'getPlatformServiceLogStream',
|
'getPlatformServiceLogStream',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
|
|
||||||
const platformService = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(
|
const platformService = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(
|
||||||
dataArg.serviceType,
|
dataArg.serviceType,
|
||||||
@@ -160,7 +160,7 @@ export class LogsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkLogStream>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkLogStream>(
|
||||||
'getNetworkLogStream',
|
'getNetworkLogStream',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
|
|
||||||
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
|
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
@@ -195,7 +195,7 @@ export class LogsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEventStream>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEventStream>(
|
||||||
'getEventStream',
|
'getEventStream',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
|
|
||||||
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
|
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
import type { TPlatformServiceType } from '../../types.ts';
|
import type { TPlatformServiceType } from '../../types.ts';
|
||||||
|
|
||||||
export class NetworkHandler {
|
export class NetworkHandler {
|
||||||
@@ -31,7 +31,7 @@ export class NetworkHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkTargets>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkTargets>(
|
||||||
'getNetworkTargets',
|
'getNetworkTargets',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const targets: interfaces.data.INetworkTarget[] = [];
|
const targets: interfaces.data.INetworkTarget[] = [];
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
@@ -83,7 +83,7 @@ export class NetworkHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkStats>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkStats>(
|
||||||
'getNetworkStats',
|
'getNetworkStats',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any;
|
const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any;
|
||||||
const logReceiverStats = this.opsServerRef.oneboxRef.caddyLogReceiver.getStats();
|
const logReceiverStats = this.opsServerRef.oneboxRef.caddyLogReceiver.getStats();
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ export class NetworkHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetTrafficStats>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetTrafficStats>(
|
||||||
'getTrafficStats',
|
'getTrafficStats',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const trafficStats = this.opsServerRef.oneboxRef.caddyLogReceiver.getTrafficStats(60);
|
const trafficStats = this.opsServerRef.oneboxRef.caddyLogReceiver.getTrafficStats(60);
|
||||||
return { stats: trafficStats };
|
return { stats: trafficStats };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as plugins from '../../plugins.ts';
|
|||||||
import { logger } from '../../logging.ts';
|
import { logger } from '../../logging.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class PlatformHandler {
|
export class PlatformHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -99,7 +99,7 @@ export class PlatformHandler {
|
|||||||
typedsocket.findAllTargetConnectionsByTag('role', 'ops_dashboard')
|
typedsocket.findAllTargetConnectionsByTag('role', 'ops_dashboard')
|
||||||
.then((connections: any[]) => {
|
.then((connections: any[]) => {
|
||||||
for (const conn of connections) {
|
for (const conn of connections) {
|
||||||
typedsocket.createTypedRequest<interfaces.requests.IReq_PushPlatformServiceLog>(
|
typedsocket.createTypedRequest(
|
||||||
'pushPlatformServiceLog',
|
'pushPlatformServiceLog',
|
||||||
conn,
|
conn,
|
||||||
).fire({ serviceType, entry }).catch(() => {});
|
).fire({ serviceType, entry }).catch(() => {});
|
||||||
@@ -121,7 +121,7 @@ export class PlatformHandler {
|
|||||||
typedsocket.findAllTargetConnectionsByTag('role', 'ops_dashboard')
|
typedsocket.findAllTargetConnectionsByTag('role', 'ops_dashboard')
|
||||||
.then((connections: any[]) => {
|
.then((connections: any[]) => {
|
||||||
for (const conn of connections) {
|
for (const conn of connections) {
|
||||||
typedsocket.createTypedRequest<interfaces.requests.IReq_PushServiceLog>(
|
typedsocket.createTypedRequest(
|
||||||
'pushServiceLog',
|
'pushServiceLog',
|
||||||
conn,
|
conn,
|
||||||
).fire({ serviceName, entry }).catch(() => {});
|
).fire({ serviceName, entry }).catch(() => {});
|
||||||
@@ -136,7 +136,7 @@ export class PlatformHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServices>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServices>(
|
||||||
'getPlatformServices',
|
'getPlatformServices',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const platformServices = this.opsServerRef.oneboxRef.platformServices.getAllPlatformServices();
|
const platformServices = this.opsServerRef.oneboxRef.platformServices.getAllPlatformServices();
|
||||||
const providers = this.opsServerRef.oneboxRef.platformServices.getAllProviders();
|
const providers = this.opsServerRef.oneboxRef.platformServices.getAllProviders();
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ export class PlatformHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformService>(
|
||||||
'getPlatformService',
|
'getPlatformService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
|
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
|
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
|
||||||
@@ -208,7 +208,7 @@ export class PlatformHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartPlatformService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartPlatformService>(
|
||||||
'startPlatformService',
|
'startPlatformService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
|
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
|
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
|
||||||
@@ -235,7 +235,7 @@ export class PlatformHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StopPlatformService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StopPlatformService>(
|
||||||
'stopPlatformService',
|
'stopPlatformService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
|
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
|
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
|
||||||
@@ -268,7 +268,7 @@ export class PlatformHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceStats>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceStats>(
|
||||||
'getPlatformServiceStats',
|
'getPlatformServiceStats',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const service = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
|
const service = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
|
||||||
if (!service || !service.containerId) {
|
if (!service || !service.containerId) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Platform service has no container');
|
throw new plugins.typedrequest.TypedResponseError('Platform service has no container');
|
||||||
@@ -289,7 +289,7 @@ export class PlatformHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceLogs>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceLogs>(
|
||||||
'getPlatformServiceLogs',
|
'getPlatformServiceLogs',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const service = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
|
const service = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
|
||||||
if (!service || !service.containerId) {
|
if (!service || !service.containerId) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Platform service has no container');
|
throw new plugins.typedrequest.TypedResponseError('Platform service has no container');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class RegistryHandler {
|
export class RegistryHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -17,7 +17,7 @@ export class RegistryHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRegistryTags>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRegistryTags>(
|
||||||
'getRegistryTags',
|
'getRegistryTags',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const tags = await this.opsServerRef.oneboxRef.registry.getImageTags(dataArg.serviceName);
|
const tags = await this.opsServerRef.oneboxRef.registry.getImageTags(dataArg.serviceName);
|
||||||
return { tags };
|
return { tags };
|
||||||
},
|
},
|
||||||
@@ -29,7 +29,7 @@ export class RegistryHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRegistryTokens>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRegistryTokens>(
|
||||||
'getRegistryTokens',
|
'getRegistryTokens',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const rawTokens = this.opsServerRef.oneboxRef.database.getAllRegistryTokens();
|
const rawTokens = this.opsServerRef.oneboxRef.database.getAllRegistryTokens();
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ export class RegistryHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRegistryToken>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRegistryToken>(
|
||||||
'createRegistryToken',
|
'createRegistryToken',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
const identity = await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const config = dataArg.tokenConfig;
|
const config = dataArg.tokenConfig;
|
||||||
|
|
||||||
// Calculate expiration
|
// Calculate expiration
|
||||||
@@ -95,7 +95,7 @@ export class RegistryHandler {
|
|||||||
expiresAt,
|
expiresAt,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
lastUsedAt: null,
|
lastUsedAt: null,
|
||||||
createdBy: dataArg.identity.username,
|
createdBy: identity.username,
|
||||||
});
|
});
|
||||||
|
|
||||||
let scopeDisplay: string;
|
let scopeDisplay: string;
|
||||||
@@ -133,7 +133,7 @@ export class RegistryHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRegistryToken>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRegistryToken>(
|
||||||
'deleteRegistryToken',
|
'deleteRegistryToken',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const token = this.opsServerRef.oneboxRef.database.getRegistryTokenById(dataArg.tokenId);
|
const token = this.opsServerRef.oneboxRef.database.getRegistryTokenById(dataArg.tokenId);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Token not found');
|
throw new plugins.typedrequest.TypedResponseError('Token not found');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class SchedulesHandler {
|
export class SchedulesHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -16,7 +16,7 @@ export class SchedulesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupSchedules>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupSchedules>(
|
||||||
'getBackupSchedules',
|
'getBackupSchedules',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const schedules = this.opsServerRef.oneboxRef.backupScheduler.getAllSchedules();
|
const schedules = this.opsServerRef.oneboxRef.backupScheduler.getAllSchedules();
|
||||||
return { schedules };
|
return { schedules };
|
||||||
},
|
},
|
||||||
@@ -27,7 +27,7 @@ export class SchedulesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateBackupSchedule>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateBackupSchedule>(
|
||||||
'createBackupSchedule',
|
'createBackupSchedule',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const schedule = await this.opsServerRef.oneboxRef.backupScheduler.createSchedule(
|
const schedule = await this.opsServerRef.oneboxRef.backupScheduler.createSchedule(
|
||||||
dataArg.scheduleConfig,
|
dataArg.scheduleConfig,
|
||||||
);
|
);
|
||||||
@@ -40,7 +40,7 @@ export class SchedulesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupSchedule>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupSchedule>(
|
||||||
'getBackupSchedule',
|
'getBackupSchedule',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const schedule = this.opsServerRef.oneboxRef.backupScheduler.getScheduleById(dataArg.scheduleId);
|
const schedule = this.opsServerRef.oneboxRef.backupScheduler.getScheduleById(dataArg.scheduleId);
|
||||||
if (!schedule) {
|
if (!schedule) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Schedule not found');
|
throw new plugins.typedrequest.TypedResponseError('Schedule not found');
|
||||||
@@ -54,7 +54,7 @@ export class SchedulesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateBackupSchedule>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateBackupSchedule>(
|
||||||
'updateBackupSchedule',
|
'updateBackupSchedule',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const schedule = await this.opsServerRef.oneboxRef.backupScheduler.updateSchedule(
|
const schedule = await this.opsServerRef.oneboxRef.backupScheduler.updateSchedule(
|
||||||
dataArg.scheduleId,
|
dataArg.scheduleId,
|
||||||
dataArg.updates,
|
dataArg.updates,
|
||||||
@@ -68,7 +68,7 @@ export class SchedulesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteBackupSchedule>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteBackupSchedule>(
|
||||||
'deleteBackupSchedule',
|
'deleteBackupSchedule',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.backupScheduler.deleteSchedule(dataArg.scheduleId);
|
await this.opsServerRef.oneboxRef.backupScheduler.deleteSchedule(dataArg.scheduleId);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
@@ -79,7 +79,7 @@ export class SchedulesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TriggerBackupSchedule>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TriggerBackupSchedule>(
|
||||||
'triggerBackupSchedule',
|
'triggerBackupSchedule',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.backupScheduler.triggerBackup(dataArg.scheduleId);
|
await this.opsServerRef.oneboxRef.backupScheduler.triggerBackup(dataArg.scheduleId);
|
||||||
// triggerBackup is void; the backup is created async by the scheduler
|
// triggerBackup is void; the backup is created async by the scheduler
|
||||||
// Return the most recent backup for the schedule
|
// Return the most recent backup for the schedule
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as plugins from '../../plugins.ts';
|
|||||||
import { logger } from '../../logging.ts';
|
import { logger } from '../../logging.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class ServicesHandler {
|
export class ServicesHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -18,7 +18,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServices>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServices>(
|
||||||
'getServices',
|
'getServices',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const services = this.opsServerRef.oneboxRef.services.listServices();
|
const services = this.opsServerRef.oneboxRef.services.listServices();
|
||||||
return { services };
|
return { services };
|
||||||
},
|
},
|
||||||
@@ -30,7 +30,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetService>(
|
||||||
'getService',
|
'getService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
||||||
if (!service) {
|
if (!service) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Service not found');
|
throw new plugins.typedrequest.TypedResponseError('Service not found');
|
||||||
@@ -45,7 +45,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateService>(
|
||||||
'createService',
|
'createService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const service = await this.opsServerRef.oneboxRef.services.deployService(dataArg.serviceConfig);
|
const service = await this.opsServerRef.oneboxRef.services.deployService(dataArg.serviceConfig);
|
||||||
return { service };
|
return { service };
|
||||||
},
|
},
|
||||||
@@ -57,7 +57,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateService>(
|
||||||
'updateService',
|
'updateService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const service = await this.opsServerRef.oneboxRef.services.updateService(
|
const service = await this.opsServerRef.oneboxRef.services.updateService(
|
||||||
dataArg.serviceName,
|
dataArg.serviceName,
|
||||||
dataArg.updates,
|
dataArg.updates,
|
||||||
@@ -72,7 +72,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteService>(
|
||||||
'deleteService',
|
'deleteService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.services.removeService(dataArg.serviceName);
|
await this.opsServerRef.oneboxRef.services.removeService(dataArg.serviceName);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
@@ -84,7 +84,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartService>(
|
||||||
'startService',
|
'startService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.services.startService(dataArg.serviceName);
|
await this.opsServerRef.oneboxRef.services.startService(dataArg.serviceName);
|
||||||
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
||||||
return { service: service! };
|
return { service: service! };
|
||||||
@@ -97,7 +97,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StopService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StopService>(
|
||||||
'stopService',
|
'stopService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.services.stopService(dataArg.serviceName);
|
await this.opsServerRef.oneboxRef.services.stopService(dataArg.serviceName);
|
||||||
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
||||||
return { service: service! };
|
return { service: service! };
|
||||||
@@ -110,7 +110,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RestartService>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RestartService>(
|
||||||
'restartService',
|
'restartService',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.services.restartService(dataArg.serviceName);
|
await this.opsServerRef.oneboxRef.services.restartService(dataArg.serviceName);
|
||||||
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
||||||
return { service: service! };
|
return { service: service! };
|
||||||
@@ -123,7 +123,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceLogs>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceLogs>(
|
||||||
'getServiceLogs',
|
'getServiceLogs',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const logs = await this.opsServerRef.oneboxRef.services.getServiceLogs(dataArg.serviceName);
|
const logs = await this.opsServerRef.oneboxRef.services.getServiceLogs(dataArg.serviceName);
|
||||||
return { logs: String(logs) };
|
return { logs: String(logs) };
|
||||||
},
|
},
|
||||||
@@ -135,7 +135,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceStats>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceStats>(
|
||||||
'getServiceStats',
|
'getServiceStats',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
||||||
if (!service || !service.containerID) {
|
if (!service || !service.containerID) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Service has no container');
|
throw new plugins.typedrequest.TypedResponseError('Service has no container');
|
||||||
@@ -154,7 +154,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceMetrics>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceMetrics>(
|
||||||
'getServiceMetrics',
|
'getServiceMetrics',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
||||||
if (!service || !service.id) {
|
if (!service || !service.id) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Service not found');
|
throw new plugins.typedrequest.TypedResponseError('Service not found');
|
||||||
@@ -170,7 +170,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServicePlatformResources>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServicePlatformResources>(
|
||||||
'getServicePlatformResources',
|
'getServicePlatformResources',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const rawResources = await this.opsServerRef.oneboxRef.services.getServicePlatformResources(
|
const rawResources = await this.opsServerRef.oneboxRef.services.getServicePlatformResources(
|
||||||
dataArg.serviceName,
|
dataArg.serviceName,
|
||||||
);
|
);
|
||||||
@@ -204,7 +204,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceBackups>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceBackups>(
|
||||||
'getServiceBackups',
|
'getServiceBackups',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const backups = this.opsServerRef.oneboxRef.backupManager.listBackups(dataArg.serviceName);
|
const backups = this.opsServerRef.oneboxRef.backupManager.listBackups(dataArg.serviceName);
|
||||||
return { backups };
|
return { backups };
|
||||||
},
|
},
|
||||||
@@ -216,7 +216,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateServiceBackup>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateServiceBackup>(
|
||||||
'createServiceBackup',
|
'createServiceBackup',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const result = await this.opsServerRef.oneboxRef.backupManager.createBackup(dataArg.serviceName);
|
const result = await this.opsServerRef.oneboxRef.backupManager.createBackup(dataArg.serviceName);
|
||||||
return { backup: result.backup };
|
return { backup: result.backup };
|
||||||
},
|
},
|
||||||
@@ -228,7 +228,7 @@ export class ServicesHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceBackupSchedules>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceBackupSchedules>(
|
||||||
'getServiceBackupSchedules',
|
'getServiceBackupSchedules',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
|
||||||
if (!service) {
|
if (!service) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Service not found');
|
throw new plugins.typedrequest.TypedResponseError('Service not found');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class SettingsHandler {
|
export class SettingsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -16,7 +16,7 @@ export class SettingsHandler {
|
|||||||
const settingsMap = db.getAllSettings(); // Returns Record<string, string>
|
const settingsMap = db.getAllSettings(); // Returns Record<string, string>
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cloudflareToken: settingsMap['cloudflareToken'] || '',
|
cloudflareToken: settingsMap['cloudflareToken'] || settingsMap['cloudflareAPIKey'] || '',
|
||||||
cloudflareZoneId: settingsMap['cloudflareZoneId'] || '',
|
cloudflareZoneId: settingsMap['cloudflareZoneId'] || '',
|
||||||
autoRenewCerts: settingsMap['autoRenewCerts'] === 'true',
|
autoRenewCerts: settingsMap['autoRenewCerts'] === 'true',
|
||||||
renewalThreshold: parseInt(settingsMap['renewalThreshold'] || '30', 10),
|
renewalThreshold: parseInt(settingsMap['renewalThreshold'] || '30', 10),
|
||||||
@@ -32,7 +32,7 @@ export class SettingsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSettings>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSettings>(
|
||||||
'getSettings',
|
'getSettings',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const settings = this.getSettingsObject();
|
const settings = this.getSettingsObject();
|
||||||
return { settings };
|
return { settings };
|
||||||
},
|
},
|
||||||
@@ -43,16 +43,21 @@ export class SettingsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateSettings>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateSettings>(
|
||||||
'updateSettings',
|
'updateSettings',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const db = this.opsServerRef.oneboxRef.database;
|
const db = this.opsServerRef.oneboxRef.database;
|
||||||
const updates = dataArg.settings;
|
const updates = dataArg.settings;
|
||||||
|
|
||||||
// Store each setting as key-value pair
|
// Store each setting as key-value pair
|
||||||
for (const [key, value] of Object.entries(updates)) {
|
for (const [key, value] of Object.entries(updates)) {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
|
if (key === 'cloudflareToken') {
|
||||||
|
db.setSetting('cloudflareToken', String(value));
|
||||||
|
db.setSetting('cloudflareAPIKey', String(value));
|
||||||
|
} else {
|
||||||
db.setSetting(key, String(value));
|
db.setSetting(key, String(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const settings = this.getSettingsObject();
|
const settings = this.getSettingsObject();
|
||||||
return { settings };
|
return { settings };
|
||||||
@@ -64,7 +69,8 @@ export class SettingsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetBackupPassword>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetBackupPassword>(
|
||||||
'setBackupPassword',
|
'setBackupPassword',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
|
this.opsServerRef.oneboxRef.database.setSetting('backup_encryption_password', dataArg.password);
|
||||||
this.opsServerRef.oneboxRef.database.setSetting('backupPassword', dataArg.password);
|
this.opsServerRef.oneboxRef.database.setSetting('backupPassword', dataArg.password);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
@@ -75,8 +81,9 @@ export class SettingsHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupPasswordStatus>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupPasswordStatus>(
|
||||||
'getBackupPasswordStatus',
|
'getBackupPasswordStatus',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const backupPassword = this.opsServerRef.oneboxRef.database.getSetting('backupPassword');
|
const backupPassword = this.opsServerRef.oneboxRef.database.getSetting('backupPassword')
|
||||||
|
|| this.opsServerRef.oneboxRef.database.getSetting('backup_encryption_password');
|
||||||
const isConfigured = !!backupPassword;
|
const isConfigured = !!backupPassword;
|
||||||
return { status: { isConfigured } };
|
return { status: { isConfigured } };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class SslHandler {
|
export class SslHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -16,7 +16,7 @@ export class SslHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ObtainCertificate>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ObtainCertificate>(
|
||||||
'obtainCertificate',
|
'obtainCertificate',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.ssl.obtainCertificate(dataArg.domain, false);
|
await this.opsServerRef.oneboxRef.ssl.obtainCertificate(dataArg.domain, false);
|
||||||
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
|
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
|
||||||
return { certificate: certificate as unknown as interfaces.data.ICertificate };
|
return { certificate: certificate as unknown as interfaces.data.ICertificate };
|
||||||
@@ -28,7 +28,7 @@ export class SslHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListCertificates>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListCertificates>(
|
||||||
'listCertificates',
|
'listCertificates',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const certificates = this.opsServerRef.oneboxRef.ssl.listCertificates();
|
const certificates = this.opsServerRef.oneboxRef.ssl.listCertificates();
|
||||||
return { certificates: certificates as unknown as interfaces.data.ICertificate[] };
|
return { certificates: certificates as unknown as interfaces.data.ICertificate[] };
|
||||||
},
|
},
|
||||||
@@ -39,7 +39,7 @@ export class SslHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificate>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificate>(
|
||||||
'getCertificate',
|
'getCertificate',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
|
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
|
||||||
if (!certificate) {
|
if (!certificate) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Certificate not found');
|
throw new plugins.typedrequest.TypedResponseError('Certificate not found');
|
||||||
@@ -53,7 +53,7 @@ export class SslHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RenewCertificate>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RenewCertificate>(
|
||||||
'renewCertificate',
|
'renewCertificate',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
await this.opsServerRef.oneboxRef.ssl.renewCertificate(dataArg.domain);
|
await this.opsServerRef.oneboxRef.ssl.renewCertificate(dataArg.domain);
|
||||||
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
|
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
|
||||||
return { certificate: certificate as unknown as interfaces.data.ICertificate };
|
return { certificate: certificate as unknown as interfaces.data.ICertificate };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../../plugins.ts';
|
import * as plugins from '../../plugins.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
|
|
||||||
export class StatusHandler {
|
export class StatusHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -16,7 +16,7 @@ export class StatusHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSystemStatus>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSystemStatus>(
|
||||||
'getSystemStatus',
|
'getSystemStatus',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const status = await this.opsServerRef.oneboxRef.getSystemStatus();
|
const status = await this.opsServerRef.oneboxRef.getSystemStatus();
|
||||||
return { status: status as unknown as interfaces.data.ISystemStatus };
|
return { status: status as unknown as interfaces.data.ISystemStatus };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as plugins from '../../plugins.ts';
|
|||||||
import { logger } from '../../logging.ts';
|
import { logger } from '../../logging.ts';
|
||||||
import type { OpsServer } from '../classes.opsserver.ts';
|
import type { OpsServer } from '../classes.opsserver.ts';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||||
import { requireValidIdentity } from '../helpers/guards.ts';
|
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||||
import { getErrorMessage } from '../../utils/error.ts';
|
import { getErrorMessage } from '../../utils/error.ts';
|
||||||
|
|
||||||
export class WorkspaceHandler {
|
export class WorkspaceHandler {
|
||||||
@@ -30,7 +30,7 @@ export class WorkspaceHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceReadFile>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceReadFile>(
|
||||||
'workspaceReadFile',
|
'workspaceReadFile',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
||||||
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
||||||
containerId,
|
containerId,
|
||||||
@@ -49,7 +49,7 @@ export class WorkspaceHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceWriteFile>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceWriteFile>(
|
||||||
'workspaceWriteFile',
|
'workspaceWriteFile',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
||||||
// Use sh -c with printf to write content (handles special characters)
|
// Use sh -c with printf to write content (handles special characters)
|
||||||
const escaped = dataArg.content.replace(/'/g, "'\\''");
|
const escaped = dataArg.content.replace(/'/g, "'\\''");
|
||||||
@@ -70,7 +70,7 @@ export class WorkspaceHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceReadDir>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceReadDir>(
|
||||||
'workspaceReadDir',
|
'workspaceReadDir',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
||||||
// Use ls with -1 -F to get entries with type indicators (/ for dirs)
|
// Use ls with -1 -F to get entries with type indicators (/ for dirs)
|
||||||
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
||||||
@@ -103,7 +103,7 @@ export class WorkspaceHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceMkdir>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceMkdir>(
|
||||||
'workspaceMkdir',
|
'workspaceMkdir',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
||||||
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
||||||
containerId,
|
containerId,
|
||||||
@@ -122,7 +122,7 @@ export class WorkspaceHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceRm>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceRm>(
|
||||||
'workspaceRm',
|
'workspaceRm',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
||||||
const args = dataArg.recursive ? ['rm', '-rf', dataArg.path] : ['rm', '-f', dataArg.path];
|
const args = dataArg.recursive ? ['rm', '-rf', dataArg.path] : ['rm', '-f', dataArg.path];
|
||||||
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
||||||
@@ -142,7 +142,7 @@ export class WorkspaceHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceExists>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceExists>(
|
||||||
'workspaceExists',
|
'workspaceExists',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
||||||
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
|
||||||
containerId,
|
containerId,
|
||||||
@@ -158,7 +158,7 @@ export class WorkspaceHandler {
|
|||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceExec>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceExec>(
|
||||||
'workspaceExec',
|
'workspaceExec',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
|
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
|
||||||
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
const containerId = await this.resolveContainerId(dataArg.serviceName);
|
||||||
const cmd = dataArg.args
|
const cmd = dataArg.args
|
||||||
? [dataArg.command, ...dataArg.args]
|
? [dataArg.command, ...dataArg.args]
|
||||||
|
|||||||
@@ -5,25 +5,13 @@ import * as interfaces from '../../../ts_interfaces/index.ts';
|
|||||||
export async function requireValidIdentity<T extends { identity?: interfaces.data.IIdentity }>(
|
export async function requireValidIdentity<T extends { identity?: interfaces.data.IIdentity }>(
|
||||||
adminHandler: AdminHandler,
|
adminHandler: AdminHandler,
|
||||||
dataArg: T,
|
dataArg: T,
|
||||||
): Promise<void> {
|
): Promise<interfaces.data.IIdentity> {
|
||||||
if (!dataArg.identity) {
|
return await adminHandler.getVerifiedIdentity(dataArg.identity);
|
||||||
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
|
||||||
}
|
|
||||||
const passed = await adminHandler.validIdentityGuard.exec({ identity: dataArg.identity });
|
|
||||||
if (!passed) {
|
|
||||||
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requireAdminIdentity<T extends { identity?: interfaces.data.IIdentity }>(
|
export async function requireAdminIdentity<T extends { identity?: interfaces.data.IIdentity }>(
|
||||||
adminHandler: AdminHandler,
|
adminHandler: AdminHandler,
|
||||||
dataArg: T,
|
dataArg: T,
|
||||||
): Promise<void> {
|
): Promise<interfaces.data.IIdentity> {
|
||||||
if (!dataArg.identity) {
|
return await adminHandler.getVerifiedAdminIdentity(dataArg.identity);
|
||||||
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
|
||||||
}
|
|
||||||
const passed = await adminHandler.adminIdentityGuard.exec({ identity: dataArg.identity });
|
|
||||||
if (!passed) {
|
|
||||||
throw new plugins.typedrequest.TypedResponseError('Admin access required');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,20 @@ export { smartregistry };
|
|||||||
import * as smartstorage from '@push.rocks/smartstorage';
|
import * as smartstorage from '@push.rocks/smartstorage';
|
||||||
export { smartstorage };
|
export { smartstorage };
|
||||||
|
|
||||||
|
// AWS S3 client for S3-compatible object operations
|
||||||
|
import {
|
||||||
|
S3Client,
|
||||||
|
ListObjectsV2Command,
|
||||||
|
GetObjectCommand,
|
||||||
|
PutObjectCommand,
|
||||||
|
} from 'npm:@aws-sdk/client-s3@3.1009.0';
|
||||||
|
export const awsS3 = {
|
||||||
|
S3Client,
|
||||||
|
ListObjectsV2Command,
|
||||||
|
GetObjectCommand,
|
||||||
|
PutObjectCommand,
|
||||||
|
};
|
||||||
|
|
||||||
// Task scheduling and cron jobs
|
// Task scheduling and cron jobs
|
||||||
import * as taskbuffer from '@push.rocks/taskbuffer';
|
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||||
export { taskbuffer };
|
export { taskbuffer };
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import * as plugins from '../plugins.ts';
|
||||||
|
|
||||||
|
const bcryptHashPattern = /^\$2[abxy]\$\d\d\$/;
|
||||||
|
|
||||||
|
export function isBcryptHash(passwordHash: string): boolean {
|
||||||
|
return bcryptHashPattern.test(passwordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function needsPasswordUpgrade(passwordHash: string): boolean {
|
||||||
|
return !isBcryptHash(passwordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hashPassword(password: string): Promise<string> {
|
||||||
|
return await plugins.bcrypt.hash(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyPassword(password: string, passwordHash: string): Promise<boolean> {
|
||||||
|
if (!passwordHash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBcryptHash(passwordHash)) {
|
||||||
|
return await plugins.bcrypt.compare(password, passwordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy compatibility for older databases that stored base64-encoded passwords.
|
||||||
|
return passwordHash === btoa(password);
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user