2025-05-07 14:09:10 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
2025-10-08 20:11:55 +02:00
|
|
|
exports = module.exports = {
|
|
|
|
|
removePrivateFields,
|
|
|
|
|
|
|
|
|
|
list,
|
|
|
|
|
add,
|
|
|
|
|
get,
|
|
|
|
|
del,
|
|
|
|
|
update,
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-14 11:17:38 +05:30
|
|
|
const assert = require('node:assert'),
|
2025-05-07 14:09:10 +02:00
|
|
|
BoxError = require('./boxerror.js'),
|
|
|
|
|
constants = require('./constants.js'),
|
2025-08-14 11:17:38 +05:30
|
|
|
crypto = require('node:crypto'),
|
2025-05-07 14:09:10 +02:00
|
|
|
database = require('./database.js'),
|
|
|
|
|
Docker = require('dockerode'),
|
|
|
|
|
eventlog = require('./eventlog.js'),
|
|
|
|
|
paths = require('./paths.js'),
|
|
|
|
|
safe = require('safetydance'),
|
2025-07-28 12:53:27 +02:00
|
|
|
tld = require('tldjs');
|
2025-05-07 14:09:10 +02:00
|
|
|
|
|
|
|
|
const REGISTRY_FIELDS = [ 'id', 'provider', 'serverAddress', 'username', 'email', 'password' ].join(',');
|
|
|
|
|
|
|
|
|
|
function removePrivateFields(registryConfig) {
|
|
|
|
|
assert.strictEqual(typeof registryConfig, 'object');
|
|
|
|
|
|
2025-10-08 15:55:43 +02:00
|
|
|
delete registryConfig.password;
|
2025-05-07 14:09:10 +02:00
|
|
|
return registryConfig;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 15:55:43 +02:00
|
|
|
function injectPrivateFields(newConfig, currentConfig) {
|
|
|
|
|
if (!Object.hasOwn(newConfig, 'password')) newConfig.password = currentConfig.password;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-07 14:09:10 +02:00
|
|
|
async function get(id) {
|
|
|
|
|
assert.strictEqual(typeof id, 'string');
|
|
|
|
|
|
|
|
|
|
const result = await database.query(`SELECT ${REGISTRY_FIELDS} FROM dockerRegistries WHERE id=?`, [ id ]);
|
|
|
|
|
if (result.length === 0) return null;
|
|
|
|
|
return result[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function list() {
|
|
|
|
|
const result = await database.query(`SELECT ${REGISTRY_FIELDS} FROM dockerRegistries`);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function validateServerAddress(serverAddress) {
|
|
|
|
|
assert.strictEqual(typeof serverAddress, 'string');
|
|
|
|
|
|
|
|
|
|
// workaround https://github.com/oncletom/tld.js/issues/73
|
|
|
|
|
const tmp = serverAddress.replace('_', '-');
|
|
|
|
|
if (!tld.isValid(tmp)) return new BoxError(BoxError.BAD_FIELD, 'Hostname is not a valid domain name');
|
|
|
|
|
if (tmp.length > 253) return new BoxError(BoxError.BAD_FIELD, 'Hostname length exceeds 253 characters');
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function testRegistryConfig(config) {
|
|
|
|
|
assert.strictEqual(typeof config, 'object');
|
|
|
|
|
|
|
|
|
|
if (constants.TEST) return;
|
|
|
|
|
|
|
|
|
|
const connection = new Docker({ socketPath: paths.DOCKER_SOCKET_PATH, timeout: 3000 });
|
|
|
|
|
const [error] = await safe(connection.checkAuth(config)); // this returns a 500 even for auth errors
|
|
|
|
|
if (error) throw new BoxError(BoxError.BAD_FIELD, `Invalid serverAddress: ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function add(registry, auditSource) {
|
|
|
|
|
assert.strictEqual(typeof registry, 'object');
|
|
|
|
|
assert(auditSource && typeof auditSource === 'object');
|
|
|
|
|
|
|
|
|
|
const error = validateServerAddress(registry.serverAddress);
|
|
|
|
|
if (error) throw error;
|
|
|
|
|
|
|
|
|
|
await testRegistryConfig(registry);
|
|
|
|
|
|
2025-07-28 12:53:27 +02:00
|
|
|
const id = `rc-${crypto.randomUUID()}`;
|
2025-05-07 14:09:10 +02:00
|
|
|
await database.query('INSERT INTO dockerRegistries (id, provider, serverAddress, username, email, password) VALUES (?, ?, ?, ?, ?, ?)',
|
|
|
|
|
[ id , registry.provider, registry.serverAddress, registry.username || null, registry.email || null, registry.password || null ]);
|
|
|
|
|
|
|
|
|
|
await eventlog.add(eventlog.ACTION_REGISTRY_ADD, auditSource, { registry: removePrivateFields(registry) });
|
|
|
|
|
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function update(oldConfig, newConfig, auditSource) {
|
|
|
|
|
assert.strictEqual(typeof oldConfig, 'object');
|
|
|
|
|
assert.strictEqual(typeof newConfig, 'object');
|
|
|
|
|
assert(auditSource && typeof auditSource === 'object');
|
|
|
|
|
|
|
|
|
|
const error = validateServerAddress(newConfig.serverAddress);
|
|
|
|
|
if (error) throw error;
|
|
|
|
|
|
|
|
|
|
injectPrivateFields(newConfig, oldConfig);
|
|
|
|
|
|
|
|
|
|
await testRegistryConfig(newConfig);
|
|
|
|
|
|
|
|
|
|
const args = [], fields = [];
|
|
|
|
|
for (const k in newConfig) {
|
|
|
|
|
fields.push(k + ' = ?');
|
|
|
|
|
args.push(newConfig[k]);
|
|
|
|
|
}
|
|
|
|
|
args.push(oldConfig.id);
|
|
|
|
|
|
|
|
|
|
const result = await database.query('UPDATE dockerRegistries SET ' + fields.join(', ') + ' WHERE id=?', args);
|
|
|
|
|
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Registry not found');
|
|
|
|
|
|
2025-09-10 17:45:52 +02:00
|
|
|
await eventlog.add(eventlog.ACTION_REGISTRY_UPDATE, auditSource, { oldRegistry: removePrivateFields(oldConfig), newRegistry: removePrivateFields(newConfig) });
|
2025-05-07 14:09:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function del(registry, auditSource) {
|
|
|
|
|
assert.strictEqual(typeof registry, 'object');
|
|
|
|
|
assert(auditSource && typeof auditSource === 'object');
|
|
|
|
|
|
|
|
|
|
const result = await database.query(`DELETE FROM dockerRegistries WHERE id=?`, [ registry.id ]);
|
|
|
|
|
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Registry not found');
|
|
|
|
|
|
|
|
|
|
await eventlog.add(eventlog.ACTION_REGISTRY_DEL, auditSource, { registry: removePrivateFields(registry) });
|
|
|
|
|
}
|
2025-10-08 15:55:43 +02:00
|
|
|
|