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);
|