Files
cloudron-box/src/dockerregistries.js

124 lines
4.3 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
removePrivateFields,
list,
add,
get,
del,
update,
};
const assert = require('node:assert'),
BoxError = require('./boxerror.js'),
constants = require('./constants.js'),
crypto = require('node:crypto'),
database = require('./database.js'),
Docker = require('dockerode'),
eventlog = require('./eventlog.js'),
paths = require('./paths.js'),
safe = require('safetydance'),
tld = require('tldjs');
const REGISTRY_FIELDS = [ 'id', 'provider', 'serverAddress', 'username', 'email', 'password' ].join(',');
function removePrivateFields(registryConfig) {
assert.strictEqual(typeof registryConfig, 'object');
delete registryConfig.password;
return registryConfig;
}
function injectPrivateFields(newConfig, currentConfig) {
if (!Object.hasOwn(newConfig, 'password')) newConfig.password = currentConfig.password;
}
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);
const id = `rc-${crypto.randomUUID()}`;
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) });
}
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) });
}