'use strict'; exports = module.exports = { getLocation, clearLocation, startPrepareLocation, // starts the task prepareLocation, // the task to setup dns and cert setupLocation, // initial setup from setup/restore changeLocation, // only on dashboard change (post setup/restore) getConfig, _setLocation: setLocation, }; const apps = require('./apps.js'), appstore = require('./appstore.js'), assert = require('node:assert'), BoxError = require('./boxerror.js'), branding = require('./branding.js'), constants = require('./constants.js'), debug = require('debug')('box:dashboard'), dns = require('./dns.js'), externalLdap = require('./externalldap.js'), eventlog = require('./eventlog.js'), Location = require('./location.js'), mailServer = require('./mailserver.js'), platform = require('./platform.js'), reverseProxy = require('./reverseproxy.js'), safe = require('safetydance'), settings = require('./settings.js'), system = require('./system.js'), tasks = require('./tasks.js'), userDirectory = require('./user-directory.js'); async function getLocation() { const domain = await settings.get(settings.DASHBOARD_DOMAIN_KEY); const subdomain = await settings.get(settings.DASHBOARD_SUBDOMAIN_KEY); return new Location(subdomain, domain, Location.TYPE_DASHBOARD); } async function setLocation(subdomain, domain) { assert.strictEqual(typeof subdomain, 'string'); assert.strictEqual(typeof domain, 'string'); await settings.set(settings.DASHBOARD_SUBDOMAIN_KEY, subdomain); await settings.set(settings.DASHBOARD_DOMAIN_KEY, domain); debug(`setLocation: ${domain || ''}`); } async function clearLocation() { await setLocation('', ''); } async function getConfig() { const ubuntuVersion = await system.getUbuntuVersion(); const kernelVersion = await system.getKernelVersion(); const profileConfig = await userDirectory.getProfileConfig(); const externalLdapConfig = await externalLdap.getConfig(); const { fqdn:adminFqdn, domain:adminDomain } = await getLocation(); // be picky about what we send out here since this is sent for 'normal' users as well return { apiServerOrigin: await appstore.getApiServerOrigin(), webServerOrigin: await appstore.getWebServerOrigin(), consoleServerOrigin: await appstore.getConsoleServerOrigin(), adminDomain, adminFqdn, mailFqdn: (await mailServer.getLocation()).fqdn, version: constants.VERSION, ubuntuVersion, kernelVersion, isDemo: constants.DEMO, cloudronName: await branding.getCloudronName(), footer: await branding.renderFooter(), features: appstore.getFeatures(), profileLocked: profileConfig.lockUserProfiles, mandatory2FA: profileConfig.mandatory2FA, external2FA: externalLdap.supports2FA(externalLdapConfig), ldapGroupsSynced: externalLdapConfig.syncGroups }; } async function startPrepareLocation(domain, auditSource) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof auditSource, 'object'); debug(`prepareLocation: ${domain}`); if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode'); const fqdn = dns.fqdn(constants.DASHBOARD_SUBDOMAIN, domain); for (const app of await apps.list()) { const appName = `${app.label || app.fqdn} (${app.id})`; if (app.fqdn === fqdn) throw new BoxError(BoxError.BAD_STATE, `Dashboard location conflicts with primary location of app '${appName}'`); if (app.secondaryDomains.some(sd => sd.fqdn === fqdn)) throw new BoxError(BoxError.BAD_STATE, `Dashboard location conflicts with secondary location of app '${appName}'`); if (app.redirectDomains.some(rd => rd.fqdn === fqdn)) throw new BoxError(BoxError.BAD_STATE, `Dashboard location conflicts with redirect location of app '${appName}'`); if (app.aliasDomains.some(ad => ad.fqdn === fqdn)) throw new BoxError(BoxError.BAD_STATE, `Dashboard location conflicts with alias location of app '${appName}'`); } const taskId = await tasks.add(tasks.TASK_PREPARE_DASHBOARD_LOCATION, [ constants.DASHBOARD_SUBDOMAIN, domain, auditSource ]); safe(tasks.startTask(taskId, {}), { debug }); // background return taskId; } async function prepareLocation(subdomain, domain, auditSource, progressCallback) { assert.strictEqual(typeof subdomain, 'string'); assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof progressCallback, 'function'); const location = { subdomain, domain }; const fqdn = dns.fqdn(subdomain, domain); progressCallback({ percent: 20, message: `Updating DNS of ${fqdn}` }); await dns.registerLocations([location], { overwriteDns: true }, progressCallback); progressCallback({ percent: 40, message: `Waiting for DNS of ${fqdn}` }); await dns.waitForLocations([location], progressCallback); progressCallback({ percent: 60, message: `Getting certificate of ${fqdn}` }); await reverseProxy.ensureCertificate(location, {}, auditSource); } async function setupLocation(subdomain, domain, auditSource) { assert.strictEqual(typeof subdomain, 'string'); assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof auditSource, 'object'); debug(`setupLocation: ${domain}`); if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode'); await setLocation(subdomain, domain); await platform.onDashboardLocationSet(subdomain, domain); } async function changeLocation(subdomain, domain, auditSource) { assert.strictEqual(typeof subdomain, 'string'); assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof auditSource, 'object'); const oldLocation = await getLocation(); await setupLocation(subdomain, domain, auditSource); debug(`setupLocation: notifying appstore and platform of domain change to ${domain}`); await eventlog.add(eventlog.ACTION_DASHBOARD_DOMAIN_UPDATE, auditSource, { subdomain, domain }); await safe(appstore.updateCloudron({ domain }), { debug }); await platform.onDashboardLocationChanged(auditSource); await safe(reverseProxy.removeDashboardConfig(oldLocation.subdomain, oldLocation.domain), { debug }); }