@nik2208/node-auth 1.0.2 → 1.1.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.
Files changed (2) hide show
  1. package/README.md +473 -5
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -14,6 +14,11 @@ A production-ready, **database-agnostic** JWT authentication library for Node.js
14
14
  - 🧩 **Strategy Pattern** – Plug in only the auth methods you need
15
15
  - 🛡️ **Middleware** – Express-compatible JWT verification middleware
16
16
  - 🚀 **Express Router** – Drop-in `/auth` router with all endpoints
17
+ - 🏷️ **Custom JWT Claims** – Inject project-specific data into tokens via `buildTokenPayload`
18
+ - 📋 **User Metadata** – Optional `IUserMetadataStore` for arbitrary per-user key/value data
19
+ - 🛡️ **Roles & Permissions** – Optional `IRolesPermissionsStore` for RBAC with tenant awareness
20
+ - 📅 **Session Management** – Optional `ISessionStore` for device-aware session listing & revocation
21
+ - 🏢 **Multi-Tenancy** – Optional `ITenantStore` for isolated multi-tenant applications
17
22
 
18
23
  ## Installation
19
24
 
@@ -274,6 +279,11 @@ When you mount `auth.router()`, the following endpoints are available:
274
279
  | `GET` | `/auth/me` | Get current user (protected) |
275
280
  | `POST` | `/auth/forgot-password` | Send password reset email |
276
281
  | `POST` | `/auth/reset-password` | Reset password with token |
282
+ | `POST` | `/auth/change-password` | Change password (authenticated, requires `currentPassword` + `newPassword`) |
283
+ | `POST` | `/auth/send-verification-email` | Send email verification link (authenticated) |
284
+ | `GET` | `/auth/verify-email?token=...` | Verify email address from link |
285
+ | `POST` | `/auth/change-email/request` | Request email change — sends verification to `newEmail` (authenticated) |
286
+ | `POST` | `/auth/change-email/confirm` | Confirm email change with token |
277
287
  | `POST` | `/auth/2fa/setup` | Get TOTP secret + QR code (protected) |
278
288
  | `POST` | `/auth/2fa/verify-setup` | Verify TOTP code and enable 2FA (protected) |
279
289
  | `POST` | `/auth/2fa/verify` | Complete 2FA login |
@@ -551,6 +561,14 @@ interface BaseUser {
551
561
  smsCode?: string | null;
552
562
  smsCodeExpiry?: Date | null;
553
563
  phoneNumber?: string | null;
564
+ // Email verification
565
+ isEmailVerified?: boolean;
566
+ emailVerificationToken?: string | null;
567
+ emailVerificationTokenExpiry?: Date | null;
568
+ // Change email
569
+ pendingEmail?: string | null;
570
+ emailChangeToken?: string | null;
571
+ emailChangeTokenExpiry?: Date | null;
554
572
  }
555
573
  ```
556
574
 
@@ -590,7 +608,159 @@ class ApiKeyStrategy extends BaseAuthStrategy<{ apiKey: string }, MyUser> {
590
608
  }
591
609
  ```
592
610
 
