@mostajs/auth 2.0.2 → 2.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.
package/README.md CHANGED
@@ -1,87 +1,64 @@
1
1
  # @mostajs/auth
2
2
 
3
- > Reusable authentication and RBAC module NextAuth + Users + Roles + Permissions.
3
+ > NextAuth authentication with RBAC — delegates user management to @mostajs/rbac.
4
+ > Author: Dr Hamid MADANI drmdh@msn.com
4
5
 
5
- [![npm version](https://img.shields.io/npm/v/@mostajs/auth.svg)](https://www.npmjs.com/package/@mostajs/auth)
6
- [![license](https://img.shields.io/npm/l/@mostajs/auth.svg)](LICENSE)
7
-
8
- Part of the [@mosta suite](https://mostajs.dev).
9
-
10
- ---
11
-
12
- ## Installation
6
+ ## Install
13
7
 
14
8
  ```bash
15
- npm install @mostajs/auth @mostajs/orm bcryptjs next-auth@beta
9
+ npm install @mostajs/auth @mostajs/rbac next-auth@5.0.0-beta.25
16
10
  ```
17
11
 
18
- ## Quick Start
19
-
20
- ### 1. Register schemas
21
-
22
- ```typescript
23
- import { registerSchemas } from '@mostajs/orm'
24
- import { UserSchema, RoleSchema, PermissionSchema, PermissionCategorySchema } from '@mostajs/auth'
25
-
26
- registerSchemas([UserSchema, RoleSchema, PermissionSchema, PermissionCategorySchema])
27
- ```
12
+ ## How to Use
28
13
 
29
- ### 2. Create auth handlers
14
+ ### 1. Create Auth Handlers
30
15
 
31
16
  ```typescript
32
- import { createAuthHandlers, createAuthChecks } from '@mostajs/auth'
17
+ // src/lib/auth.ts
18
+ import { createAuthHandlers } from '@mostajs/auth/server'
33
19
 
34
20
  const ROLE_PERMISSIONS = {
35
21
  admin: ['*'],
36
- editor: ['dashboard:view', 'user:view'],
22
+ agent: ['client:view', 'ticket:create'],
37
23
  }
38
24
 
39
- const { handlers, auth, signIn, signOut } = createAuthHandlers(ROLE_PERMISSIONS)
40
- const { checkAuth, checkPermission } = createAuthChecks(auth, ROLE_PERMISSIONS)
25
+ const { handlers, auth, signIn, signOut } = createAuthHandlers(ROLE_PERMISSIONS, {
26
+ pages: { signIn: '/login', error: '/login' },
27
+ })
41
28
 
42
- export { handlers, auth, checkAuth, checkPermission }
29
+ export { handlers, auth, signIn, signOut }
43
30
  ```
44
31
 
45
- ### 3. Protect API routes
32
+ ### 2. Auth Checks in API Routes
46
33
 
47
34
  ```typescript
48
- export async function GET(req: Request) {
49
- const { error, session } = await checkPermission('user:view')
50
- if (error) return error
51
- // ...
52
- }
53
- ```
54
-
55
- ### 4. Client-side permission guard
35
+ import { createAuthChecks } from '@mostajs/auth/server'
36
+ import { auth } from '@/lib/auth'
56
37
 
57
- ```tsx
58
- import PermissionGuard from '@mostajs/auth/components/PermissionGuard'
38
+ const { checkAuth, checkPermission } = createAuthChecks(auth, ROLE_PERMISSIONS)
59
39
 
60
- <PermissionGuard permissions={['user:delete']}>
61
- <DeleteButton />
62
- </PermissionGuard>
40
+ // In API route:
41
+ const { error } = await checkPermission('client:view')
42
+ if (error) return error
63
43
  ```
64
44
 
65
- ## API Reference
45
+ ### 3. Middleware
66
46
 
67
- | Export | Description |
68
- |--------|-------------|
69
- | `createAuthHandlers()` | NextAuth configuration factory |
70
- | `createAuthChecks()` | Server-side `checkAuth()` / `checkPermission()` |
71
- | `createAuthMiddleware()` | Next.js middleware for route protection |
72
- | `seedRBAC()` | Idempotent seed of categories, permissions, roles |
73
- | `hashPassword()` / `comparePassword()` | bcryptjs wrappers |
74
- | `hasPermission()` / `getPermissionsForRole()` | Client-safe permission helpers |
75
- | `usePermissions()` | React hook for permission checking |
76
- | `PermissionGuard` | Conditional render component |
77
- | `SessionProvider` | NextAuth session wrapper |
78
- | `UserRepository` / `RoleRepository` / `PermissionRepository` | Database repositories |
47
+ ```typescript
48
+ import { createAuthMiddleware } from '@mostajs/auth/server'
49
+ export default createAuthMiddleware({ publicPaths: ['/login'], protectedPrefixes: ['/dashboard'] })
50
+ ```
79
51
 
80
- ## Related Packages
52
+ ### 4. Create Admin (delegates to rbac)
81
53
 
82
- - [@mostajs/orm](https://www.npmjs.com/package/@mostajs/orm) — Multi-dialect ORM (required)
83
- - [@mostajs/audit](https://www.npmjs.com/package/@mostajs/audit) — Audit logging
54
+ ```typescript
55
+ import { createAdmin } from '@mostajs/auth/server'
56
+ await createAdmin({ email: 'admin@test.com', password: 'Admin123!', firstName: 'Admin', lastName: 'Test' })
57
+ ```
84
58
 
85
- ## License
59
+ ### 5. Client Components
86
60
 
87
- MIT — © 2025 Dr Hamid MADANI <drmdh@msn.com>
61
+ ```typescript
62
+ import { usePermissions } from '@mostajs/auth'
63
+ import { PermissionGuard, SessionProvider } from '@mostajs/auth'
64
+ ```
package/dist/index.d.ts CHANGED
@@ -5,3 +5,8 @@ export { default as PermissionGuard } from './components/PermissionGuard';
5
5
  export { default as SessionProvider } from './components/SessionProvider';
6
6
  export type { UserDTO, RoleDTO, PermissionDTO, PermissionCategoryDTO, PermissionDefinition, RoleDefinition, CategoryDefinition, } from '@mostajs/rbac';
7
7
  export type { MostaAuthConfig } from './types/index';
8
+ export type { RegistrationConfig } from './lib/registration';
9
+ export type { VerificationConfig } from './lib/email-verification';
10
+ export type { PasswordResetConfig } from './lib/password-reset';
11
+ export type { ApiKeyProviderConfig } from './lib/apikey-provider';
12
+ export type { SessionEnrichmentConfig } from './lib/session-enrichment';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Creates a NextAuth credentials provider that authenticates via API key
3
+ * instead of email/password. Used for programmatic access.
4
+ */
5
+ export interface ApiKeyProviderConfig {
6
+ /** Function to resolve API key (from @mostajs/api-keys) */
7
+ resolveApiKey: (rawKey: string) => Promise<any | null>;
8
+ /** Function to load account by ID */
9
+ loadAccount: (accountId: string) => Promise<any | null>;
10
+ /** Function to get permissions for account */
11
+ getPermissions?: (account: any) => Promise<string[]>;
12
+ }
13
+ export declare function createApiKeyProvider(config: ApiKeyProviderConfig): {
14
+ id: string;
15
+ name: string;
16
+ type: "credentials";
17
+ credentials: {
18
+ apiKey: {
19
+ label: string;
20
+ type: string;
21
+ };
22
+ };
23
+ authorize(credentials: any): Promise<{
24
+ id: any;
25
+ email: any;
26
+ name: any;
27
+ role: any;
28
+ roles: any[];
29
+ permissions: string[];
30
+ apiKeyId: any;
31
+ } | null>;
32
+ };
@@ -0,0 +1,34 @@
1
+ // @mostajs/auth — API key authentication provider for NextAuth
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ export function createApiKeyProvider(config) {
4
+ return {
5
+ id: 'api-key',
6
+ name: 'API Key',
7
+ type: 'credentials',
8
+ credentials: {
9
+ apiKey: { label: 'API Key', type: 'text' },
10
+ },
11
+ async authorize(credentials) {
12
+ if (!credentials?.apiKey)
13
+ return null;
14
+ const apiKey = await config.resolveApiKey(credentials.apiKey);
15
+ if (!apiKey)
16
+ return null;
17
+ const account = await config.loadAccount(apiKey.accountId);
18
+ if (!account || account.banned)
19
+ return null;
20
+ const permissions = config.getPermissions
21
+ ? await config.getPermissions(account)
22
+ : [];
23
+ return {
24
+ id: account.id ?? account._id,
25
+ email: account.email,
26
+ name: account.name,
27
+ role: account.role ?? 'user',
28
+ roles: [account.role ?? 'user'],
29
+ permissions,
30
+ apiKeyId: apiKey.id ?? apiKey._id,
31
+ };
32
+ },
33
+ };
34
+ }
@@ -0,0 +1,24 @@
1
+ export interface VerificationConfig {
2
+ /** Function to update account with verify token */
3
+ updateAccount: (id: string, data: any) => Promise<void>;
4
+ /** Function to find account by verify token */
5
+ findByVerifyToken: (token: string) => Promise<any | null>;
6
+ /** Function to send verification email */
7
+ sendEmail?: (to: string, token: string, verifyUrl: string) => Promise<void>;
8
+ /** Base URL for verify link */
9
+ verifyBaseUrl: string;
10
+ /** Token expiry in hours (default 24) */
11
+ tokenExpiryHours?: number;
12
+ }
13
+ export declare function generateVerifyToken(): string;
14
+ export declare function createVerificationHandlers(config: VerificationConfig): {
15
+ sendVerification: (accountId: string, email: string) => Promise<{
16
+ token: string;
17
+ url: string;
18
+ }>;
19
+ verifyEmail: (token: string) => Promise<{
20
+ ok: boolean;
21
+ error?: string;
22
+ }>;
23
+ generateVerifyToken: typeof generateVerifyToken;
24
+ };
@@ -0,0 +1,38 @@
1
+ // @mostajs/auth — Email verification
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import crypto from 'node:crypto';
4
+ export function generateVerifyToken() {
5
+ return crypto.randomBytes(32).toString('hex');
6
+ }
7
+ export function createVerificationHandlers(config) {
8
+ /** POST /auth/send-verification — Send verification email */
9
+ async function sendVerification(accountId, email) {
10
+ const token = generateVerifyToken();
11
+ const expiresAt = new Date(Date.now() + (config.tokenExpiryHours ?? 24) * 3600000).toISOString();
12
+ await config.updateAccount(accountId, { verifyToken: token, verifyTokenExpiresAt: expiresAt });
13
+ const url = `${config.verifyBaseUrl}?token=${token}`;
14
+ if (config.sendEmail) {
15
+ await config.sendEmail(email, token, url);
16
+ }
17
+ return { token, url };
18
+ }
19
+ /** GET /auth/verify?token=xxx — Verify email */
20
+ async function verifyEmail(token) {
21
+ if (!token)
22
+ return { ok: false, error: 'Token required' };
23
+ const account = await config.findByVerifyToken(token);
24
+ if (!account)
25
+ return { ok: false, error: 'Invalid or expired token' };
26
+ // Check expiry
27
+ if (account.verifyTokenExpiresAt && new Date(account.verifyTokenExpiresAt) < new Date()) {
28
+ return { ok: false, error: 'Token expired' };
29
+ }
30
+ await config.updateAccount(account.id ?? account._id, {
31
+ verified: true,
32
+ verifyToken: null,
33
+ verifyTokenExpiresAt: null,
34
+ });
35
+ return { ok: true };
36
+ }
37
+ return { sendVerification, verifyEmail, generateVerifyToken };
38
+ }
@@ -0,0 +1,26 @@
1
+ export interface PasswordResetConfig {
2
+ /** Find account by email */
3
+ findByEmail: (email: string) => Promise<any | null>;
4
+ /** Find account by reset token */
5
+ findByResetToken: (token: string) => Promise<any | null>;
6
+ /** Update account data */
7
+ updateAccount: (id: string, data: any) => Promise<void>;
8
+ /** Send reset email */
9
+ sendEmail?: (to: string, token: string, resetUrl: string) => Promise<void>;
10
+ /** Base URL for reset link */
11
+ resetBaseUrl: string;
12
+ /** Token expiry in hours (default 1) */
13
+ tokenExpiryHours?: number;
14
+ }
15
+ export declare function generateResetToken(): string;
16
+ export declare function createPasswordResetHandlers(config: PasswordResetConfig): {
17
+ forgotPassword: (email: string) => Promise<{
18
+ ok: boolean;
19
+ error?: string;
20
+ }>;
21
+ resetPassword: (token: string, newPassword: string) => Promise<{
22
+ ok: boolean;
23
+ error?: string;
24
+ }>;
25
+ generateResetToken: typeof generateResetToken;
26
+ };
@@ -0,0 +1,49 @@
1
+ // @mostajs/auth — Password reset
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import crypto from 'node:crypto';
4
+ import { hashPassword } from './password.js';
5
+ export function generateResetToken() {
6
+ return crypto.randomBytes(32).toString('hex');
7
+ }
8
+ export function createPasswordResetHandlers(config) {
9
+ /** POST /auth/forgot-password — Request password reset */
10
+ async function forgotPassword(email) {
11
+ const account = await config.findByEmail(email);
12
+ if (!account) {
13
+ // Don't reveal if email exists
14
+ return { ok: true };
15
+ }
16
+ const token = generateResetToken();
17
+ const expiresAt = new Date(Date.now() + (config.tokenExpiryHours ?? 1) * 3600000).toISOString();
18
+ await config.updateAccount(account.id ?? account._id, {
19
+ resetToken: token,
20
+ resetTokenExpiresAt: expiresAt,
21
+ });
22
+ const url = `${config.resetBaseUrl}?token=${token}`;
23
+ if (config.sendEmail) {
24
+ await config.sendEmail(email, token, url);
25
+ }
26
+ return { ok: true };
27
+ }
28
+ /** POST /auth/reset-password — Reset password with token */
29
+ async function resetPassword(token, newPassword) {
30
+ if (!token || !newPassword)
31
+ return { ok: false, error: 'Token and new password required' };
32
+ if (newPassword.length < 8)
33
+ return { ok: false, error: 'Password must be at least 8 characters' };
34
+ const account = await config.findByResetToken(token);
35
+ if (!account)
36
+ return { ok: false, error: 'Invalid or expired token' };
37
+ if (account.resetTokenExpiresAt && new Date(account.resetTokenExpiresAt) < new Date()) {
38
+ return { ok: false, error: 'Token expired' };
39
+ }
40
+ const hashed = await hashPassword(newPassword);
41
+ await config.updateAccount(account.id ?? account._id, {
42
+ password: hashed,
43
+ resetToken: null,
44
+ resetTokenExpiresAt: null,
45
+ });
46
+ return { ok: true };
47
+ }
48
+ return { forgotPassword, resetPassword, generateResetToken };
49
+ }
@@ -0,0 +1,18 @@
1
+ export interface RegistrationConfig {
2
+ /** Function to create account in DB (returns created account) */
3
+ createAccount: (data: {
4
+ email: string;
5
+ name: string;
6
+ password: string;
7
+ role?: string;
8
+ }) => Promise<any>;
9
+ /** Function to check if email already exists */
10
+ findByEmail: (email: string) => Promise<any | null>;
11
+ /** Default role for new users */
12
+ defaultRole?: string;
13
+ /** Default plan slug for new subscription */
14
+ defaultPlan?: string;
15
+ /** Called after successful registration */
16
+ onRegistered?: (account: any) => Promise<void>;
17
+ }
18
+ export declare function createRegistrationHandler(config: RegistrationConfig): (req: Request) => Promise<Response>;
@@ -0,0 +1,30 @@
1
+ // @mostajs/auth — Registration handler
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import { hashPassword } from './password.js';
4
+ export function createRegistrationHandler(config) {
5
+ return async function POST(req) {
6
+ const body = await req.json();
7
+ if (!body.email || !body.name || !body.password) {
8
+ return Response.json({ error: 'email, name, and password are required' }, { status: 400 });
9
+ }
10
+ if (body.password.length < 8) {
11
+ return Response.json({ error: 'Password must be at least 8 characters' }, { status: 400 });
12
+ }
13
+ // Check duplicate
14
+ const existing = await config.findByEmail(body.email);
15
+ if (existing) {
16
+ return Response.json({ error: 'Email already registered' }, { status: 409 });
17
+ }
18
+ const hashed = await hashPassword(body.password);
19
+ const account = await config.createAccount({
20
+ email: body.email,
21
+ name: body.name,
22
+ password: hashed,
23
+ role: config.defaultRole ?? 'user',
24
+ });
25
+ if (config.onRegistered) {
26
+ await config.onRegistered(account);
27
+ }
28
+ return Response.json({ ok: true, id: account.id ?? account._id }, { status: 201 });
29
+ };
30
+ }
@@ -0,0 +1,16 @@
1
+ export interface SessionEnrichmentConfig {
2
+ /** Load subscription for account */
3
+ loadSubscription?: (accountId: string) => Promise<any | null>;
4
+ /** Load plan by ID */
5
+ loadPlan?: (planId: string) => Promise<any | null>;
6
+ }
7
+ /**
8
+ * Enrich a JWT token with plan and subscription data.
9
+ * Call this in the NextAuth jwt callback.
10
+ */
11
+ export declare function enrichTokenWithPlan(token: any, config: SessionEnrichmentConfig): Promise<any>;
12
+ /**
13
+ * Copy plan data from token to session.
14
+ * Call this in the NextAuth session callback.
15
+ */
16
+ export declare function enrichSessionWithPlan(session: any, token: any): any;
@@ -0,0 +1,47 @@
1
+ // @mostajs/auth — Session enrichment with plan/subscription data
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ /**
4
+ * Enrich a JWT token with plan and subscription data.
5
+ * Call this in the NextAuth jwt callback.
6
+ */
7
+ export async function enrichTokenWithPlan(token, config) {
8
+ if (!config.loadSubscription || !config.loadPlan)
9
+ return token;
10
+ if (!token.sub)
11
+ return token;
12
+ try {
13
+ const sub = await config.loadSubscription(token.sub);
14
+ if (sub) {
15
+ token.subscriptionStatus = sub.status;
16
+ token.subscriptionId = sub.id ?? sub._id;
17
+ const planId = sub.plan ?? sub.planId;
18
+ if (planId) {
19
+ const plan = await config.loadPlan(planId);
20
+ if (plan) {
21
+ token.planSlug = plan.slug;
22
+ token.planName = plan.name;
23
+ token.planLimits = typeof plan.limits === 'string' ? JSON.parse(plan.limits) : plan.limits;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ catch {
29
+ // Don't break auth if plan loading fails
30
+ }
31
+ return token;
32
+ }
33
+ /**
34
+ * Copy plan data from token to session.
35
+ * Call this in the NextAuth session callback.
36
+ */
37
+ export function enrichSessionWithPlan(session, token) {
38
+ if (token.planSlug)
39
+ session.user.planSlug = token.planSlug;
40
+ if (token.planName)
41
+ session.user.planName = token.planName;
42
+ if (token.planLimits)
43
+ session.user.planLimits = token.planLimits;
44
+ if (token.subscriptionStatus)
45
+ session.user.subscriptionStatus = token.subscriptionStatus;
46
+ return session;
47
+ }
package/dist/server.d.ts CHANGED
@@ -10,3 +10,13 @@ export { createAdmin } from '@mostajs/rbac/lib/create-admin';
10
10
  export type { CreateAdminOptions, CreateAdminResult } from '@mostajs/rbac/lib/create-admin';
11
11
  export { getSchemas, moduleInfo } from '@mostajs/rbac/lib/module-info';
12
12
  export { UserRepository, RoleRepository, PermissionRepository, PermissionCategoryRepository } from '@mostajs/rbac/server';
13
+ export { createRegistrationHandler } from './lib/registration';
14
+ export type { RegistrationConfig } from './lib/registration';
15
+ export { createVerificationHandlers, generateVerifyToken } from './lib/email-verification';
16
+ export type { VerificationConfig } from './lib/email-verification';
17
+ export { createPasswordResetHandlers, generateResetToken } from './lib/password-reset';
18
+ export type { PasswordResetConfig } from './lib/password-reset';
19
+ export { createApiKeyProvider } from './lib/apikey-provider';
20
+ export type { ApiKeyProviderConfig } from './lib/apikey-provider';
21
+ export { enrichTokenWithPlan, enrichSessionWithPlan } from './lib/session-enrichment';
22
+ export type { SessionEnrichmentConfig } from './lib/session-enrichment';
package/dist/server.js CHANGED
@@ -17,3 +17,13 @@ export { createAdmin } from '@mostajs/rbac/lib/create-admin';
17
17
  export { getSchemas, moduleInfo } from '@mostajs/rbac/lib/module-info';
18
18
  // Repositories (re-export from rbac/server)
19
19
  export { UserRepository, RoleRepository, PermissionRepository, PermissionCategoryRepository } from '@mostajs/rbac/server';
20
+ // Registration
21
+ export { createRegistrationHandler } from './lib/registration';
22
+ // Email verification
23
+ export { createVerificationHandlers, generateVerifyToken } from './lib/email-verification';
24
+ // Password reset
25
+ export { createPasswordResetHandlers, generateResetToken } from './lib/password-reset';
26
+ // API key provider
27
+ export { createApiKeyProvider } from './lib/apikey-provider';
28
+ // Session enrichment
29
+ export { enrichTokenWithPlan, enrichSessionWithPlan } from './lib/session-enrichment';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/auth",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Authentication — NextAuth, password hashing, session management",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "MIT",
@@ -62,6 +62,31 @@
62
62
  "types": "./dist/register.d.ts",
63
63
  "import": "./dist/register.js",
64
64
  "default": "./dist/register.js"
65
+ },
66
+ "./lib/registration": {
67
+ "types": "./dist/lib/registration.d.ts",
68
+ "import": "./dist/lib/registration.js",
69
+ "default": "./dist/lib/registration.js"
70
+ },
71
+ "./lib/email-verification": {
72
+ "types": "./dist/lib/email-verification.d.ts",
73
+ "import": "./dist/lib/email-verification.js",
74
+ "default": "./dist/lib/email-verification.js"
75
+ },
76
+ "./lib/password-reset": {
77
+ "types": "./dist/lib/password-reset.d.ts",
78
+ "import": "./dist/lib/password-reset.js",
79
+ "default": "./dist/lib/password-reset.js"
80
+ },
81
+ "./lib/apikey-provider": {
82
+ "types": "./dist/lib/apikey-provider.d.ts",
83
+ "import": "./dist/lib/apikey-provider.js",
84
+ "default": "./dist/lib/apikey-provider.js"
85
+ },
86
+ "./lib/session-enrichment": {
87
+ "types": "./dist/lib/session-enrichment.d.ts",
88
+ "import": "./dist/lib/session-enrichment.js",
89
+ "default": "./dist/lib/session-enrichment.js"
65
90
  }
66
91
  },
67
92
  "files": [