@nik2208/node-auth 1.1.6 → 1.2.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/README.md CHANGED
@@ -286,12 +286,12 @@ When you mount `auth.router()`, the following endpoints are available:
286
286
  | `POST` | `/auth/change-email/confirm` | Confirm email change with token |
287
287
  | `POST` | `/auth/2fa/setup` | Get TOTP secret + QR code (protected) |
288
288
  | `POST` | `/auth/2fa/verify-setup` | Verify TOTP code and enable 2FA (protected) |
289
- | `POST` | `/auth/2fa/verify` | Complete 2FA login |
289
+ | `POST` | `/auth/2fa/verify` | Complete TOTP 2FA login |
290
290
  | `POST` | `/auth/2fa/disable` | Disable 2FA (protected) |
291
- | `POST` | `/auth/magic-link/send` | Send magic link email |
292
- | `POST` | `/auth/magic-link/verify` | Verify magic link token |
293
- | `POST` | `/auth/sms/send` | Send SMS verification code |
294
- | `POST` | `/auth/sms/verify` | Verify SMS code |
291
+ | `POST` | `/auth/magic-link/send` | Send magic link direct login (`mode='login'`, default) or 2FA challenge (`mode='2fa'`, requires `tempToken`) |
292
+ | `POST` | `/auth/magic-link/verify` | Verify magic link direct login (`mode='login'`, default) or 2FA completion (`mode='2fa'`, requires `tempToken`) |
293
+ | `POST` | `/auth/sms/send` | Send SMS code — direct login (`mode='login'`, default, accepts `userId` **or** `email`) or 2FA challenge (`mode='2fa'`, requires `tempToken`) |
294
+ | `POST` | `/auth/sms/verify` | Verify SMS code — direct login (`mode='login'`, default) or 2FA completion (`mode='2fa'`, requires `tempToken`) |
295
295
  | `GET` | `/auth/oauth/google` | Initiate Google OAuth |
296
296
  | `GET` | `/auth/oauth/google/callback` | Google OAuth callback |
297
297
  | `GET` | `/auth/oauth/github` | Initiate GitHub OAuth |
@@ -715,6 +715,271 @@ await fetch('/auth/change-email/confirm', {
715
715
  });