593
- ## Rate Limiting
611
+ ## Email Verification
612
+
613
+ The email-verification flow reuses the same token infrastructure as password reset and is available out of the box once you implement the three optional store methods.
614
+
615
+ ### IUserStore additions
616
+
617
+ ```typescript
618
+ // Required to support /auth/send-verification-email and /auth/verify-email
619
+ updateEmailVerificationToken(userId, token, expiry): Promise<void>
620
+ updateEmailVerified(userId, isVerified): Promise<void>
621
+ findByEmailVerificationToken(token): Promise<U | null>
622
+ ```
623
+
624
+ ### AuthConfig email callbacks
625
+
626
+ ```typescript
627
+ email: {
628
+ // Called when a verification email is needed (takes precedence over mailer)
629
+ sendVerificationEmail: async (to, token, link, lang?) => { /* ... */ },
630
+ // Called after a successful email change (notifies the old address)
631
+ sendEmailChanged: async (to, newEmail, lang?) => { /* ... */ },
632
+ }
633
+ ```
634
+
635
+ ### Flow
636
+
637
+ 1. After registration, call `POST /auth/send-verification-email` (authenticated) — the library generates a 24-hour token, calls `updateEmailVerificationToken`, then fires `sendVerificationEmail`.
638
+ 2. The user clicks the link in their inbox; the link points to `GET /auth/verify-email?token=<token>` — the library calls `updateEmailVerified(userId, true)` and clears the token.
639
+
640
+ ```typescript
641
+ // Example: send on registration
642
+ app.post('/register', async (req, res) => {
643
+ const user = await userStore.create({ email: req.body.email, password: hashedPw });
644
+ // Log them in
645
+ const tokens = tokenService.generateTokenPair({ sub: user.id, email: user.email }, config);
646
+ tokenService.setTokenCookies(res, tokens, config);
647
+ // Trigger verification email (the library does this automatically via the auth router)
648
+ // or call the endpoint directly:
649
+ await fetch('/auth/send-verification-email', {
650
+ method: 'POST',
651
+ headers: { Cookie: `accessToken=${tokens.accessToken}` },
652
+ });
653
+ res.json({ success: true });
654
+ });
655
+ ```
656
+
657
+ ## Change Password
658
+
659
+ `POST /auth/change-password` — **authenticated** — lets users update their password without going through the forgot-password flow.
660
+
661
+ **Request body:**
662
+ ```json
663
+ { "currentPassword": "OldP@ss1", "newPassword": "NewP@ss2" }
664
+ ```
665
+
666
+ The endpoint verifies `currentPassword` against the stored bcrypt hash before applying the change. It returns `401` if the current password is wrong, or `400` for OAuth accounts that have no password set.
667
+
668
+ ```typescript
669
+ // Client example (fetch)
670
+ await fetch('/auth/change-password', {
671
+ method: 'POST',
672
+ credentials: 'include', // include HttpOnly cookies
673
+ headers: { 'Content-Type': 'application/json' },
674
+ body: JSON.stringify({ currentPassword: 'old', newPassword: 'new' }),
675
+ });
676
+ ```
677
+
678
+ No extra `IUserStore` methods are needed — `updatePassword` is already a required method.
679
+
680
+ ## Change Email
681
+
682
+ The change-email flow sends a confirmation link to the new address before committing the update, preventing account hijacking.
683
+
684
+ ### IUserStore additions
685
+
686
+ ```typescript
687
+ // Required to support /auth/change-email/request and /auth/change-email/confirm
688
+ updateEmailChangeToken(userId, pendingEmail, token, expiry): Promise<void>
689
+ updateEmail(userId, newEmail): Promise<void>
690
+ findByEmailChangeToken(token): Promise<U | null>
691
+ ```
692
+
693
+ ### Flow
694
+
695
+ 1. Authenticated user calls `POST /auth/change-email/request` with `{ "newEmail": "new@example.com" }`.
696
+ - The library checks the new address is not already in use.
697
+ - A 1-hour token is generated, stored via `updateEmailChangeToken`, and a verification email is sent to the **new address**.
698
+ 2. User clicks the link; it points to `POST /auth/change-email/confirm` with `{ "token": "..." }`.
699
+ - The library calls `updateEmail` (commits the change) and sends an email-changed notification to the **old address** via `sendEmailChanged`.
700
+
701
+ ```typescript
702
+ // 1. Request change
703
+ await fetch('/auth/change-email/request', {
704
+ method: 'POST',
705
+ credentials: 'include',
706
+ headers: { 'Content-Type': 'application/json' },
707
+ body: JSON.stringify({ newEmail: 'new@example.com' }),
708
+ });
709
+
710
+ // 2. Confirm (called from the link in the email)
711
+ await fetch('/auth/change-email/confirm', {
712
+ method: 'POST',
713
+ headers: { 'Content-Type': 'application/json' },
714
+ body: JSON.stringify({ token: tokenFromLink }),
715
+ });
716
+ ```
717
+
718
+ ## Admin Panel
719
+
720
+ `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.
721
+
722
+ ```typescript
723
+ import { createAdminRouter } from 'node-auth';
724
+
725
+ app.use('/admin', createAdminRouter(userStore, {
726
+ 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
730
+ }));
731
+ ```
732
+
733
+ Open `http://localhost:3000/admin/` in your browser, enter the admin secret, and you get a tabbed dashboard:
734
+
735
+ | Tab | Requires | Features |
736
+ |-----|----------|---------|
737
+ | **Users** | `IUserStore.listUsers` | Paginated user table, delete user |
738
+ | **Sessions** | `ISessionStore.getAllSessions` | All active sessions, revoke by handle |
739
+ | **Roles & Permissions** | `IRolesPermissionsStore.getAllRoles` | List roles with permissions, create/delete roles |
740
+ | **Tenants** | `ITenantStore.getAllTenants` | List tenants, create/delete tenants |
741
+
742
+ Tabs that are not configured are hidden automatically.
743
+
744
+ ### Admin REST API
745
+
746
+ All admin API endpoints require `Authorization: Bearer <adminSecret>`.
747
+
748
+ | Method | Path | Description |
749
+ |--------|------|-------------|
750
+ | `GET` | `/admin/api/ping` | Health check / auth verification |
751
+ | `GET` | `/admin/api/users` | List users (`?limit=&offset=`) |
752
+ | `GET` | `/admin/api/users/:id` | Get single user |
753
+ | `DELETE` | `/admin/api/users/:id` | Delete user (requires `IUserStore.deleteUser`) |
754
+ | `GET` | `/admin/api/sessions` | List all sessions (`?limit=&offset=`) |
755
+ | `DELETE` | `/admin/api/sessions/:handle` | Revoke a session |
756
+ | `GET` | `/admin/api/roles` | List all roles with permissions |
757
+ | `POST` | `/admin/api/roles` | Create a role |
758
+ | `DELETE` | `/admin/api/roles/:name` | Delete a role |
759
+ | `GET` | `/admin/api/tenants` | List all tenants |
760
+ | `POST` | `/admin/api/tenants` | Create a tenant |
761
+ | `DELETE` | `/admin/api/tenants/:id` | Delete a tenant |
762
+
763
+ > **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.
594
764
 
