@mostajs/auth 2.3.3 → 2.4.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.
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface CredentialsProviderConfig {
|
|
2
|
+
/** Lookup the user row by email (case-insensitive). Return null if not found.
|
|
3
|
+
* Conseillé : passer une fonction qui utilise UserRepository de @mostajs/rbac
|
|
4
|
+
* pour bénéficier de findByEmail (qui lowercase l'email côté repo). */
|
|
5
|
+
findUserByEmail: (email: string) => Promise<any | null>;
|
|
6
|
+
/** Default role assigned when the user has no role relations (default: 'user'). */
|
|
7
|
+
defaultRole?: string;
|
|
8
|
+
/** Custom check before allowing login (e.g. email verified, 2FA).
|
|
9
|
+
* Return false to block. Default: status !== 'disabled' && status !== 'locked'. */
|
|
10
|
+
checkUserAllowed?: (user: any) => boolean | Promise<boolean>;
|
|
11
|
+
/** Custom role resolution. Default: first user.roles[].name or fallback. */
|
|
12
|
+
resolveRole?: (user: any) => string | Promise<string>;
|
|
13
|
+
/** Custom display name. Default: "<firstName> <lastName>" trimmed → email. */
|
|
14
|
+
resolveName?: (user: any) => string;
|
|
15
|
+
/** Provider id (default: 'credentials'). Useful if multiple providers coexist. */
|
|
16
|
+
id?: string;
|
|
17
|
+
/** Provider display name (default: 'Email + Password'). */
|
|
18
|
+
name?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build a NextAuth Credentials provider config (returned as plain object,
|
|
22
|
+
* directly passable to `NextAuth({ providers: [provider], ... })` without
|
|
23
|
+
* any next-auth imports côté consumer).
|
|
24
|
+
*
|
|
25
|
+
* Usage :
|
|
26
|
+
* import { createCredentialsProvider } from '@mostajs/auth/server'
|
|
27
|
+
*
|
|
28
|
+
* NextAuth({
|
|
29
|
+
* providers: [
|
|
30
|
+
* createCredentialsProvider({
|
|
31
|
+
* findUserByEmail: async (email) => userRepo.findByEmail(email),
|
|
32
|
+
* defaultRole: 'user',
|
|
33
|
+
* }),
|
|
34
|
+
* ],
|
|
35
|
+
* })
|
|
36
|
+
*/
|
|
37
|
+
export declare function createCredentialsProvider(config: CredentialsProviderConfig): {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
type: "credentials";
|
|
41
|
+
credentials: {
|
|
42
|
+
email: {
|
|
43
|
+
label: string;
|
|
44
|
+
type: string;
|
|
45
|
+
};
|
|
46
|
+
password: {
|
|
47
|
+
label: string;
|
|
48
|
+
type: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
authorize(credentials: any): Promise<{
|
|
52
|
+
id: any;
|
|
53
|
+
email: any;
|
|
54
|
+
name: string;
|
|
55
|
+
role: string;
|
|
56
|
+
} | null>;
|
|
57
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @mostajs/auth — Email + password credentials provider for NextAuth
|
|
2
|
+
//
|
|
3
|
+
// Factorise le boilerplate NextAuth Credentials que chaque app refait. Le
|
|
4
|
+
// consumer obtient un provider PRÊT À L'EMPLOI à passer à NextAuth({providers}) :
|
|
5
|
+
// il n'importe ni `next-auth/providers/credentials`, ni bcrypt, ni le
|
|
6
|
+
// UserRepository — tout est encapsulé.
|
|
7
|
+
//
|
|
8
|
+
// Pendant naturel de createApiKeyProvider (même style de retour : objet
|
|
9
|
+
// provider config plat). Différence : authentication via email/password
|
|
10
|
+
// (UI register/signin) vs API key (programmatic).
|
|
11
|
+
//
|
|
12
|
+
// Le caller fournit `findUserByEmail` (ou laisse passer pour un default
|
|
13
|
+
// fail-closed). Pour leverager directement @mostajs/rbac, il suffit de
|
|
14
|
+
// passer `(email) => new UserRepository(dialect).findByEmail(email)`.
|
|
15
|
+
//
|
|
16
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
17
|
+
import { comparePassword } from './password.js';
|
|
18
|
+
function defaultStatusCheck(user) {
|
|
19
|
+
return user.status !== 'disabled' && user.status !== 'locked';
|
|
20
|
+
}
|
|
21
|
+
function defaultRoleResolver(user, fallback) {
|
|
22
|
+
if (user.roles?.length > 0) {
|
|
23
|
+
const r = user.roles[0];
|
|
24
|
+
return typeof r === 'string' ? r : (r?.name ?? fallback);
|
|
25
|
+
}
|
|
26
|
+
return user.role ?? fallback;
|
|
27
|
+
}
|
|
28
|
+
function defaultNameResolver(user) {
|
|
29
|
+
return `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() || (user.email ?? '');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build a NextAuth Credentials provider config (returned as plain object,
|
|
33
|
+
* directly passable to `NextAuth({ providers: [provider], ... })` without
|
|
34
|
+
* any next-auth imports côté consumer).
|
|
35
|
+
*
|
|
36
|
+
* Usage :
|
|
37
|
+
* import { createCredentialsProvider } from '@mostajs/auth/server'
|
|
38
|
+
*
|
|
39
|
+
* NextAuth({
|
|
40
|
+
* providers: [
|
|
41
|
+
* createCredentialsProvider({
|
|
42
|
+
* findUserByEmail: async (email) => userRepo.findByEmail(email),
|
|
43
|
+
* defaultRole: 'user',
|
|
44
|
+
* }),
|
|
45
|
+
* ],
|
|
46
|
+
* })
|
|
47
|
+
*/
|
|
48
|
+
export function createCredentialsProvider(config) {
|
|
49
|
+
const defaultRole = config.defaultRole ?? 'user';
|
|
50
|
+
const checkUserAllowed = config.checkUserAllowed ?? defaultStatusCheck;
|
|
51
|
+
const resolveRole = config.resolveRole ?? ((u) => defaultRoleResolver(u, defaultRole));
|
|
52
|
+
const resolveName = config.resolveName ?? defaultNameResolver;
|
|
53
|
+
return {
|
|
54
|
+
id: config.id ?? 'credentials',
|
|
55
|
+
name: config.name ?? 'Email + Password',
|
|
56
|
+
type: 'credentials',
|
|
57
|
+
credentials: {
|
|
58
|
+
email: { label: 'Email', type: 'email' },
|
|
59
|
+
password: { label: 'Password', type: 'password' },
|
|
60
|
+
},
|
|
61
|
+
async authorize(credentials) {
|
|
62
|
+
if (!credentials?.email || !credentials?.password)
|
|
63
|
+
return null;
|
|
64
|
+
const email = String(credentials.email).toLowerCase().trim();
|
|
65
|
+
const user = await config.findUserByEmail(email);
|
|
66
|
+
if (!user)
|
|
67
|
+
return null;
|
|
68
|
+
const allowed = await checkUserAllowed(user);
|
|
69
|
+
if (!allowed)
|
|
70
|
+
return null;
|
|
71
|
+
const valid = await comparePassword(String(credentials.password), user.password);
|
|
72
|
+
if (!valid)
|
|
73
|
+
return null;
|
|
74
|
+
const role = await resolveRole(user);
|
|
75
|
+
const name = resolveName(user);
|
|
76
|
+
return {
|
|
77
|
+
id: user.id,
|
|
78
|
+
email: user.email,
|
|
79
|
+
name,
|
|
80
|
+
role,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -20,5 +20,7 @@ export { createPasswordResetHandlers, generateResetToken } from './lib/password-
|
|
|
20
20
|
export type { PasswordResetConfig } from './lib/password-reset';
|
|
21
21
|
export { createApiKeyProvider } from './lib/apikey-provider';
|
|
22
22
|
export type { ApiKeyProviderConfig } from './lib/apikey-provider';
|
|
23
|
+
export { createCredentialsProvider } from './lib/credentials-provider';
|
|
24
|
+
export type { CredentialsProviderConfig } from './lib/credentials-provider';
|
|
23
25
|
export { enrichTokenWithPlan, enrichSessionWithPlan } from './lib/session-enrichment';
|
|
24
26
|
export type { SessionEnrichmentConfig } from './lib/session-enrichment';
|
package/dist/server.js
CHANGED
|
@@ -27,5 +27,6 @@ export { createVerificationHandlers, generateVerifyToken } from './lib/email-ver
|
|
|
27
27
|
export { createPasswordResetHandlers, generateResetToken } from './lib/password-reset.js';
|
|
28
28
|
// API key provider
|
|
29
29
|
export { createApiKeyProvider } from './lib/apikey-provider.js';
|
|
30
|
+
export { createCredentialsProvider } from './lib/credentials-provider.js';
|
|
30
31
|
// Session enrichment
|
|
31
32
|
export { enrichTokenWithPlan, enrichSessionWithPlan } from './lib/session-enrichment.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Authentication — NextAuth, password hashing, session management",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -83,6 +83,11 @@
|
|
|
83
83
|
"import": "./dist/lib/apikey-provider.js",
|
|
84
84
|
"default": "./dist/lib/apikey-provider.js"
|
|
85
85
|
},
|
|
86
|
+
"./lib/credentials-provider": {
|
|
87
|
+
"types": "./dist/lib/credentials-provider.d.ts",
|
|
88
|
+
"import": "./dist/lib/credentials-provider.js",
|
|
89
|
+
"default": "./dist/lib/credentials-provider.js"
|
|
90
|
+
},
|
|
86
91
|
"./lib/check-request": {
|
|
87
92
|
"types": "./dist/lib/check-request.d.ts",
|
|
88
93
|
"import": "./dist/lib/check-request.js",
|
|
@@ -120,7 +125,7 @@
|
|
|
120
125
|
},
|
|
121
126
|
"scripts": {
|
|
122
127
|
"build": "tsc && npm run fix-esm",
|
|
123
|
-
"fix-esm": "find dist -name '*.js' -exec sed -i -E \"s|from '(\\\\.{1,2}/[^']+)'(;?)|from '\\\\1.js'\\\\2|g; s|from \\\"(\\\\.{1,2}/[^\\\"]+)\\\"(;?)|from \\\"\\\\1.js\\\"\\\\2|g\" {} \\; && find dist -name '*.js' -exec sed -i -E \"s|\\\\.js\\\\.js|.js|g\" {} \\;",
|
|
128
|
+
"fix-esm": "find dist -name '*.js' -exec sed -i -E \"s|from '(\\\\.{1,2}/[^']+)'(;?)|from '\\\\1.js'\\\\2|g; s|from \\\"(\\\\.{1,2}/[^\\\"]+)\\\"(;?)|from \\\"\\\\1.js\\\"\\\\2|g\" {} \\; && find dist -name '*.js' -exec sed -i -E \"s|\\\\.js\\\\.js|.js|g; s|\\\\.json\\\\.js|.json|g; s|\\\\.css\\\\.js|.css|g\" {} \\;",
|
|
124
129
|
"prepublishOnly": "npm run build"
|
|
125
130
|
},
|
|
126
131
|
"dependencies": {
|