/** * @file apxauthrefactor.js * @description Refactored WCO component for user authentication and identity management. * @version 2.0 */ ((window) => { 'use strict'; // --- Component Definition --- const apxauth = {}; // --- Private State & Configuration --- const _state = { container: null, // The main DOM element for this component config: {}, // Initial configuration templates: {}, // To cache loaded Mustache templates currentUser: { alias: 'anonymous', profils: ['anonymous'], // ... other user data }, headers: { // To be sent with API requests xalias: 'anonymous', xdays: null, xhash: null, xprofils: ['anonymous'], xtribe: null, } }; // --- Private Helper Modules --- /** * Logger utility for consistent console output. */ const _log = (level, ...args) => { console[level]('[apxauth]', ...args); }; /** * Handles all interactions with the backend API. */ const _api = { /** * Generic method to perform an API request. * @param {string} method - 'get', 'post', 'put', etc. * @param {string} endpoint - The API endpoint URL. * @param {object} [data=null] - The request payload for POST/PUT. * @returns {Promise} - The response data. */ async request(method, endpoint, data = null) { try { const options = { method: method.toUpperCase(), headers: { ..._state.headers, 'Content-Type': 'application/json' }, }; if (data) { options.body = JSON.stringify(data); } const response = await fetch(endpoint, options); const responseData = await response.json(); if (!response.ok) { _log('error', `API Error on ${endpoint}:`, responseData); throw responseData; // Throw error object from backend } return responseData; } catch (error) { _log('error', `Request failed for ${endpoint}:`, error); throw error; // Re-throw to be caught by the caller } }, getAliasInfo: (alias) => _api.request('get', `/api/apxtri/pagans/alias/${alias}`), checkAuth: () => _api.request('get', '/api/apxtri/pagans/isauth'), logout: () => _api.request('get', '/api/apxtri/pagans/logout'), recoverKey: (data) => _api.request('post', '/api/apxtri/pagans/keyrecovery', data), registerIdentity: (data) => _api.request('post', '/api/apxtri/pagans', data), joinTribe: (tribe, data) => _api.request('put', `/api/apxtri/pagans/person/${tribe}`, data), }; /** * Encapsulates all OpenPGP-related functions. */ const _pgp = { async generateKey(alias, passphrase) { return openpgp.generateKey({ type: 'ecc', curve: 'curve25519', userIDs: [{ alias }], passphrase, format: 'armored', }); }, async createClearSignature(privateKeyArmored, passphrase, message) { const privateKey = await openpgp.decryptKey({ privateKey: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored }), passphrase, }); const cleartextMessage = await openpgp.sign({ message: await openpgp.createCleartextMessage({ text: message }), signingKeys: privateKey, }); return btoa(cleartextMessage); // Base64 encode for transport } }; /** * Handles rendering of Mustache templates. */ const _render = { /** * Renders a screen template into a specific area of the component. * @param {string} screenName - The name of the screen to render (e.g., 'signin', 'logout'). * @param {object} [data={}] - Additional data to pass to the template. */ async screen(screenName, data = {}) { const target = _state.container.querySelector('.screenaction'); if (!target) { _log('error', 'Render target ".screenaction" not found.'); return; } const templateName = `apxauthscreen${screenName}`; if (!_state.templates[templateName]) { _log('error', `Template ${templateName} not found or loaded.`); return; } const renderData = { ..._state.currentUser, ..._state.config, ...data }; target.innerHTML = Mustache.render(_state.templates[templateName], renderData); _log('log', `Rendered screen: ${screenName}`); }, /** * Displays a notification message. * @param {string} message - The message to display. * @param {'success'|'error'|'info'} type - The type of message. */ notification(message, type = 'error') { const target = _state.container.querySelector('.msginfo'); if(target) { target.innerHTML = `
${message}
`; } } }; // --- Public API & Business Logic --- /** * Sets the authentication headers required for API calls. * @private */ async function _setAuthHeaders(alias, publicKey, privateKey, passphrase) { _state.headers.xalias = alias; _state.headers.xdays = dayjs().valueOf(); const message = `${alias}_${_state.headers.xdays}`; try { _state.headers.xhash = await _pgp.createClearSignature(privateKey, passphrase, message); return true; } catch (error) { _log('error', 'Failed to create signature:', error); _render.notification('Could not create a valid signature. Is the passphrase correct?', 'error'); return false; } } /** * Main authentication flow. */ apxauth.login = async (alias, passphrase, privateKey) => { try { const { data: { publickey } } = await _api.getAliasInfo(alias); const headersSet = await _setAuthHeaders(alias, publickey, privateKey, passphrase); if (!headersSet) return; const { data: authData } = await _api.checkAuth(); // Successfully authenticated _state.currentUser.alias = alias; _state.currentUser.profils = authData.xprofils; _state.headers.xprofils = authData.xprofils; _log('log', 'Authentication successful. User:', _state.currentUser); // TODO: Redirect or render the 'logout' or 'mytribes' screen _render.screen('logout'); // Example: render logout screen } catch (error) { _log('error', 'Login failed:', error); _render.notification(error.msg || 'Login failed. Please check credentials.', 'error'); } }; /** * Initializes the component. * @param {object} config - The component configuration. * @param {string} config.containerId - The ID of the DOM element to render into. * @param {object} config.templates - An object containing the pre-loaded Mustache templates. * @param {object} [config.initialData={}] - Any initial data to populate state. */ apxauth.init = (config) => { _state.container = document.getElementById(config.containerId); if (!_state.container) { _log('error', `Container with ID "${config.containerId}" not found.`); return; } _state.config = config.initialData || {}; _state.templates = config.templates || {}; // Render the main component layout _state.container.innerHTML = Mustache.render(_state.templates.apxauthmain, _state.config); // Render the initial screen (e.g., 'signin' or 'logout' based on auth status) // For now, we default to 'signin' _render.screen('signin'); // TODO: Add event listeners using event delegation // _state.container.addEventListener('click', _handleEvents); _log('log', 'apxauth component initialized.'); }; // Expose the component to the global window object window.apxauth = apxauth; })(window);