595
765
  Auth routes should be rate-limited in production to prevent brute-force attacks. Pass an optional `rateLimiter` middleware to `createAuthRouter()`:
596
766
 
@@ -604,6 +774,273 @@ app.use('/auth', auth.router({ rateLimiter: limiter }));
604
774
 
605
775
  All sensitive endpoints (login, refresh, password reset, 2FA, magic links, SMS) will be protected.
606
776
 
777
+ ## Custom JWT Claims
778
+
779
+ Inject arbitrary project-specific data into both the access and refresh JWTs by providing `buildTokenPayload` in `AuthConfig`. The returned object is **merged** on top of the standard `{ sub, email, role }` claims.
780
+
781
+ ```typescript
782
+ import { AuthConfigurator, AuthConfig } from 'node-auth';
783
+
784
+ const config: AuthConfig = {
785
+ accessTokenSecret: '...',
786
+ refreshTokenSecret: '...',
787
+ buildTokenPayload: (user) => ({
788
+ permissions: user.permissions, // your extended user fields
789
+ tenantId: user.tenantId,
790
+ plan: user.plan,
791
+ }),
792
+ };
793
+
794
+ const auth = new AuthConfigurator(config, userStore);
795
+ ```
796
+
797
+ After login the custom claims are available on `req.user` in any protected route:
798
+
799
+ ```typescript
800
+ app.get('/protected', auth.middleware(), (req, res) => {
801
+ // req.user.tenantId, req.user.permissions, etc.
802
+ res.json(req.user);
803
+ });
804
+ ```
805
+
806
+ ## User Metadata
807
+
808
+ `IUserMetadataStore` is an **optional** interface for attaching arbitrary key/value metadata to users without altering `BaseUser` or your users table.
809
+
810
+ ```typescript
811
+ import { IUserMetadataStore } from 'node-auth';
812
+
813
+ export class MyUserMetadataStore implements IUserMetadataStore {
814
+ /** Return all metadata for a user; empty object when none exists. */
815
+ async getMetadata(userId: string): Promise<Record<string, unknown>> {
816
+ const row = await db('user_metadata').where({ userId }).first();
817
+ return row ? JSON.parse(row.data) : {};
818
+ }
819
+
820
+ /** Shallow-merge new key/value pairs into the existing metadata. */
821
+ async updateMetadata(userId: string, metadata: Record<string, unknown>): Promise<void> {
822
+ const existing = await this.getMetadata(userId);
823
+ const merged = { ...existing, ...metadata };
824
+ await db('user_metadata')
825
+ .insert({ userId, data: JSON.stringify(merged) })
826
+ .onConflict('userId').merge();
827
+ }
828
+
829
+ /** Remove all metadata for the user (e.g. on account deletion). */
830
+ async clearMetadata(userId: string): Promise<void> {
831
+ await db('user_metadata').where({ userId }).delete();
832
+ }
833
+ }
834
+ ```
835
+
836
+ ### Usage
837
+
838
+ ```typescript
839
+ const metaStore = new MyUserMetadataStore();
840
+
841
+ // Store preferences after login
842
+ await metaStore.updateMetadata(userId, { theme: 'dark', lang: 'it', onboarded: true });
843
+
844
+ // Read them back
845
+ const meta = await metaStore.getMetadata(userId);
846
+ console.log(meta.theme); // 'dark'
847
+ ```
848
+
849
+ ## Roles & Permissions
850
+
851
+ `IRolesPermissionsStore` is an **optional** interface for role-based access control (RBAC). It supports both single-tenant and multi-tenant applications via an optional `tenantId` parameter.
852
+
853
+ ```typescript
854
+ import { IRolesPermissionsStore } from 'node-auth';
855
+
856
+ export class MyRbacStore implements IRolesPermissionsStore {
857
+ // User ↔ Role
858
+ async addRoleToUser(userId: string, role: string, tenantId?: string): Promise<void> { /* ... */ }
859
+ async removeRoleFromUser(userId: string, role: string, tenantId?: string): Promise<void> { /* ... */ }
860
+ async getRolesForUser(userId: string, tenantId?: string): Promise<string[]> { /* ... */ }
861
+
862
+ // Role management
863
+ async createRole(role: string, permissions?: string[]): Promise<void> { /* ... */ }
864
+ async deleteRole(role: string): Promise<void> { /* ... */ }
865
+
866
+ // Role ↔ Permission
867
+ async addPermissionToRole(role: string, permission: string): Promise<void> { /* ... */ }
868
+ async removePermissionFromRole(role: string, permission: string): Promise<void> { /* ... */ }
869
+ async getPermissionsForRole(role: string): Promise<string[]> { /* ... */ }
870
+
871
+ // Convenience
872
+ async getPermissionsForUser(userId: string, tenantId?: string): Promise<string[]> { /* ... */ }
873
+ async userHasPermission(userId: string, permission: string, tenantId?: string): Promise<boolean> { /* ... */ }
874
+ }
875
+ ```
876
+
877
+ ### Usage
878
+
879
+ ```typescript
880
+ const rbac = new MyRbacStore();
881
+
882
+ // Create roles with permissions
883
+ await rbac.createRole('editor', ['posts:read', 'posts:write']);
884
+ await rbac.createRole('admin', ['posts:read', 'posts:write', 'users:manage']);
885
+
886
+ // Assign a role to a user (optionally scoped to a tenant)
887
+ await rbac.addRoleToUser(userId, 'editor', 'tenant-acme');
888
+
889
+ // Protect a route
890
+ app.delete('/posts/:id', auth.middleware(), async (req, res) => {
891
+ const allowed = await rbac.userHasPermission(req.user!.sub, 'posts:write', req.user!.tenantId as string | undefined);
892
+ if (!allowed) return res.status(403).json({ error: 'Forbidden' });
893
+ // ... delete post
894
+ });
895
+ ```
896
+
897
+ ### Combining with `buildTokenPayload`
898
+
899
+ You can embed roles/permissions directly in the JWT so route guards do not need an async DB call:
900
+
901
+ ```typescript
902
+ buildTokenPayload: async (user) => ({
903
+ roles: await rbac.getRolesForUser(user.id),
904
+ permissions: await rbac.getPermissionsForUser(user.id),
905
+ }),
906
+ ```
907
+
908
+ ## Session Management
909
+
910
+ `ISessionStore` is an **optional** interface for device-aware session management — useful for "active sessions" screens where users can see and revoke individual devices.
911
+
912
+ ```typescript
913
+ import { ISessionStore, SessionInfo } from 'node-auth';
914
+
915
+ export class MySessionStore implements ISessionStore {
916
+ async createSession(info: Omit<SessionInfo, 'sessionHandle'>): Promise<SessionInfo> {
917
+ const sessionHandle = crypto.randomUUID();
918
+ await db('sessions').insert({ sessionHandle, ...info });
919
+ return { sessionHandle, ...info };
920
+ }
921
+ async getSession(sessionHandle: string): Promise<SessionInfo | null> {
922
+ return db('sessions').where({ sessionHandle }).first() ?? null;
923
+ }
924
+ async getSessionsForUser(userId: string, tenantId?: string): Promise<SessionInfo[]> {
925
+ return db('sessions').where({ userId, ...(tenantId ? { tenantId } : {}) });
926
+ }
927
+ async updateSessionLastActive(sessionHandle: string): Promise<void> {
928
+ await db('sessions').where({ sessionHandle }).update({ lastActiveAt: new Date() });
929
+ }
930
+ async revokeSession(sessionHandle: string): Promise<void> {
931
+ await db('sessions').where({ sessionHandle }).delete();
932
+ }
933
+ async revokeAllSessionsForUser(userId: string, tenantId?: string): Promise<void> {
934
+ await db('sessions').where({ userId, ...(tenantId ? { tenantId } : {}) }).delete();
935
+ }
936
+ }
937
+ ```
938
+
939
+ ### Usage
940
+
941
+ ```typescript
942
+ const sessions = new MySessionStore();
943
+
944
+ // Create a session on login (combine with the auth router's login handler)
945
+ const session = await sessions.createSession({
946
+ userId,
947
+ createdAt: new Date(),
948
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
949
+ userAgent: req.headers['user-agent'],
950
+ ipAddress: req.ip,
951
+ });
952
+
953
+ // List active sessions for a "Manage devices" page
954
+ app.get('/sessions', auth.middleware(), async (req, res) => {
955
+ const list = await sessions.getSessionsForUser(req.user!.sub);
956
+ res.json(list);
957
+ });
958
+
959
+ // Revoke a specific session
960
+ app.delete('/sessions/:handle', auth.middleware(), async (req, res) => {
961
+ await sessions.revokeSession(req.params.handle);
962
+ res.json({ success: true });
963
+ });
964
+ ```
965
+
966
+ ### `SessionInfo` model
967
+
968
+ ```typescript
969
+ interface SessionInfo {
970
+ sessionHandle: string; // Unique opaque identifier
971
+ userId: string;
972
+ tenantId?: string; // Optional tenant scope
973
+ createdAt: Date;
974
+ expiresAt: Date;
975
+ lastActiveAt?: Date; // Bumped on each authenticated request
976
+ userAgent?: string;
977
+ ipAddress?: string;
978
+ data?: Record<string, unknown>; // Any extra device/context data
979
+ }
980
+ ```
981
+
982
+ ## Multi-Tenancy
983
+
984
+ `ITenantStore` is an **optional** interface for applications that serve multiple independent tenants (organisations, workspaces, teams).
985
+
986
+ ```typescript
987
+ import { ITenantStore, Tenant } from 'node-auth';
988
+
989
+ export class MyTenantStore implements ITenantStore {
990
+ // Tenant CRUD
991
+ async createTenant(data: Omit<Tenant, 'id'>): Promise<Tenant> { /* ... */ }
992
+ async getTenantById(id: string): Promise<Tenant | null> { /* ... */ }
993
+ async getAllTenants(): Promise<Tenant[]> { /* ... */ }
994
+ async updateTenant(id: string, data: Partial<Omit<Tenant, 'id'>>): Promise<void> { /* ... */ }
995
+ async deleteTenant(id: string): Promise<void> { /* ... */ }
996
+
997
+ // User ↔ Tenant membership
998
+ async associateUserWithTenant(userId: string, tenantId: string): Promise<void> { /* ... */ }
999
+ async disassociateUserFromTenant(userId: string, tenantId: string): Promise<void> { /* ... */ }
1000
+ async getTenantsForUser(userId: string): Promise<Tenant[]> { /* ... */ }
1001
+ async getUsersForTenant(tenantId: string): Promise<string[]> { /* ... */ }
1002
+ }
1003
+ ```
1004
+
1005
+ ### Usage
1006
+
1007
+ ```typescript
1008
+ const tenants = new MyTenantStore();
1009
+
1010
+ // Onboarding: create a tenant and assign the first user as owner
1011
+ const tenant = await tenants.createTenant({ name: 'Acme Corp', isActive: true });
1012
+ await tenants.associateUserWithTenant(userId, tenant.id);
1013
+
1014
+ // Inject tenantId into the JWT via buildTokenPayload.
1015
+ // For users that belong to a single tenant this works directly.
1016
+ // For users with multiple tenants, embed the full list and let the
1017
+ // client pass an `X-Tenant-ID` header that is validated per-request.
1018
+ buildTokenPayload: async (user) => ({
1019
+ tenants: (await tenants.getTenantsForUser(user.id)).map(t => t.id),
1020
+ }),
1021
+
1022
+ // Guard: ensure user belongs to the requested tenant
1023
+ app.get('/tenants/:id/data', auth.middleware(), async (req, res) => {
1024
+ const userTenants = await tenants.getTenantsForUser(req.user!.sub);
1025
+ if (!userTenants.find(t => t.id === req.params.id)) {
1026
+ return res.status(403).json({ error: 'Forbidden' });
1027
+ }
1028
+ // ... return tenant data
1029
+ });
1030
+ ```
1031
+
1032
+ ### `Tenant` model
1033
+
1034
+ ```typescript
1035
+ interface Tenant {
1036
+ id: string; // Unique identifier (slug, UUID, etc.)
1037
+ name: string;
1038
+ isActive?: boolean; // Defaults to true
1039
+ config?: Record<string, unknown>; // Per-tenant settings (branding, feature flags, etc.)
1040
+ createdAt?: Date;
1041
+ }
1042
+ ```
1043
+
607
1044
  ## Building
