najm-auth 2.0.12 → 2.0.14
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/README.md +6 -0
- package/dist/{NajmAuthClient-0yzFb9CR.d.ts → NajmAuthClient-Cn9bObLB.d.ts} +8 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +11 -0
- package/dist/client/react/index.d.ts +1 -1
- package/dist/client/react/index.js +36 -33
- package/dist/client/server/index.d.ts +1 -1
- package/dist/client/server/index.js +11 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +29 -5
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -746,6 +746,12 @@ const options = {
|
|
|
746
746
|
// Verify the password without minting access/refresh tokens.
|
|
747
747
|
const user = await authService.verifyCredentials({ identifier, password });
|
|
748
748
|
|
|
749
|
+
// Or narrowly accept only an unverified pending account with one exact role.
|
|
750
|
+
const pendingSponsor = await authService.verifyPendingCredentials(
|
|
751
|
+
{ identifier, password },
|
|
752
|
+
'sponsor',
|
|
753
|
+
);
|
|
754
|
+
|
|
749
755
|
if (await appRequiresPasswordSetup(user.id)) {
|
|
750
756
|
// Revokes normal sessions and writes only an HttpOnly, SameSite=Strict,
|
|
751
757
|
// browser-session cookie. The database stores only its SHA-256 hash.
|
|
@@ -222,6 +222,14 @@ declare class NajmAuthClient {
|
|
|
222
222
|
*/
|
|
223
223
|
hydrate(session: HydrateSession | null): void;
|
|
224
224
|
isHydrated(): boolean;
|
|
225
|
+
/**
|
|
226
|
+
* A fresh client with the same config — unhydrated, and without tab sync.
|
|
227
|
+
*
|
|
228
|
+
* Server rendering needs one client per request. A single process serves
|
|
229
|
+
* every user, so the hydration latch on a shared client would otherwise pin
|
|
230
|
+
* every later render to the first request's session.
|
|
231
|
+
*/
|
|
232
|
+
fork(): NajmAuthClient;
|
|
225
233
|
on<K extends AuthEvent>(event: K, handler: AuthEventHandler<K>): void;
|
|
226
234
|
off<K extends AuthEvent>(event: K, handler: AuthEventHandler<K>): void;
|
|
227
235
|
subscribe(listener: (state: AuthState) => void): () => void;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DecodedToken, T as TabSyncMessage, S as SyncPayload } from '../NajmAuthClient-
|
|
2
|
-
export { A as AuthClientConfig, a as AuthError, b as AuthEvent, c as AuthEventHandler, d as AuthEventMap, e as AuthState, f as AuthUser, F as FetchClient, H as HydrateSession, N as NajmAuthClient, O as OAuthLoginOptions, g as OAuthProvider, R as RequestOptions, h as RetryConfig, i as ServerResponse, j as TokenPair, k as createAuthClient } from '../NajmAuthClient-
|
|
1
|
+
import { D as DecodedToken, T as TabSyncMessage, S as SyncPayload } from '../NajmAuthClient-Cn9bObLB.js';
|
|
2
|
+
export { A as AuthClientConfig, a as AuthError, b as AuthEvent, c as AuthEventHandler, d as AuthEventMap, e as AuthState, f as AuthUser, F as FetchClient, H as HydrateSession, N as NajmAuthClient, O as OAuthLoginOptions, g as OAuthProvider, R as RequestOptions, h as RetryConfig, i as ServerResponse, j as TokenPair, k as createAuthClient } from '../NajmAuthClient-Cn9bObLB.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Decode a JWT token payload without verification.
|
package/dist/client/index.js
CHANGED
|
@@ -422,6 +422,16 @@ var NajmAuthClient = class _NajmAuthClient {
|
|
|
422
422
|
isHydrated() {
|
|
423
423
|
return this._hydrated;
|
|
424
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* A fresh client with the same config — unhydrated, and without tab sync.
|
|
427
|
+
*
|
|
428
|
+
* Server rendering needs one client per request. A single process serves
|
|
429
|
+
* every user, so the hydration latch on a shared client would otherwise pin
|
|
430
|
+
* every later render to the first request's session.
|
|
431
|
+
*/
|
|
432
|
+
fork() {
|
|
433
|
+
return new _NajmAuthClient({ ...this.config, tabSync: false });
|
|
434
|
+
}
|
|
425
435
|
// =========================================================================
|
|
426
436
|
// Events
|
|
427
437
|
// =========================================================================
|
|
@@ -505,6 +515,7 @@ var NajmAuthClient = class _NajmAuthClient {
|
|
|
505
515
|
}
|
|
506
516
|
scheduleRefresh(decoded) {
|
|
507
517
|
this.clearRefreshTimer();
|
|
518
|
+
if (typeof window === "undefined") return;
|
|
508
519
|
const ttl = getTokenTTL(decoded);
|
|
509
520
|
if (ttl <= 0) return;
|
|
510
521
|
const delay = ttl * this.threshold * 1e3;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, CSSProperties, ReactElement } from 'react';
|
|
4
|
-
import { N as NajmAuthClient, H as HydrateSession, e as AuthState, f as AuthUser, a as AuthError, O as OAuthLoginOptions, b as AuthEvent, d as AuthEventMap } from '../../NajmAuthClient-
|
|
4
|
+
import { N as NajmAuthClient, H as HydrateSession, e as AuthState, f as AuthUser, a as AuthError, O as OAuthLoginOptions, b as AuthEvent, d as AuthEventMap } from '../../NajmAuthClient-Cn9bObLB.js';
|
|
5
5
|
|
|
6
6
|
interface AuthProviderProps {
|
|
7
7
|
client: NajmAuthClient;
|
|
@@ -3,7 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
4
|
|
|
5
5
|
// src/client/react/AuthProvider.tsx
|
|
6
|
-
import { useEffect, useRef } from "react";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
7
|
|
|
8
8
|
// src/client/react/context.ts
|
|
9
9
|
import { createContext, useContext } from "react";
|
|
@@ -49,12 +49,15 @@ function AuthProvider({
|
|
|
49
49
|
initialSession,
|
|
50
50
|
autoRefresh = true
|
|
51
51
|
}) {
|
|
52
|
+
const [active] = useState(
|
|
53
|
+
() => typeof window === "undefined" && initialSession !== void 0 ? client.fork() : client
|
|
54
|
+
);
|
|
52
55
|
const hydrated = useRef(false);
|
|
53
56
|
if (!hydrated.current && initialSession !== void 0) {
|
|
54
|
-
|
|
57
|
+
active.hydrate(initialSession);
|
|
55
58
|
hydrated.current = true;
|
|
56
59
|
}
|
|
57
|
-
return /* @__PURE__ */ jsxs(AuthClientContext.Provider, { value:
|
|
60
|
+
return /* @__PURE__ */ jsxs(AuthClientContext.Provider, { value: active, children: [
|
|
58
61
|
autoRefresh ? /* @__PURE__ */ jsx(AutoRefresh, {}) : null,
|
|
59
62
|
children
|
|
60
63
|
] });
|
|
@@ -98,11 +101,11 @@ function useUser() {
|
|
|
98
101
|
__name(useUser, "useUser");
|
|
99
102
|
|
|
100
103
|
// src/client/react/useSession.ts
|
|
101
|
-
import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
|
|
104
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
102
105
|
function useSession(opts) {
|
|
103
106
|
const client = useAuthClient();
|
|
104
107
|
const state = useAuth();
|
|
105
|
-
const [isLoading, setIsLoading] =
|
|
108
|
+
const [isLoading, setIsLoading] = useState2(() => !client.isHydrated());
|
|
106
109
|
const didInit = useRef2(false);
|
|
107
110
|
useEffect2(() => {
|
|
108
111
|
if (didInit.current) return;
|
|
@@ -145,11 +148,11 @@ function usePermissions() {
|
|
|
145
148
|
__name(usePermissions, "usePermissions");
|
|
146
149
|
|
|
147
150
|
// src/client/react/useLogin.ts
|
|
148
|
-
import { useCallback, useState as
|
|
151
|
+
import { useCallback, useState as useState3 } from "react";
|
|
149
152
|
function useLogin(opts) {
|
|
150
153
|
const client = useAuthClient();
|
|
151
|
-
const [isLoading, setIsLoading] =
|
|
152
|
-
const [error, setError] =
|
|
154
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
155
|
+
const [error, setError] = useState3(null);
|
|
153
156
|
const login = useCallback(async (credentials) => {
|
|
154
157
|
setIsLoading(true);
|
|
155
158
|
setError(null);
|
|
@@ -169,11 +172,11 @@ function useLogin(opts) {
|
|
|
169
172
|
__name(useLogin, "useLogin");
|
|
170
173
|
|
|
171
174
|
// src/client/react/useRegister.ts
|
|
172
|
-
import { useCallback as useCallback2, useState as
|
|
175
|
+
import { useCallback as useCallback2, useState as useState4 } from "react";
|
|
173
176
|
function useRegister(opts) {
|
|
174
177
|
const client = useAuthClient();
|
|
175
|
-
const [isLoading, setIsLoading] =
|
|
176
|
-
const [error, setError] =
|
|
178
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
179
|
+
const [error, setError] = useState4(null);
|
|
177
180
|
const register = useCallback2(async (data) => {
|
|
178
181
|
setIsLoading(true);
|
|
179
182
|
setError(null);
|
|
@@ -193,10 +196,10 @@ function useRegister(opts) {
|
|
|
193
196
|
__name(useRegister, "useRegister");
|
|
194
197
|
|
|
195
198
|
// src/client/react/useLogout.ts
|
|
196
|
-
import { useCallback as useCallback3, useState as
|
|
199
|
+
import { useCallback as useCallback3, useState as useState5 } from "react";
|
|
197
200
|
function useLogout(opts) {
|
|
198
201
|
const client = useAuthClient();
|
|
199
|
-
const [isLoading, setIsLoading] =
|
|
202
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
200
203
|
const logout = useCallback3(async () => {
|
|
201
204
|
setIsLoading(true);
|
|
202
205
|
try {
|
|
@@ -218,12 +221,12 @@ function useLogout(opts) {
|
|
|
218
221
|
__name(useLogout, "useLogout");
|
|
219
222
|
|
|
220
223
|
// src/client/react/useForgotPassword.ts
|
|
221
|
-
import { useCallback as useCallback4, useState as
|
|
224
|
+
import { useCallback as useCallback4, useState as useState6 } from "react";
|
|
222
225
|
function useForgotPassword(opts) {
|
|
223
226
|
const client = useAuthClient();
|
|
224
|
-
const [isLoading, setIsLoading] =
|
|
225
|
-
const [error, setError] =
|
|
226
|
-
const [isSuccess, setIsSuccess] =
|
|
227
|
+
const [isLoading, setIsLoading] = useState6(false);
|
|
228
|
+
const [error, setError] = useState6(null);
|
|
229
|
+
const [isSuccess, setIsSuccess] = useState6(false);
|
|
227
230
|
const forgotPassword = useCallback4(async (data) => {
|
|
228
231
|
setIsLoading(true);
|
|
229
232
|
setError(null);
|
|
@@ -249,12 +252,12 @@ function useForgotPassword(opts) {
|
|
|
249
252
|
__name(useForgotPassword, "useForgotPassword");
|
|
250
253
|
|
|
251
254
|
// src/client/react/useResetPassword.ts
|
|
252
|
-
import { useCallback as useCallback5, useState as
|
|
255
|
+
import { useCallback as useCallback5, useState as useState7 } from "react";
|
|
253
256
|
function useResetPassword(opts) {
|
|
254
257
|
const client = useAuthClient();
|
|
255
|
-
const [isLoading, setIsLoading] =
|
|
256
|
-
const [error, setError] =
|
|
257
|
-
const [isSuccess, setIsSuccess] =
|
|
258
|
+
const [isLoading, setIsLoading] = useState7(false);
|
|
259
|
+
const [error, setError] = useState7(null);
|
|
260
|
+
const [isSuccess, setIsSuccess] = useState7(false);
|
|
258
261
|
const resetPassword = useCallback5(async (data) => {
|
|
259
262
|
setIsLoading(true);
|
|
260
263
|
setError(null);
|
|
@@ -280,12 +283,12 @@ function useResetPassword(opts) {
|
|
|
280
283
|
__name(useResetPassword, "useResetPassword");
|
|
281
284
|
|
|
282
285
|
// src/client/react/useChangePassword.ts
|
|
283
|
-
import { useCallback as useCallback6, useState as
|
|
286
|
+
import { useCallback as useCallback6, useState as useState8 } from "react";
|
|
284
287
|
function useChangePassword(opts) {
|
|
285
288
|
const client = useAuthClient();
|
|
286
|
-
const [isLoading, setIsLoading] =
|
|
287
|
-
const [error, setError] =
|
|
288
|
-
const [isSuccess, setIsSuccess] =
|
|
289
|
+
const [isLoading, setIsLoading] = useState8(false);
|
|
290
|
+
const [error, setError] = useState8(null);
|
|
291
|
+
const [isSuccess, setIsSuccess] = useState8(false);
|
|
289
292
|
const changePassword = useCallback6(async (data) => {
|
|
290
293
|
setIsLoading(true);
|
|
291
294
|
setError(null);
|
|
@@ -311,11 +314,11 @@ function useChangePassword(opts) {
|
|
|
311
314
|
__name(useChangePassword, "useChangePassword");
|
|
312
315
|
|
|
313
316
|
// src/client/react/useGoogleLogin.ts
|
|
314
|
-
import { useCallback as useCallback7, useState as
|
|
317
|
+
import { useCallback as useCallback7, useState as useState9 } from "react";
|
|
315
318
|
function useGoogleLogin(options) {
|
|
316
319
|
const client = useAuthClient();
|
|
317
|
-
const [isRedirecting, setIsRedirecting] =
|
|
318
|
-
const [error, setError] =
|
|
320
|
+
const [isRedirecting, setIsRedirecting] = useState9(false);
|
|
321
|
+
const [error, setError] = useState9(null);
|
|
319
322
|
const fail = useCallback7((value) => {
|
|
320
323
|
const next = value instanceof Error ? value : new Error(String(value));
|
|
321
324
|
setIsRedirecting(false);
|
|
@@ -345,12 +348,12 @@ function useGoogleLogin(options) {
|
|
|
345
348
|
__name(useGoogleLogin, "useGoogleLogin");
|
|
346
349
|
|
|
347
350
|
// src/client/react/useOAuthCallback.ts
|
|
348
|
-
import { useCallback as useCallback8, useRef as useRef3, useState as
|
|
351
|
+
import { useCallback as useCallback8, useRef as useRef3, useState as useState10 } from "react";
|
|
349
352
|
function useOAuthCallback(options) {
|
|
350
353
|
const client = useAuthClient();
|
|
351
354
|
const promise = useRef3(null);
|
|
352
|
-
const [isLoading, setIsLoading] =
|
|
353
|
-
const [error, setError] =
|
|
355
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
356
|
+
const [error, setError] = useState10(null);
|
|
354
357
|
const complete = useCallback8(async () => {
|
|
355
358
|
if (promise.current) return promise.current;
|
|
356
359
|
setIsLoading(true);
|
|
@@ -388,7 +391,7 @@ function useAuthEvent(event, handler) {
|
|
|
388
391
|
__name(useAuthEvent, "useAuthEvent");
|
|
389
392
|
|
|
390
393
|
// src/client/react/useAuthEvents.ts
|
|
391
|
-
import { useEffect as useEffect4, useRef as useRef5, useState as
|
|
394
|
+
import { useEffect as useEffect4, useRef as useRef5, useState as useState11 } from "react";
|
|
392
395
|
var ALL_EVENTS = [
|
|
393
396
|
"login",
|
|
394
397
|
"logout",
|
|
@@ -401,7 +404,7 @@ var ALL_EVENTS = [
|
|
|
401
404
|
function useAuthEvents(opts) {
|
|
402
405
|
const client = useAuthClient();
|
|
403
406
|
const max = opts?.maxEntries ?? 100;
|
|
404
|
-
const [entries, setEntries] =
|
|
407
|
+
const [entries, setEntries] = useState11([]);
|
|
405
408
|
const maxRef = useRef5(max);
|
|
406
409
|
maxRef.current = max;
|
|
407
410
|
useEffect4(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as AuthUser, F as FetchClient, N as NajmAuthClient, h as RetryConfig } from '../../NajmAuthClient-
|
|
1
|
+
import { f as AuthUser, F as FetchClient, N as NajmAuthClient, h as RetryConfig } from '../../NajmAuthClient-Cn9bObLB.js';
|
|
2
2
|
import { SessionRecoveryFailure } from '../edge.js';
|
|
3
3
|
export { SessionRecoveryErrorDetails, SessionRecoveryFailureReason, withAuthMiddleware } from '../edge.js';
|
|
4
4
|
import 'next/server';
|
|
@@ -1119,6 +1119,16 @@ var NajmAuthClient = class _NajmAuthClient {
|
|
|
1119
1119
|
isHydrated() {
|
|
1120
1120
|
return this._hydrated;
|
|
1121
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* A fresh client with the same config — unhydrated, and without tab sync.
|
|
1124
|
+
*
|
|
1125
|
+
* Server rendering needs one client per request. A single process serves
|
|
1126
|
+
* every user, so the hydration latch on a shared client would otherwise pin
|
|
1127
|
+
* every later render to the first request's session.
|
|
1128
|
+
*/
|
|
1129
|
+
fork() {
|
|
1130
|
+
return new _NajmAuthClient({ ...this.config, tabSync: false });
|
|
1131
|
+
}
|
|
1122
1132
|
// =========================================================================
|
|
1123
1133
|
// Events
|
|
1124
1134
|
// =========================================================================
|
|
@@ -1202,6 +1212,7 @@ var NajmAuthClient = class _NajmAuthClient {
|
|
|
1202
1212
|
}
|
|
1203
1213
|
scheduleRefresh(decoded) {
|
|
1204
1214
|
this.clearRefreshTimer();
|
|
1215
|
+
if (typeof window === "undefined") return;
|
|
1205
1216
|
const ttl = getTokenTTL(decoded);
|
|
1206
1217
|
if (ttl <= 0) return;
|
|
1207
1218
|
const delay = ttl * this.threshold * 1e3;
|
package/dist/index.d.ts
CHANGED
|
@@ -1272,6 +1272,13 @@ declare class AuthService {
|
|
|
1272
1272
|
* this before issuing a purpose-bound CredentialSetupService session.
|
|
1273
1273
|
*/
|
|
1274
1274
|
verifyCredentials(body: LoginDto): Promise<SanitizedUser>;
|
|
1275
|
+
/**
|
|
1276
|
+
* Verify a pending, unverified account for one exact application role.
|
|
1277
|
+
* This deliberately does not establish a normal auth session. Applications
|
|
1278
|
+
* should exchange the result for a short-lived, purpose-bound setup session.
|
|
1279
|
+
*/
|
|
1280
|
+
verifyPendingCredentials(body: LoginDto, expectedRole: string): Promise<SanitizedUser>;
|
|
1281
|
+
private verifyCredentialsForPolicy;
|
|
1275
1282
|
/** Establish a complete normal auth session for an already verified user. */
|
|
1276
1283
|
establishSession(user: SanitizedUser): Promise<TokenPair & {
|
|
1277
1284
|
user: SanitizedUser;
|
package/dist/index.js
CHANGED
|
@@ -2323,6 +2323,23 @@ var AuthService = class AuthService2 {
|
|
|
2323
2323
|
* this before issuing a purpose-bound CredentialSetupService session.
|
|
2324
2324
|
*/
|
|
2325
2325
|
async verifyCredentials(body) {
|
|
2326
|
+
return this.verifyCredentialsForPolicy(body, { kind: "active" });
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Verify a pending, unverified account for one exact application role.
|
|
2330
|
+
* This deliberately does not establish a normal auth session. Applications
|
|
2331
|
+
* should exchange the result for a short-lived, purpose-bound setup session.
|
|
2332
|
+
*/
|
|
2333
|
+
async verifyPendingCredentials(body, expectedRole) {
|
|
2334
|
+
if (!expectedRole.trim()) {
|
|
2335
|
+
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2336
|
+
}
|
|
2337
|
+
return this.verifyCredentialsForPolicy(body, {
|
|
2338
|
+
kind: "pending",
|
|
2339
|
+
expectedRole: expectedRole.trim().toLowerCase()
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
async verifyCredentialsForPolicy(body, policy) {
|
|
2326
2343
|
const password = body.password;
|
|
2327
2344
|
const rawIdentifier = "identifier" in body ? body.identifier : body.email;
|
|
2328
2345
|
const identifier = normalizeAuthIdentifier(rawIdentifier);
|
|
@@ -2353,11 +2370,18 @@ var AuthService = class AuthService2 {
|
|
|
2353
2370
|
}
|
|
2354
2371
|
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2355
2372
|
}
|
|
2356
|
-
if (
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2373
|
+
if (policy.kind === "pending") {
|
|
2374
|
+
const role = typeof user.role === "string" ? user.role.toLowerCase() : "";
|
|
2375
|
+
if (user.status !== "pending" || user.emailVerified || role !== policy.expectedRole) {
|
|
2376
|
+
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2377
|
+
}
|
|
2378
|
+
} else {
|
|
2379
|
+
if (user.status !== "active") {
|
|
2380
|
+
Err8(this.t("errors.accountInactive"), 403);
|
|
2381
|
+
}
|
|
2382
|
+
if (this.config.requireVerifiedEmail && !user.emailVerified) {
|
|
2383
|
+
Err8(this.t("errors.emailNotVerified"), 403);
|
|
2384
|
+
}
|
|
2361
2385
|
}
|
|
2362
2386
|
if ((user.failedLoginAttempts ?? 0) > 0 || user.lockoutUntil) {
|
|
2363
2387
|
await this.userService.resetFailedAttempts(user.id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "najm-auth",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"description": "Authentication and authorization library for najm framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"bcryptjs": "^3.0.2",
|
|
80
80
|
"najm-cookies": "^2.0.2",
|
|
81
|
-
"najm-core": "^2.0.
|
|
81
|
+
"najm-core": "^2.0.5",
|
|
82
82
|
"najm-database": "^2.0.3",
|
|
83
83
|
"najm-guard": "^2.0.2",
|
|
84
|
-
"najm-i18n": "^2.0.
|
|
84
|
+
"najm-i18n": "^2.0.3",
|
|
85
85
|
"najm-cache": "^2.0.2",
|
|
86
86
|
"najm-email": "^2.0.2",
|
|
87
87
|
"najm-rate": "^2.0.2",
|