cleansend
Version:
A TypeScript implementation of the OpenMsg Protocol - secure, decentralized messaging system with end-to-end encryption for cross-domain communication
304 lines • 13.8 kB
JavaScript
;
/**
* OpenMsg Setup and Testing Routes
*
* This module provides administrative and testing endpoints for the OpenMsg
* protocol. These endpoints facilitate connection establishment, message sending,
* and pass code generation for development and testing purposes.
*
* Testing Features:
* - Initiate handshakes with other OpenMsg users
* - Send messages to connected users
* - Generate one-time pass codes for authentication
*
* Note: In production, these endpoints would typically be integrated
* into a user interface or replaced with user authentication systems.
*/
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 /setup/initiate-handshake
*
* Testing endpoint to initiate a connection with another OpenMsg user.
* This simulates the process a user would go through to connect with
* someone on a different OpenMsg server.
*
* Process:
* 1. Creates a handshake record with the provided pass code
* 2. Sends authentication request to the other user's server
* 3. Stores the resulting connection credentials
*
* Note: In production, the sender's address would come from user authentication
*/
router.post('/initiate-handshake', async (req, res) => {
try {
const { other_openmsg_address, pass_code } = req.body;
// TODO: Replace with session/user context in production
// These would normally come from authenticated user session
const selfOpenmsgAddressName = "John Doe";
const selfOpenmsgAddress = `1000001*${settings_1.default.openmsgDomain}`;
const selfAllowReplies = true;
const result = await initiateHandshake(other_openmsg_address, pass_code, selfOpenmsgAddress, selfOpenmsgAddressName, selfAllowReplies);
if (result === "Success") {
res.json({ success: true, message: `Connected. You can now message us: ${selfOpenmsgAddress}` });
}
else {
res.json({ error: true, message: `Error: ${result}` });
}
}
catch (error) {
console.error('Initiate handshake error:', error);
res.json({ error: true, message: 'Internal server error' });
}
});
/**
* POST /setup/send-message
*
* Testing endpoint to send an encrypted message to a connected OpenMsg user.
* This demonstrates the complete message sending process including encryption,
* hash creation, and cross-domain delivery.
*
* Requirements:
* - A connection must already exist between sender and recipient
* - The connection provides the necessary encryption keys and auth codes
*/
router.post('/send-message', async (req, res) => {
try {
const { message_text, sending_openmsg_address, receiving_openmsg_address } = req.body;
const result = await sendMessage(message_text, sending_openmsg_address, receiving_openmsg_address);
res.json(result);
}
catch (error) {
console.error('Send message error:', error);
res.json({
error: true,
response_code: 'SM_E000',
error_message: 'Internal server error'
});
}
});
/**
* POST /setup/request-pass-code
*
* Testing endpoint to generate a one-time pass code for authentication.
* Pass codes are used in the handshake process to authorize new connections.
*
* Security features:
* - Pass codes are 6-digit random numbers
* - Expire after 1 hour
* - Single-use only (consumed during authentication)
*/
router.post('/request-pass-code', async (req, res) => {
try {
const { self_openmsg_address } = req.body;
if (!self_openmsg_address) {
return res.json({
error: true,
error_message: 'self_openmsg_address is required'
});
}
// Generate a 6-digit random pass code
const passCode = Math.floor(100000 + Math.random() * 900000).toString();
// Store the pass code with expiration (1 hour)
await database_1.pool.execute('INSERT INTO openmsg_passCodes (self_openmsg_address, pass_code) VALUES (?, ?)', [self_openmsg_address, passCode]);
return res.json({
success: true,
pass_code: passCode,
message: 'Pass code generated. Valid for 1 hour.'
});
}
catch (error) {
console.error('Request pass code error:', error);
res.json({
error: true,
error_message: 'Internal server error'
});
return;
}
});
/**
* Initiate handshake with another OpenMsg user
*
* This function implements the client side of the OpenMsg handshake protocol:
* 1. Validates input parameters
* 2. Creates a handshake record in the database
* 3. Sends authentication request to the other user's server
* 4. Stores the resulting connection credentials
*
* @param otherOpenmsgAddress - Full address of user to connect with
* @param passCode - Pass code obtained from that user
* @param selfOpenmsgAddress - Our full OpenMsg address
* @param selfOpenmsgAddressName - Our display name
* @param selfAllowReplies - Whether we accept reply messages
* @returns Success message or error description
*/
async function initiateHandshake(otherOpenmsgAddress, passCode, selfOpenmsgAddress, selfOpenmsgAddressName, selfAllowReplies) {
// Validate required parameters
if (!otherOpenmsgAddress || !passCode || !selfOpenmsgAddress || !selfOpenmsgAddressName || !settings_1.default.openmsgDomain) {
return "Missing data (8BgrT)";
}
// Validate pass code format (must be numeric)
if (!/^\d+$/.test(passCode)) {
return "Please enter a valid Pass Code";
}
// Parse and validate the target user's address
const addressParts = otherOpenmsgAddress.split('*');
if (addressParts.length !== 2)
return "Invalid address format";
const [otherOpenmsgAddressId, otherOpenmsgAddressDomain] = addressParts;
// Validate address components
if (!/^\d+$/.test(otherOpenmsgAddressId)) {
return `other_openmsg_address_id not valid ${otherOpenmsgAddressId} (wD861)`;
}
if (!/^[A-Za-z0-9.\-]+$/.test(otherOpenmsgAddressDomain)) {
return `openmsg_address_domain not valid ${otherOpenmsgAddressDomain} (D8hgB)`;
}
try {
// Create handshake record (for confirmation by other server)
await database_1.pool.execute('INSERT INTO openmsg_handshakes (other_openmsg_address, pass_code) VALUES (?, ?)', [otherOpenmsgAddress, passCode]);
// Send authentication request to the other server
const url = `https://${otherOpenmsgAddressDomain}/openmsg${settings_1.default.sandboxDir}/auth/`;
const requestData = {
receiving_openmsg_address_id: otherOpenmsgAddressId,
pass_code: passCode,
sending_openmsg_address: selfOpenmsgAddress,
sending_openmsg_address_name: selfOpenmsgAddressName,
sending_allow_replies: selfAllowReplies,
openmsg_version: 1.0
};
let response;
try {
response = await axios_1.default.post(url, requestData, {
headers: { 'Content-Type': 'application/json' },
timeout: 15000, // 15 second timeout for handshake
maxRedirects: 3 // Allow up to 3 redirects
});
}
catch (error) {
return `Error. Request error: ${error.message}`;
}
const responseData = response.data;
// Verify the authentication response
if (response.status !== 200 || responseData.error || responseData.success !== true) {
return responseData.error_message || "Handshake failed";
}
// Extract connection credentials from response
const { auth_code, ident_code, message_crypt_key, receiving_openmsg_address_name } = responseData;
if (!auth_code || !ident_code || !message_crypt_key || !receiving_openmsg_address_name) {
return "Error: Missing required data in response";
}
// Remove any existing connection (allows re-establishment)
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 credentials
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, receiving_openmsg_address_name, 1, auth_code, ident_code, message_crypt_key]);
return "Success";
}
catch (error) {
console.error('Database error in initiateHandshake:', error);
return "Database error";
}
}
/**
* Send encrypted message to connected OpenMsg user
*
* This function implements the complete message sending process:
* 1. Looks up the connection between sender and recipient
* 2. Encrypts the message using the connection's encryption key
* 3. Creates verification hash with auth code and timestamp
* 4. Sends encrypted package to recipient's server
* 5. Stores message in outbox for confirmation tracking
*
* @param messageText - Plaintext message to send
* @param sendingOpenmsgAddress - Sender's full OpenMsg address
* @param receivingOpenmsgAddress - Recipient's full OpenMsg address
* @returns SendMessageResponse indicating success or failure
*/
async function sendMessage(messageText, sendingOpenmsgAddress, receivingOpenmsgAddress) {
try {
// Look up the connection between sender and recipient
const [connectionRows] = await database_1.pool.execute('SELECT auth_code, ident_code, message_crypt_key FROM openmsg_user_connections WHERE self_openmsg_address = ? AND other_openmsg_address = ?', [sendingOpenmsgAddress, receivingOpenmsgAddress]);
if (connectionRows.length === 0) {
return {
error: true,
error_message: `No matching connection between these users: ${sendingOpenmsgAddress}, ${receivingOpenmsgAddress} (Qmyxm)`,
response_code: 'SM_E001'
};
}
const { auth_code, ident_code, message_crypt_key } = connectionRows[0];
// Parse recipient's domain from their address
const addressParts = receivingOpenmsgAddress.split('*');
if (addressParts.length !== 2) {
return {
error: true,
error_message: 'Invalid receiving address format',
response_code: 'SM_E003'
};
}
const [receivingOpenmsgAddressId, receivingOpenmsgAddressDomain] = addressParts;
// Encrypt the message using the connection's encryption key
const messagePackage = (0, crypto_1.createMessagePackage)(messageText, message_crypt_key);
// Generate salt and timestamp for hash verification
const messageSalt = (0, crypto_1.generateMessageSalt)();
const messageTimestamp = Math.floor(Date.now() / 1000);
// Create verification hash to prove message authenticity
const messageHash = (0, crypto_1.createMessageHash)(messagePackage.package, auth_code, messageSalt, messageTimestamp);
// Store message in outbox for confirmation tracking
await database_1.pool.execute('INSERT INTO openmsg_messages_outbox (self_openmsg_address, ident_code, message_hash, message_nonce, message_text) VALUES (?, ?, ?, ?, ?)', [sendingOpenmsgAddress, ident_code, messageHash, messagePackage.nonce, messageText]);
// Send encrypted message to recipient's server
const url = `https://${receivingOpenmsgAddressDomain}/openmsg${settings_1.default.sandboxDir}/message/receive`;
const requestData = {
receiving_openmsg_address_id: receivingOpenmsgAddressId,
ident_code: ident_code,
message_package: messagePackage.package,
message_hash: messageHash,
message_salt: messageSalt,
message_timestamp: messageTimestamp,
openmsg_version: 1.0
};
let response;
try {
response = await axios_1.default.post(url, requestData, {
headers: { 'Content-Type': 'application/json' },
timeout: 15000 // 15 second timeout for message delivery
});
}
catch (error) {
return {
error: true,
error_message: `Request failed: ${error.message}`,
response_code: 'SM_E000'
};
}
// Return the response from the recipient's server
const responseData = response.data;
if (response.status !== 200 || responseData.error) {
return {
error: true,
error_message: responseData.error_message || 'Message delivery failed',
response_code: responseData.response_code || 'SM_E000'
};
}
return {
success: true,
response_code: responseData.response_code || 'SM_S888'
};
}
catch (error) {
console.error('sendMessage error:', error);
return {
error: true,
error_message: 'Database error',
response_code: 'SM_E000'
};
}
}
exports.default = router;
//# sourceMappingURL=setup.js.map