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