@mostajs/auth 3.2.0 → 3.3.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/dist/lib/passwordless.d.ts +41 -0
- package/dist/lib/passwordless.js +62 -0
- package/package.json +6 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Repo minimum requis — typiquement `UserRepository` de @mostajs/rbac. */
|
|
2
|
+
export interface PasswordlessUserRepo<U = any> {
|
|
3
|
+
findByEmail(email: string): Promise<U | null>;
|
|
4
|
+
create(data: any): Promise<U | void>;
|
|
5
|
+
}
|
|
6
|
+
export interface FindOrCreatePasswordlessOptions {
|
|
7
|
+
/** Prénom — défaut : local-part de l'email. */
|
|
8
|
+
firstName?: string;
|
|
9
|
+
/** Nom — défaut : '·' (placeholder visible mais non vide). */
|
|
10
|
+
lastName?: string;
|
|
11
|
+
/** Locale — défaut : 'fr'. */
|
|
12
|
+
locale?: string;
|
|
13
|
+
/** Téléphone optionnel. */
|
|
14
|
+
phone?: string;
|
|
15
|
+
/** verified par défaut true (l'utilisateur a prouvé l'email via le canal magic-link). */
|
|
16
|
+
verified?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Lookup-or-create idempotent pour un flux passwordless.
|
|
20
|
+
*
|
|
21
|
+
* Comportement :
|
|
22
|
+
* - normalise l'email (lowercase + trim)
|
|
23
|
+
* - valide format (regex minimaliste, retourne null si invalide)
|
|
24
|
+
* - retourne le user existant
|
|
25
|
+
* - sinon crée avec `password = await hashPassword(randomBytes(24).toString('hex'))`
|
|
26
|
+
* → hash Argon2id valide d'un password aléatoire **inconnu de tous**
|
|
27
|
+
* → `comparePassword(any, this.password)` retournera toujours `false`
|
|
28
|
+
* → impossible de login par password sur ce user (exactement le but)
|
|
29
|
+
* - firstName / lastName placeholder pour respecter required:true du schema rbac
|
|
30
|
+
*
|
|
31
|
+
* Si l'app permet plus tard l'auth password, le user passe par
|
|
32
|
+
* password-reset pour se définir un password réel.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* import { findOrCreatePasswordless } from '@mostajs/auth/lib/passwordless'
|
|
36
|
+
* import { UserRepository } from '@mostajs/rbac/server'
|
|
37
|
+
*
|
|
38
|
+
* const userRepo = new UserRepository(dialect)
|
|
39
|
+
* const user = await findOrCreatePasswordless(userRepo, 'alice@example.com')
|
|
40
|
+
*/
|
|
41
|
+
export declare function findOrCreatePasswordless<U = any>(userRepo: PasswordlessUserRepo<U>, email: string, opts?: FindOrCreatePasswordlessOptions): Promise<U | null>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @mostajs/auth — Passwordless user provisioning — v3.3.0+
|
|
2
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
3
|
+
//
|
|
4
|
+
// Helper pour les flux **magic-link / OAuth / passkey-only** où
|
|
5
|
+
// l'utilisateur n'a jamais à saisir de password. Le User en DB est
|
|
6
|
+
// créé avec un password aléatoire HASHÉ Argon2id que **personne ne
|
|
7
|
+
// connaît** — `comparePassword` retournera toujours `false` pour ce
|
|
8
|
+
// user, garantissant l'impossibilité d'un login par mot de passe (ce
|
|
9
|
+
// qui est exactement le comportement voulu).
|
|
10
|
+
//
|
|
11
|
+
// Le helper prend le `userRepo` en paramètre (DI) — il n'impose pas
|
|
12
|
+
// `@mostajs/rbac` comme dependency, mais accepte n'importe quel repo
|
|
13
|
+
// qui implémente `findByEmail` + `create` avec la signature attendue.
|
|
14
|
+
import { randomBytes } from 'node:crypto';
|
|
15
|
+
import { hashPassword } from './password.js';
|
|
16
|
+
const EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
|
|
17
|
+
/**
|
|
18
|
+
* Lookup-or-create idempotent pour un flux passwordless.
|
|
19
|
+
*
|
|
20
|
+
* Comportement :
|
|
21
|
+
* - normalise l'email (lowercase + trim)
|
|
22
|
+
* - valide format (regex minimaliste, retourne null si invalide)
|
|
23
|
+
* - retourne le user existant
|
|
24
|
+
* - sinon crée avec `password = await hashPassword(randomBytes(24).toString('hex'))`
|
|
25
|
+
* → hash Argon2id valide d'un password aléatoire **inconnu de tous**
|
|
26
|
+
* → `comparePassword(any, this.password)` retournera toujours `false`
|
|
27
|
+
* → impossible de login par password sur ce user (exactement le but)
|
|
28
|
+
* - firstName / lastName placeholder pour respecter required:true du schema rbac
|
|
29
|
+
*
|
|
30
|
+
* Si l'app permet plus tard l'auth password, le user passe par
|
|
31
|
+
* password-reset pour se définir un password réel.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* import { findOrCreatePasswordless } from '@mostajs/auth/lib/passwordless'
|
|
35
|
+
* import { UserRepository } from '@mostajs/rbac/server'
|
|
36
|
+
*
|
|
37
|
+
* const userRepo = new UserRepository(dialect)
|
|
38
|
+
* const user = await findOrCreatePasswordless(userRepo, 'alice@example.com')
|
|
39
|
+
*/
|
|
40
|
+
export async function findOrCreatePasswordless(userRepo, email, opts) {
|
|
41
|
+
const e = String(email ?? '').toLowerCase().trim();
|
|
42
|
+
if (!e || !EMAIL_REGEX.test(e))
|
|
43
|
+
return null;
|
|
44
|
+
const existing = await userRepo.findByEmail(e);
|
|
45
|
+
if (existing)
|
|
46
|
+
return existing;
|
|
47
|
+
const localPart = e.split('@')[0] || 'user';
|
|
48
|
+
// Hash Argon2id d'un random — personne ne connaît le plain
|
|
49
|
+
const randomPlain = randomBytes(24).toString('hex');
|
|
50
|
+
const hashedPassword = await hashPassword(randomPlain);
|
|
51
|
+
await userRepo.create({
|
|
52
|
+
email: e,
|
|
53
|
+
password: hashedPassword,
|
|
54
|
+
firstName: opts?.firstName ?? localPart,
|
|
55
|
+
lastName: opts?.lastName ?? '·',
|
|
56
|
+
phone: opts?.phone,
|
|
57
|
+
locale: opts?.locale ?? 'fr',
|
|
58
|
+
status: 'active',
|
|
59
|
+
verified: opts?.verified ?? true,
|
|
60
|
+
});
|
|
61
|
+
return userRepo.findByEmail(e);
|
|
62
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/auth",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Authentication — complete: email/password (Argon2id) + OAuth + magic link + MFA TOTP + WebAuthn/Passkeys + RGPD lifecycle + device_flow/pkce events + accountId propagation server↔client",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -123,6 +123,11 @@
|
|
|
123
123
|
"import": "./dist/lib/invite-token.js",
|
|
124
124
|
"default": "./dist/lib/invite-token.js"
|
|
125
125
|
},
|
|
126
|
+
"./lib/passwordless": {
|
|
127
|
+
"types": "./dist/lib/passwordless.d.ts",
|
|
128
|
+
"import": "./dist/lib/passwordless.js",
|
|
129
|
+
"default": "./dist/lib/passwordless.js"
|
|
130
|
+
},
|
|
126
131
|
"./lib/mfa-totp": {
|
|
127
132
|
"types": "./dist/lib/mfa-totp.d.ts",
|
|
128
133
|
"import": "./dist/lib/mfa-totp.js",
|