@mostajs/auth 1.0.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/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/components/PermissionGuard.d.ts +19 -0
- package/dist/components/PermissionGuard.js +23 -0
- package/dist/components/SessionProvider.d.ts +4 -0
- package/dist/components/SessionProvider.js +11 -0
- package/dist/hooks/usePermissions.d.ts +15 -0
- package/dist/hooks/usePermissions.js +48 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +55 -0
- package/dist/lib/auth-check.d.ts +42 -0
- package/dist/lib/auth-check.js +55 -0
- package/dist/lib/auth.d.ts +14 -0
- package/dist/lib/auth.js +139 -0
- package/dist/lib/password.d.ts +2 -0
- package/dist/lib/password.js +17 -0
- package/dist/lib/permissions-server.d.ts +5 -0
- package/dist/lib/permissions-server.js +27 -0
- package/dist/lib/permissions.d.ts +9 -0
- package/dist/lib/permissions.js +23 -0
- package/dist/lib/rbac-seed.d.ts +15 -0
- package/dist/lib/rbac-seed.js +41 -0
- package/dist/middleware/auth-middleware.d.ts +13 -0
- package/dist/middleware/auth-middleware.js +33 -0
- package/dist/repositories/permission-category.repository.d.ts +10 -0
- package/dist/repositories/permission-category.repository.js +21 -0
- package/dist/repositories/permission.repository.d.ts +12 -0
- package/dist/repositories/permission.repository.js +25 -0
- package/dist/repositories/role.repository.d.ts +18 -0
- package/dist/repositories/role.repository.js +37 -0
- package/dist/repositories/user.repository.d.ts +24 -0
- package/dist/repositories/user.repository.js +49 -0
- package/dist/schemas/permission-category.schema.d.ts +2 -0
- package/dist/schemas/permission-category.schema.js +20 -0
- package/dist/schemas/permission.schema.d.ts +2 -0
- package/dist/schemas/permission.schema.js +15 -0
- package/dist/schemas/role.schema.d.ts +2 -0
- package/dist/schemas/role.schema.js +16 -0
- package/dist/schemas/user.schema.d.ts +2 -0
- package/dist/schemas/user.schema.js +23 -0
- package/dist/types/index.d.ts +81 -0
- package/dist/types/index.js +4 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Dr Hamid MADANI <drmdh@msn.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @mostajs/auth
|
|
2
|
+
|
|
3
|
+
> Reusable authentication and RBAC module — NextAuth + Users + Roles + Permissions.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@mostajs/auth)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Part of the [@mosta suite](https://mostajs.dev).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @mostajs/auth @mostajs/orm bcryptjs next-auth@beta
|
|
16
|
+
```
|
|
17
|
+
|
|
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
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Create auth handlers
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createAuthHandlers, createAuthChecks } from '@mostajs/auth'
|
|
33
|
+
|
|
34
|
+
const ROLE_PERMISSIONS = {
|
|
35
|
+
admin: ['*'],
|
|
36
|
+
editor: ['dashboard:view', 'user:view'],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { handlers, auth, signIn, signOut } = createAuthHandlers(ROLE_PERMISSIONS)
|
|
40
|
+
const { checkAuth, checkPermission } = createAuthChecks(auth, ROLE_PERMISSIONS)
|
|
41
|
+
|
|
42
|
+
export { handlers, auth, checkAuth, checkPermission }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. Protect API routes
|
|
46
|
+
|
|
47
|
+
```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
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import PermissionGuard from '@mostajs/auth/components/PermissionGuard'
|
|
59
|
+
|
|
60
|
+
<PermissionGuard permissions={['user:delete']}>
|
|
61
|
+
<DeleteButton />
|
|
62
|
+
</PermissionGuard>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## API Reference
|
|
66
|
+
|
|
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 |
|
|
79
|
+
|
|
80
|
+
## Related Packages
|
|
81
|
+
|
|
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
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT — © 2025 Dr Hamid MADANI <drmdh@msn.com>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface PermissionGuardProps {
|
|
3
|
+
/** Required permissions */
|
|
4
|
+
permissions: string[];
|
|
5
|
+
/** Require ALL permissions (AND) vs any (OR). Default: false (OR). */
|
|
6
|
+
requireAll?: boolean;
|
|
7
|
+
/** Content shown if authorized */
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
/** Content shown if unauthorized (default: null) */
|
|
10
|
+
fallback?: React.ReactNode;
|
|
11
|
+
/** Admin role name for bypass (default: 'admin') */
|
|
12
|
+
adminRole?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Conditionally renders children based on user permissions.
|
|
16
|
+
* Admin users bypass all permission checks.
|
|
17
|
+
*/
|
|
18
|
+
export default function PermissionGuard({ permissions, requireAll, children, fallback, adminRole, }: PermissionGuardProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @mosta/auth — PermissionGuard component
|
|
3
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
|
+
'use client';
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = PermissionGuard;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const usePermissions_1 = require("../hooks/usePermissions");
|
|
9
|
+
/**
|
|
10
|
+
* Conditionally renders children based on user permissions.
|
|
11
|
+
* Admin users bypass all permission checks.
|
|
12
|
+
*/
|
|
13
|
+
function PermissionGuard({ permissions, requireAll = false, children, fallback = null, adminRole = 'admin', }) {
|
|
14
|
+
const { hasPermission, hasAnyPermission, isAdmin } = (0, usePermissions_1.usePermissions)(adminRole);
|
|
15
|
+
if (isAdmin())
|
|
16
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
17
|
+
const hasAccess = requireAll
|
|
18
|
+
? permissions.every((p) => hasPermission(p))
|
|
19
|
+
: hasAnyPermission(permissions);
|
|
20
|
+
if (!hasAccess)
|
|
21
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback });
|
|
22
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @mosta/auth — NextAuth SessionProvider wrapper
|
|
3
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
|
+
'use client';
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = SessionProvider;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_1 = require("next-auth/react");
|
|
9
|
+
function SessionProvider({ children }) {
|
|
10
|
+
return (0, jsx_runtime_1.jsx)(react_1.SessionProvider, { children: children });
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side hook for permission checking.
|
|
3
|
+
* Reads permissions from the NextAuth session.
|
|
4
|
+
*
|
|
5
|
+
* @param adminRole - The role name considered as admin (default: 'admin')
|
|
6
|
+
*/
|
|
7
|
+
export declare function usePermissions(adminRole?: string): {
|
|
8
|
+
permissions: string[];
|
|
9
|
+
role: string;
|
|
10
|
+
hasPermission: (permission: string) => boolean;
|
|
11
|
+
hasAnyPermission: (permissions: string[]) => boolean;
|
|
12
|
+
hasRole: (role: string) => boolean;
|
|
13
|
+
isAdmin: () => boolean;
|
|
14
|
+
canAccess: (permission: string) => boolean;
|
|
15
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @mosta/auth — usePermissions hook (client-side)
|
|
3
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
|
+
'use client';
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.usePermissions = usePermissions;
|
|
7
|
+
const react_1 = require("next-auth/react");
|
|
8
|
+
/**
|
|
9
|
+
* Client-side hook for permission checking.
|
|
10
|
+
* Reads permissions from the NextAuth session.
|
|
11
|
+
*
|
|
12
|
+
* @param adminRole - The role name considered as admin (default: 'admin')
|
|
13
|
+
*/
|
|
14
|
+
function usePermissions(adminRole = 'admin') {
|
|
15
|
+
const { data: session } = (0, react_1.useSession)();
|
|
16
|
+
const userPermissions = session?.user?.permissions || [];
|
|
17
|
+
const userRole = session?.user?.role || '';
|
|
18
|
+
function hasPermission(permission) {
|
|
19
|
+
if (!userPermissions || userPermissions.length === 0)
|
|
20
|
+
return false;
|
|
21
|
+
if (userPermissions.includes('*'))
|
|
22
|
+
return true;
|
|
23
|
+
return userPermissions.includes(permission);
|
|
24
|
+
}
|
|
25
|
+
function hasAnyPermission(permissions) {
|
|
26
|
+
return permissions.some((p) => hasPermission(p));
|
|
27
|
+
}
|
|
28
|
+
function hasRole(role) {
|
|
29
|
+
return userRole === role;
|
|
30
|
+
}
|
|
31
|
+
function isAdmin() {
|
|
32
|
+
return userRole === adminRole;
|
|
33
|
+
}
|
|
34
|
+
function canAccess(permission) {
|
|
35
|
+
if (isAdmin())
|
|
36
|
+
return true;
|
|
37
|
+
return hasPermission(permission);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
permissions: userPermissions,
|
|
41
|
+
role: userRole,
|
|
42
|
+
hasPermission,
|
|
43
|
+
hasAnyPermission,
|
|
44
|
+
hasRole,
|
|
45
|
+
isAdmin,
|
|
46
|
+
canAccess,
|
|
47
|
+
};
|
|
48
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { createAuthHandlers } from './lib/auth';
|
|
2
|
+
export { createAuthChecks } from './lib/auth-check';
|
|
3
|
+
export { createAuthMiddleware } from './middleware/auth-middleware';
|
|
4
|
+
export { hashPassword, comparePassword } from './lib/password';
|
|
5
|
+
export { hasPermission, getPermissionsForRole } from './lib/permissions';
|
|
6
|
+
export { getPermissionsForRoleFromDB } from './lib/permissions-server';
|
|
7
|
+
export { seedRBAC } from './lib/rbac-seed';
|
|
8
|
+
export type { SeedRBACOptions } from './lib/rbac-seed';
|
|
9
|
+
export { UserSchema } from './schemas/user.schema';
|
|
10
|
+
export { RoleSchema } from './schemas/role.schema';
|
|
11
|
+
export { PermissionSchema } from './schemas/permission.schema';
|
|
12
|
+
export { PermissionCategorySchema } from './schemas/permission-category.schema';
|
|
13
|
+
export { UserRepository } from './repositories/user.repository';
|
|
14
|
+
export { RoleRepository } from './repositories/role.repository';
|
|
15
|
+
export { PermissionRepository } from './repositories/permission.repository';
|
|
16
|
+
export { PermissionCategoryRepository } from './repositories/permission-category.repository';
|
|
17
|
+
export { usePermissions } from './hooks/usePermissions';
|
|
18
|
+
export { default as PermissionGuard } from './components/PermissionGuard';
|
|
19
|
+
export { default as SessionProvider } from './components/SessionProvider';
|
|
20
|
+
export type { MostaAuthConfig, PermissionDefinition, RoleDefinition, CategoryDefinition, UserDTO, RoleDTO, PermissionDTO, PermissionCategoryDTO, } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @mosta/auth — Barrel exports
|
|
3
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.SessionProvider = exports.PermissionGuard = exports.usePermissions = exports.PermissionCategoryRepository = exports.PermissionRepository = exports.RoleRepository = exports.UserRepository = exports.PermissionCategorySchema = exports.PermissionSchema = exports.RoleSchema = exports.UserSchema = exports.seedRBAC = exports.getPermissionsForRoleFromDB = exports.getPermissionsForRole = exports.hasPermission = exports.comparePassword = exports.hashPassword = exports.createAuthMiddleware = exports.createAuthChecks = exports.createAuthHandlers = void 0;
|
|
9
|
+
// Auth factory
|
|
10
|
+
var auth_1 = require("./lib/auth");
|
|
11
|
+
Object.defineProperty(exports, "createAuthHandlers", { enumerable: true, get: function () { return auth_1.createAuthHandlers; } });
|
|
12
|
+
var auth_check_1 = require("./lib/auth-check");
|
|
13
|
+
Object.defineProperty(exports, "createAuthChecks", { enumerable: true, get: function () { return auth_check_1.createAuthChecks; } });
|
|
14
|
+
var auth_middleware_1 = require("./middleware/auth-middleware");
|
|
15
|
+
Object.defineProperty(exports, "createAuthMiddleware", { enumerable: true, get: function () { return auth_middleware_1.createAuthMiddleware; } });
|
|
16
|
+
// Password utils
|
|
17
|
+
var password_1 = require("./lib/password");
|
|
18
|
+
Object.defineProperty(exports, "hashPassword", { enumerable: true, get: function () { return password_1.hashPassword; } });
|
|
19
|
+
Object.defineProperty(exports, "comparePassword", { enumerable: true, get: function () { return password_1.comparePassword; } });
|
|
20
|
+
// Permission helpers (client-safe)
|
|
21
|
+
var permissions_1 = require("./lib/permissions");
|
|
22
|
+
Object.defineProperty(exports, "hasPermission", { enumerable: true, get: function () { return permissions_1.hasPermission; } });
|
|
23
|
+
Object.defineProperty(exports, "getPermissionsForRole", { enumerable: true, get: function () { return permissions_1.getPermissionsForRole; } });
|
|
24
|
+
// Server-side permission DB lookup
|
|
25
|
+
var permissions_server_1 = require("./lib/permissions-server");
|
|
26
|
+
Object.defineProperty(exports, "getPermissionsForRoleFromDB", { enumerable: true, get: function () { return permissions_server_1.getPermissionsForRoleFromDB; } });
|
|
27
|
+
// RBAC seed
|
|
28
|
+
var rbac_seed_1 = require("./lib/rbac-seed");
|
|
29
|
+
Object.defineProperty(exports, "seedRBAC", { enumerable: true, get: function () { return rbac_seed_1.seedRBAC; } });
|
|
30
|
+
// Schemas
|
|
31
|
+
var user_schema_1 = require("./schemas/user.schema");
|
|
32
|
+
Object.defineProperty(exports, "UserSchema", { enumerable: true, get: function () { return user_schema_1.UserSchema; } });
|
|
33
|
+
var role_schema_1 = require("./schemas/role.schema");
|
|
34
|
+
Object.defineProperty(exports, "RoleSchema", { enumerable: true, get: function () { return role_schema_1.RoleSchema; } });
|
|
35
|
+
var permission_schema_1 = require("./schemas/permission.schema");
|
|
36
|
+
Object.defineProperty(exports, "PermissionSchema", { enumerable: true, get: function () { return permission_schema_1.PermissionSchema; } });
|
|
37
|
+
var permission_category_schema_1 = require("./schemas/permission-category.schema");
|
|
38
|
+
Object.defineProperty(exports, "PermissionCategorySchema", { enumerable: true, get: function () { return permission_category_schema_1.PermissionCategorySchema; } });
|
|
39
|
+
// Repositories
|
|
40
|
+
var user_repository_1 = require("./repositories/user.repository");
|
|
41
|
+
Object.defineProperty(exports, "UserRepository", { enumerable: true, get: function () { return user_repository_1.UserRepository; } });
|
|
42
|
+
var role_repository_1 = require("./repositories/role.repository");
|
|
43
|
+
Object.defineProperty(exports, "RoleRepository", { enumerable: true, get: function () { return role_repository_1.RoleRepository; } });
|
|
44
|
+
var permission_repository_1 = require("./repositories/permission.repository");
|
|
45
|
+
Object.defineProperty(exports, "PermissionRepository", { enumerable: true, get: function () { return permission_repository_1.PermissionRepository; } });
|
|
46
|
+
var permission_category_repository_1 = require("./repositories/permission-category.repository");
|
|
47
|
+
Object.defineProperty(exports, "PermissionCategoryRepository", { enumerable: true, get: function () { return permission_category_repository_1.PermissionCategoryRepository; } });
|
|
48
|
+
// Hooks
|
|
49
|
+
var usePermissions_1 = require("./hooks/usePermissions");
|
|
50
|
+
Object.defineProperty(exports, "usePermissions", { enumerable: true, get: function () { return usePermissions_1.usePermissions; } });
|
|
51
|
+
// Components
|
|
52
|
+
var PermissionGuard_1 = require("./components/PermissionGuard");
|
|
53
|
+
Object.defineProperty(exports, "PermissionGuard", { enumerable: true, get: function () { return __importDefault(PermissionGuard_1).default; } });
|
|
54
|
+
var SessionProvider_1 = require("./components/SessionProvider");
|
|
55
|
+
Object.defineProperty(exports, "SessionProvider", { enumerable: true, get: function () { return __importDefault(SessionProvider_1).default; } });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
type AuthFn = () => Promise<any>;
|
|
3
|
+
/**
|
|
4
|
+
* Create server-side guard functions bound to your auth() instance.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const { checkAuth, checkPermission, getUserFromSession } = createAuthChecks(auth)
|
|
8
|
+
*/
|
|
9
|
+
export declare function createAuthChecks(auth: AuthFn, fallbackMap?: Record<string, string[]>): {
|
|
10
|
+
checkAuth: () => Promise<{
|
|
11
|
+
error: NextResponse<{
|
|
12
|
+
error: {
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
session: null;
|
|
18
|
+
} | {
|
|
19
|
+
error: null;
|
|
20
|
+
session: any;
|
|
21
|
+
}>;
|
|
22
|
+
checkPermission: (requiredPermission: string) => Promise<{
|
|
23
|
+
error: NextResponse<{
|
|
24
|
+
error: {
|
|
25
|
+
code: string;
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
session: null;
|
|
30
|
+
} | {
|
|
31
|
+
error: null;
|
|
32
|
+
session: any;
|
|
33
|
+
}>;
|
|
34
|
+
getUserFromSession: (session: any) => {
|
|
35
|
+
id: any;
|
|
36
|
+
email: any;
|
|
37
|
+
name: any;
|
|
38
|
+
role: any;
|
|
39
|
+
permissions: any;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAuthChecks = createAuthChecks;
|
|
4
|
+
// @mosta/auth — Server-side auth guards
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
const server_1 = require("next/server");
|
|
7
|
+
const permissions_1 = require("./permissions");
|
|
8
|
+
const permissions_server_1 = require("./permissions-server");
|
|
9
|
+
/**
|
|
10
|
+
* Create server-side guard functions bound to your auth() instance.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const { checkAuth, checkPermission, getUserFromSession } = createAuthChecks(auth)
|
|
14
|
+
*/
|
|
15
|
+
function createAuthChecks(auth, fallbackMap) {
|
|
16
|
+
async function checkAuth() {
|
|
17
|
+
const session = await auth();
|
|
18
|
+
if (!session?.user) {
|
|
19
|
+
return {
|
|
20
|
+
error: server_1.NextResponse.json({ error: { code: 'UNAUTHORIZED', message: 'Non authentifié' } }, { status: 401 }),
|
|
21
|
+
session: null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return { error: null, session };
|
|
25
|
+
}
|
|
26
|
+
async function checkPermission(requiredPermission) {
|
|
27
|
+
const { error, session } = await checkAuth();
|
|
28
|
+
if (error)
|
|
29
|
+
return { error, session: null };
|
|
30
|
+
let userPermissions = session.user.permissions || [];
|
|
31
|
+
if (userPermissions.length === 0) {
|
|
32
|
+
const role = session.user.role;
|
|
33
|
+
if (role) {
|
|
34
|
+
userPermissions = await (0, permissions_server_1.getPermissionsForRoleFromDB)(role, fallbackMap);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!(0, permissions_1.hasPermission)(userPermissions, requiredPermission)) {
|
|
38
|
+
return {
|
|
39
|
+
error: server_1.NextResponse.json({ error: { code: 'FORBIDDEN', message: 'Permission insuffisante' } }, { status: 403 }),
|
|
40
|
+
session: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { error: null, session: session };
|
|
44
|
+
}
|
|
45
|
+
function getUserFromSession(session) {
|
|
46
|
+
return {
|
|
47
|
+
id: session.user.id,
|
|
48
|
+
email: session.user.email,
|
|
49
|
+
name: session.user.name,
|
|
50
|
+
role: session.user.role,
|
|
51
|
+
permissions: session.user.permissions || [],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { checkAuth, checkPermission, getUserFromSession };
|
|
55
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MostaAuthConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Create NextAuth handlers configured for MostaAuth RBAC.
|
|
4
|
+
*
|
|
5
|
+
* @param rolePermissions - Static role→permissions map (used as fallback)
|
|
6
|
+
* @param config - Optional MostaAuthConfig overrides
|
|
7
|
+
*/
|
|
8
|
+
export declare function createAuthHandlers(rolePermissions: Record<string, string[]>, config?: MostaAuthConfig): {
|
|
9
|
+
handlers: any;
|
|
10
|
+
auth: any;
|
|
11
|
+
signIn: any;
|
|
12
|
+
signOut: any;
|
|
13
|
+
getServerSession: any;
|
|
14
|
+
};
|
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createAuthHandlers = createAuthHandlers;
|
|
7
|
+
// @mosta/auth — NextAuth configuration factory
|
|
8
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
9
|
+
const next_auth_1 = __importDefault(require("next-auth"));
|
|
10
|
+
const credentials_1 = __importDefault(require("next-auth/providers/credentials"));
|
|
11
|
+
const orm_1 = require("@mostajs/orm");
|
|
12
|
+
const user_repository_1 = require("../repositories/user.repository");
|
|
13
|
+
const role_repository_1 = require("../repositories/role.repository");
|
|
14
|
+
const password_1 = require("./password");
|
|
15
|
+
/**
|
|
16
|
+
* Create NextAuth handlers configured for MostaAuth RBAC.
|
|
17
|
+
*
|
|
18
|
+
* @param rolePermissions - Static role→permissions map (used as fallback)
|
|
19
|
+
* @param config - Optional MostaAuthConfig overrides
|
|
20
|
+
*/
|
|
21
|
+
function createAuthHandlers(rolePermissions, config) {
|
|
22
|
+
const { handlers, auth, signIn, signOut } = (0, next_auth_1.default)({
|
|
23
|
+
secret: process.env.AUTH_SECRET || process.env.NEXTAUTH_SECRET,
|
|
24
|
+
trustHost: true,
|
|
25
|
+
debug: false,
|
|
26
|
+
useSecureCookies: false,
|
|
27
|
+
cookies: {
|
|
28
|
+
sessionToken: {
|
|
29
|
+
name: 'authjs.session-token',
|
|
30
|
+
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: false },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
providers: [
|
|
34
|
+
(0, credentials_1.default)({
|
|
35
|
+
name: 'credentials',
|
|
36
|
+
credentials: {
|
|
37
|
+
email: { label: 'Email', type: 'email' },
|
|
38
|
+
password: { label: 'Password', type: 'password' },
|
|
39
|
+
},
|
|
40
|
+
async authorize(credentials) {
|
|
41
|
+
if (!credentials?.email || !credentials?.password)
|
|
42
|
+
return null;
|
|
43
|
+
const uRepo = new user_repository_1.UserRepository(await (0, orm_1.getDialect)());
|
|
44
|
+
const user = await uRepo.findByEmail(credentials.email);
|
|
45
|
+
if (!user)
|
|
46
|
+
return null;
|
|
47
|
+
if (user.status !== 'active')
|
|
48
|
+
return null;
|
|
49
|
+
const valid = await (0, password_1.comparePassword)(credentials.password, user.password);
|
|
50
|
+
if (!valid)
|
|
51
|
+
return null;
|
|
52
|
+
await uRepo.updateLastLogin(user.id);
|
|
53
|
+
const { roleNames, permissions } = await resolveUserPermissions(user.id, rolePermissions);
|
|
54
|
+
return {
|
|
55
|
+
id: user.id,
|
|
56
|
+
email: user.email,
|
|
57
|
+
name: `${user.firstName} ${user.lastName}`,
|
|
58
|
+
role: roleNames[0] || '',
|
|
59
|
+
roles: roleNames,
|
|
60
|
+
permissions,
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
],
|
|
65
|
+
session: { strategy: 'jwt' },
|
|
66
|
+
callbacks: {
|
|
67
|
+
async jwt({ token, user }) {
|
|
68
|
+
if (user) {
|
|
69
|
+
token.role = user.role;
|
|
70
|
+
token.roles = user.roles;
|
|
71
|
+
token.permissions = user.permissions;
|
|
72
|
+
}
|
|
73
|
+
if (token.role && (!token.permissions || token.permissions.length === 0)) {
|
|
74
|
+
token.permissions = rolePermissions[token.role] || [];
|
|
75
|
+
}
|
|
76
|
+
return token;
|
|
77
|
+
},
|
|
78
|
+
async session({ session, token }) {
|
|
79
|
+
if (token && session.user) {
|
|
80
|
+
session.user.id = token.sub;
|
|
81
|
+
session.user.role = token.role;
|
|
82
|
+
session.user.roles = token.roles;
|
|
83
|
+
session.user.permissions = token.permissions;
|
|
84
|
+
}
|
|
85
|
+
return session;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
pages: {
|
|
89
|
+
signIn: config?.pages?.signIn || '/login',
|
|
90
|
+
error: config?.pages?.error || '/login',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
return { handlers, auth, signIn, signOut, getServerSession: auth };
|
|
94
|
+
}
|
|
95
|
+
// ─── Internal: resolve user→roles→permissions ─────────────────
|
|
96
|
+
async function resolveUserPermissions(userId, fallbackMap) {
|
|
97
|
+
try {
|
|
98
|
+
const uRepo = new user_repository_1.UserRepository(await (0, orm_1.getDialect)());
|
|
99
|
+
const rRepo = new role_repository_1.RoleRepository(await (0, orm_1.getDialect)());
|
|
100
|
+
const userWithRoles = await uRepo.findByIdWithRoles(userId);
|
|
101
|
+
if (userWithRoles?.roles?.length) {
|
|
102
|
+
const roleNames = [];
|
|
103
|
+
const permissions = [];
|
|
104
|
+
for (const roleObj of userWithRoles.roles) {
|
|
105
|
+
const roleName = typeof roleObj === 'string' ? roleObj : roleObj?.name;
|
|
106
|
+
const roleId = typeof roleObj === 'string' ? roleObj : roleObj?.id;
|
|
107
|
+
if (roleName)
|
|
108
|
+
roleNames.push(roleName);
|
|
109
|
+
if (roleId) {
|
|
110
|
+
const roleWithPerms = await rRepo.findByIdWithPermissions(roleId);
|
|
111
|
+
if (roleWithPerms?.permissions) {
|
|
112
|
+
for (const perm of roleWithPerms.permissions) {
|
|
113
|
+
const permName = typeof perm === 'string' ? perm : perm?.name;
|
|
114
|
+
if (permName && !permissions.includes(permName))
|
|
115
|
+
permissions.push(permName);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (roleName) {
|
|
120
|
+
for (const p of fallbackMap[roleName] || []) {
|
|
121
|
+
if (!permissions.includes(p))
|
|
122
|
+
permissions.push(p);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (roleNames.length > 0)
|
|
127
|
+
return { roleNames, permissions };
|
|
128
|
+
}
|
|
129
|
+
// Legacy: user.role = string
|
|
130
|
+
const raw = userWithRoles;
|
|
131
|
+
if (raw?.role && typeof raw.role === 'string') {
|
|
132
|
+
return { roleNames: [raw.role], permissions: fallbackMap[raw.role] || [] };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error('[MostaAuth] Error resolving permissions:', err);
|
|
137
|
+
}
|
|
138
|
+
return { roleNames: [], permissions: [] };
|
|
139
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.hashPassword = hashPassword;
|
|
7
|
+
exports.comparePassword = comparePassword;
|
|
8
|
+
// @mosta/auth — Password hashing wrapper
|
|
9
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
10
|
+
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
11
|
+
const DEFAULT_ROUNDS = 12;
|
|
12
|
+
async function hashPassword(plain, rounds = DEFAULT_ROUNDS) {
|
|
13
|
+
return bcryptjs_1.default.hash(plain, rounds);
|
|
14
|
+
}
|
|
15
|
+
async function comparePassword(plain, hashed) {
|
|
16
|
+
return bcryptjs_1.default.compare(plain, hashed);
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPermissionsForRoleFromDB = getPermissionsForRoleFromDB;
|
|
4
|
+
// @mosta/auth — Server-side permission lookup (DO NOT import client-side)
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
const orm_1 = require("@mostajs/orm");
|
|
7
|
+
const role_repository_1 = require("../repositories/role.repository");
|
|
8
|
+
/**
|
|
9
|
+
* Resolve permissions for a role by querying the database.
|
|
10
|
+
* Falls back to the provided static map if DB lookup fails.
|
|
11
|
+
*/
|
|
12
|
+
async function getPermissionsForRoleFromDB(role, fallbackMap) {
|
|
13
|
+
try {
|
|
14
|
+
const repo = new role_repository_1.RoleRepository(await (0, orm_1.getDialect)());
|
|
15
|
+
const dbRole = await repo.findByName(role);
|
|
16
|
+
if (dbRole) {
|
|
17
|
+
const roleWithPerms = await repo.findByIdWithPermissions(dbRole.id);
|
|
18
|
+
if (roleWithPerms?.permissions && roleWithPerms.permissions.length > 0) {
|
|
19
|
+
return roleWithPerms.permissions.map((p) => typeof p === 'string' ? p : p.name || p);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.error('[MostaAuth] DB permission lookup failed, using fallback:', err);
|
|
25
|
+
}
|
|
26
|
+
return fallbackMap?.[role] || [];
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a user has a specific permission.
|
|
3
|
+
* Supports wildcard '*' for full access.
|
|
4
|
+
*/
|
|
5
|
+
export declare function hasPermission(userPermissions: string[], requiredPermission: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Get permissions for a role from a static map.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getPermissionsForRole(rolePermissions: Record<string, string[]>, role: string): string[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @mosta/auth — Permission helpers (client-safe)
|
|
3
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.hasPermission = hasPermission;
|
|
6
|
+
exports.getPermissionsForRole = getPermissionsForRole;
|
|
7
|
+
/**
|
|
8
|
+
* Check if a user has a specific permission.
|
|
9
|
+
* Supports wildcard '*' for full access.
|
|
10
|
+
*/
|
|
11
|
+
function hasPermission(userPermissions, requiredPermission) {
|
|
12
|
+
if (!userPermissions || userPermissions.length === 0)
|
|
13
|
+
return false;
|
|
14
|
+
if (userPermissions.includes('*'))
|
|
15
|
+
return true;
|
|
16
|
+
return userPermissions.includes(requiredPermission);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get permissions for a role from a static map.
|
|
20
|
+
*/
|
|
21
|
+
function getPermissionsForRole(rolePermissions, role) {
|
|
22
|
+
return rolePermissions[role] || [];
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CategoryDefinition, PermissionDefinition, RoleDefinition } from '../types';
|
|
2
|
+
export interface SeedRBACOptions {
|
|
3
|
+
categories: CategoryDefinition[];
|
|
4
|
+
permissions: PermissionDefinition[];
|
|
5
|
+
roles: Record<string, RoleDefinition>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Idempotent seed of categories, permissions and roles.
|
|
9
|
+
* Uses upsert — safe to call multiple times.
|
|
10
|
+
*/
|
|
11
|
+
export declare function seedRBAC(options: SeedRBACOptions): Promise<{
|
|
12
|
+
categoryCount: number;
|
|
13
|
+
permissionCount: number;
|
|
14
|
+
roleCount: number;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.seedRBAC = seedRBAC;
|
|
4
|
+
// @mosta/auth — RBAC Seed function
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
const orm_1 = require("@mostajs/orm");
|
|
7
|
+
const permission_category_repository_1 = require("../repositories/permission-category.repository");
|
|
8
|
+
const permission_repository_1 = require("../repositories/permission.repository");
|
|
9
|
+
const role_repository_1 = require("../repositories/role.repository");
|
|
10
|
+
/**
|
|
11
|
+
* Idempotent seed of categories, permissions and roles.
|
|
12
|
+
* Uses upsert — safe to call multiple times.
|
|
13
|
+
*/
|
|
14
|
+
async function seedRBAC(options) {
|
|
15
|
+
const dialect = await (0, orm_1.getDialect)();
|
|
16
|
+
const catRepo = new permission_category_repository_1.PermissionCategoryRepository(dialect);
|
|
17
|
+
const permRepo = new permission_repository_1.PermissionRepository(dialect);
|
|
18
|
+
const roleRepo = new role_repository_1.RoleRepository(dialect);
|
|
19
|
+
// 1. Upsert categories
|
|
20
|
+
for (const cat of options.categories) {
|
|
21
|
+
await catRepo.upsert({ name: cat.name }, cat);
|
|
22
|
+
}
|
|
23
|
+
// 2. Upsert permissions — build code→id map
|
|
24
|
+
const permissionMap = {};
|
|
25
|
+
for (const pDef of options.permissions) {
|
|
26
|
+
const perm = await permRepo.upsert({ name: pDef.name }, { name: pDef.name, description: pDef.description, category: pDef.category });
|
|
27
|
+
permissionMap[pDef.code] = perm.id;
|
|
28
|
+
}
|
|
29
|
+
// 3. Upsert roles with permission IDs
|
|
30
|
+
for (const [, roleDef] of Object.entries(options.roles)) {
|
|
31
|
+
const permissionIds = roleDef.permissions
|
|
32
|
+
.map((code) => permissionMap[code])
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
await roleRepo.upsert({ name: roleDef.name }, { name: roleDef.name, description: roleDef.description, permissions: permissionIds });
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
categoryCount: options.categories.length,
|
|
38
|
+
permissionCount: options.permissions.length,
|
|
39
|
+
roleCount: Object.keys(options.roles).length,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
export interface AuthMiddlewareOptions {
|
|
3
|
+
/** Paths that don't require authentication (default: ['/login', '/api/auth', '/setup', '/api/setup']) */
|
|
4
|
+
publicPaths?: string[];
|
|
5
|
+
/** Path prefixes that require authentication (default: ['/dashboard', '/admin', '/agent']) */
|
|
6
|
+
protectedPrefixes?: string[];
|
|
7
|
+
/** Redirect path when unauthenticated (default: '/login') */
|
|
8
|
+
loginPath?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create a Next.js middleware that protects routes based on session cookie.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createAuthMiddleware(options?: AuthMiddlewareOptions): (req: NextRequest) => NextResponse<unknown>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAuthMiddleware = createAuthMiddleware;
|
|
4
|
+
// @mosta/auth — Configurable authentication middleware for Next.js
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
const server_1 = require("next/server");
|
|
7
|
+
/**
|
|
8
|
+
* Create a Next.js middleware that protects routes based on session cookie.
|
|
9
|
+
*/
|
|
10
|
+
function createAuthMiddleware(options) {
|
|
11
|
+
const publicPaths = options?.publicPaths ?? ['/login', '/api/auth', '/setup', '/api/setup'];
|
|
12
|
+
const protectedPrefixes = options?.protectedPrefixes ?? ['/dashboard', '/admin', '/agent'];
|
|
13
|
+
const loginPath = options?.loginPath ?? '/login';
|
|
14
|
+
return function middleware(req) {
|
|
15
|
+
const { pathname } = req.nextUrl;
|
|
16
|
+
// Allow public paths
|
|
17
|
+
if (publicPaths.some((p) => pathname.startsWith(p))) {
|
|
18
|
+
return server_1.NextResponse.next();
|
|
19
|
+
}
|
|
20
|
+
// Allow static files
|
|
21
|
+
if (pathname.startsWith('/_next') || pathname.startsWith('/favicon') || pathname.includes('.')) {
|
|
22
|
+
return server_1.NextResponse.next();
|
|
23
|
+
}
|
|
24
|
+
// Check for session token
|
|
25
|
+
const token = req.cookies.get('authjs.session-token')?.value ||
|
|
26
|
+
req.cookies.get('__Secure-authjs.session-token')?.value;
|
|
27
|
+
// Redirect protected routes to login if no session
|
|
28
|
+
if (!token && protectedPrefixes.some((p) => pathname.startsWith(p))) {
|
|
29
|
+
return server_1.NextResponse.redirect(new URL(loginPath, req.url));
|
|
30
|
+
}
|
|
31
|
+
return server_1.NextResponse.next();
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseRepository } from '@mostajs/orm';
|
|
2
|
+
import type { IDialect } from '@mostajs/orm';
|
|
3
|
+
import type { PermissionCategoryDTO } from '../types';
|
|
4
|
+
export declare class PermissionCategoryRepository extends BaseRepository<PermissionCategoryDTO> {
|
|
5
|
+
constructor(dialect: IDialect);
|
|
6
|
+
/** Find all sorted by order then name */
|
|
7
|
+
findAllOrdered(): Promise<PermissionCategoryDTO[]>;
|
|
8
|
+
/** Find by name (unique) */
|
|
9
|
+
findByName(name: string): Promise<PermissionCategoryDTO | null>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PermissionCategoryRepository = void 0;
|
|
4
|
+
// @mosta/auth — PermissionCategoryRepository
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
const orm_1 = require("@mostajs/orm");
|
|
7
|
+
const permission_category_schema_1 = require("../schemas/permission-category.schema");
|
|
8
|
+
class PermissionCategoryRepository extends orm_1.BaseRepository {
|
|
9
|
+
constructor(dialect) {
|
|
10
|
+
super(permission_category_schema_1.PermissionCategorySchema, dialect);
|
|
11
|
+
}
|
|
12
|
+
/** Find all sorted by order then name */
|
|
13
|
+
async findAllOrdered() {
|
|
14
|
+
return this.findAll({}, { sort: { order: 1, name: 1 } });
|
|
15
|
+
}
|
|
16
|
+
/** Find by name (unique) */
|
|
17
|
+
async findByName(name) {
|
|
18
|
+
return this.findOne({ name });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.PermissionCategoryRepository = PermissionCategoryRepository;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseRepository } from '@mostajs/orm';
|
|
2
|
+
import type { IDialect } from '@mostajs/orm';
|
|
3
|
+
import type { PermissionDTO } from '../types';
|
|
4
|
+
export declare class PermissionRepository extends BaseRepository<PermissionDTO> {
|
|
5
|
+
constructor(dialect: IDialect);
|
|
6
|
+
/** Find all sorted by category then name */
|
|
7
|
+
findAllSorted(): Promise<PermissionDTO[]>;
|
|
8
|
+
/** Find by name (unique) */
|
|
9
|
+
findByName(name: string): Promise<PermissionDTO | null>;
|
|
10
|
+
/** Count permissions in a category */
|
|
11
|
+
countByCategory(category: string): Promise<number>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PermissionRepository = void 0;
|
|
4
|
+
// @mosta/auth — PermissionRepository
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
const orm_1 = require("@mostajs/orm");
|
|
7
|
+
const permission_schema_1 = require("../schemas/permission.schema");
|
|
8
|
+
class PermissionRepository extends orm_1.BaseRepository {
|
|
9
|
+
constructor(dialect) {
|
|
10
|
+
super(permission_schema_1.PermissionSchema, dialect);
|
|
11
|
+
}
|
|
12
|
+
/** Find all sorted by category then name */
|
|
13
|
+
async findAllSorted() {
|
|
14
|
+
return this.findAll({}, { sort: { category: 1, name: 1 } });
|
|
15
|
+
}
|
|
16
|
+
/** Find by name (unique) */
|
|
17
|
+
async findByName(name) {
|
|
18
|
+
return this.findOne({ name });
|
|
19
|
+
}
|
|
20
|
+
/** Count permissions in a category */
|
|
21
|
+
async countByCategory(category) {
|
|
22
|
+
return this.count({ category });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.PermissionRepository = PermissionRepository;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseRepository } from '@mostajs/orm';
|
|
2
|
+
import type { IDialect } from '@mostajs/orm';
|
|
3
|
+
import type { RoleDTO } from '../types';
|
|
4
|
+
export declare class RoleRepository extends BaseRepository<RoleDTO> {
|
|
5
|
+
constructor(dialect: IDialect);
|
|
6
|
+
/** Find all roles with permissions populated */
|
|
7
|
+
findAllWithPermissions(): Promise<RoleDTO[]>;
|
|
8
|
+
/** Find a role by name */
|
|
9
|
+
findByName(name: string): Promise<RoleDTO | null>;
|
|
10
|
+
/** Find role by ID with permissions populated */
|
|
11
|
+
findByIdWithPermissions(id: string): Promise<RoleDTO | null>;
|
|
12
|
+
/** Add a permission to a role */
|
|
13
|
+
addPermission(roleId: string, permissionId: string): Promise<RoleDTO | null>;
|
|
14
|
+
/** Remove a permission from a role */
|
|
15
|
+
removePermission(roleId: string, permissionId: string): Promise<RoleDTO | null>;
|
|
16
|
+
/** Remove a permission from ALL roles (cascade) */
|
|
17
|
+
removePermissionFromAll(permissionId: string): Promise<number>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RoleRepository = void 0;
|
|
4
|
+
// @mosta/auth — RoleRepository
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
const orm_1 = require("@mostajs/orm");
|
|
7
|
+
const role_schema_1 = require("../schemas/role.schema");
|
|
8
|
+
class RoleRepository extends orm_1.BaseRepository {
|
|
9
|
+
constructor(dialect) {
|
|
10
|
+
super(role_schema_1.RoleSchema, dialect);
|
|
11
|
+
}
|
|
12
|
+
/** Find all roles with permissions populated */
|
|
13
|
+
async findAllWithPermissions() {
|
|
14
|
+
return this.findWithRelations({}, ['permissions']);
|
|
15
|
+
}
|
|
16
|
+
/** Find a role by name */
|
|
17
|
+
async findByName(name) {
|
|
18
|
+
return this.findOne({ name });
|
|
19
|
+
}
|
|
20
|
+
/** Find role by ID with permissions populated */
|
|
21
|
+
async findByIdWithPermissions(id) {
|
|
22
|
+
return this.findByIdWithRelations(id, ['permissions']);
|
|
23
|
+
}
|
|
24
|
+
/** Add a permission to a role */
|
|
25
|
+
async addPermission(roleId, permissionId) {
|
|
26
|
+
return this.addToSet(roleId, 'permissions', permissionId);
|
|
27
|
+
}
|
|
28
|
+
/** Remove a permission from a role */
|
|
29
|
+
async removePermission(roleId, permissionId) {
|
|
30
|
+
return this.pull(roleId, 'permissions', permissionId);
|
|
31
|
+
}
|
|
32
|
+
/** Remove a permission from ALL roles (cascade) */
|
|
33
|
+
async removePermissionFromAll(permissionId) {
|
|
34
|
+
return this.updateMany({ permissions: permissionId }, { $pull: { permissions: permissionId } });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.RoleRepository = RoleRepository;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BaseRepository } from '@mostajs/orm';
|
|
2
|
+
import type { IDialect, FilterQuery, QueryOptions } from '@mostajs/orm';
|
|
3
|
+
import type { UserDTO } from '../types';
|
|
4
|
+
export declare class UserRepository extends BaseRepository<UserDTO> {
|
|
5
|
+
constructor(dialect: IDialect);
|
|
6
|
+
/** List users without password field */
|
|
7
|
+
findAllSafe(filter?: FilterQuery, options?: QueryOptions): Promise<UserDTO[]>;
|
|
8
|
+
/** Find a single user by ID without password */
|
|
9
|
+
findByIdSafe(id: string): Promise<UserDTO | null>;
|
|
10
|
+
/** Find user by email (for authentication) */
|
|
11
|
+
findByEmail(email: string): Promise<UserDTO | null>;
|
|
12
|
+
/** Update lastLoginAt timestamp */
|
|
13
|
+
updateLastLogin(id: string): Promise<void>;
|
|
14
|
+
/** Find user by ID with roles populated */
|
|
15
|
+
findByIdWithRoles(id: string): Promise<UserDTO | null>;
|
|
16
|
+
/** Find all users with roles populated (no password) */
|
|
17
|
+
findAllWithRoles(filter?: FilterQuery, options?: QueryOptions): Promise<UserDTO[]>;
|
|
18
|
+
/** Count users having a specific role */
|
|
19
|
+
countByRole(roleId: string): Promise<number>;
|
|
20
|
+
/** Add a role to a user */
|
|
21
|
+
addRole(userId: string, roleId: string): Promise<UserDTO | null>;
|
|
22
|
+
/** Remove a role from a user */
|
|
23
|
+
removeRole(userId: string, roleId: string): Promise<UserDTO | null>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserRepository = void 0;
|
|
4
|
+
// @mosta/auth — UserRepository
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
const orm_1 = require("@mostajs/orm");
|
|
7
|
+
const user_schema_1 = require("../schemas/user.schema");
|
|
8
|
+
class UserRepository extends orm_1.BaseRepository {
|
|
9
|
+
constructor(dialect) {
|
|
10
|
+
super(user_schema_1.UserSchema, dialect);
|
|
11
|
+
}
|
|
12
|
+
/** List users without password field */
|
|
13
|
+
async findAllSafe(filter = {}, options) {
|
|
14
|
+
return this.findAll(filter, { ...options, exclude: ['password'] });
|
|
15
|
+
}
|
|
16
|
+
/** Find a single user by ID without password */
|
|
17
|
+
async findByIdSafe(id) {
|
|
18
|
+
return this.findById(id, { exclude: ['password'] });
|
|
19
|
+
}
|
|
20
|
+
/** Find user by email (for authentication) */
|
|
21
|
+
async findByEmail(email) {
|
|
22
|
+
return this.findOne({ email: email.toLowerCase() });
|
|
23
|
+
}
|
|
24
|
+
/** Update lastLoginAt timestamp */
|
|
25
|
+
async updateLastLogin(id) {
|
|
26
|
+
await this.update(id, { lastLoginAt: new Date() });
|
|
27
|
+
}
|
|
28
|
+
/** Find user by ID with roles populated */
|
|
29
|
+
async findByIdWithRoles(id) {
|
|
30
|
+
return this.findByIdWithRelations(id, ['roles']);
|
|
31
|
+
}
|
|
32
|
+
/** Find all users with roles populated (no password) */
|
|
33
|
+
async findAllWithRoles(filter = {}, options) {
|
|
34
|
+
return this.findWithRelations(filter, ['roles'], { ...options, exclude: ['password'] });
|
|
35
|
+
}
|
|
36
|
+
/** Count users having a specific role */
|
|
37
|
+
async countByRole(roleId) {
|
|
38
|
+
return this.count({ roles: roleId });
|
|
39
|
+
}
|
|
40
|
+
/** Add a role to a user */
|
|
41
|
+
async addRole(userId, roleId) {
|
|
42
|
+
return this.addToSet(userId, 'roles', roleId);
|
|
43
|
+
}
|
|
44
|
+
/** Remove a role from a user */
|
|
45
|
+
async removeRole(userId, roleId) {
|
|
46
|
+
return this.pull(userId, 'roles', roleId);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.UserRepository = UserRepository;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PermissionCategorySchema = void 0;
|
|
4
|
+
exports.PermissionCategorySchema = {
|
|
5
|
+
name: 'PermissionCategory',
|
|
6
|
+
collection: 'permission_categories',
|
|
7
|
+
timestamps: true,
|
|
8
|
+
fields: {
|
|
9
|
+
name: { type: 'string', required: true, unique: true, lowercase: true, trim: true },
|
|
10
|
+
label: { type: 'string', required: true, trim: true },
|
|
11
|
+
description: { type: 'string' },
|
|
12
|
+
icon: { type: 'string' },
|
|
13
|
+
order: { type: 'number', default: 0 },
|
|
14
|
+
system: { type: 'boolean', default: false },
|
|
15
|
+
},
|
|
16
|
+
relations: {},
|
|
17
|
+
indexes: [
|
|
18
|
+
{ fields: { order: 'asc', name: 'asc' } },
|
|
19
|
+
],
|
|
20
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PermissionSchema = void 0;
|
|
4
|
+
exports.PermissionSchema = {
|
|
5
|
+
name: 'Permission',
|
|
6
|
+
collection: 'permissions',
|
|
7
|
+
timestamps: true,
|
|
8
|
+
fields: {
|
|
9
|
+
name: { type: 'string', required: true, unique: true },
|
|
10
|
+
description: { type: 'string' },
|
|
11
|
+
category: { type: 'string' },
|
|
12
|
+
},
|
|
13
|
+
relations: {},
|
|
14
|
+
indexes: [],
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RoleSchema = void 0;
|
|
4
|
+
exports.RoleSchema = {
|
|
5
|
+
name: 'Role',
|
|
6
|
+
collection: 'roles',
|
|
7
|
+
timestamps: true,
|
|
8
|
+
fields: {
|
|
9
|
+
name: { type: 'string', required: true, unique: true },
|
|
10
|
+
description: { type: 'string' },
|
|
11
|
+
},
|
|
12
|
+
relations: {
|
|
13
|
+
permissions: { target: 'Permission', type: 'many-to-many', through: 'role_permissions' },
|
|
14
|
+
},
|
|
15
|
+
indexes: [],
|
|
16
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserSchema = void 0;
|
|
4
|
+
exports.UserSchema = {
|
|
5
|
+
name: 'User',
|
|
6
|
+
collection: 'users',
|
|
7
|
+
timestamps: true,
|
|
8
|
+
fields: {
|
|
9
|
+
email: { type: 'string', required: true, unique: true, lowercase: true, trim: true },
|
|
10
|
+
password: { type: 'string', required: true },
|
|
11
|
+
firstName: { type: 'string', required: true, trim: true },
|
|
12
|
+
lastName: { type: 'string', required: true, trim: true },
|
|
13
|
+
phone: { type: 'string', trim: true },
|
|
14
|
+
status: { type: 'string', enum: ['active', 'locked', 'disabled'], default: 'active' },
|
|
15
|
+
lastLoginAt: { type: 'date' },
|
|
16
|
+
},
|
|
17
|
+
relations: {
|
|
18
|
+
roles: { target: 'Role', type: 'many-to-many', through: 'user_roles' },
|
|
19
|
+
},
|
|
20
|
+
indexes: [
|
|
21
|
+
{ fields: { status: 'asc' } },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export interface MostaAuthConfig {
|
|
2
|
+
/** Extra fields to add to the User schema */
|
|
3
|
+
extraUserFields?: Record<string, any>;
|
|
4
|
+
/** Default roles to seed */
|
|
5
|
+
defaultRoles?: Record<string, RoleDefinition>;
|
|
6
|
+
/** Default permissions to seed */
|
|
7
|
+
defaultPermissions?: PermissionDefinition[];
|
|
8
|
+
/** Default permission categories to seed */
|
|
9
|
+
defaultCategories?: CategoryDefinition[];
|
|
10
|
+
/** bcrypt salt rounds (default: 12) */
|
|
11
|
+
bcryptRounds?: number;
|
|
12
|
+
/** NextAuth pages */
|
|
13
|
+
pages?: {
|
|
14
|
+
signIn?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
};
|
|
17
|
+
/** Public paths (not requiring auth) */
|
|
18
|
+
publicPaths?: string[];
|
|
19
|
+
/** Protected path prefixes (redirect to login if no session) */
|
|
20
|
+
protectedPrefixes?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface PermissionDefinition {
|
|
23
|
+
code: string;
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
category: string;
|
|
27
|
+
}
|
|
28
|
+
export interface RoleDefinition {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
system: boolean;
|
|
32
|
+
permissions: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface CategoryDefinition {
|
|
35
|
+
name: string;
|
|
36
|
+
label: string;
|
|
37
|
+
description: string;
|
|
38
|
+
icon: string;
|
|
39
|
+
order: number;
|
|
40
|
+
system: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface UserDTO {
|
|
43
|
+
id: string;
|
|
44
|
+
email: string;
|
|
45
|
+
password?: string;
|
|
46
|
+
firstName: string;
|
|
47
|
+
lastName: string;
|
|
48
|
+
phone?: string;
|
|
49
|
+
roles: any[];
|
|
50
|
+
status: 'active' | 'locked' | 'disabled';
|
|
51
|
+
lastLoginAt?: string;
|
|
52
|
+
createdAt: string;
|
|
53
|
+
updatedAt: string;
|
|
54
|
+
}
|
|
55
|
+
export interface RoleDTO {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
permissions: any[];
|
|
60
|
+
createdAt: string;
|
|
61
|
+
updatedAt: string;
|
|
62
|
+
}
|
|
63
|
+
export interface PermissionDTO {
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
category?: string;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
updatedAt: string;
|
|
70
|
+
}
|
|
71
|
+
export interface PermissionCategoryDTO {
|
|
72
|
+
id: string;
|
|
73
|
+
name: string;
|
|
74
|
+
label: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
icon?: string;
|
|
77
|
+
order: number;
|
|
78
|
+
system: boolean;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
updatedAt: string;
|
|
81
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mostajs/auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reusable authentication and RBAC module — NextAuth + Users + Roles + Permissions",
|
|
5
|
+
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./components/SessionProvider": {
|
|
17
|
+
"types": "./dist/components/SessionProvider.d.ts",
|
|
18
|
+
"import": "./dist/components/SessionProvider.js",
|
|
19
|
+
"require": "./dist/components/SessionProvider.js",
|
|
20
|
+
"default": "./dist/components/SessionProvider.js"
|
|
21
|
+
},
|
|
22
|
+
"./components/PermissionGuard": {
|
|
23
|
+
"types": "./dist/components/PermissionGuard.d.ts",
|
|
24
|
+
"import": "./dist/components/PermissionGuard.js",
|
|
25
|
+
"require": "./dist/components/PermissionGuard.js",
|
|
26
|
+
"default": "./dist/components/PermissionGuard.js"
|
|
27
|
+
},
|
|
28
|
+
"./hooks/usePermissions": {
|
|
29
|
+
"types": "./dist/hooks/usePermissions.d.ts",
|
|
30
|
+
"import": "./dist/hooks/usePermissions.js",
|
|
31
|
+
"require": "./dist/hooks/usePermissions.js",
|
|
32
|
+
"default": "./dist/hooks/usePermissions.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"keywords": [
|
|
41
|
+
"authentication",
|
|
42
|
+
"authorization",
|
|
43
|
+
"rbac",
|
|
44
|
+
"nextauth",
|
|
45
|
+
"permissions",
|
|
46
|
+
"roles",
|
|
47
|
+
"nextjs",
|
|
48
|
+
"mosta"
|
|
49
|
+
],
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/apolocine/mosta-auth"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://mostajs.dev/packages/auth",
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc",
|
|
60
|
+
"prepublishOnly": "npm run build"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@mostajs/orm": "^1.0.0",
|
|
64
|
+
"bcryptjs": "^2.4.3",
|
|
65
|
+
"next-auth": "5.0.0-beta.25"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"next": ">=14",
|
|
69
|
+
"react": ">=18"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@types/bcryptjs": "^2.4.0",
|
|
73
|
+
"@types/node": "^25.3.3",
|
|
74
|
+
"@types/react": "^19.0.0",
|
|
75
|
+
"next": "^15.0.0",
|
|
76
|
+
"react": "^19.0.0",
|
|
77
|
+
"typescript": "^5.6.0"
|
|
78
|
+
}
|
|
79
|
+
}
|