608
1045
 
609
1046
  ```bash
@@ -621,16 +1058,47 @@ npm run test:coverage
621
1058
 
622
1059
  ```
623
1060
  src/
624
- ├── interfaces/ # IUserStore, ITokenStore, IAuthStrategy
625
- ├── models/ # BaseUser, TokenPair, AuthConfig, AuthError
1061
+ ├── interfaces/ # IUserStore, ITokenStore, IAuthStrategy,
1062
+ # IUserMetadataStore, IRolesPermissionsStore,
1063
+ │ # ISessionStore, ITenantStore
1064
+ ├── models/ # BaseUser, TokenPair, AuthConfig, AuthError,
1065
+ │ # SessionInfo, Tenant
626
1066
  ├── abstract/ # BaseAuthStrategy, BaseOAuthStrategy
627
1067
  ├── strategies/ # Local, Google, GitHub, MagicLink, SMS, TOTP
628
- ├── services/ # TokenService, PasswordService, SmsService
1068
+ ├── services/ # TokenService, PasswordService, SmsService, MailerService
629
1069
  ├── middleware/ # createAuthMiddleware()
630
- ├── router/ # createAuthRouter() – full Express router
1070
+ ├── router/ # createAuthRouter() – auth endpoints
1071
+ │ # createAdminRouter() – admin panel UI + REST API
631
1072
  └── auth-configurator.ts # Main entry point
