@mostajs/auth-flow-qr 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +20 -0
- package/llms.txt +11 -0
- package/package.json +33 -0
- package/src/index.js +47 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @mostajs/auth-flow-qr
|
|
2
|
+
|
|
3
|
+
**Auteur** : Dr Hamid MADANI <drmdh@msn.com>
|
|
4
|
+
|
|
5
|
+
Connexion **par appairage QR — option B**, type **« WhatsApp Web »** :
|
|
6
|
+
1. Un appareil **anonyme** (page de login) appelle `start()` → affiche un **QR** du lien d'approbation et **interroge** (`poll`) le statut.
|
|
7
|
+
2. Un appareil **déjà connecté** scanne le QR → ouvre la page d'approbation → `approve(id, email)`.
|
|
8
|
+
3. Le `poll` de l'anonyme passe à **`approved`** → il **`consume(id)`** (one-time) → l'hôte ouvre la session pour `subject`.
|
|
9
|
+
|
|
10
|
+
Store d'appairage en mémoire (`pending → approved → consume`), **pur** (`node:crypto`). L'hôte branche le **rendu QR** (`@mostajs/qrpanel`) et la **résolution du sujet** (`@mostajs/auth`) — composition (DEVRULES §10). Membre de `mosta-auth-stack`.
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
import { createAuthFlowQr } from '@mostajs/auth-flow-qr';
|
|
14
|
+
const flow = createAuthFlowQr({ ttl: 120 });
|
|
15
|
+
const { id } = flow.start(); // QR → /login/pair/approve?id=${id}
|
|
16
|
+
// téléphone connecté : flow.approve(id, session.email)
|
|
17
|
+
// desktop poll : flow.poll(id).status === 'approved' → flow.consume(id).subject → session
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
API : `llms.txt`. Tests : `npm test` (4). AGPL-3.0-or-later.
|
package/llms.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @mostajs/auth-flow-qr
|
|
2
|
+
Connexion par appairage QR (option B, type WhatsApp Web). Store mémoire pending→approved→consume (one-time). Pur node:crypto. Membre mosta-auth-stack. Pilotable .env.
|
|
3
|
+
## API
|
|
4
|
+
createAuthFlowQr({ ttl=120, now }) →
|
|
5
|
+
- start() → { id, ttl } : nouvel appairage (appareil anonyme) ; afficher un QR de l'URL d'approbation contenant id
|
|
6
|
+
- pending(id) → bool
|
|
7
|
+
- approve(id, subject) → bool : appareil DÉJÀ connecté approuve (subject=email/userId)
|
|
8
|
+
- poll(id) → { status:'pending'|'approved'|'expired'|'unknown' } : polling de l'anonyme
|
|
9
|
+
- consume(id) → { subject } | null : one-time, après 'approved' → ouvrir la session
|
|
10
|
+
## Flux hôte
|
|
11
|
+
Desktop: POST start → QR de /login/pair/approve?id=… + poll. Téléphone connecté: scanne → approve(id, session.email). Desktop: poll 'approved' → consume → findByEmail(subject) → session.
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mostajs/auth-flow-qr",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Connexion par appairage QR (option B, type « WhatsApp Web ») : un appareil anonyme affiche un QR + poll ; un appareil déjà connecté l'approuve → session. Store d'appairage pending/approved/consume (one-time). Membre de mosta-auth-stack. Pilotable .env.",
|
|
5
|
+
"license": "AGPL-3.0-or-later",
|
|
6
|
+
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"llms.txt",
|
|
12
|
+
"README.md",
|
|
13
|
+
"CHANGELOG.md"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./src/index.js"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mostajs",
|
|
20
|
+
"auth",
|
|
21
|
+
"qr",
|
|
22
|
+
"pairing",
|
|
23
|
+
"device-flow",
|
|
24
|
+
"login",
|
|
25
|
+
"whatsapp-web"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@mostajs/mjs-unit": "^0.3.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "bash test-scripts/run-tests.sh"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @mostajs/auth-flow-qr — connexion par appairage QR (option B, type « WhatsApp Web »). Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
2
|
+
// Flux : un appareil ANONYME (page login) appelle start() → affiche un QR du lien d'approbation ; il POLL le statut.
|
|
3
|
+
// Un appareil DÉJÀ CONNECTÉ scanne le QR → approve(id, sujet). Le poll passe à 'approved' → l'anonyme consume(id) (une seule fois) → ouvre la session.
|
|
4
|
+
// Pur (node:crypto, store mémoire). L'hôte branche le rendu QR (@mostajs/qrpanel) et la résolution du sujet (auth) — DEVRULES §10.
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param ttl durée de vie d'un appairage en secondes (def. 120).
|
|
9
|
+
* @param now injection horloge (tests).
|
|
10
|
+
*/
|
|
11
|
+
export function createAuthFlowQr({ ttl = 120, now = () => Date.now() } = {}) {
|
|
12
|
+
const pairings = new Map(); // id → { status:'pending'|'approved', subject, exp }
|
|
13
|
+
const gc = () => { const t = now(); for (const [k, v] of pairings) if (v.exp < t) pairings.delete(k); };
|
|
14
|
+
|
|
15
|
+
/** Démarre un appairage (appareil anonyme) → { id, ttl }. */
|
|
16
|
+
function start() {
|
|
17
|
+
gc();
|
|
18
|
+
const id = crypto.randomBytes(18).toString('base64url');
|
|
19
|
+
pairings.set(id, { status: 'pending', subject: null, exp: now() + ttl * 1000 });
|
|
20
|
+
return { id, ttl };
|
|
21
|
+
}
|
|
22
|
+
/** L'appairage est-il en attente d'approbation (et non expiré) ? */
|
|
23
|
+
function pending(id) { const s = pairings.get(id); return !!s && s.exp >= now() && s.status === 'pending'; }
|
|
24
|
+
/** Approuve un appairage (appareil déjà connecté) avec le sujet (email/userId). → true si OK. */
|
|
25
|
+
function approve(id, subject) {
|
|
26
|
+
const s = pairings.get(id);
|
|
27
|
+
if (!s || s.exp < now() || s.status !== 'pending' || !subject) return false;
|
|
28
|
+
s.status = 'approved'; s.subject = subject; return true;
|
|
29
|
+
}
|
|
30
|
+
/** Statut courant (pour le polling de l'appareil anonyme). Pas de gc() ici → un appairage expiré renvoie 'expired' (UX), pas 'unknown'. */
|
|
31
|
+
function poll(id) {
|
|
32
|
+
const s = pairings.get(id);
|
|
33
|
+
if (!s) return { status: 'unknown' };
|
|
34
|
+
if (s.exp < now()) return { status: 'expired' };
|
|
35
|
+
return { status: s.status };
|
|
36
|
+
}
|
|
37
|
+
/** Consomme un appairage APPROUVÉ (une seule fois) → { subject } | null. */
|
|
38
|
+
function consume(id) {
|
|
39
|
+
const s = pairings.get(id);
|
|
40
|
+
if (!s || s.status !== 'approved' || s.exp < now()) return null;
|
|
41
|
+
pairings.delete(id);
|
|
42
|
+
return { subject: s.subject };
|
|
43
|
+
}
|
|
44
|
+
return { start, pending, approve, poll, consume, get size() { return pairings.size; } };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default createAuthFlowQr;
|