229 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @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<object>} - 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 = `<div class="notification ${type}">${message}</div>`;
 | |
|         }
 | |
|     }
 | |
|   };
 | |
|   
 | |
|   // --- 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);
 |