536 lines
46 KiB
JavaScript
536 lines
46 KiB
JavaScript
import assert from 'node:assert';
|
|
import AuditSource from './auditsource.js';
|
|
import constants from './constants.js';
|
|
import logger from './logger.js';
|
|
import eventlog from './eventlog.js';
|
|
import express from 'express';
|
|
import http from 'node:http';
|
|
import { HttpError } from '@cloudron/connect-lastmile';
|
|
import middleware from './middleware/index.js';
|
|
import platform from './platform.js';
|
|
import routes from './routes/index.js';
|
|
import safe from 'safetydance';
|
|
import users from './users.js';
|
|
import util from 'node:util';
|
|
import { WebSocketServer } from 'ws';
|
|
|
|
const { log } = logger('server');
|
|
|
|
|
|
let gHttpServer = null;
|
|
|
|
function notFoundHandler(req, res, next) {
|
|
const cleanUrl = req.url.replace(/(access_token=)[^&]+/, '$1' + '<redacted>');
|
|
log(`no such route: ${req.method} ${cleanUrl}`);
|
|
return next(new HttpError(404, 'No such route'));
|
|
}
|
|
|
|
async function initializeExpressSync() {
|
|
const app = express();
|
|
// disable slowloris prevention: https://github.com/nodejs/node/issues/47421 . duplicate headers are discarded for the standard headers (https://nodejs.org/api/http.html#messageheaders)
|
|
const httpServer = http.createServer({ headersTimeout: 0, requestTimeout: 0, joinDuplicateHeaders: false }, app); // see also nginx client_header_timeout (30s)
|
|
|
|
const wsServer = new WebSocketServer({ noServer: true }); // in noServer mode, we have to handle 'upgrade' and call handleUpgrade
|
|
|
|
const REQUEST_TIMEOUT = 60000; // timeout for all requests (see also setTimeout on the httpServer)
|
|
|
|
// NOTE: routes that use multi-part have to be whitelisted in the reverse proxy
|
|
const FILE_SIZE_LIMIT = '256mb', // max file size that can be uploaded (see also client_max_body_size in nginx)
|
|
FILE_TIMEOUT = 60 * 1000; // increased timeout for file uploads (1 min)
|
|
|
|
const QUERY_LIMIT = '2mb', // max size for json queries (see also client_max_body_size in nginx)
|
|
FIELD_LIMIT = 2 * 1024 * 1024; // max fields that can appear in multipart
|
|
|
|
const multipart = middleware.multipart({ maxFieldsSize: FIELD_LIMIT, limit: FILE_SIZE_LIMIT, timeout: FILE_TIMEOUT }, true); // forces multipart content-type
|
|
const json = middleware.json({ strict: true, limit: QUERY_LIMIT }, true); // forces json content-type
|
|
const jsonOrMultipart = [
|
|
middleware.json({ strict: true, limit: QUERY_LIMIT }, false),
|
|
middleware.multipart({ maxFieldsSize: FIELD_LIMIT, limit: FILE_SIZE_LIMIT, timeout: FILE_TIMEOUT }, false)
|
|
];
|
|
|
|
app.set('json spaces', 2); // pretty json
|
|
app.enable('trust proxy'); // trust the X-Forwarded-* headers
|
|
app.set('query parser', 'simple'); // uses the built-in querystring module for query parsing. req.query always has strings or array of strings
|
|
|
|
const router = new express.Router();
|
|
router.del = router.delete; // amend router.del for readability further on
|
|
|
|
app
|
|
// the timeout middleware will respond with a 503. the request itself cannot be 'aborted' and will continue
|
|
// search for req.clearTimeout in route handlers to see places where this timeout is reset
|
|
.use(middleware.timeout(REQUEST_TIMEOUT, { respond: true }))
|
|
.use((req, res , next) => {
|
|
// we store our route resources, like app,volumes,... in req.resources. Those are added in the load() routes
|
|
req.resources = {};
|
|
next();
|
|
})
|
|
.use(router)
|
|
.use(notFoundHandler)
|
|
.use(middleware.lastMile());
|
|
|
|
// authentication
|
|
const password = routes.accesscontrol.passwordAuth;
|
|
const token = routes.accesscontrol.tokenAuth;
|
|
|
|
// authorization
|
|
const authorizeOwner = routes.accesscontrol.authorize(users.ROLE_OWNER);
|
|
const authorizeAdmin = routes.accesscontrol.authorize(users.ROLE_ADMIN);
|
|
const authorizeOperator = routes.accesscontrol.authorizeOperator;
|
|
const authorizeUserManager = routes.accesscontrol.authorize(users.ROLE_USER_MANAGER);
|
|
const authorizeMailManager = routes.accesscontrol.authorize(users.ROLE_MAIL_MANAGER);
|
|
const authorizeUser = routes.accesscontrol.authorize(users.ROLE_USER);
|
|
|
|
// boostrapping/provision routes
|
|
const verifyUnprovisioned = routes.provision.verifyUnprovisioned;
|
|
router.post('/api/v1/provision/setup', json, verifyUnprovisioned, routes.provision.providerTokenAuth, routes.provision.setup);
|
|
router.post('/api/v1/provision/restore', json, verifyUnprovisioned, routes.provision.restore);
|
|
router.post('/api/v1/provision/activate', json, verifyUnprovisioned, routes.provision.activate);
|
|
router.get ('/api/v1/provision/block_devices', verifyUnprovisioned, routes.provision.getBlockDevices);
|
|
router.post('/api/v1/provision/detect_ip', verifyUnprovisioned, routes.provision.detectIP);
|
|
router.get ('/api/v1/provision/status', routes.provision.getStatus);
|
|
|
|
// auth routes
|
|
router.post('/api/v1/auth/login', json, password, routes.auth.login);
|
|
router.get ('/api/v1/auth/logout', token, routes.auth.logout); // this will invalidate the token if any and redirect to / always
|
|
router.post('/api/v1/auth/password_reset_request', json, routes.auth.passwordResetRequest);
|
|
router.post('/api/v1/auth/password_reset', json, routes.auth.passwordReset);
|
|
router.post('/api/v1/auth/setup_account', json, routes.auth.setupAccount);
|
|
|
|
router.get ('/api/v1/cloudron/status', routes.cloudron.getStatus); // healthcheck
|
|
router.get ('/api/v1/cloudron/avatar', routes.branding.getCloudronAvatar); // public route for dashboard, email
|
|
router.get ('/api/v1/cloudron/background', routes.branding.getCloudronBackground); // public route for login
|
|
|
|
// cloudron routes
|
|
router.get ('/api/v1/cloudron/languages', routes.cloudron.listLanguages);
|
|
router.get ('/api/v1/cloudron/language', token, authorizeAdmin, routes.cloudron.getLanguage);
|
|
router.post('/api/v1/cloudron/language', json, token, authorizeAdmin, routes.cloudron.setLanguage);
|
|
router.get ('/api/v1/cloudron/time_zone', token, authorizeAdmin, routes.cloudron.getTimeZone);
|
|
router.post('/api/v1/cloudron/time_zone', json, token, authorizeAdmin, routes.cloudron.setTimeZone);
|
|
|
|
// config route for dashboard that any auth user (not just admin) can access
|
|
router.get ('/api/v1/dashboard/config', token, authorizeUser, routes.dashboard.getConfig);
|
|
router.post('/api/v1/dashboard/prepare_location', json, token, authorizeAdmin, routes.dashboard.startPrepareLocation);
|
|
router.post('/api/v1/dashboard/location', json, token, authorizeAdmin, routes.dashboard.changeLocation);
|
|
|
|
// system (vm/server)
|
|
router.get ('/api/v1/system/info', token, authorizeAdmin, routes.system.getInfo); // vendor, product name etc
|
|
router.post('/api/v1/system/reboot', json, token, authorizeAdmin, routes.system.reboot);
|
|
router.get ('/api/v1/system/metrics', token, authorizeAdmin, routes.system.getMetrics);
|
|
router.get ('/api/v1/system/metricstream', token, authorizeAdmin, routes.system.getMetricStream);
|
|
router.get ('/api/v1/system/block_devices', token, authorizeAdmin, routes.system.getBlockDevices);
|
|
router.get ('/api/v1/system/filesystems', token, authorizeAdmin, routes.system.getFilesystems);
|
|
router.get ('/api/v1/system/filesystem_usage', token, authorizeAdmin, routes.system.getFilesystemUsage);
|
|
router.get ('/api/v1/system/logs/:unit', token, authorizeAdmin, routes.system.getLogs);
|
|
router.get ('/api/v1/system/logstream/:unit', token, authorizeAdmin, routes.system.getLogStream);
|
|
// app operators require cpu and memory info for the Resources UI
|
|
router.get ('/api/v1/system/cpus', token, authorizeUser, routes.system.getCpus);
|
|
router.get ('/api/v1/system/memory', token, authorizeUser, routes.system.getMemory);
|
|
|
|
// eventlog
|
|
router.get ('/api/v1/eventlog', token, authorizeAdmin, routes.eventlog.list);
|
|
router.get ('/api/v1/eventlog/:eventId', token, authorizeAdmin, routes.eventlog.get);
|
|
|
|
// updater
|
|
router.get ('/api/v1/updater/box_update', token, authorizeUser, routes.updater.getBoxUpdate); // allowed for normal users to make it work for app operators
|
|
router.post('/api/v1/updater/box_update', json, token, authorizeAdmin, routes.updater.updateBox);
|
|
router.post('/api/v1/updater/check_box_update', json, token, authorizeAdmin, routes.updater.checkBoxUpdate);
|
|
router.get ('/api/v1/updater/autoupdate_pattern', token, authorizeAdmin, routes.updater.getAutoupdatePattern);
|
|
router.post('/api/v1/updater/autoupdate_pattern', json, token, authorizeAdmin, routes.updater.setAutoupdatePattern);
|
|
|
|
// task routes
|
|
router.get ('/api/v1/tasks', token, authorizeAdmin, routes.tasks.list);
|
|
router.get ('/api/v1/tasks/:taskId', token, authorizeAdmin, routes.tasks.load, routes.tasks.get);
|
|
router.get ('/api/v1/tasks/:taskId/logs', token, authorizeAdmin, routes.tasks.load, routes.tasks.getLogs);
|
|
router.get ('/api/v1/tasks/:taskId/logstream', token, authorizeAdmin, routes.tasks.load, routes.tasks.getLogStream);
|
|
router.post('/api/v1/tasks/:taskId/stop', json, token, authorizeAdmin, routes.tasks.load, routes.tasks.stopTask);
|
|
|
|
// notification routes (these are server level)
|
|
router.get ('/api/v1/notifications', token, authorizeAdmin, routes.notifications.list);
|
|
router.get ('/api/v1/notifications/:notificationId', token, authorizeAdmin, routes.notifications.load, routes.notifications.get);
|
|
router.post('/api/v1/notifications/:notificationId', json, token, authorizeAdmin, routes.notifications.load, routes.notifications.update);
|
|
|
|
// backup routes
|
|
router.get ('/api/v1/backups', token, authorizeAdmin, routes.backups.list);
|
|
router.get ('/api/v1/backups/:id', token, authorizeAdmin, routes.backups.load, routes.backups.get);
|
|
router.post('/api/v1/backups/:id', json, token, authorizeAdmin, routes.backups.load, routes.backups.update);
|
|
router.post('/api/v1/backups/:id/start_integrity_check', json, token, authorizeAdmin, routes.backups.load, routes.backups.startIntegrityCheck);
|
|
router.post('/api/v1/backups/:id/stop_integrity_check', json, token, authorizeAdmin, routes.backups.load, routes.backups.stopIntegrityCheck);
|
|
|
|
// backup site (destination) routes
|
|
router.get ('/api/v1/backup_sites/', token, authorizeAdmin, routes.backupSites.list);
|
|
router.get ('/api/v1/backup_sites/:id', token, authorizeAdmin, routes.backupSites.load, routes.backupSites.get);
|
|
router.post('/api/v1/backup_sites', json, token, authorizeOwner, routes.backupSites.add);
|
|
router.del ('/api/v1/backup_sites/:id', token, authorizeOwner, routes.backupSites.load, routes.backupSites.del);
|
|
router.get ('/api/v1/backup_sites/:id/status', token, authorizeAdmin, routes.backupSites.load, routes.backupSites.getStatus);
|
|
router.get ('/api/v1/backup_sites/:id/backups', token, authorizeAdmin, routes.backupSites.load, routes.backupSites.listBackups);
|
|
router.post('/api/v1/backup_sites/:id/create_backup', token, authorizeAdmin, routes.backupSites.load, routes.backupSites.createBackup);
|
|
router.post('/api/v1/backup_sites/:id/cleanup', json, token, authorizeAdmin, routes.backupSites.load, routes.backupSites.cleanup);
|
|
router.post('/api/v1/backup_sites/:id/remount', json, token, authorizeAdmin, routes.backupSites.load, routes.backupSites.remount);
|
|
router.post('/api/v1/backup_sites/:id/configure/name', json, token, authorizeOwner, routes.backupSites.load, routes.backupSites.setName);
|
|
router.post('/api/v1/backup_sites/:id/configure/config', json, token, authorizeOwner, routes.backupSites.load, routes.backupSites.setConfig);
|
|
router.post('/api/v1/backup_sites/:id/configure/limits', json, token, authorizeOwner, routes.backupSites.load, routes.backupSites.setLimits);
|
|
router.post('/api/v1/backup_sites/:id/configure/schedule', json, token, authorizeOwner, routes.backupSites.load, routes.backupSites.setSchedule);
|
|
router.post('/api/v1/backup_sites/:id/configure/retention', json, token, authorizeOwner, routes.backupSites.load, routes.backupSites.setRetention);
|
|
router.post('/api/v1/backup_sites/:id/configure/encryption', json, token, authorizeOwner, routes.backupSites.load, routes.backupSites.setEncryption);
|
|
router.post('/api/v1/backup_sites/:id/configure/contents', json, token, authorizeOwner, routes.backupSites.load, routes.backupSites.setContents);
|
|
router.post('/api/v1/backup_sites/:id/configure/enable_for_updates', json, token, authorizeOwner, routes.backupSites.load, routes.backupSites.setEnabledForUpdates);
|
|
|
|
// app archive routes
|
|
router.get ('/api/v1/archives', token, authorizeAdmin, routes.archives.list);
|
|
router.get ('/api/v1/archives/:id', token, authorizeAdmin, routes.archives.load, routes.archives.get);
|
|
router.del ('/api/v1/archives/:id', token, authorizeAdmin, routes.archives.load, routes.archives.del);
|
|
router.post('/api/v1/archives/:id/unarchive', json, token, authorizeAdmin, routes.archives.load, routes.archives.unarchive);
|
|
router.get ('/api/v1/archives/:id/icon', routes.archives.load, routes.archives.getIcon);
|
|
|
|
// working off the user behind the provided token
|
|
router.get ('/api/v1/profile', token, authorizeUser, routes.profile.get);
|
|
router.post('/api/v1/profile/display_name', json, token, authorizeUser, routes.profile.canEditProfile, routes.profile.setDisplayName);
|
|
router.post('/api/v1/profile/email', json, token, authorizeUser, routes.profile.canEditProfile, routes.users.verifyPassword, routes.profile.setEmail);
|
|
router.post('/api/v1/profile/fallback_email', json, token, authorizeUser, routes.profile.canEditProfile, routes.users.verifyPassword, routes.profile.setFallbackEmail);
|
|
router.post('/api/v1/profile/language', json, token, authorizeUser, routes.profile.setLanguage);
|
|
router.get ('/api/v1/profile/avatar/:identifier', routes.profile.getAvatarById); // this is not scoped so it can used directly in img tag
|
|
router.post('/api/v1/profile/avatar', token, authorizeUser, routes.profile.canEditProfile, multipart, routes.profile.setAvatar);
|
|
router.del ('/api/v1/profile/avatar', token, authorizeUser, routes.profile.canEditProfile, routes.profile.unsetAvatar);
|
|
router.get ('/api/v1/profile/background_image', token, authorizeUser, routes.profile.getBackgroundImage);
|
|
router.post('/api/v1/profile/background_image', token, authorizeUser, multipart, routes.profile.setBackgroundImage); // backgroundImage is not exposed in LDAP. so it's personal and not locked
|
|
router.del ('/api/v1/profile/background_image', token, authorizeUser, routes.profile.unsetBackgroundImage); // backgroundImage is not exposed in LDAP. so it's personal and not locked
|
|
router.post('/api/v1/profile/password', json, token, authorizeUser, routes.users.verifyPassword, routes.profile.setPassword);
|
|
router.post('/api/v1/profile/totp_secret', json, token, authorizeUser, routes.profile.setTwoFactorAuthenticationSecret);
|
|
router.post('/api/v1/profile/totp_enable', json, token, authorizeUser, routes.profile.enableTwoFactorAuthentication);
|
|
router.post('/api/v1/profile/totp_disable', json, token, authorizeUser, routes.users.verifyPassword, routes.profile.disableTwoFactorAuthentication);
|
|
router.post('/api/v1/profile/notification_config', json, token, authorizeUser, routes.profile.setNotificationConfig); // non-admins cannot get notifications anyway
|
|
router.del ('/api/v1/profile/sessions', token, authorizeUser, routes.profile.destroyUserSession);
|
|
|
|
// passkey routes (singular - only one passkey per user)
|
|
router.get ('/api/v1/profile/passkey', token, authorizeUser, routes.profile.getPasskey);
|
|
router.post('/api/v1/profile/passkey/register/options', json, token, authorizeUser, routes.profile.getPasskeyRegistrationOptions);
|
|
router.post('/api/v1/profile/passkey/register', json, token, authorizeUser, routes.profile.registerPasskey);
|
|
router.post('/api/v1/profile/passkey/disable', json, token, authorizeUser, routes.users.verifyPassword, routes.profile.deletePasskey);
|
|
|
|
// app password routes
|
|
router.get ('/api/v1/app_passwords', token, authorizeUser, routes.appPasswords.list);
|
|
router.post('/api/v1/app_passwords', json, token, authorizeUser, routes.appPasswords.add);
|
|
router.get ('/api/v1/app_passwords/:id', token, authorizeUser, routes.appPasswords.get);
|
|
router.del ('/api/v1/app_passwords/:id', token, authorizeUser, routes.appPasswords.del);
|
|
|
|
// access tokens
|
|
router.get ('/api/v1/tokens', token, authorizeUser, routes.tokens.list);
|
|
router.post('/api/v1/tokens', json, token, authorizeUser, routes.tokens.add);
|
|
router.get ('/api/v1/tokens/:id', token, authorizeUser, routes.tokens.verifyOwnership, routes.tokens.get);
|
|
router.del ('/api/v1/tokens/:id', token, authorizeUser, routes.tokens.verifyOwnership, routes.tokens.del);
|
|
|
|
// user routes
|
|
router.get ('/api/v1/users', token, authorizeUserManager, routes.users.list);
|
|
router.post('/api/v1/users', json, token, authorizeUserManager, routes.users.add);
|
|
router.get ('/api/v1/users/:userId', token, authorizeUserManager, routes.users.load, routes.users.get); // this is manage scope because it returns non-restricted fields
|
|
router.del ('/api/v1/users/:userId', token, authorizeUserManager, routes.users.load, routes.users.del);
|
|
router.put ('/api/v1/users/:userId/role', json, token, authorizeUserManager, routes.users.load, routes.users.setRole);
|
|
router.put ('/api/v1/users/:userId/active', json, token, authorizeUserManager, routes.users.load, routes.users.setActive);
|
|
router.post('/api/v1/users/:userId/profile', json, token, authorizeUserManager, routes.users.load, routes.users.updateProfile);
|
|
router.get ('/api/v1/users/:userId/avatar', token, authorizeUserManager, routes.users.load, routes.users.getAvatar);
|
|
router.post('/api/v1/users/:userId/avatar', token, authorizeUserManager, multipart, routes.users.load, routes.users.setAvatar);
|
|
router.del ('/api/v1/users/:userId/avatar', token, authorizeUserManager, routes.users.load, routes.users.unsetAvatar);
|
|
router.post('/api/v1/users/:userId/password', json, token, authorizeUserManager, routes.users.load, routes.users.setPassword);
|
|
router.post('/api/v1/users/:userId/ghost', json, token, authorizeAdmin, routes.users.load, routes.users.setGhost);
|
|
router.put ('/api/v1/users/:userId/groups', json, token, authorizeUserManager, routes.users.load, routes.users.setLocalGroups);
|
|
router.get ('/api/v1/users/:userId/password_reset_link', token, authorizeUserManager, routes.users.load, routes.users.getPasswordResetLink);
|
|
router.post('/api/v1/users/:userId/send_password_reset_email', json, token, authorizeUserManager, routes.users.load, routes.users.sendPasswordResetEmail);
|
|
router.get ('/api/v1/users/:userId/invite_link', token, authorizeUserManager, routes.users.load, routes.users.getInviteLink);
|
|
router.post('/api/v1/users/:userId/send_invite_email', json, token, authorizeUserManager, routes.users.load, routes.users.sendInviteEmail);
|
|
router.post('/api/v1/users/:userId/totp_disable', json, token, authorizeUserManager, routes.users.load, routes.users.disableTwoFactorAuthentication);
|
|
|
|
// Group management
|
|
router.get ('/api/v1/groups', token, authorizeUserManager, routes.groups.list);
|
|
router.post('/api/v1/groups', json, token, authorizeUserManager, routes.groups.add);
|
|
router.get ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.load, routes.groups.get);
|
|
router.put ('/api/v1/groups/:groupId/members', json, token, authorizeUserManager, routes.groups.load, routes.groups.setMembers);
|
|
router.put ('/api/v1/groups/:groupId/apps', json, token, authorizeUserManager, routes.groups.load, routes.groups.setAllowedApps);
|
|
router.put ('/api/v1/groups/:groupId/name', json, token, authorizeUserManager, routes.groups.load, routes.groups.setName);
|
|
router.del ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.load, routes.groups.del);
|
|
|
|
// User directory
|
|
router.get ('/api/v1/user_directory/profile_config', token, authorizeAdmin, routes.userDirectory.getProfileConfig);
|
|
router.post('/api/v1/user_directory/profile_config', json, token, authorizeAdmin, routes.userDirectory.setProfileConfig);
|
|
|
|
// External LDAP
|
|
router.get ('/api/v1/external_ldap/config', token, authorizeAdmin, routes.externalLdap.getConfig);
|
|
router.post('/api/v1/external_ldap/config', json, token, authorizeAdmin, routes.externalLdap.setConfig);
|
|
router.post('/api/v1/external_ldap/sync', json, token, authorizeAdmin, routes.externalLdap.sync);
|
|
|
|
// Directory Server
|
|
router.get ('/api/v1/directory_server/config', token, authorizeAdmin, routes.directoryServer.getConfig);
|
|
router.post('/api/v1/directory_server/config', json, token, authorizeAdmin, routes.directoryServer.setConfig);
|
|
|
|
// appstore and subscription routes
|
|
router.post('/api/v1/appstore/unlink_account', json, token, authorizeOwner, routes.appstore.unlinkAccount);
|
|
router.get ('/api/v1/appstore/subscription', token, authorizeUser, routes.appstore.getSubscription); // for all users
|
|
router.get ('/api/v1/appstore/apps', token, authorizeAdmin, routes.appstore.getApps);
|
|
router.get ('/api/v1/appstore/apps/:appstoreId', token, authorizeAdmin, routes.appstore.getApp);
|
|
router.get ('/api/v1/appstore/apps/:appstoreId/versions/:versionId', token, authorizeAdmin, routes.appstore.getAppVersion);
|
|
|
|
// community app routes
|
|
router.get ('/api/v1/community/app', token, authorizeAdmin, routes.community.getAppVersion);
|
|
|
|
// app routes
|
|
router.post('/api/v1/apps', jsonOrMultipart, token, authorizeAdmin, routes.apps.install);
|
|
router.get ('/api/v1/apps', token, authorizeUser, routes.apps.listByUser);
|
|
router.get ('/api/v1/apps/:id', token, routes.apps.load, authorizeOperator, routes.apps.getApp);
|
|
router.get ('/api/v1/apps/:id/icon', routes.apps.load, routes.apps.getAppIcon);
|
|
router.post('/api/v1/apps/:id/archive', json, token, routes.apps.load, authorizeAdmin, routes.apps.archive);
|
|
router.post('/api/v1/apps/:id/uninstall', json, token, routes.apps.load, authorizeAdmin, routes.apps.uninstall);
|
|
router.post('/api/v1/apps/:id/configure/access_restriction', json, token, routes.apps.load, authorizeAdmin, routes.apps.setAccessRestriction);
|
|
router.post('/api/v1/apps/:id/configure/operators', json, token, routes.apps.load, authorizeAdmin, routes.apps.setOperators);
|
|
router.post('/api/v1/apps/:id/configure/label', json, token, routes.apps.load, authorizeOperator, routes.apps.setLabel);
|
|
router.post('/api/v1/apps/:id/configure/tags', json, token, routes.apps.load, authorizeOperator, routes.apps.setTags);
|
|
router.post('/api/v1/apps/:id/configure/icon', json, token, routes.apps.load, authorizeOperator, routes.apps.setIcon);
|
|
router.post('/api/v1/apps/:id/configure/notes', json, token, routes.apps.load, authorizeOperator, routes.apps.setNotes);
|
|
router.post('/api/v1/apps/:id/configure/memory_limit', json, token, routes.apps.load, authorizeOperator, routes.apps.setMemoryLimit);
|
|
router.post('/api/v1/apps/:id/configure/cpu_quota', json, token, routes.apps.load, authorizeOperator, routes.apps.setCpuQuota);
|
|
router.post('/api/v1/apps/:id/configure/automatic_backup', json, token, routes.apps.load, authorizeOperator, routes.apps.setAutomaticBackup);
|
|
router.post('/api/v1/apps/:id/configure/automatic_update', json, token, routes.apps.load, authorizeOperator, routes.apps.setAutomaticUpdate);
|
|
router.post('/api/v1/apps/:id/configure/reverse_proxy', json, token, routes.apps.load, authorizeOperator, routes.apps.setReverseProxyConfig);
|
|
router.post('/api/v1/apps/:id/configure/cert', json, token, routes.apps.load, authorizeOperator, routes.apps.setCertificate);
|
|
router.post('/api/v1/apps/:id/configure/debug_mode', json, token, routes.apps.load, authorizeOperator, routes.apps.setDebugMode);
|
|
router.post('/api/v1/apps/:id/configure/mailbox', json, token, routes.apps.load, authorizeAdmin, routes.apps.setMailbox);
|
|
router.post('/api/v1/apps/:id/configure/inbox', json, token, routes.apps.load, authorizeAdmin, routes.apps.setInbox);
|
|
router.post('/api/v1/apps/:id/configure/turn', json, token, routes.apps.load, authorizeAdmin, routes.apps.setTurn);
|
|
router.post('/api/v1/apps/:id/configure/redis', json, token, routes.apps.load, authorizeAdmin, routes.apps.setRedis);
|
|
router.post('/api/v1/apps/:id/configure/env', json, token, routes.apps.load, authorizeOperator, routes.apps.setEnvironment);
|
|
router.post('/api/v1/apps/:id/configure/storage', json, token, routes.apps.load, authorizeAdmin, routes.apps.setStorage);
|
|
router.post('/api/v1/apps/:id/configure/location', json, token, routes.apps.load, authorizeAdmin, routes.apps.setLocation);
|
|
router.post('/api/v1/apps/:id/configure/mounts', json, token, routes.apps.load, authorizeAdmin, routes.apps.setMounts);
|
|
router.post('/api/v1/apps/:id/configure/devices', json, token, routes.apps.load, authorizeAdmin, routes.apps.setDevices);
|
|
router.post('/api/v1/apps/:id/configure/crontab', json, token, routes.apps.load, authorizeOperator, routes.apps.setCrontab);
|
|
router.post('/api/v1/apps/:id/configure/upstream_uri', json, token, routes.apps.load, authorizeOperator, routes.apps.setUpstreamUri);
|
|
router.post('/api/v1/apps/:id/repair', json, token, routes.apps.load, authorizeOperator, routes.apps.repair);
|
|
router.post('/api/v1/apps/:id/check_update', json, token, routes.apps.load, authorizeOperator, routes.apps.checkUpdate);
|
|
router.post('/api/v1/apps/:id/update', jsonOrMultipart, token, routes.apps.load, authorizeOperator, routes.apps.update);
|
|
router.post('/api/v1/apps/:id/restore', json, token, routes.apps.load, authorizeOperator, routes.apps.restore);
|
|
router.post('/api/v1/apps/:id/import', json, token, routes.apps.load, authorizeOperator, routes.apps.importApp);
|
|
router.post('/api/v1/apps/:id/export', json, token, routes.apps.load, authorizeOperator, routes.apps.exportApp);
|
|
router.post('/api/v1/apps/:id/backup', json, token, routes.apps.load, authorizeOperator, routes.apps.backup);
|
|
router.get ('/api/v1/apps/:id/backups', token, routes.apps.load, authorizeOperator, routes.apps.listBackups);
|
|
router.get ('/api/v1/apps/:id/backup_sites', token, routes.apps.load, authorizeOperator, routes.apps.listBackupSites);
|
|
router.post('/api/v1/apps/:id/backups/:backupId', json, token, routes.apps.load, authorizeOperator, routes.apps.updateBackup);
|
|
router.get ('/api/v1/apps/:id/backups/:backupId/download', token, routes.apps.load, authorizeOperator, routes.apps.downloadBackup);
|
|
router.put ('/api/v1/apps/:id/checklist/:key', json, token, routes.apps.load, authorizeOperator, routes.apps.setChecklistItem);
|
|
router.post('/api/v1/apps/:id/start', json, token, routes.apps.load, authorizeOperator, routes.apps.start);
|
|
router.post('/api/v1/apps/:id/stop', json, token, routes.apps.load, authorizeOperator, routes.apps.stop);
|
|
router.post('/api/v1/apps/:id/restart', json, token, routes.apps.load, authorizeOperator, routes.apps.restart);
|
|
router.get ('/api/v1/apps/:id/logstream', token, routes.apps.load, authorizeOperator, routes.apps.getLogStream);
|
|
router.get ('/api/v1/apps/:id/logs', token, routes.apps.load, authorizeOperator, routes.apps.getLogs);
|
|
router.get ('/api/v1/apps/:id/eventlog', token, routes.apps.load, authorizeOperator, routes.apps.listEventlog);
|
|
router.get ('/api/v1/apps/:id/task', token, routes.apps.load, authorizeOperator, routes.apps.getTask);
|
|
router.get ('/api/v1/apps/:id/metrics', token, routes.apps.load, authorizeOperator, routes.apps.getMetrics);
|
|
router.get ('/api/v1/apps/:id/metricstream', token, routes.apps.load, authorizeOperator, routes.apps.getMetricStream);
|
|
router.post('/api/v1/apps/:id/clone', json, token, routes.apps.load, authorizeAdmin, routes.apps.clone);
|
|
router.use ('/api/v1/apps/:id/files/*filepath', token, routes.apps.load, authorizeOperator, routes.filemanager.proxy('app'));
|
|
router.post('/api/v1/apps/:id/exec', json, token, routes.apps.load, authorizeOperator, routes.apps.createExec);
|
|
router.get ('/api/v1/apps/:id/exec/:execId/start', token, routes.apps.load, authorizeOperator, routes.apps.startExec);
|
|
router.get ('/api/v1/apps/:id/exec/:execId', token, routes.apps.load, authorizeOperator, routes.apps.getExec);
|
|
// these two routes are wrappers on exec. It allows upload/download to anywhere in filesystem unlike the files route which is only /app/data
|
|
router.get ('/api/v1/apps/:id/download', token, routes.apps.load, authorizeOperator, routes.apps.downloadFile);
|
|
router.post('/api/v1/apps/:id/upload', token, multipart, routes.apps.load, authorizeOperator, routes.apps.uploadFile);
|
|
|
|
// websocket cannot do bearer authentication
|
|
router.get ('/api/v1/apps/:id/exec/:execId/startws', token, routes.apps.load, authorizeOperator, routes.apps.startExecWebSocket);
|
|
|
|
// app links in dashboard
|
|
router.get ('/api/v1/applinks', token, authorizeUser, routes.applinks.listByUser);
|
|
router.post('/api/v1/applinks', json, token, authorizeAdmin, routes.applinks.add);
|
|
router.get ('/api/v1/applinks/:id', token, routes.applinks.load, authorizeAdmin, routes.applinks.get);
|
|
router.post('/api/v1/applinks/:id', json, token, routes.applinks.load, authorizeAdmin, routes.applinks.update);
|
|
router.del ('/api/v1/applinks/:id', token, routes.applinks.load, authorizeAdmin, routes.applinks.del);
|
|
router.get ('/api/v1/applinks/:id/icon', token, routes.applinks.load, authorizeUser, routes.applinks.getIcon);
|
|
|
|
// branding routes
|
|
router.get ('/api/v1/branding/cloudron_name', token, authorizeAdmin, routes.branding.getCloudronName);
|
|
router.post('/api/v1/branding/cloudron_name', json, token, authorizeAdmin, routes.branding.setCloudronName);
|
|
router.get ('/api/v1/branding/cloudron_avatar', token, authorizeAdmin, routes.branding.getCloudronAvatar);
|
|
router.post('/api/v1/branding/cloudron_avatar', token, authorizeAdmin, multipart, routes.branding.setCloudronAvatar);
|
|
router.get ('/api/v1/branding/cloudron_background', token, authorizeAdmin, routes.branding.getCloudronBackground);
|
|
router.post('/api/v1/branding/cloudron_background', token, authorizeAdmin, multipart, routes.branding.setCloudronBackground);
|
|
router.del ('/api/v1/branding/cloudron_background', token, authorizeAdmin, routes.branding.unsetCloudronBackground);
|
|
router.get ('/api/v1/branding/footer', token, authorizeAdmin, routes.branding.getFooter);
|
|
router.post('/api/v1/branding/footer', json, token, authorizeAdmin, routes.branding.setFooter);
|
|
|
|
// reverseproxy routes
|
|
router.post('/api/v1/reverseproxy/renew_certs', json, token, authorizeAdmin, routes.reverseProxy.renewCerts);
|
|
router.get ('/api/v1/reverseproxy/trusted_ips', token, authorizeAdmin, routes.reverseProxy.getTrustedIps);
|
|
router.post('/api/v1/reverseproxy/trusted_ips', json, token, authorizeAdmin, routes.reverseProxy.setTrustedIps);
|
|
|
|
// network routes
|
|
router.get ('/api/v1/network/blocklist', token, authorizeAdmin, routes.network.getBlocklist);
|
|
router.post('/api/v1/network/blocklist', json, token, authorizeAdmin, routes.network.setBlocklist);
|
|
router.get ('/api/v1/network/dynamic_dns', token, authorizeAdmin, routes.network.getDynamicDns);
|
|
router.post('/api/v1/network/dynamic_dns', json, token, authorizeAdmin, routes.network.setDynamicDns);
|
|
router.get ('/api/v1/network/ipv4_config', token, authorizeAdmin, routes.network.getIPv4Config);
|
|
router.post('/api/v1/network/ipv4_config', json, token, authorizeAdmin, routes.network.setIPv4Config);
|
|
router.get ('/api/v1/network/ipv6_config', token, authorizeAdmin, routes.network.getIPv6Config);
|
|
router.post('/api/v1/network/ipv6_config', json, token, authorizeAdmin, routes.network.setIPv6Config);
|
|
router.get ('/api/v1/network/ipv4', token, authorizeAdmin, routes.network.getIPv4);
|
|
router.get ('/api/v1/network/ipv6', token, authorizeAdmin, routes.network.getIPv6);
|
|
|
|
// private docker registry
|
|
router.get ('/api/v1/docker/registries', token, authorizeAdmin, routes.dockerRegistries.list);
|
|
router.post('/api/v1/docker/registries', json, token, authorizeAdmin, routes.dockerRegistries.add);
|
|
router.get ('/api/v1/docker/registries/:id', token, routes.dockerRegistries.load, authorizeAdmin, routes.dockerRegistries.get);
|
|
router.post('/api/v1/docker/registries/:id', json, token, routes.dockerRegistries.load, authorizeAdmin, routes.dockerRegistries.update);
|
|
router.del ('/api/v1/docker/registries/:id', token, routes.dockerRegistries.load, authorizeAdmin, routes.dockerRegistries.del);
|
|
|
|
// email routes
|
|
router.get ('/api/v1/mailserver/eventlog', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.post('/api/v1/mailserver/clear_eventlog', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.use ('/api/v1/mailserver/files/*filepath', token, authorizeAdmin, routes.filemanager.proxy('mail'));
|
|
router.get ('/api/v1/mailserver/location', token, authorizeAdmin, routes.mailserver.getLocation);
|
|
router.post('/api/v1/mailserver/location', json, token, authorizeAdmin, routes.mailserver.setLocation);
|
|
router.get ('/api/v1/mailserver/max_email_size', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.post('/api/v1/mailserver/max_email_size', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.get ('/api/v1/mailserver/spam_acl', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.post('/api/v1/mailserver/spam_acl', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.get ('/api/v1/mailserver/spam_custom_config', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.post('/api/v1/mailserver/spam_custom_config', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.get ('/api/v1/mailserver/dnsbl_config', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.post('/api/v1/mailserver/dnsbl_config', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.get ('/api/v1/mailserver/fts_config', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.post('/api/v1/mailserver/fts_config', token, authorizeAdmin, routes.mailserver.proxyAndRestart);
|
|
router.get ('/api/v1/mailserver/mailbox_sharing', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.post('/api/v1/mailserver/mailbox_sharing', token, authorizeAdmin, routes.mailserver.proxyAndRestart);
|
|
router.get ('/api/v1/mailserver/virtual_all_mail', token, authorizeAdmin, routes.mailserver.proxy);
|
|
router.post('/api/v1/mailserver/virtual_all_mail', token, authorizeAdmin, routes.mailserver.proxyAndRestart);
|
|
router.get ('/api/v1/mailserver/usage', token, authorizeMailManager, routes.mailserver.proxy);
|
|
|
|
router.use ('/api/v1/mailserver/queue', token, authorizeAdmin, routes.mailserver.queueProxy);
|
|
|
|
router.get ('/api/v1/mail/:domain', token, authorizeMailManager, routes.mail.getDomain);
|
|
router.get ('/api/v1/mail', token, authorizeMailManager, routes.mail.listDomains);
|
|
router.post('/api/v1/mail/:domain/enable', json, token, authorizeAdmin, routes.mail.setMailEnabled);
|
|
router.get ('/api/v1/mail/:domain/status', token, authorizeMailManager, routes.mail.getStatus);
|
|
router.post('/api/v1/mail/:domain/mail_from_validation', json, token, authorizeMailManager, routes.mail.setMailFromValidation);
|
|
router.post('/api/v1/mail/:domain/catch_all', json, token, authorizeMailManager, routes.mail.setCatchAllAddress);
|
|
router.post('/api/v1/mail/:domain/relay', json, token, authorizeAdmin, routes.mail.setMailRelay);
|
|
router.post('/api/v1/mail/:domain/banner', json, token, authorizeMailManager, routes.mail.setBanner);
|
|
router.post('/api/v1/mail/:domain/send_test_mail', json, token, authorizeMailManager, routes.mail.sendTestMail);
|
|
router.get ('/api/v1/mail/:domain/stats', token, authorizeMailManager, routes.mail.getStats);
|
|
router.get ('/api/v1/mail/:domain/mailboxes', token, authorizeMailManager, routes.mail.listMailboxesByDomain);
|
|
router.get ('/api/v1/mail/:domain/mailboxes/:name', token, authorizeMailManager, routes.mail.getMailbox);
|
|
router.post('/api/v1/mail/:domain/mailboxes', json, token, authorizeMailManager, routes.mail.addMailbox);
|
|
router.post('/api/v1/mail/:domain/mailboxes/:name', json, token, authorizeMailManager, routes.mail.updateMailbox);
|
|
router.del ('/api/v1/mail/:domain/mailboxes/:name', json, token, authorizeMailManager, routes.mail.delMailbox);
|
|
router.get ('/api/v1/mail/:domain/mailboxes/:name/aliases', token, authorizeMailManager, routes.mail.getAliases);
|
|
router.put ('/api/v1/mail/:domain/mailboxes/:name/aliases', json, token, authorizeMailManager, routes.mail.setAliases);
|
|
router.get ('/api/v1/mail/:domain/lists', token, authorizeMailManager, routes.mail.listMailingListsByDomain);
|
|
router.post('/api/v1/mail/:domain/lists', json, token, authorizeMailManager, routes.mail.addMailingList);
|
|
router.get ('/api/v1/mail/:domain/lists/:name', token, authorizeMailManager, routes.mail.getMailingList);
|
|
router.post('/api/v1/mail/:domain/lists/:name', json, token, authorizeMailManager, routes.mail.updateMailingList);
|
|
router.del ('/api/v1/mail/:domain/lists/:name', token, authorizeMailManager, routes.mail.delMailingList);
|
|
|
|
// domain routes
|
|
router.post('/api/v1/domains', json, token, authorizeAdmin, routes.domains.add);
|
|
router.post('/api/v1/domains/sync_dns', json, token, authorizeAdmin, routes.domains.syncDnsRecords);
|
|
router.get ('/api/v1/domains', token, authorizeUser, routes.domains.list);
|
|
router.get ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.get); // this is manage scope because it returns non-restricted fields
|
|
router.post('/api/v1/domains/:domain/config', json, token, authorizeAdmin, routes.domains.setConfig);
|
|
router.post('/api/v1/domains/:domain/wellknown', json, token, authorizeAdmin, routes.domains.setWellKnown);
|
|
router.del ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.del);
|
|
router.get ('/api/v1/domains/:domain/dns_check', token, authorizeAdmin, routes.domains.checkDnsRecords);
|
|
|
|
// volume routes
|
|
router.post('/api/v1/volumes', json, token, authorizeAdmin, routes.volumes.add);
|
|
router.get ('/api/v1/volumes', token, authorizeAdmin, routes.volumes.list);
|
|
router.get ('/api/v1/volumes/:id', token, authorizeAdmin, routes.volumes.load, routes.volumes.get);
|
|
router.post('/api/v1/volumes/:id', json, token, authorizeAdmin, routes.volumes.load, routes.volumes.update);
|
|
router.del ('/api/v1/volumes/:id', token, authorizeAdmin, routes.volumes.load, routes.volumes.del);
|
|
router.get ('/api/v1/volumes/:id/status', token, authorizeAdmin, routes.volumes.load, routes.volumes.getStatus);
|
|
router.post('/api/v1/volumes/:id/remount', token, authorizeAdmin, routes.volumes.load, routes.volumes.remount);
|
|
router.use ('/api/v1/volumes/:id/files/*filepath', token, authorizeAdmin, routes.filemanager.proxy('volume'));
|
|
|
|
// service routes
|
|
router.get ('/api/v1/services', token, authorizeAdmin, routes.services.list);
|
|
router.get ('/api/v1/services/platform_status', token, authorizeUser, routes.services.getPlatformStatus);
|
|
router.get ('/api/v1/services/:service', token, authorizeAdmin, routes.services.get);
|
|
router.post('/api/v1/services/:service', json, token, authorizeAdmin, routes.services.configure);
|
|
router.get ('/api/v1/services/:service/metrics', token, authorizeAdmin, routes.services.getMetrics);
|
|
router.get ('/api/v1/services/:service/logs', token, authorizeAdmin, routes.services.getLogs);
|
|
router.get ('/api/v1/services/:service/logstream', token, authorizeAdmin, routes.services.getLogStream);
|
|
router.post('/api/v1/services/:service/restart', json, token, authorizeAdmin, routes.services.restart);
|
|
router.post('/api/v1/services/:service/rebuild', json, token, authorizeAdmin, routes.services.rebuild);
|
|
|
|
// well known
|
|
router.get ('/well-known-handler/*location', routes.wellknown.get);
|
|
|
|
// OpenID connect clients
|
|
router.get ('/api/v1/oidc/clients', token, authorizeAdmin, routes.oidcClients.list);
|
|
router.post('/api/v1/oidc/clients', json, token, authorizeAdmin, routes.oidcClients.add);
|
|
router.get ('/api/v1/oidc/clients/:clientId', token, authorizeAdmin, routes.oidcClients.load, routes.oidcClients.get);
|
|
router.post('/api/v1/oidc/clients/:clientId', json, token, authorizeAdmin, routes.oidcClients.load, routes.oidcClients.update);
|
|
router.del ('/api/v1/oidc/clients/:clientId', token, authorizeAdmin, routes.oidcClients.load, routes.oidcClients.del);
|
|
|
|
// dashboard assets. Only entry points are served up here, rest is via static assets from nginx
|
|
router.get ('/', routes.dashboard.renderIndex);
|
|
router.get ('/filemanager.html', routes.dashboard.renderFilemanager);
|
|
router.get ('/logs.html', routes.dashboard.renderLogs);
|
|
router.get ('/terminal.html', routes.dashboard.renderTerminal);
|
|
router.get ('/passwordreset.html', routes.dashboard.renderPasswordreset);
|
|
router.get ('/setupaccount.html', routes.dashboard.renderSetupaccount);
|
|
router.get ('/activation.html', routes.dashboard.renderActivation);
|
|
router.get ('/setup.html', routes.dashboard.renderSetup);
|
|
router.get ('/restore.html', routes.dashboard.renderRestore);
|
|
|
|
// upgrade handler
|
|
httpServer.on('upgrade', function (req, socket, head) {
|
|
// create a node response object for express
|
|
const res = new http.ServerResponse({});
|
|
res.assignSocket(socket);
|
|
|
|
if (req.headers.upgrade === 'websocket') {
|
|
res.handleUpgrade = function (callback) {
|
|
wsServer.handleUpgrade(req, socket, head, callback);
|
|
};
|
|
} else {
|
|
res.sendUpgradeHandshake = function () { // could extend express.response as well
|
|
socket.write('HTTP/1.1 101 TCP Handshake\r\n' +
|
|
'Upgrade: tcp\r\n' +
|
|
'Connection: Upgrade\r\n' +
|
|
'\r\n');
|
|
};
|
|
}
|
|
|
|
// route through express middleware. if we provide no callback, express will provide a 'finalhandler'
|
|
// TODO: it's not clear if socket needs to be destroyed
|
|
app(req, res);
|
|
});
|
|
|
|
return httpServer;
|
|
}
|
|
|
|
async function start() {
|
|
assert(gHttpServer === null, 'Server is already up and running.');
|
|
|
|
log('==========================================');
|
|
log(` Cloudron ${constants.VERSION} `);
|
|
log('==========================================');
|
|
|
|
await platform.initialize();
|
|
|
|
gHttpServer = await initializeExpressSync();
|
|
|
|
await util.promisify(gHttpServer.listen.bind(gHttpServer))(constants.PORT, '127.0.0.1');
|
|
await safe(eventlog.add(eventlog.ACTION_START, AuditSource.BOOT, { version: constants.VERSION })); // can fail if db down
|
|
}
|
|
|
|
async function stop() {
|
|
if (!gHttpServer) return;
|
|
|
|
await platform.uninitialize();
|
|
gHttpServer.closeAllConnections();
|
|
await util.promisify(gHttpServer.close.bind(gHttpServer))();
|
|
|
|
gHttpServer = null;
|
|
}
|
|
|
|
export default {
|
|
start,
|
|
stop
|
|
};
|