new wco wwws compatible with new backeend

This commit is contained in:
2025-07-23 12:36:48 +02:00
parent bc76840707
commit b6ae865332
23 changed files with 1702 additions and 94 deletions

View File

@@ -0,0 +1,228 @@
/**
* @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);

View File

@@ -0,0 +1,7 @@
<!-- screen action-->
<div class="screenaction mt-5 sm:mx-auto sm:w-full sm:max-w-sm">
</div>
<!-- feedback action-->
<div class="my-5">
<p class="msginfo text-center text-info"></p>
</div>

View File

@@ -0,0 +1,44 @@
<div class="mt-1">
<label class="input validator mbt-1">
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="1"
fill="black"
stroke="black"
>
<path d="M11.89 4.111a5.5 5.5 0 1 0 0 7.778.75.75 0 1 1 1.06 1.061A7 7 0 1 1 15 8a2.5 2.5 0 0 1-4.083 1.935A3.5 3.5 0 1 1 11.5 8a1 1 0 0 0 2 0 5.48 5.48 0 0 0-1.61-3.889ZM10 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"></path>
</g>
</svg>
|
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="1"
fill="black"
stroke="black"
>
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
</g>
</svg>
<input id="inputaliasrecovery" type="text" placeholder="mail@site.com | alias" required />
</label>
<div class="validator-hint hidden">
Enter a valid email or an alias (lowercase a-z and 0-9)
</div>
</div>
<div class="my-5">
<p>
Si vous avez fait confiance à ce domaine pour garder vos clés, un email va être envoyé avec vos clés.
</p>
</div>
<div class="my-5">
<button
class="btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
onclick="apx.apxauth.recoverykey('{{id}}',document.getElementById('inputaliasrecovery').value);"
>
M'envoyer un email avec mes clés
</button>
</div>

View File

@@ -0,0 +1,41 @@
<div class="space-y-6 text-justify">
<h2>Qu'est-ce qu'une identité numérique décentralisée?</h2>
<p>
C'est <span class="text-secondary">un moyen de s'identifier en prouvant qu'on est le propriétaire
d'un alias ou d'une clé publique</span>. Cette clé publique est accessible à tous et utilisée dans le
monde numérique pour informer, payer, échanger,... et porte une
réputation publique.
</p>
<p>
Concrètement, c'est une paire de fichiers texte appelée clé publique
et clé privée. La clé publique ne porte pas d'information
personnelle autre que celles que vous avez bien voulu y associer.
</p>
<p>
Une fonction mathématique permet au propriétaire de la clé privée de
signer un message. Le destinataire dispose d'une autre fonction qui
permet de vérifier que la signature a été faite avec la clé privée.
</p>
<p>
Cette interface permet de créer une identité et de l'utiliser pour
s'authentifier pour 24 heures. Elle n'envoie que le couple alias/clé
publique sur internet, la clé privée est
<span class="text-secondary">votre propriété et ne doit jamais être communiquée</span>. Si vous
la perdez, vous ne pourrez plus récupérer les informations
associées. Sauf si vous
<span class="text-secondary">avez fait confiance à ce nom de domaine</span>, vous pourrez lui
demander d'envoyer un email avec ces clés.
</p>
<p>
Vous pouvez avoir autant d'identités que vous voulez, vous pouvez
créer une identité pour des objets uniques. La seule limite est qu'à
partir du moment où vous associez des informations personnelles à
cette clé, le destinataire de ces informations peut les relier aux
activités de cette identité inscrite dans la blockchain apxtri.
</p>
<p>
Pour auditer le code js, utiliser l'outil de développement de votre
navigateur. Pour toute remarque, question ou détection de failles :
{{supportemail}}
</p>
</div>

View File

@@ -0,0 +1,39 @@
<div class="flex flex-col space-y-1 text-center">
<div class="mt-1">
<h1 class="mb-6">
Bonjour {{xalias}},
</h1>
<p>
Si cet appareil ne vous appartiens pas et que vous n'utilisez pas l'application, vous devriez vous deconnecter.
</p>
<p class="text-center text-gray-500">
Nettoyer mes traces de cet appareil?
<a class="font-semibold leading-6 text-secondary hover:text-primary"
onclick="apx.apxauth.logout('{{id}}','logout','logout','apxauth')">Se deconnecter</a>
</p>
</div>
<div class="mt-4">
<p class="text-center text-gray-500">
Voir mes échanges?
<a class="font-semibold leading-6 text-secondary hover:text-primary"
href="https://wall-ants.ndda.fr/apxwallet_fr.html" >Mon activité </a>
</p>
{{#member}}
<p>
Vous êtes membre de {{xtribe}} {{#noprofils}} sand profil particulier {{/noprofils}} {{^noprofils}}avec le(s) profil(s):<br><span class="text-info"> {{#profils}} {{.}}<br> {{/profils}} </span> {{/noprofils}}
</p>
{{/member}}
{{^member}}
<p> Vous n'êtes pas encore membre de {{xtribe}} </p>
<p class=" mt-1 text-center text-gray-500">
Envie d'jouter cette tribut {{xtribe}}?
<a class="font-semibold leading-6 text-secondary hover:text-primary"
onclick="apx.apxauth.jointribe('{{id}}')">Rejoindre {{xtribe}}</a>
</p>
{{/member}}
<p>Les applications ou pages web de {{xtribe}} à visiter:<br>
{{#websites}}<a class="font-semibold leading-6 text-secondary hover:text-primary" href='{{{href}}}'>{{{name}}}</a><br> {{/websites}}
</p>
<button class="btn btn-primary" onclick="apx.apxauth.runtest()">testbtn</button>
</div>
</div>

View File

@@ -0,0 +1,23 @@
<div class="flex flex-col space-y-1 text-center">
<div class="mt-1">
<h1 class="mb-6">
Bonjour {{xalias}},
</h1>
</div>
<div class="mt-4">
<p class="text-center text-gray-500">
Redirige vers
<a class="font-semibold leading-6 text-secondary hover:text-primary"
onclick="apx.apxauth.redirecturlwithauth('http://recruiter.smatchit.newdev.ants/src/offer_fr.html','smatchit','recruiter',true);" >Redirige vers recruiter.smatchit.io/offer_fr.html&xhash....</a>
</p>
{{#member}}
<p>
Vous êtes membre de {{xtribe}} {{#noprofils}} sand profil particulier {{/noprofils}} {{^noprofils}}avec le(s) profil(s):<br><span class="text-info"> {{#profils}} {{.}}<br> {{/profils}} </span> {{/noprofils}}
</p>
{{/member}}
<p>Les applications ou pages web de {{xtribe}} à visiter:<br>
{{#websites}}<a class="font-semibold leading-6 text-secondary hover:text-primary" href='{{{href}}}'>{{{name}}}</a><br> {{/websites}}
</p>
<button class="btn btn-primary" onclick="apx.apxauth.runtest()">testbtn</button>
</div>
</div>

View File

@@ -0,0 +1,69 @@
<p data-wco="createid" class="text-center text-neutral-content">
{{{signintitle}}}
</p>
<div class="mt-2">
<label class="input validator">
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="1"
fill="black"
stroke="black"
>
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
</g>
</svg>
<input
class="signinalias"
type="input"
required
placeholder="alias"
pattern="[a-z0-9\-]*"
minlength="3"
maxlength="30"
title="{{{aliastitle}}}"
/>
</label>
<p class="validator-hint hidden"> {{{aliasinvalid}}}</p>
</div>
<div class="mt-2">
<label class="input mt-1">
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="1"
fill="black"
stroke="black"
>
<path d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"></path>
</g>
</svg>
<input type="text" class="signinpassphrase" placeholder="passphrase (option)" />
</label>
</div>
<div class="mt-2">
<textarea rows=5 class="mt-2 textarea signinprivatekey" placeholder="{{{privatekeyplaceholder}}}"></textarea>
</div>
<div class="flex m-6">
<div class="w-14 flex-none">
<input type="checkbox" checked="checked" class="checkbox signinrememberme" />
</div>
<div class="flex-1">
<p class="text-sm text-justify" >{{{remembermetext}}}</p>
</div>
</div>
<div class="m-4">
<button
class="btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
onclick="const loginid= document.getElementById('{{id}}');apx.apxauth.authentifyme(
'{{id}}',
loginid.querySelector('.signinalias').value,
loginid.querySelector('.signinpassphrase').value,
loginid.querySelector('.signinprivatekey').value,
loginid.querySelector('.signinrememberme').checked
)">
{{{authentifyme}}}
</button>
</div>

View File

@@ -0,0 +1,121 @@
<p data-wco="createid" class="text-center text-neutral-content">
{{{signuptitle}}}
</p>
<div class="paramid">
<div class="mt-2">
<label class="input validator mbt-1">
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="1"
fill="black"
stroke="black"
>
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
</g>
</svg>
<input
class="signupalias"
type="input"
required
placeholder="alias"
pattern="[a-z0-9\-]*"
minlength="3"
maxlength="30"
title="{{{aliastitle}}}"
/>
</label>
<div class="validator-hint hidden">
<p>{{{aliasinvalid}}}</p>
</div>
</div>
<div class="mt-2">
<label class="input validator mt-1">
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="1"
fill="black"
stroke="black"
>
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
</g>
</svg>
<input class="signupemailrecovery" type="email" placeholder="mail@site.com" required />
</label>
<div class="validator-hint hidden">
{{{emailinvalid}}}
</div>
</div>
<div class="mt-2">
<label class="input mt-1">
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="1"
fill="black"
stroke="black"
>
<path d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"></path>
</g>
</svg>
<input type="text" class="signuppassphrase" placeholder="passphrase (option)" />
</label>
</div>
<div class="mt-5">
<button
class="btncreatekey btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
onclick="const authid=document.getElementById('{{id}}');console.log('{{id}}'); apx.apxauth.createIdentity(
'{{id}}',
authid.querySelector('.signupalias').value,
authid.querySelector('.signupemailrecovery').value,
authid.querySelector('.signuppassphrase').value
)"
>
{{{createkey}}}
</button>
</div>
</div>
<div class="getmykeys hidden mt-1">
<div class="flex m-6">
<div class="w-14 flex-none">
<input type="checkbox" checked="checked" class="signuptrustedcheck checkbox checkbox-secondary" />
</div>
<div class="flex-1">
<p class="text-sm text-justify" >{{{trusttext}}}</p>
</div>
</div>
<div class="downloadkeys text-center mt-1">
<button
class="signuppublickey btn btn-outline btn-accent text-white shadow-sm"
>
{{{downloadPuK}}}
</button>
<button
class="signupprivatekey btn btn-outline btn-accent text-white shadow-sm"
>
{{{downloadPrK}}}
</button>
</div>
<div class="mt-2">
<button
class="btncreateidentity btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
onclick="const authid=document.getElementById('{{id}}');apx.apxauth.registerIdentity(
'{{id}}',
authid.querySelector('.signuptrustedcheck').checked
)"
>{{{saveidentity}}}
</button>
<button
class="signupbtnreload hidden btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
onclick="location.reload(true)"
>
{{{nextpage}}}
</button>
</div>
</div>