716
716
  ```
717
717
 
718
+ ## TOTP Two-Factor Authentication — Full UI Integration Guide
719
+
720
+ TOTP (Time-based One-Time Password) is the **Google Authenticator / Authy** style 2FA. The following is the complete flow from both the server and UI perspective.
721
+
722
+ ### Prerequisites
723
+
724
+ The user must be logged in (have a valid `accessToken` cookie or Bearer token).
725
+
726
+ ### Step 1 — Generate a secret and display the QR code
727
+
728
+ Call `POST /auth/2fa/setup` from your settings page. The response contains:
729
+ - `secret` — base32-encoded TOTP secret (store it temporarily in the UI, **never** in localStorage)
730
+ - `otpauthUrl` — the `otpauth://` URI (used to generate the QR code)
731
+ - `qrCode` — a `data:image/png;base64,...` data URL you can put directly into an `<img>` tag
732
+
733
+ ```typescript
734
+ // Client-side (authenticated)
735
+ const res = await fetch('/auth/2fa/setup', {
736
+ method: 'POST',
737
+ credentials: 'include', // sends the accessToken cookie
738
+ });
739
+ const { secret, qrCode } = await res.json();
740
+
741
+ // Display in your UI
742
+ document.getElementById('qr-img').src = qrCode;
743
+ document.getElementById('secret-text').textContent = secret; // for manual entry
744
+ ```
745
+
746
+ **UI tip:** Show both the QR code and the plain-text secret. Some users cannot scan QR codes (accessibility, older devices).
747
+
748
+ ```html
749
+ <!-- Example setup UI -->
750
+ <div id="totp-setup">
751
+ <p>Scan this QR code with Google Authenticator, Authy, or any TOTP app:</p>
752
+ <img id="qr-img" alt="TOTP QR code" />
753
+ <p>Or enter this code manually: <code id="secret-text"></code></p>
754
+
755
+ <label>Enter the 6-digit code shown in the app to confirm:</label>
756
+ <input id="totp-input" type="text" maxlength="6" inputmode="numeric" autocomplete="one-time-code" />
757
+ <button onclick="verifySetup()">Enable 2FA</button>
758
+ </div>
759
+ ```
760
+
761
+ ### Step 2 — Verify the setup and persist the secret
762
+
763
+ The user enters the 6-digit code from their authenticator app. Call `POST /auth/2fa/verify-setup` with both the code **and** the secret returned from step 1.
764
+
765
+ ```typescript
766
+ async function verifySetup() {
767
+ const code = document.getElementById('totp-input').value.trim();
768
+ const secret = document.getElementById('secret-text').textContent; // from step 1
769
+
770
+ const res = await fetch('/auth/2fa/verify-setup', {
771
+ method: 'POST',
772
+ credentials: 'include',
773
+ headers: { 'Content-Type': 'application/json' },
774
+ body: JSON.stringify({ token: code, secret }),
775
+ });
776
+
777
+ if (res.ok) {
778
+ // 2FA is now enabled — update UI, redirect to settings
779
+ alert('Two-factor authentication enabled!');
780
+ } else {
781
+ const { error } = await res.json();
782
+ alert('Invalid code: ' + error);
783
+ }
784
+ }
785
+ ```
786
+
787
+ The server calls `updateTotpSecret(userId, secret)` which sets `isTotpEnabled = true` on the user.
788
+
789
+ ### Step 3 — Login with TOTP
790
+
791
+ When a user with `isTotpEnabled = true` calls `POST /auth/login`, the server responds:
792
+
793
+ ```json
794
+ {
795
+ "requiresTwoFactor": true,
796
+ "tempToken": "<short-lived JWT>",
797
+ "available2faMethods": ["totp", "sms", "magic-link"]
798
+ }
799
+ ```
800
+
801
+ - `tempToken` expires in **5 minutes** — use it immediately.
802
+ - `available2faMethods` lists which 2FA channels are available to this specific user (see [Multi-channel 2FA](#multi-channel-2fa) below).
803
+
804
+ Show a code-entry UI and call `POST /auth/2fa/verify`:
805
+
806
+ ```typescript
807
+ // After detecting requiresTwoFactor === true in the login response:
808
+ let tempToken = data.tempToken;
809
+
810
+ async function submit2fa() {
811
+ const totpCode = document.getElementById('totp-code-input').value.trim();
812
+
813
+ const res = await fetch('/auth/2fa/verify', {
814
+ method: 'POST',
815
+ headers: { 'Content-Type': 'application/json' },
816
+ body: JSON.stringify({ tempToken, totpCode }),
817
+ });
818
+
819
+ if (res.ok) {
820
+ // Full session tokens are now set as HttpOnly cookies
821
+ window.location.href = '/dashboard';
822
+ } else {
823
+ const { error } = await res.json();
824
+ alert('Invalid code: ' + error);
825
+ }
826
+ }
827
+ ```
828
+
829
+ ```html
830
+ <!-- TOTP verification UI (shown after login step 1) -->
831
+ <div id="totp-verify">
832
+ <p>Enter the 6-digit code from your authenticator app:</p>
833
+ <input id="totp-code-input" type="text" maxlength="6" inputmode="numeric"
834
+ autocomplete="one-time-code" autofocus />
835
+ <button onclick="submit2fa()">Verify</button>
836
+ </div>
837
+ ```
838
+
839
+ ### Step 4 — Disable 2FA
840
+
841
+ Call `POST /auth/2fa/disable` (authenticated):
842
+
843
+ ```typescript
844
+ await fetch('/auth/2fa/disable', {
845
+ method: 'POST',
846
+ credentials: 'include',
847
+ });
848
+ ```
849
+
850
+ The server clears `totpSecret` and sets `isTotpEnabled = false`.
851
+
852
+ ---
853
+
854
+ ## Multi-Channel 2FA — SMS and Magic-Link as Second Factor
855
+
856
+ After a successful `POST /auth/login` that returns `requiresTwoFactor: true`, the response includes `available2faMethods` — an array listing which 2FA channels are configured for the user:
857
+
858
+ | Value | Requires |
859
+ |-------|----------|
860
+ | `'totp'` | User has `isTotpEnabled = true` and a stored `totpSecret` |
861
+ | `'sms'` | User has a stored `phoneNumber` **and** `config.sms` is configured |
862
+ | `'magic-link'` | `config.email.sendMagicLink` or `config.email.mailer` is configured |
863
+
864
+ Your UI can let the user pick their preferred channel:
865
+
866
+ ```typescript
867
+ const loginRes = await fetch('/auth/login', { /* ... */ });
868
+ const { requiresTwoFactor, tempToken, available2faMethods } = await loginRes.json();
869
+
870
+ if (requiresTwoFactor) {
871
+ // Offer available channels to the user
872
+ show2faChannelPicker(available2faMethods, tempToken);
873
+ }
874
+ ```
875
+
876
+ ### 2FA via SMS
877
+
878
+ **Step A — Request the code:**
879
+
880
+ ```typescript
881
+ await fetch('/auth/sms/send', {
882
+ method: 'POST',
883
+ headers: { 'Content-Type': 'application/json' },
884
+ body: JSON.stringify({ mode: '2fa', tempToken }),
885
+ });
886
+ // The server validates the tempToken, finds the user's stored phoneNumber, and sends an OTP.
887
+ ```
888
+
889
+ **Step B — Submit the code:**
890
+
891
+ ```typescript
892
+ const res = await fetch('/auth/sms/verify', {
893
+ method: 'POST',
894
+ headers: { 'Content-Type': 'application/json' },
895
+ body: JSON.stringify({ mode: '2fa', tempToken, code: userEnteredCode }),
896
+ });
897
+ // On success, full session tokens are issued via HttpOnly cookies.
898
+ ```
899
+
900
+ ### 2FA via Magic-Link
901
+
902
+ **Step A — Request the magic link:**
903
+
904
+ ```typescript
905
+ await fetch('/auth/magic-link/send', {
906
+ method: 'POST',
907
+ headers: { 'Content-Type': 'application/json' },
908
+ body: JSON.stringify({ mode: '2fa', tempToken }),
909
+ });
910
+ // The server validates the tempToken, finds the user's email, and sends a magic link.
911
+ ```
912
+
913
+ **Step B — Verify the link** (called from the link in the email):
914
+
915
+ ```typescript
916
+ // Extract `token` from the link: /auth/magic-link/verify?token=...
917
+ const res = await fetch('/auth/magic-link/verify', {
918
+ method: 'POST',
919
+ headers: { 'Content-Type': 'application/json' },
920
+ body: JSON.stringify({ token: tokenFromLink, mode: '2fa', tempToken }),
921
+ });
922
+ // On success, full session tokens are issued via HttpOnly cookies.
923
+ ```
924
+
925
+ > **Security note:** In `mode='2fa'`, both the magic-link token and the `tempToken` are validated. The magic link must belong to the same user identified by the `tempToken`, preventing account takeover even if a magic-link token is stolen.
926
+
927
+ ---
928
+
929
+ ## Direct Passwordless Login
930
+
931
+ ### SMS Direct Login
932
+
933
+ Users can log in by phone without a password. You can identify the user by their stored `userId` **or** by the `email` associated with their account (the stored `phoneNumber` is used either way):
934
+
935
+ ```typescript
936
+ // Option A — identify by userId
937
+ await fetch('/auth/sms/send', {
938
+ method: 'POST',
939
+ headers: { 'Content-Type': 'application/json' },
940
+ body: JSON.stringify({ userId: '123' }), // mode: 'login' is the default
941
+ });
942
+
943
+ // Option B — identify by email (user enters their email; the stored phone is used)
944
+ await fetch('/auth/sms/send', {
945
+ method: 'POST',
946
+ headers: { 'Content-Type': 'application/json' },
947
+ body: JSON.stringify({ email: 'user@example.com' }),
948
+ });
949
+ // If the email is not found the endpoint silently returns { success: true }
950
+ // to prevent user enumeration.
951
+ ```
952
+
953
+ Then verify the code to get full session tokens:
954
+
955
+ ```typescript
956
+ await fetch('/auth/sms/verify', {
957
+ method: 'POST',
958
+ headers: { 'Content-Type': 'application/json' },
959
+ body: JSON.stringify({ userId: '123', code: '123456' }),
960
+ });
961
+ ```
962
+
963
+ ### Magic-Link Direct Login
964
+
965
+ Magic-link direct login is unchanged — no `mode` parameter needed:
966
+
967
+ ```typescript
968
+ // Send
969
+ await fetch('/auth/magic-link/send', {
970
+ method: 'POST',
971
+ headers: { 'Content-Type': 'application/json' },
972
+ body: JSON.stringify({ email: 'user@example.com' }),
973
+ });
974
+
975
+ // Verify (called when user clicks the link)
976
+ await fetch('/auth/magic-link/verify', {
977
+ method: 'POST',
978
+ headers: { 'Content-Type': 'application/json' },
979
+ body: JSON.stringify({ token: tokenFromLink }),
980
+ });
981
+ ```
982
+
718
983
  ## Admin Panel
719
984
 
720
985
  `createAdminRouter` mounts a **self-contained admin panel** — both the REST API and a vanilla-JS UI — at any path you choose. No build step, no external UI dependencies.
@@ -724,9 +989,10 @@ import { createAdminRouter } from 'node-auth';
724
989
 
725
990
  app.use('/admin', createAdminRouter(userStore, {
726
991
  adminSecret: process.env.ADMIN_SECRET!, // Bearer token required for all admin routes
727
- sessionStore, // optional — enables Sessions tab
728
- rbacStore, // optional — enables Roles & Permissions tab
729
- tenantStore, // optional — enables Tenants tab
992
+ sessionStore, // optional — enables Sessions tab
993
+ rbacStore, // optional — enables Roles & Permissions tab + user-role assignment
994
+ tenantStore, // optional — enables Tenants tab + user-tenant membership
995
+ userMetadataStore, // optional — enables Metadata editor in the user panel
730
996
  }));
731
997
  ```
732
998
 
@@ -734,12 +1000,16 @@ Open `http://localhost:3000/admin/` in your browser, enter the admin secret, and
734
1000
 
735
1001
  | Tab | Requires | Features |
736
1002
  |-----|----------|---------|
737
- | **Users** | `IUserStore.listUsers` | Paginated user table, delete user |
1003
+ | **Users** | `IUserStore.listUsers` | Paginated user table, delete user, **Manage** panel per user |
738
1004
  | **Sessions** | `ISessionStore.getAllSessions` | All active sessions, revoke by handle |
739
1005
  | **Roles & Permissions** | `IRolesPermissionsStore.getAllRoles` | List roles with permissions, create/delete roles |
740
- | **Tenants** | `ITenantStore.getAllTenants` | List tenants, create/delete tenants |
1006
+ | **Tenants** | `ITenantStore.getAllTenants` | List tenants, create/delete tenants, manage members |
1007
+
1008
+ The **Manage** panel (click the "Manage" button in the Users table) provides:
1009
+ - **Role assignment** — assign/remove roles when `rbacStore` is configured
1010
+ - **Metadata editor** — view and edit raw JSON metadata when `userMetadataStore` is configured
741
1011
 
742
- Tabs that are not configured are hidden automatically.
1012
+ Tabs and features that are not configured are hidden automatically.
743
1013
 
744
1014
  ### Admin REST API
745
1015
 
@@ -751,6 +1021,11 @@ All admin API endpoints require `Authorization: Bearer <adminSecret>`.
751
1021
  | `GET` | `/admin/api/users` | List users (`?limit=&offset=`) |
752
1022
  | `GET` | `/admin/api/users/:id` | Get single user |
753
1023
  | `DELETE` | `/admin/api/users/:id` | Delete user (requires `IUserStore.deleteUser`) |
1024
+ | `GET` | `/admin/api/users/:id/roles` | List roles assigned to a user |
1025
+ | `POST` | `/admin/api/users/:id/roles` | Assign a role to a user (`{ role, tenantId? }`) |
1026
+ | `DELETE` | `/admin/api/users/:id/roles/:role` | Remove a role from a user |
1027
+ | `GET` | `/admin/api/users/:id/metadata` | Get user metadata |
1028
+ | `PUT` | `/admin/api/users/:id/metadata` | Replace user metadata (full JSON body) |
754
1029
  | `GET` | `/admin/api/sessions` | List all sessions (`?limit=&offset=`) |
755
1030
  | `DELETE` | `/admin/api/sessions/:handle` | Revoke a session |
756
1031
  | `GET` | `/admin/api/roles` | List all roles with permissions |
@@ -759,6 +1034,9 @@ All admin API endpoints require `Authorization: Bearer <adminSecret>`.
759
1034
  | `GET` | `/admin/api/tenants` | List all tenants |
760
1035
  | `POST` | `/admin/api/tenants` | Create a tenant |
761
1036
  | `DELETE` | `/admin/api/tenants/:id` | Delete a tenant |
1037
+ | `GET` | `/admin/api/tenants/:id/users` | List user IDs belonging to a tenant |
1038
+ | `POST` | `/admin/api/tenants/:id/users` | Add a user to a tenant (`{ userId }`) |
1039
+ | `DELETE` | `/admin/api/tenants/:id/users/:userId` | Remove a user from a tenant |
762
1040
 
763
1041
  > **Security note:** Mount the admin router behind a VPN or IP allow-list in production. The `adminSecret` is a single shared token — treat it like a root password.
764
1042
 
@@ -52,6 +52,11 @@ export interface IUserStore<U extends BaseUser = BaseUser> {
52
52
  * Required to support POST /auth/change-email/confirm.
53
53
  */
54
54
  findByEmailChangeToken?(token: string): Promise<U | null>;
55
+ /**
56
+ * Set or clear the "2FA required" flag for a single user.
57
+ * Required to support `POST /admin/api/2fa-policy` (batch 2FA enforcement).
58
+ */
59
+ updateRequire2FA?(userId: string, required: boolean): Promise<void>;
55
60
  /**
56
61
  * Return a paginated list of users.
57
62
  * Used by the optional admin router to display the users table.
@@ -1 +1 @@
1
- {"version":3,"file":"user-store.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/user-store.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IACvD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACxC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrC,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7F,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3F,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/F,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvF;;;OAGG;IACH,gBAAgB,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEpD;;;OAGG;IACH,oBAAoB,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAIxD;;;OAGG;IACH,4BAA4B,CAAC,CAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,MAAM,EAAE,IAAI,GAAG,IAAI,GAClB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;OAGG;IACH,mBAAmB,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzE;;;OAGG;IACH,4BAA4B,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAIhE;;;;;OAKG;IACH,sBAAsB,CAAC,CACrB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,MAAM,EAAE,IAAI,GAAG,IAAI,GAClB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;OAIG;IACH,WAAW,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D;;;OAGG;IACH,sBAAsB,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAI1D;;;;;;OAMG;IACH,SAAS,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CACzD"}
1
+ {"version":3,"file":"user-store.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/user-store.interface.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE,kBAAkB,CAAC;SAC3B;KACF;CACF;AAID,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,cAAc,CAevE"}
1
+ {"version":3,"file":"auth.middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE,kBAAkB,CAAC;SAC3B;KACF;CACF;AAID,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,cAAc,CAuBvE"}
@@ -10,6 +10,14 @@ function createAuthMiddleware(config) {
10
10
  res.status(403).json({ error: 'No access token provided' });
11
11
  return;
12
12
  }
13
+ if (config.csrf?.enabled) {
14
+ const csrfCookie = tokenService.extractTokenFromCookie(req, 'csrf-token');
15
+ const csrfHeader = req.headers['x-csrf-token'];
16
+ if (!csrfCookie || !csrfHeader || csrfCookie !== csrfHeader) {
17
+ res.status(403).json({ error: 'CSRF token validation failed', code: 'CSRF_INVALID' });
18
+ return;
19
+ }
20
+ }
13
21
  try {
14
22
  const payload = tokenService.verifyAccessToken(token, config);
15
23
  req.user = payload;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.middleware.js","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":";;AAeA,oDAeC;AA3BD,6DAAyD;AAUzD,MAAM,YAAY,GAAG,IAAI,4BAAY,EAAE,CAAC;AAExC,SAAgB,oBAAoB,CAAC,MAAkB;IACrD,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,MAAM,KAAK,GAAG,YAAY,CAAC,sBAAsB,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACtE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;YAC5D,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC9D,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC;YACnB,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"auth.middleware.js","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":";;AAeA,oDAuBC;AAnCD,6DAAyD;AAUzD,MAAM,YAAY,GAAG,IAAI,4BAAY,EAAE,CAAC;AAExC,SAAgB,oBAAoB,CAAC,MAAkB;IACrD,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,MAAM,KAAK,GAAG,YAAY,CAAC,sBAAsB,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACtE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;YAC5D,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;YACzB,MAAM,UAAU,GAAG,YAAY,CAAC,sBAAsB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;YAC1E,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAuB,CAAC;YACrE,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,UAAU,EAAE,CAAC;gBAC5D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;gBACtF,OAAO;YACT,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC9D,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC;YACnB,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -41,6 +41,30 @@ export interface AuthConfig {
41
41
  secure?: boolean;
42
42
  sameSite?: 'strict' | 'lax' | 'none';
43
43
  domain?: string;
44
+ /**
45
+ * Path for the refresh-token cookie.
46
+ * Defaults to `'/'` so the cookie is sent on every request and the
47
+ * router can be mounted at any prefix.
48
+ * For extra security you can restrict it to your refresh endpoint path
49
+ * (e.g. `'/auth/refresh'`).
50
+ */
51
+ refreshTokenPath?: string;
52
+ };
53
+ /**
54
+ * Anti-CSRF protection using the double-submit cookie pattern.
55
+ *
56
+ * When enabled the library sets a non-HttpOnly `csrf-token` cookie alongside
57
+ * the JWT cookies. Client-side JavaScript must read this cookie and include
58
+ * its value in the `X-CSRF-Token` header on every authenticated request.
59
+ * The `createAuthMiddleware` middleware will then verify the header matches
60
+ * the cookie.
61
+ *
62
+ * Recommended when `cookieOptions.sameSite` is `'none'` or when you need
63
+ * defence-in-depth beyond `sameSite: 'lax'`.
64
+ */
65
+ csrf?: {
66
+ /** @default false */
67
+ enabled?: boolean;
44
68
  };
45
69
  bcryptSaltRounds?: number;
46
70
  sms?: {
@@ -106,6 +130,23 @@ export interface AuthConfig {
106
130
  twoFactor?: {
107
131
  appName?: string;
108
132
  };
133
+ /**
134
+ * When `true`, the local (email+password) login endpoint will reject users
135
+ * whose email address has not yet been verified (`isEmailVerified !== true`).
136
+ *
137
+ * **Default:** `false` — email verification is optional by default.
138
+ *
139
+ * If you enable this flag you must also configure the email-verification flow
140
+ * so that users can actually verify their address:
141
+ * - configure `email.sendVerificationEmail` (or `email.mailer`) so the
142
+ * library can send verification emails.
143
+ * - expose the `POST /auth/send-verification-email` and
144
+ * `GET /auth/verify-email` endpoints from `createAuthRouter`.
145
+ *
146
+ * When a user tries to log in with an unverified email the server responds
147
+ * with HTTP **403** and error code `EMAIL_NOT_VERIFIED`.
148
+ */
149
+ requireEmailVerification?: boolean;
109
150
  /**
110
151
  * Optional callback to inject custom claims into both the access and refresh JWTs.
111
152
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth-config.model.d.ts","sourceRoot":"","sources":["../../src/models/auth-config.model.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-config.model.d.ts","sourceRoot":"","sources":["../../src/models/auth-config.model.ts"],"names":[],"mappings":"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"}
@@ -8,14 +8,15 @@ export interface BaseUser {
8
8
  resetToken?: string | null;
9
9
  resetTokenExpiry?: Date | null;
10
10
  totpSecret?: string | null;
11
+ isEmailVerified?: boolean;
11
12
  isTotpEnabled?: boolean;
12
13
  magicLinkToken?: string | null;
13
14
  magicLinkTokenExpiry?: Date | null;
14
15
  smsCode?: string | null;
15
16
  smsCodeExpiry?: Date | null;
16
17
  phoneNumber?: string | null;
17
- /** Whether this user has verified their email address. */
18
- isEmailVerified?: boolean;
18
+ /** When `true` this user must have 2FA active to complete login. */
19
+ require2FA?: boolean;
19
20
  /** Token sent in the email-verification link. */
20
21
  emailVerificationToken?: string | null;
21
22
  /** Expiry for the email-verification token. */
@@ -1 +1 @@
1
- {"version":3,"file":"user.model.d.ts","sourceRoot":"","sources":["../../src/models/user.model.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,kBAAkB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oBAAoB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,0DAA0D;IAC1D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iDAAiD;IACjD,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,+CAA+C;IAC/C,4BAA4B,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3C,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,yCAAyC;IACzC,sBAAsB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACtC"}
1
+ {"version":3,"file":"user.model.d.ts","sourceRoot":"","sources":["../../src/models/user.model.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,kBAAkB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oBAAoB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,+CAA+C;IAC/C,4BAA4B,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3C,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,yCAAyC;IACzC,sBAAsB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACtC"}
@@ -3,6 +3,7 @@ import { IUserStore } from '../interfaces/user-store.interface';
3
3
  import { ISessionStore } from '../interfaces/session-store.interface';
4
4
  import { IRolesPermissionsStore } from '../interfaces/roles-permissions-store.interface';
5
5
  import { ITenantStore } from '../interfaces/tenant-store.interface';
6
+ import { IUserMetadataStore } from '../interfaces/user-metadata-store.interface';
6
7
  export interface AdminOptions {
7
8
  /**
8
9
  * Secret token required to access all admin endpoints.
@@ -12,10 +13,15 @@ export interface AdminOptions {
12
13
  adminSecret: string;
13
14
  /** Optional session store — enables the Sessions tab in the admin UI. */
14
15
  sessionStore?: ISessionStore;
15
- /** Optional RBAC store — enables the Roles & Permissions tab. */
16
+ /** Optional RBAC store — enables the Roles & Permissions tab and user-role assignment. */
16
17
  rbacStore?: IRolesPermissionsStore;
17
- /** Optional tenant store — enables the Tenants tab. */
18
+ /** Optional tenant store — enables the Tenants tab and user-tenant assignment. */
18
19
  tenantStore?: ITenantStore;
20
+ /**
21
+ * Optional user-metadata store — enables the Metadata section in the user detail
22
+ * panel (view and edit arbitrary per-user key/value data).
23
+ */
24
+ userMetadataStore?: IUserMetadataStore;
19
25
  }
20
26
  export declare function createAdminRouter(userStore: IUserStore, options: AdminOptions): Router;
21
27
  //# sourceMappingURL=admin.router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin.router.d.ts","sourceRoot":"","sources":["../../src/router/admin.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqC,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,iEAAiE;IACjE,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,uDAAuD;IACvD,WAAW,CAAC,EAAE,YAAY,CAAC;CAC5B;AAgaD,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,YAAY,GACpB,MAAM,CAgMR"}
1
+ {"version":3,"file":"admin.router.d.ts","sourceRoot":"","sources":["../../src/router/admin.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqC,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AAEjF,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,0FAA0F;IAC1F,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,kFAAkF;IAClF,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;CACxC;AAmmBD,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,YAAY,GACpB,MAAM,CAgVR"}