**Motivations:** - website-skeleton needs a real service contract with valid UUIDs and validators - Service wallet required for production use with configurable public key - Iframe styling needs improvement to remove scrollbars and match UserWallet theme **Root causes:** - DEFAULT_VALIDATEURS used placeholder public key that cannot verify signatures - No service wallet generation script for production deployment - Iframe had fixed height causing scrollbars and visual mismatch with dark theme **Correctifs:** - Created real service contract in src/serviceContract.ts with dedicated UUIDs (skeleton-service-uuid-4nkweb-2026) - Added service wallet generation script (generate-service-wallet.mjs) with .env and .env.private files - Improved iframe container styling: increased height (800px), dark background (#1a1a1a), better shadows, hidden scrollbars - Added .env.private to .gitignore for security **Evolutions:** - Service contract automatically loaded on startup and sent to UserWallet iframe - Public key configurable via VITE_SKELETON_SERVICE_PUBLIC_KEY environment variable - Added npm script 'generate-wallet' for easy wallet generation - Enhanced iframe visual integration with UserWallet dark theme **Pages affectées:** - website-skeleton/src/serviceContract.ts (new) - website-skeleton/src/config.ts - website-skeleton/src/main.ts - website-skeleton/generate-service-wallet.mjs (new) - website-skeleton/index.html - website-skeleton/package.json - website-skeleton/.gitignore - website-skeleton/.env (new) - website-skeleton/.env.private (new)
190 lines
8.1 KiB
Markdown
190 lines
8.1 KiB
Markdown
# website-skeleton
|
||
|
||
Squelette d'un site qui intègre UserWallet en iframe : écoute des messages `postMessage` (login-proof, error, contract), vérification des preuves de login via `service-login-verify`, et affichage du statut (accepté / refusé). Connexion via un seul parcours : « Se connecter » puis authentification MFA dans l'iframe.
|
||
|
||
## Prérequis
|
||
|
||
- **service-login-verify** : `npm run build` dans `../service-login-verify` avant d'installer ou builder le skeleton.
|
||
- **UserWallet** : à servir sur l'URL configurée (voir ci‑dessous) pour que l'iframe fonctionne.
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
cd website-skeleton
|
||
npm install
|
||
npm run build
|
||
```
|
||
|
||
## Développement
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
Ouvre par défaut sur `http://localhost:3024`. L'iframe pointe vers UserWallet (`USERWALLET_ORIGIN`).
|
||
|
||
## Configuration
|
||
|
||
- **Origine UserWallet** : `src/config.ts` définit `USERWALLET_ORIGIN`. En dev, défaut `http://localhost:3018` (si UserWallet tourne en dev sur ce port). En prod, défaut `https://userwallet.certificator.4nkweb.com`. Pour override : variable d'environnement `VITE_USERWALLET_ORIGIN` (ex. `VITE_USERWALLET_ORIGIN=http://localhost:3018 npm run dev`).
|
||
- **Contrat de service** : Le skeleton a un contrat de service réel défini dans `src/serviceContract.ts` avec UUID `32b9095a-562d-4239-ae45-2d7ffb1a40de`. Le contrat est chargé automatiquement au démarrage et envoyé à l'iframe UserWallet.
|
||
- **Clé publique du service** : Configurez `VITE_SKELETON_SERVICE_PUBLIC_KEY` avec une clé publique secp256k1 compressée valide (66 hex chars, commençant par 02 ou 03). Exemple : `VITE_SKELETON_SERVICE_PUBLIC_KEY=02abc123... npm run dev`. Si non configurée, un placeholder est utilisé mais les signatures ne pourront pas être vérifiées.
|
||
- **Validateurs** : Les validateurs sont extraits automatiquement du contrat de service skeleton. Le skeleton peut aussi recevoir un contrat personnalisé via `postMessage` (type `contract`) qui remplacera le contrat par défaut.
|
||
|
||
### Chargement dynamique des contrats
|
||
|
||
Le skeleton écoute les messages `postMessage` de type `contract` pour recevoir le contrat et mettre à jour les validateurs dynamiquement :
|
||
|
||
```javascript
|
||
// Exemple d'envoi de contrat depuis le parent
|
||
window.postMessage({
|
||
type: 'contract',
|
||
payload: {
|
||
contrat: {
|
||
uuid: '...',
|
||
validateurs: { membres_du_role: [...] },
|
||
datajson: { types_names_chiffres: 'contrat' }
|
||
},
|
||
contrats_fils: [...], // Optionnel
|
||
actions: [...] // Optionnel, pour extraire l'action login
|
||
}
|
||
}, '*');
|
||
```
|
||
|
||
Le skeleton :
|
||
1. Reçoit le message `contract`
|
||
2. Extrait les validateurs de l'action login (recherche dans `actions` pour un type contenant "login")
|
||
3. Met à jour les `allowedPubkeys` utilisés pour la vérification
|
||
4. Affiche un statut de confirmation
|
||
|
||
Le skeleton utilise automatiquement le contrat de service skeleton au démarrage. Si un contrat personnalisé est reçu via `postMessage`, il remplace le contrat par défaut.
|
||
|
||
## Utilisation
|
||
|
||
1. Lancer UserWallet (dev ou déployé) sur l'URL configurée.
|
||
2. Lancer le skeleton (`npm run dev` ou servir `dist/`).
|
||
3. Ouvrir la page du skeleton : l'iframe affiche UserWallet.
|
||
4. **Envoyer contrat (optionnel)** : envoyer un message `contract` avec le contrat et ses actions pour mettre à jour les validateurs.
|
||
5. **Se connecter** : cliquer « Se connecter » → envoi du contrat à l'iframe, affichage de l'iframe.
|
||
6. **Login MFA** : depuis l'iframe, effectuer le flux de login (MFA) ; à la fin, UserWallet envoie `login-proof` au parent. Le skeleton vérifie la preuve (`verifyLoginProof`) et affiche « Login accepté » ou « Login refusé : … ».
|
||
7. **Description du contrat** : page `contrat.html` (lien depuis l'accueil) décrit le contrat de service skeleton.
|
||
8. **Description du membre** : page `membre.html` décrit le <strong>membre connecté</strong> (l'utilisateur), pas le validateur du service. Clés générées dans l'iframe (UserWallet), stockées en IndexedDB. Distinction créateur du service (wallet .env.private, jamais exposé) vs utilisateur (iframe, IndexedDB).
|
||
|
||
## Exemple d'intégration
|
||
|
||
### Envoi de contrat depuis le parent
|
||
|
||
```javascript
|
||
// Depuis la page parente qui héberge l'iframe
|
||
const iframe = document.getElementById('userwallet');
|
||
const userwalletOrigin = 'https://userwallet.example.com';
|
||
|
||
// Envoyer le contrat avec l'action login
|
||
iframe.contentWindow.postMessage({
|
||
type: 'contract',
|
||
payload: {
|
||
contrat: {
|
||
uuid: 'contrat-uuid-123',
|
||
validateurs: {
|
||
membres_du_role: [
|
||
{
|
||
membre_uuid: 'membre-uuid-456',
|
||
signatures_obligatoires: [
|
||
{
|
||
membre_uuid: 'membre-uuid-456',
|
||
cle_publique: '02abc123...', // Clé publique secp256k1
|
||
cardinalite_minimale: 1
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
datajson: {
|
||
types_names_chiffres: 'contrat'
|
||
}
|
||
},
|
||
contrats_fils: [], // Optionnel
|
||
actions: [
|
||
{
|
||
uuid: 'action-login-uuid',
|
||
types: {
|
||
types_names_chiffres: 'action-login',
|
||
types_uuid: ['action-uuid']
|
||
},
|
||
validateurs_action: {
|
||
membres_du_role: [
|
||
{
|
||
membre_uuid: 'membre-uuid-456',
|
||
signatures_obligatoires: [
|
||
{
|
||
membre_uuid: 'membre-uuid-456',
|
||
cle_publique: '02abc123...',
|
||
cardinalite_minimale: 1
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}, userwalletOrigin);
|
||
```
|
||
|
||
### Réception et vérification de login-proof
|
||
|
||
Le skeleton écoute automatiquement les messages `login-proof` et vérifie la preuve :
|
||
|
||
```javascript
|
||
// Le skeleton fait automatiquement :
|
||
window.addEventListener('message', (event) => {
|
||
if (event.data?.type === 'login-proof') {
|
||
const result = verifyLoginProof(event.data.payload, {
|
||
allowedPubkeys, // Construit depuis les validateurs
|
||
nonceCache, // Cache anti-rejeu
|
||
timestampWindowMs: 300000 // 5 minutes
|
||
});
|
||
|
||
if (result.accept) {
|
||
// Ouvrir la session utilisateur
|
||
console.log('Login accepté');
|
||
} else {
|
||
// Refuser le login
|
||
console.error('Login refusé:', result.reason);
|
||
}
|
||
}
|
||
});
|
||
```
|
||
|
||
### Gestion des erreurs
|
||
|
||
Les raisons de refus possibles :
|
||
- `invalid_proof_structure` : Structure de la preuve invalide
|
||
- `timestamp_out_of_window` : Timestamp hors fenêtre (défaut ±5 min)
|
||
- `nonce_reused` : Nonce déjà utilisé (anti-rejeu)
|
||
- `validators_not_verifiable` : Aucune clé publique dans les validateurs
|
||
- `no_validator_signature` : Aucune signature valide de validateurs
|
||
- `signature_cle_publique_not_authorized` : Signature avec clé non autorisée
|
||
|
||
## Structure
|
||
|
||
- `index.html` : page avec iframe, liens « Description du contrat » / « Description du membre », bouton « Se connecter ».
|
||
- `src/main.ts` : chargement de l'iframe, écoute `message`, envoi du contrat au clic « Se connecter », appel à `verifyLoginProof`, gestion des messages `contract`.
|
||
- `contrat.html` : page de description du contrat de service skeleton (labels, UUIDs, usage).
|
||
- `membre.html` : page de description du <strong>membre connecté</strong> (utilisateur) et de son device (Pair) ; clés iframe / IndexedDB. Distinction service (.env.private) vs utilisateur.
|
||
- `src/config.ts` : `USERWALLET_ORIGIN`, `DEFAULT_VALIDATEURS` (extraits du contrat skeleton), types `Contrat` et `Action`.
|
||
- `src/serviceContract.ts` : contrat de service skeleton avec UUID dédié, action login, et configuration de la clé publique via `VITE_SKELETON_SERVICE_PUBLIC_KEY`.
|
||
- `src/contract.ts` : extraction des validateurs depuis les contrats (`extractLoginValidators`), validation de structure (`isValidContract`, `isValidAction`).
|
||
|
||
## Déploiement
|
||
|
||
- **Production** : `https://skeleton.certificator.4nkweb.com` (proxy → 192.168.1.105:3024).
|
||
- **Installation** : `./install-website-skeleton.sh` sur le backend, puis `./update-proxy-nginx.sh` pour proxy + certificat. Voir `docs/WEBSITE_SKELETON.md`.
|
||
|
||
## Références
|
||
|
||
- `docs/WEBSITE_SKELETON.md` (documentation détaillée)
|
||
- `features/service-login-verify.md`
|
||
- `features/userwallet-contrat-login-reste-a-faire.md` (§ 3.7)
|
||
- `userwallet/` (iframe)
|
||
- `service-login-verify/` (vérification)
|