UNPKG

cleansend

Version:

A TypeScript implementation of the OpenMsg Protocol - secure, decentralized messaging system with end-to-end encryption for cross-domain communication

226 lines 11 kB
"use strict"; /** * OpenMsg Authentication Routes * * This module handles the authentication and connection establishment process * between OpenMsg users across different domains. It implements a secure * handshake protocol using pass codes and generates encryption keys for * end-to-end messaging. * * Authentication Flow: * 1. User A requests pass code from User B * 2. User A sends auth request to User B's server with pass code * 3. User B's server validates pass code and confirms with User A's server * 4. Connection is established with unique encryption keys * * Security Features: * - Pass codes expire after 1 hour * - Cross-domain verification prevents spoofing * - Unique encryption keys per connection * - Authentication codes for message verification */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const axios_1 = __importDefault(require("axios")); const database_1 = require("../config/database"); const crypto_1 = require("../utils/crypto"); const settings_1 = __importDefault(require("../config/settings")); const router = express_1.default.Router(); /** * POST /auth * * Main authentication endpoint for establishing connections between OpenMsg users. * This is called by other OpenMsg servers when their users want to connect * with a user on this server. * * Process: * 1. Validates request data and pass code * 2. Confirms authenticity with the sending server * 3. Generates encryption keys and connection credentials * 4. Stores connection in database */ router.post('/', async (req, res) => { try { const { receiving_openmsg_address_id, pass_code, sending_openmsg_address, sending_openmsg_address_name, sending_allow_replies } = req.body; // Process the authentication request const result = await authCheck(receiving_openmsg_address_id, pass_code, sending_openmsg_address, sending_openmsg_address_name, sending_allow_replies); res.json(result); } catch (error) { console.error('Auth error:', error); res.json({ error: true, error_message: 'Internal server error' }); } }); /** * POST /auth/confirm * * Authentication confirmation endpoint used to verify that a handshake * request is legitimate. This prevents unauthorized users from spoofing * connection attempts. * * Called by other OpenMsg servers to confirm that: * - The handshake was actually initiated by the claimed user * - The pass code is valid and not expired */ router.post('/confirm', async (req, res) => { try { const { other_openmsg_address, pass_code } = req.body; const result = await authConfirm(other_openmsg_address, pass_code); res.json(result); } catch (error) { console.error('Auth confirm error:', error); res.json({ error: true, error_message: 'Internal server error' }); } }); /** * Process authentication request from another OpenMsg server * * This function implements the core authentication logic: * 1. Validates all required fields * 2. Checks if the receiving user exists on this server * 3. Validates the pass code (must be recent and valid) * 4. Performs cross-domain verification with the sending server * 5. Generates unique encryption keys for the connection * 6. Stores the connection in the database * * @param selfOpenmsgAddressId - User ID on this server (receiving user) * @param passCode - One-time pass code provided by receiving user * @param otherOpenmsgAddress - Full address of sending user * @param otherOpenmsgAddressName - Display name of sending user * @param otherAllowsReplies - Whether sending user accepts reply messages * @returns AuthResponse with connection credentials or error */ async function authCheck(selfOpenmsgAddressId, passCode, otherOpenmsgAddress, otherOpenmsgAddressName, otherAllowsReplies) { // Validate required fields if (!selfOpenmsgAddressId || !passCode || !otherOpenmsgAddress || !otherOpenmsgAddressName) { return { error: true, error_message: `Required fields missing: ${selfOpenmsgAddressId}, ${passCode}, ${otherOpenmsgAddress}, ${otherOpenmsgAddressName}` }; } // Construct full OpenMsg address for the receiving user const selfOpenmsgAddress = `${selfOpenmsgAddressId}*${settings_1.default.openmsgDomain}`; try { // Verify that the receiving user exists on this server const [userRows] = await database_1.pool.execute('SELECT self_openmsg_address_name FROM openmsg_users WHERE self_openmsg_address = ?', [selfOpenmsgAddress]); if (userRows.length === 0) { return { error: true, error_message: 'User not found' }; } const selfOpenmsgAddressName = userRows[0].self_openmsg_address_name; // Validate the pass code - must exist and not be expired const [passCodeRows] = await database_1.pool.execute('SELECT UNIX_TIMESTAMP(timestamp) as passCode_timestamp FROM openmsg_passCodes WHERE self_openmsg_address = ? AND pass_code = ?', [selfOpenmsgAddress, passCode]); if (passCodeRows.length === 0) { return { error: true, error_message: 'Invalid pass code' }; } // Check if pass code has expired (1 hour expiry) const oneHour = 3600; // seconds if (passCodeRows[0].passCode_timestamp < Math.floor(Date.now() / 1000) - oneHour) { return { error: true, error_message: 'Expired pass code' }; } // Consume the pass code (delete it so it can't be reused) await database_1.pool.execute('DELETE FROM openmsg_passCodes WHERE self_openmsg_address = ? AND pass_code = ? LIMIT 1', [selfOpenmsgAddress, passCode]); // Parse and validate the sending user's address const addressParts = otherOpenmsgAddress.split('*'); if (addressParts.length !== 2) { return { error: true, error_message: 'Invalid openmsg address format' }; } const [otherOpenmsgAddressId, otherOpenmsgAddressDomain] = addressParts; if (!/^\d+$/.test(otherOpenmsgAddressId)) { return { error: true, error_message: 'Address ID should be numeric' }; } // Perform cross-domain verification // Contact the sending server to confirm this handshake is legitimate const url = `https://${otherOpenmsgAddressDomain}/openmsg${settings_1.default.sandboxDir}/auth/confirm`; const requestData = { other_openmsg_address: `${selfOpenmsgAddressId}*${settings_1.default.openmsgDomain}`, pass_code: passCode }; let response; try { response = await axios_1.default.post(url, requestData, { headers: { 'Content-Type': 'application/json' }, timeout: 10000 // 10 second timeout for cross-domain requests }); } catch (error) { return { error: true, error_message: `Request error: ${error.message}` }; } // Verify the confirmation response if (response.status !== 200 || response.data.error || response.data.success !== true) { return { error: true, error_message: response.data.error_message || 'Remote confirmation failed' }; } // Generate unique cryptographic materials for this connection const authCode = (0, crypto_1.generateAuthCode)(); // For message authentication const identCode = (0, crypto_1.generateIdentCode)(); // For connection identification const messageCryptKey = (0, crypto_1.generateMessageCryptKey)(); // For message encryption // Remove any existing connection between these users // (allows re-establishment with new keys) await database_1.pool.execute('DELETE FROM openmsg_user_connections WHERE self_openmsg_address = ? AND other_openmsg_address = ?', [selfOpenmsgAddress, otherOpenmsgAddress]); // Store the new connection with encryption keys await database_1.pool.execute(`INSERT INTO openmsg_user_connections (self_openmsg_address, other_openmsg_address, other_openmsg_address_name, other_acceptsMessages, auth_code, ident_code, message_crypt_key) VALUES (?, ?, ?, ?, ?, ?, ?)`, [selfOpenmsgAddress, otherOpenmsgAddress, otherOpenmsgAddressName, otherAllowsReplies ? 1 : 0, authCode, identCode, messageCryptKey]); // Return success with connection credentials return { success: true, auth_code: authCode, ident_code: identCode, message_crypt_key: messageCryptKey, receiving_openmsg_address_name: selfOpenmsgAddressName }; } catch (error) { console.error('authCheck DB error:', error); return { error: true, error_message: 'Database error' }; } } /** * Confirm authentication handshake legitimacy * * This function is called by other OpenMsg servers to verify that * a handshake request actually came from a user on this server. * It prevents spoofing attacks where malicious users claim to be * from a different domain. * * Verification process: * 1. Check if there's a pending handshake with the given details * 2. Verify the handshake isn't expired (60 second limit) * 3. Remove the handshake record (consume it) * * @param otherOpenmsgAddress - Address of the user making the auth request * @param passCode - Pass code used in the auth request * @returns AuthConfirmResponse indicating success or failure */ async function authConfirm(otherOpenmsgAddress, passCode) { try { // Look for a pending handshake matching these details const [rows] = await database_1.pool.execute('SELECT UNIX_TIMESTAMP(timestamp) as initiation_timestamp FROM openmsg_handshakes WHERE other_openmsg_address = ? AND pass_code = ?', [otherOpenmsgAddress, passCode]); if (rows.length === 0) { return { error: true, error_message: `Pending authorization not found for ${otherOpenmsgAddress}` }; } // Check if the handshake has expired (60 second limit for security) const now = Math.floor(Date.now() / 1000); if (rows[0].initiation_timestamp < now - 60) { return { error: true, error_message: 'Handshake expired (over 60s)' }; } // Consume the handshake record (prevent reuse) await database_1.pool.execute('DELETE FROM openmsg_handshakes WHERE other_openmsg_address = ? AND pass_code = ? LIMIT 1', [otherOpenmsgAddress, passCode]); return { success: true }; } catch (error) { console.error('authConfirm DB error:', error); return { error: true, error_message: 'Database error' }; } } exports.default = router; //# sourceMappingURL=auth.js.map