diff --git a/nodejs/app.js b/nodejs/app.js index 769dfd9..b64b5d4 100755 --- a/nodejs/app.js +++ b/nodejs/app.js @@ -19,6 +19,15 @@ const middleware = require('./middleware/auth'); // Grab the projects PubSub app.contoller = require('./controller'); +/** + * Start background services + * These services run independently of the HTTP server: + * - host_lookup: Unix socket server for OpenResty host lookups + * - host_scheduler: Scheduled tasks for wildcard cert renewal + */ +require('./services/host_lookup'); +require('./services/host_scheduler'); + // Push pubsub over the socket and back. app.onListen.push(function(){ app.io.use(middleware.authIO); diff --git a/nodejs/controller/auth.js b/nodejs/controller/auth.js deleted file mode 100644 index d117173..0000000 --- a/nodejs/controller/auth.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -const Table = require('../models'); -const {User, AuthToken} = Table.models; - - -class Auth{ - static errors = { - login: function(){ - let error = new Error('LoginFailed'); - error.name = 'LoginFailed'; - error.message = `Invalid Credentials, login failed.`; - error.status = 401; - - return error; - } - } - - static async login(data){ - try{ - let user = await User.login(data); - let token = await AuthToken.create({username: user.username}); - - return {user, token} - }catch(error){ - console.log('login error', error); - throw this.errors.login(); - } - } - - static async checkToken(token){ - try{ - token = await AuthToken.get(token); - if(token && token.check()) return token; - - throw this.errors.login(); - }catch(error){ - console.log('check error', error); - throw this.errors.login(); - } - } - - static async logout(data){ - let token = await AuthToken.get(data); - await token.destroy(); - } -} - -module.exports = {Auth}; diff --git a/nodejs/controller/host.js b/nodejs/controller/host.js deleted file mode 100644 index ff5a591..0000000 --- a/nodejs/controller/host.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -const {Host} = require('../models/host'); -const {SocketServerJson} = require('../models/socket_server_json'); -const conf = require('../conf'); - - -const socket = new SocketServerJson({ - socketFile: conf.socketFile, - onData: function(data, clientSocket) { - try{ - console.log('socket lookup', data) - let parentHost = Host.lookUp(data['domain']); - console.log('socket found host', parentHost); - if(!parentHost) return clientSocket.write(JSON.stringify({})); - if(!parentHost.wildcard_parent){ - parentHost.wildcard_parent = parentHost.host; - Host.addCache(data['domain'], parentHost); - } - - for(const [key, value] of Object.entries(parentHost)) { - parentHost[key] = String(value); - }; - - clientSocket.write(JSON.stringify(parentHost)); - }catch(error){ - console.error('controler/hosts onData error', error) - } - }, - onListen: function(){ - console.log('Unix socket listening on', conf.socketFile) - } -}); diff --git a/nodejs/controller/index.js b/nodejs/controller/index.js index 32084e9..c84dbbf 100644 --- a/nodejs/controller/index.js +++ b/nodejs/controller/index.js @@ -1,8 +1,6 @@ 'use strict'; module.exports = { - auth: require('./auth'), ps: require('./pubsub'), - host: require('./host'), } diff --git a/nodejs/middleware/auth.js b/nodejs/middleware/auth.js index 802d7d1..7918d9a 100755 --- a/nodejs/middleware/auth.js +++ b/nodejs/middleware/auth.js @@ -1,6 +1,6 @@ 'use strict'; -const {Auth} = require('../controller/auth'); +const {Auth} = require('../models/auth'); async function auth(req, res, next){ try{ diff --git a/nodejs/models/socket_server_json.js b/nodejs/models/socket_server_json.js deleted file mode 100644 index f34d850..0000000 --- a/nodejs/models/socket_server_json.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict'; - -const net = require('net'); -const fs = require('fs'); -const {CallbackQueue} = require('../utils/callback_queue') - -class SocketServerJson { - constructor(args){ - this.socketFile = args.socketFile; - this.onData = new CallbackQueue(args.onData, this); - this.onListen = new CallbackQueue(args.onListen, this); - this.onError = new CallbackQueue(args.onError); - this.onCLientNew = new CallbackQueue(args.onCLientNew); - this.onCLientClose = new CallbackQueue(args.onCLientClose); - this.onCLientError = new CallbackQueue(args.onCLientClose); - - this.onListen.push(function(){ - fs.chmodSync(args.socketFile, '777'); - }) - - this.listen(); - - } - - __resetSocketFile(callback){ - let instance = this; - - fs.stat(this.socketFile, function (err, stats) { - if (stats) { - fs.unlink(instance.socketFile, function(err){ - if(err){ - // This should never happen. - console.error(err); - } - callback(...arguments) - }); - }else{ - callback() - } - - }); - } - - - __setUpServer(){ - - let instance = this; - this.socket = net.createServer(); - - this.socket.on('connection', function(clientSocket){ - let buffer = ''; - - clientSocket.on('data', function(data){ - buffer += data.toString(); - try{ - instance.onData.call(JSON.parse(data), clientSocket) - buffer = '' - // clientSocket.write(JSON.stringify(Host.lookUp(buffer)|| {host: 'none'})); - - }catch(error){ - ; - } - }); - - clientSocket.on('close', instance.onCLientClose.call.bind(instance.onCLientClose)); - - clientSocket.on('error', instance.onCLientError.call.bind(instance.onCLientError)); - - }); - - this.socket.on('error', this.onError.call.bind(this.onError)) - - this.socket.on('listening', this.onListen.call.bind(this.onListen)); - } - - listen(){ - let instance = this; - - this.__setUpServer(); - - this.__resetSocketFile(function(){ - instance.socket.listen(instance.socketFile); - }); - - } -}; - -module.exports = {SocketServerJson}; diff --git a/nodejs/routes/auth.js b/nodejs/routes/auth.js index be6e7f8..4b0bef8 100755 --- a/nodejs/routes/auth.js +++ b/nodejs/routes/auth.js @@ -1,7 +1,7 @@ 'use strict'; const router = require('express').Router(); -const { Auth } = require('../controller/auth'); +const { Auth } = require('../models/auth'); router.post('/login', async function(req, res, next){ diff --git a/nodejs/services/host_lookup.js b/nodejs/services/host_lookup.js new file mode 100644 index 0000000..73429c3 --- /dev/null +++ b/nodejs/services/host_lookup.js @@ -0,0 +1,62 @@ +'use strict'; + +const {Host} = require('../models/host'); +const {SocketServerJson} = require('../utils/unix_socket_json'); +const conf = require('../conf'); + +/** + * Host Lookup Service + * + * Unix socket server that handles host/domain lookup requests from OpenResty. + * This provides the bridge between nginx (Lua) and the Node.js host management system. + * + * Flow: + * 1. OpenResty sends domain lookup request via Unix socket + * 2. Service queries Host model (supports wildcards via lookup tree) + * 3. Returns host configuration (IP, port, SSL settings, etc.) + * 4. All values converted to strings for Redis compatibility + * + * Redis Compatibility: + * All object values are converted to strings before sending because: + * - Redis stores everything as strings + * - OpenResty's primary lookup path uses Redis directly (hgetall) + * - This socket is a fallback when Redis cache misses + * - Both paths must return identical data structures to Lua consumer + */ + +const socket = new SocketServerJson({ + socketFile: conf.socketFile, + + onData: function(data, clientSocket) { + try{ + // Try to match the requested host name using the lookup tree + let parentHost = Host.lookUp(data['domain']); + + // If we don't have a match, return empty object + if(!parentHost) return clientSocket.write(JSON.stringify({})); + + // If the matched host belongs to a wildcard domain, set wildcard_parent + // This allows child domains to use the parent's wildcard SSL certificate + if(!parentHost.wildcard_parent){ + parentHost.wildcard_parent = parentHost.host; + Host.addCache(data['domain'], parentHost); + } + + // Convert all values to strings for Redis compatibility + // OpenResty expects the same data format from both Redis and this socket + for(const [key, value] of Object.entries(parentHost)) { + parentHost[key] = String(value); + } + + clientSocket.write(JSON.stringify(parentHost)); + }catch(error){ + console.error('services/host_lookup onData error', error); + } + }, + + onListen: function(){ + console.log('Host lookup service listening on', conf.socketFile); + } +}); + +module.exports = {socket}; diff --git a/nodejs/services/host_scheduler.js b/nodejs/services/host_scheduler.js new file mode 100644 index 0000000..e556fdd --- /dev/null +++ b/nodejs/services/host_scheduler.js @@ -0,0 +1,32 @@ +'use strict'; + +const {Host} = require('../models/host'); + +/** + * Host Scheduler Service + * + * Manages scheduled tasks for host-related operations: + * - Wildcard SSL certificate renewal checks + * + * Schedule: + * - Initial check: 30 seconds after application starts + * - Recurring checks: Every 24 hours (86400000ms) + * + * The checkWildcardForRenew method: + * - Iterates through all hosts in the system + * - Checks if wildcard certificates are expiring within 30 days + * - Automatically renews certificates that are approaching expiration + */ + +// Initial wildcard cert check 30 seconds after app starts +// Delay allows the system to fully initialize before checking certs +setTimeout(Host.checkWildcardForRenew, 30000); + +// Check wildcard certs once every 24 hours +// Ensures certificates are renewed well before expiration +setInterval(Host.checkWildcardForRenew, 86400000); + +console.log('Host scheduler service initialized'); +console.log('- Wildcard cert check: 30s after start, then every 24h'); + +module.exports = {};