diff --git a/src/backend/src/modules/web/WebServerService.js b/src/backend/src/modules/web/WebServerService.js index dbb4a3c51..c59bfcc70 100644 --- a/src/backend/src/modules/web/WebServerService.js +++ b/src/backend/src/modules/web/WebServerService.js @@ -500,7 +500,11 @@ class WebServerService extends BaseService { if (allowedDomains.some(allowedDomain => hostName === allowedDomain || hostName.endsWith('.' + allowedDomain))) { next(); // Proceed if the host is valid } else { - return res.status(400).send('Invalid Host header.'); + if ( ! config.custom_domains_enabled ) { + return res.status(400).send('Invalid Host header.'); + } + req.is_custom_domain = true; + next(); } }) diff --git a/src/backend/src/om/mappings/subdomain.js b/src/backend/src/om/mappings/subdomain.js index a8f159832..5b823f02c 100644 --- a/src/backend/src/om/mappings/subdomain.js +++ b/src/backend/src/om/mappings/subdomain.js @@ -54,6 +54,19 @@ module.exports = { } } }, + domain: { + type: 'string', + maxlen: 253, + + // It turns out validating domain names kind of sucks + // source: https://stackoverflow.com/questions/10306690 + regex: '^(((?!-))(xn--|_)?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$', + + // TODO: can this 'adapt' be data instead? + async adapt (value) { + return value.toLowerCase(); + }, + }, root_dir: { type: 'puter-node', fs_permission: 'read', diff --git a/src/backend/src/routers/hosting/puter-site.js b/src/backend/src/routers/hosting/puter-site.js index 97ee33c57..56e2d1ee7 100644 --- a/src/backend/src/routers/hosting/puter-site.js +++ b/src/backend/src/routers/hosting/puter-site.js @@ -41,10 +41,17 @@ class PuterSiteMiddleware extends AdvancedBase { app.use(this.run.bind(this)); } async run (req, res, next) { - if ( - ! req.hostname.endsWith(config.static_hosting_domain) - && ( req.subdomains[0] !== 'devtest' ) - ) return next(); + + ! req.hostname.endsWith(config.static_hosting_domain) + && ( req.subdomains[0] !== 'devtest' ) + + const is_subdomain = + req.hostname.endsWith(config.static_hosting_domain) + || + req.subdomains[0] === 'devtest' + ; + + if ( ! is_subdomain && ! req.is_custom_domain ) return next(); res.setHeader('Access-Control-Allow-Origin', '*'); @@ -64,6 +71,7 @@ class PuterSiteMiddleware extends AdvancedBase { } async run_ (req, res, next) { const subdomain = + req.is_custom_domain ? req.hostname : req.subdomains[0] === 'devtest' ? 'devtest' : req.hostname.slice(0, -1 * (config.static_hosting_domain.length + 1)); @@ -100,7 +108,9 @@ class PuterSiteMiddleware extends AdvancedBase { await get_username_site() || await (async () => { const svc_puterSite = services.get('puter-site'); - const site = await svc_puterSite.get_subdomain(subdomain); + const site = await svc_puterSite.get_subdomain(subdomain, { + is_custom_domain: req.is_custom_domain, + }); return site; })(); diff --git a/src/backend/src/services/PuterSiteService.js b/src/backend/src/services/PuterSiteService.js index 590cd2224..45746c23e 100644 --- a/src/backend/src/services/PuterSiteService.js +++ b/src/backend/src/services/PuterSiteService.js @@ -113,15 +113,18 @@ class PuterSiteService extends BaseService { * @returns {Promise} Returns an object with subdomain details or null if not found. * @note In development environment, 'devtest' subdomain returns hardcoded values. */ - async get_subdomain (subdomain) { + async get_subdomain (subdomain, options) { if ( subdomain === 'devtest' && this.global_config.env === 'dev' ) { return { user_id: null, root_dir_id: this.config.devtest_directory, }; } + console.log('???', subdomain, options); const rows = await this.db.read( - `SELECT * FROM subdomains WHERE subdomain = ? LIMIT 1`, + `SELECT * FROM subdomains WHERE ${ + options.is_custom_domain ? 'domain' : 'subdomain' + } = ? LIMIT 1`, [subdomain] ); if ( rows.length === 0 ) return null; diff --git a/src/backend/src/services/database/SqliteDatabaseAccessService.js b/src/backend/src/services/database/SqliteDatabaseAccessService.js index 85e880d02..fbada0773 100644 --- a/src/backend/src/services/database/SqliteDatabaseAccessService.js +++ b/src/backend/src/services/database/SqliteDatabaseAccessService.js @@ -161,6 +161,9 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { [33, [ '0036_dev-to-app.sql', ]], + [34, [ + '0038_custom-domains.sql', + ]], ]; // Database upgrade logic diff --git a/src/backend/src/services/database/sqlite_setup/0038_custom-domains.sql b/src/backend/src/services/database/sqlite_setup/0038_custom-domains.sql new file mode 100644 index 000000000..04df78f10 --- /dev/null +++ b/src/backend/src/services/database/sqlite_setup/0038_custom-domains.sql @@ -0,0 +1,2 @@ +ALTER TABLE `subdomains` ADD COLUMN `domain` varchar(256) DEFAULT NULL; +-- reminder: add index \ No newline at end of file