@ambushsoftworks/nestjs-auth-graphql 0.9.0-rc.5 → 0.9.1
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 +25 -16
- package/README.md +50 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -27,7 +27,20 @@ for buried obligations. **The marker was introduced in 0.9.0 and has not been
|
|
|
27
27
|
retro-applied**, so it is reliable from 0.9.0 onward only. Convention adopted
|
|
28
28
|
from `@ambushsoftworks/nestjs-payments-graphql`.
|
|
29
29
|
|
|
30
|
-
## [0.9.
|
|
30
|
+
## [0.9.1] - 2026-09-14
|
|
31
|
+
|
|
32
|
+
Documentation only — no code changes. npm shows the README of the latest
|
|
33
|
+
published version, so these corrections needed a release to reach the package
|
|
34
|
+
page.
|
|
35
|
+
|
|
36
|
+
### Documentation
|
|
37
|
+
- **README: account-status enforcement was undocumented.** 0.9.0 made suspension and deletion revoke access — `ACCOUNT_INACTIVE` (HTTP 403) at login, on refresh and on every authenticated request — and added the `isUserActive` option, but the README mentioned none of it, including in **Migrating to v0.9.0**. Adds an **Account Status** section, an `isUserActive` row under **Common Options**, a **Security Features** entry, and a migration item: clients need to handle `ACCOUNT_INACTIVE`, and repositories must stop hiding inactive users.
|
|
38
|
+
- **README: JWT Validation Modes** now says `'full'` rejects inactive accounts on the next request, and that in `'payload-only'` a suspended user keeps working until their access token expires while refresh stays blocked.
|
|
39
|
+
- **README: Utilities** lists the helpers 0.9.0 exported: `isUserActiveByDefault`, `emailForLog`, `ipForLog`, `maskPhoneDigits`, `redactSecurityEventMetadata` and `hashIp`.
|
|
40
|
+
- **README: Migrating to v0.9.0** no longer says code mode is entirely unchanged. With no base URL at all, code-mode emails now carry no page link.
|
|
41
|
+
- **CHANGELOG: the 0.9.0 release note** said the `rc.4` and `rc.5` changes had not been run on an adopter's staging. Lift had deployed `rc.5` to staging; the note is corrected in place.
|
|
42
|
+
|
|
43
|
+
## [0.9.0] - 2026-09-14
|
|
31
44
|
|
|
32
45
|
Three independent workstreams land here: a trim of the email interface surface
|
|
33
46
|
down to what the package actually fires, a fix making
|
|
@@ -40,22 +53,18 @@ seven `features` flags that nothing ever read are removed — see **Breaking**;
|
|
|
40
53
|
and the package's logs stop carrying raw email addresses, IP addresses and
|
|
41
54
|
phone numbers — see **Security**.
|
|
42
55
|
|
|
43
|
-
>
|
|
44
|
-
>
|
|
45
|
-
>
|
|
46
|
-
>
|
|
47
|
-
>
|
|
48
|
-
>
|
|
56
|
+
> Shipped through five release candidates on the `next` dist-tag before
|
|
57
|
+
> reaching `latest`; this release is the content of `0.9.0-rc.5` under its
|
|
58
|
+
> final version. Ariadne verified token and code mode end to end before
|
|
59
|
+
> `rc.1`. Lift ran its automated staging checks and its real-inbox, phone and
|
|
60
|
+
> Google sign-in checks against `rc.3`, then deployed `rc.5` to staging: 18/18
|
|
61
|
+
> automated auth checks, a clean boot, no code-mode base URL warning, and no
|
|
62
|
+
> raw email, IP address or phone number in the package's log output. `rc.2`
|
|
63
|
+
> carried eight stale compiled files; see **Fixed (release tooling)**.
|
|
49
64
|
>
|
|
50
|
-
>
|
|
51
|
-
>
|
|
52
|
-
>
|
|
53
|
-
> claims nothing else changed, which is wrong on that point. `rc.4` removed
|
|
54
|
-
> seven unread `features` flags (**Breaking**) and made the build start from an
|
|
55
|
-
> empty `dist/`. `rc.5` keeps personal data out of the package's logs
|
|
56
|
-
> (**Security**) and stops code mode warning about a base URL it does not need
|
|
57
|
-
> (**Fixed**). `rc.1` predates the account-status fix in **Security** and still
|
|
58
|
-
> has the suspension window open.
|
|
65
|
+
> *Corrected in 0.9.1. As published in 0.9.0, this note said the `rc.4` and
|
|
66
|
+
> `rc.5` changes had not been run on an adopter's staging; Lift's `rc.5`
|
|
67
|
+
> staging run had been recorded minutes before the note was written.*
|
|
59
68
|
|
|
60
69
|
### ⚠ External configuration required
|
|
61
70
|
- **`verificationMode: 'token'` requires your frontend to serve two routes.**
|
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Production-grade authentication module for NestJS GraphQL + REST APIs. JWT, cook
|
|
|
19
19
|
- [Brute Force Protection](#brute-force-protection)
|
|
20
20
|
- [Password Policy](#password-policy)
|
|
21
21
|
- [Lifecycle Hooks](#lifecycle-hooks)
|
|
22
|
+
- [Account Status](#account-status)
|
|
22
23
|
- [JWT Validation Modes](#jwt-validation-modes)
|
|
23
24
|
- [JWT Payload Factory](#jwt-payload-factory)
|
|
24
25
|
- [Configuration Reference](#configuration-reference)
|
|
@@ -847,10 +848,33 @@ All hooks are optional. Async hooks are awaited but failures are logged and do n
|
|
|
847
848
|
|
|
848
849
|
`onAccountLocked` fires from `BruteForceProtectionService.recordFailedAttempt` on the transition from "not locked" → "locked". Subsequent failed attempts while the account remains locked do **not** re-fire (use `onAuthFailure` for per-attempt signal). Wire it to send the account-locked email yourself — the package no longer ships a `sendAccountLockedEmail` method on `IEmailService`; notification surface area beyond verification, password-reset, and password-changed is consumer-owned via this hook.
|
|
849
850
|
|
|
851
|
+
### Account Status
|
|
852
|
+
|
|
853
|
+
Suspending or deleting a user takes effect on their next request. The package checks each user's `status` and `deletedAt` itself:
|
|
854
|
+
|
|
855
|
+
| Where | Effect |
|
|
856
|
+
|-------|--------|
|
|
857
|
+
| `login` | Rejected after the password is verified, so the check cannot reveal to someone without the password that an address is suspended |
|
|
858
|
+
| Every authenticated request (`jwtValidation: 'full'`) | Rejected on the next request, not at token expiry |
|
|
859
|
+
| `refreshToken` | Rejected, including within the refresh grace period |
|
|
860
|
+
| Any token issuance | Rejected, as a backstop for every flow that mints tokens |
|
|
861
|
+
|
|
862
|
+
A rejected request throws `AccountInactiveException`: a GraphQL error with `extensions.code` `ACCOUNT_INACTIVE`, `statusCode` `403`, and the message `This account is no longer active.` It does not say whether the account was suspended or deleted. Unlike `AccountLockedException` it does not clear on its own, so clients should sign the user out rather than offer a retry. Each rejection logs `SecurityEvent.ACCOUNT_INACTIVE_BLOCKED` with a `phase` of `login`, `refresh`, `session` or `issue`.
|
|
863
|
+
|
|
864
|
+
**Which accounts are inactive.** By default (`isUserActiveByDefault`), a user with `deletedAt` set, or whose `status` is `SUSPENDED` or `DELETED`, compared case-insensitively. Any other status — including one the package does not recognise — counts as active, so a status vocabulary that differs from `UserStatus` cannot lock everyone out. If yours differs, supply a predicate:
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
isUserActive: (user) => user.status !== 'FROZEN' && user.status !== 'CLOSED' && !user.deletedAt,
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
It must be synchronous and pure: it runs on every authenticated request, against the user already loaded.
|
|
871
|
+
|
|
872
|
+
**Your repository must return inactive users.** `findById` and `findByEmail` should return suspended and soft-deleted users like any other. A repository that hides them turns `ACCOUNT_INACTIVE` into a "not found" failure, and the package can no longer tell the client why.
|
|
873
|
+
|
|
850
874
|
### JWT Validation Modes
|
|
851
875
|
|
|
852
|
-
- **`'full'`** (default) -- Calls `userRepository.findById()` on every request
|
|
853
|
-
- **`'payload-only'`** -- Returns `{ id, email }` from the JWT payload without a DB lookup. Faster, but
|
|
876
|
+
- **`'full'`** (default) -- Calls `userRepository.findById()` on every request, rejecting the request if the user no longer exists or is no longer active (see [Account Status](#account-status)).
|
|
877
|
+
- **`'payload-only'`** -- Returns `{ id, email }` from the JWT payload without a DB lookup. Faster, but nothing about the account is re-checked per request: a suspended or deleted user keeps working until their access token expires (`jwtExpiresIn`, 15 minutes by default). Refresh is still blocked, so one access-token lifetime is the whole window.
|
|
854
878
|
|
|
855
879
|
```typescript
|
|
856
880
|
jwtValidation: 'payload-only',
|
|
@@ -890,6 +914,7 @@ jwtPayloadFactoryInstance: {
|
|
|
890
914
|
| `jwtExpiresIn` | `string` | `'15m'` | Access token TTL |
|
|
891
915
|
| `refreshTokenExpiresIn` | `string` | `'30d'` | Refresh token TTL |
|
|
892
916
|
| `jwtValidation` | `'full' \| 'payload-only'` | `'full'` | JWT validation strategy |
|
|
917
|
+
| `isUserActive` | `(user) => boolean` | `isUserActiveByDefault` | Decides whether an account may authenticate. See [Account Status](#account-status) |
|
|
893
918
|
| `bcryptRounds` | `number` | `12` | Password hashing cost |
|
|
894
919
|
| `nodeEnv` | `string` | `'production'` | Environment identifier |
|
|
895
920
|
| `passwordPolicy` | `PasswordPolicyConfig` | See [Password Policy](#password-policy) | Password strength rules |
|
|
@@ -1034,12 +1059,17 @@ When realm support is enabled, `RealmMiddleware` runs before all guards (as Nest
|
|
|
1034
1059
|
|
|
1035
1060
|
## Utilities
|
|
1036
1061
|
|
|
1037
|
-
Helper functions exported for use in custom guards and
|
|
1062
|
+
Helper functions exported for use in custom guards, middleware and loggers:
|
|
1038
1063
|
|
|
1039
1064
|
| Function | Description |
|
|
1040
1065
|
|----------|-------------|
|
|
1041
1066
|
| `getRequestFromContext(context)` | Extract request from `ExecutionContext` (handles GraphQL + HTTP) |
|
|
1042
1067
|
| `extractIpAddress(request)` | Extract client IP (checks `clientIp`, `req.ip`, falls back to `'unknown'`) |
|
|
1068
|
+
| `isUserActiveByDefault(user)` | The default account-active predicate (see [Account Status](#account-status)) |
|
|
1069
|
+
| `emailForLog(email)`, `ipForLog(ip)` | An email or IP as it may appear in a log line — `emailHash=…` / `ipHash=…`, salted like the package's own logs |
|
|
1070
|
+
| `maskPhoneDigits(phone)` | Masks every digit but the last two, keeping formatting |
|
|
1071
|
+
| `redactSecurityEventMetadata(metadata)` | Security-event metadata with email, IP and phone fields hashed or masked — what `ConsoleAuthLogger` prints. Call it from a custom `IAuthLogger` for the same treatment |
|
|
1072
|
+
| `hashIp(ip, salt)` | HMAC-SHA256 IP hash (16 hex characters) used for rate-limit keys |
|
|
1043
1073
|
|
|
1044
1074
|
```typescript
|
|
1045
1075
|
import { getRequestFromContext, extractIpAddress } from '@ambushsoftworks/nestjs-auth-graphql';
|
|
@@ -1059,6 +1089,7 @@ export class CustomGuard implements CanActivate {
|
|
|
1059
1089
|
|
|
1060
1090
|
- **Refresh token rotation** with HMAC-SHA256 hashing and idempotent 10-second grace period
|
|
1061
1091
|
- **Bcrypt password hashing** with configurable rounds (default: 12)
|
|
1092
|
+
- **Account status enforcement** -- suspended and deleted users are rejected at login, on refresh and on every authenticated request (`ACCOUNT_INACTIVE`)
|
|
1062
1093
|
- **AES-256-GCM encryption** for OAuth tokens at rest
|
|
1063
1094
|
- **Constant-time comparison** for verification codes
|
|
1064
1095
|
- **Account enumeration prevention** on signup (optional)
|
|
@@ -1070,6 +1101,15 @@ export class CustomGuard implements CanActivate {
|
|
|
1070
1101
|
|
|
1071
1102
|
## Migrating to v0.9.0
|
|
1072
1103
|
|
|
1104
|
+
**Behaviour change — suspended and deleted accounts are rejected.** Before
|
|
1105
|
+
0.9.0 the package never checked `status` or `deletedAt`, so suspending a user
|
|
1106
|
+
revoked nothing unless your repository happened to hide them. Now those users
|
|
1107
|
+
get `ACCOUNT_INACTIVE` (HTTP 403) at login, on refresh and on their next
|
|
1108
|
+
request. Handle that error in your clients by signing the user out, check that
|
|
1109
|
+
your status values are recognised (supply `isUserActive` if not), and stop
|
|
1110
|
+
filtering inactive users out of `findById` / `findByEmail`. See
|
|
1111
|
+
[Account Status](#account-status).
|
|
1112
|
+
|
|
1073
1113
|
**Changed — log output no longer contains raw personal data.** If you parse
|
|
1074
1114
|
`ConsoleAuthLogger` output or the package's log lines, expect `emailHash` /
|
|
1075
1115
|
`ipHash` in place of `email` / `ipAddress`, masked phone numbers, and user IDs
|
|
@@ -1123,8 +1163,13 @@ Three things to check, in the order they are most likely to bite:
|
|
|
1123
1163
|
Token TTL also differs from code TTL: 60 minutes by default
|
|
1124
1164
|
(`verification.tokenExpiresInMinutes`) versus 15 for codes.
|
|
1125
1165
|
|
|
1126
|
-
**`verificationMode: 'code'` — the default — is unaffected
|
|
1127
|
-
|
|
1166
|
+
**`verificationMode: 'code'` — the default — is unaffected by the token-mode
|
|
1167
|
+
fix**, including the legacy `FRONTEND_URL` fallback for the page link. One
|
|
1168
|
+
change applies to code mode separately: with neither `verification.baseUrl` nor
|
|
1169
|
+
`FRONTEND_URL` set, code-mode emails now carry no page link, where they used to
|
|
1170
|
+
carry an `https://app.example.com` placeholder that never resolved. The bundled
|
|
1171
|
+
email service never showed that link in code mode; only a custom `IEmailService`
|
|
1172
|
+
that rendered it notices.
|
|
1128
1173
|
|
|
1129
1174
|
**`bcrypt` 5 → 6.** No code change and no hash migration: existing hashes
|
|
1130
1175
|
verify unchanged, and hashes written by 0.9.0 still verify if you roll back to
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ambushsoftworks/nestjs-auth-graphql",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Production-grade authentication package for NestJS with GraphQL, supporting JWT, OAuth, email/SMS verification, and biometric auth",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|