632
1073
  ```
633
1074
 
1075
+ ## Comparison with SuperTokens
1076
+
1077
+ The table below maps SuperTokens recipes to node-auth equivalents so you can evaluate feature coverage for your project.
1078
+
1079
+ | SuperTokens Feature | node-auth equivalent | Notes |
1080
+ |---------------------|---------------------|-------|
1081
+ | EmailPassword recipe | `LocalStrategy` + `POST /auth/login` | Full email/password auth with bcrypt |
1082
+ | ThirdParty / OAuth | `GoogleStrategy`, `GithubStrategy` | Extend abstract strategies for any provider |
1083
+ | Passwordless (magic link) | `MagicLinkStrategy` | Email token via built-in mailer or callback |
1084
+ | Passwordless (OTP via SMS) | `SmsStrategy` | SMS code via configurable HTTP endpoint |
1085
+ | TOTP 2FA | `TotpStrategy` | Authenticator-app compatible; QR code included |
1086
+ | Session management | `ISessionStore` _(optional)_ | Device-aware sessions; list & revoke |
1087
+ | User Roles | `IRolesPermissionsStore` _(optional)_ | Full RBAC with tenant scoping |
1088
+ | User Metadata | `IUserMetadataStore` _(optional)_ | Arbitrary key/value store per user |
1089
+ | Multi-tenancy | `ITenantStore` _(optional)_ | Tenant CRUD + user membership |
1090
+ | Custom JWT claims | `buildTokenPayload` callback | Inject any data into access & refresh tokens |
1091
+ | Database agnostic | `IUserStore` interface | One interface, any DB |
1092
+ | Rate limiting | `rateLimiter` option in `router()` | Pass any Express middleware |
1093
+ | Email verification | `POST /send-verification-email` + `GET /verify-email` | Token-based, 24 h expiry, built-in templates |
1094
+ | Change password | `POST /change-password` | Authenticated; verifies current password |
1095
+ | Change email | `POST /change-email/request` + `POST /change-email/confirm` | Verification to new address, notification to old |
1096
+ | Admin dashboard UI | `createAdminRouter()` | Self-contained UI + REST API, Bearer-token protected |
1097
+ | Account linking | _(not built-in)_ | Link OAuth profiles in your `IUserStore.create` |
1098
+ | Attack protection | _(not built-in)_ | Use `rateLimiter` + external WAF |
1099
+
1100
+ > **Roadmap ideas:** account-linking helpers, email-verification enforcement middleware, SCIM provisioning.
1101
+
634
1102
  ## License
635
1103
 
636
1104
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nik2208/node-auth",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Database-agnostic JWT authentication library for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {