@mostajs/auth 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/PermissionGuard.js +1 -1
- package/dist/index.js +4 -4
- package/dist/lib/auth-check.js +1 -1
- package/dist/lib/auth.js +1 -1
- package/dist/lib/check-request.d.ts +54 -0
- package/dist/lib/check-request.js +112 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +11 -9
- package/package.json +5 -3
- package/dist/lib/permissions.d.ts +0 -9
- package/dist/lib/permissions.js +0 -19
- package/dist/lib/rbac-seed.d.ts +0 -15
- package/dist/lib/rbac-seed.js +0 -38
- package/dist/repositories/permission-category.repository.d.ts +0 -10
- package/dist/repositories/permission-category.repository.js +0 -17
- package/dist/repositories/permission.repository.d.ts +0 -12
- package/dist/repositories/permission.repository.js +0 -21
- package/dist/repositories/role.repository.d.ts +0 -18
- package/dist/repositories/role.repository.js +0 -33
- package/dist/repositories/user.repository.d.ts +0 -24
- package/dist/repositories/user.repository.js +0 -45
- package/dist/schemas/permission-category.schema.d.ts +0 -2
- package/dist/schemas/permission-category.schema.js +0 -17
- package/dist/schemas/permission.schema.d.ts +0 -2
- package/dist/schemas/permission.schema.js +0 -12
- package/dist/schemas/role.schema.d.ts +0 -2
- package/dist/schemas/role.schema.js +0 -13
- package/dist/schemas/user.schema.d.ts +0 -2
- package/dist/schemas/user.schema.js +0 -20
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
3
|
'use client';
|
|
4
4
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
-
import { usePermissions } from '../hooks/usePermissions';
|
|
5
|
+
import { usePermissions } from '../hooks/usePermissions.js';
|
|
6
6
|
/**
|
|
7
7
|
* Conditionally renders children based on user permissions.
|
|
8
8
|
* Admin users bypass all permission checks.
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
3
|
// For server-side code (repos, seed, auth handlers), use '@mostajs/auth/server'
|
|
4
4
|
// Permission helpers (pure utilities — no external dependency)
|
|
5
|
-
export { hasPermission, hasAllPermissions, hasAnyPermission } from './helpers/permissions';
|
|
5
|
+
export { hasPermission, hasAllPermissions, hasAnyPermission } from './helpers/permissions.js';
|
|
6
6
|
// Schemas (re-export from rbac — pure data, no ORM)
|
|
7
7
|
export { UserSchema, RoleSchema, PermissionSchema, PermissionCategorySchema } from '@mostajs/rbac';
|
|
8
8
|
// Hooks
|
|
9
|
-
export { usePermissions } from './hooks/usePermissions';
|
|
9
|
+
export { usePermissions } from './hooks/usePermissions.js';
|
|
10
10
|
// Components
|
|
11
|
-
export { default as PermissionGuard } from './components/PermissionGuard';
|
|
12
|
-
export { default as SessionProvider } from './components/SessionProvider';
|
|
11
|
+
export { default as PermissionGuard } from './components/PermissionGuard.js';
|
|
12
|
+
export { default as SessionProvider } from './components/SessionProvider.js';
|
package/dist/lib/auth-check.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @mosta/auth — Server-side auth guards
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
3
|
import { NextResponse } from 'next/server';
|
|
4
|
-
import { hasPermission } from '../helpers/permissions';
|
|
4
|
+
import { hasPermission } from '../helpers/permissions.js';
|
|
5
5
|
import { getPermissionsForRoleFromDB } from '@mostajs/rbac/lib/permissions-server';
|
|
6
6
|
/**
|
|
7
7
|
* Create server-side guard functions bound to your auth() instance.
|
package/dist/lib/auth.js
CHANGED
|
@@ -5,7 +5,7 @@ import NextAuth from 'next-auth';
|
|
|
5
5
|
import CredentialsProvider from 'next-auth/providers/credentials';
|
|
6
6
|
import { getEnv } from '@mostajs/config';
|
|
7
7
|
import { getRbacRepos } from '@mostajs/rbac/lib/repos-factory';
|
|
8
|
-
import { comparePassword } from './password';
|
|
8
|
+
import { comparePassword } from './password.js';
|
|
9
9
|
/**
|
|
10
10
|
* Create NextAuth handlers configured for MostaAuth RBAC.
|
|
11
11
|
*
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { IDialect } from '@mostajs/orm';
|
|
2
|
+
export type AuthErrorCode = 'UNAUTHORIZED' | 'FORBIDDEN' | 'UNAVAILABLE' | 'AUTH_ERROR';
|
|
3
|
+
export interface CheckRequestParams {
|
|
4
|
+
/** Dialect of the metadata DB (where api_keys / users / roles tables live). */
|
|
5
|
+
dialect: IDialect | null | undefined;
|
|
6
|
+
/** Normalized HTTP-style headers (lowercased keys preferred). */
|
|
7
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
8
|
+
/** Query string params. */
|
|
9
|
+
query?: Record<string, string | string[] | undefined>;
|
|
10
|
+
/** Pre-resolved client IP (else derived from X-Forwarded-For). */
|
|
11
|
+
ip?: string;
|
|
12
|
+
/** Transport identifier — 'rest' | 'graphql' | 'mcp' | 'sse' | etc. */
|
|
13
|
+
transport: string;
|
|
14
|
+
/**
|
|
15
|
+
* (scope, value) checks the apikey must satisfy. The module is fully
|
|
16
|
+
* generic — it doesn't know what 'projects' or 'transports' mean.
|
|
17
|
+
* Common pattern :
|
|
18
|
+
* [{scope:'projects',value:slug}, {scope:'transports',value:'rest'},
|
|
19
|
+
* {scope:'operations',value:'read'|'write'|'admin'}]
|
|
20
|
+
*/
|
|
21
|
+
checks?: Array<{
|
|
22
|
+
scope: string;
|
|
23
|
+
value: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** Allow request through without apikey (legacy / public-mode). */
|
|
26
|
+
openMode?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface CheckRequestResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
/** HTTP status code suggested for the failure response (200 if ok). */
|
|
31
|
+
status: number;
|
|
32
|
+
/** Suggested JSON body for the failure response. */
|
|
33
|
+
body?: any;
|
|
34
|
+
/** Authorization context built from the resolved apikey (if ok). */
|
|
35
|
+
ctx?: {
|
|
36
|
+
transport: string;
|
|
37
|
+
apiKey?: string;
|
|
38
|
+
projectName?: string;
|
|
39
|
+
accountId?: string;
|
|
40
|
+
apikeyId?: string;
|
|
41
|
+
subscription?: string;
|
|
42
|
+
permissions?: Record<string, any>;
|
|
43
|
+
meta?: Record<string, any>;
|
|
44
|
+
};
|
|
45
|
+
/** Resolved apikey row (read-only — sensitive fields like hash are present). */
|
|
46
|
+
apikey?: any;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Single-call request authorization.
|
|
50
|
+
*
|
|
51
|
+
* Returns a verdict that the consumer maps onto its framework's response
|
|
52
|
+
* (e.g. `if (!result.ok) reply.code(result.status).send(result.body)`).
|
|
53
|
+
*/
|
|
54
|
+
export declare function checkRequest(params: CheckRequestParams): Promise<CheckRequestResult>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// @mostajs/auth — Framework-agnostic request authorization check.
|
|
2
|
+
//
|
|
3
|
+
// Orchestrateur unique pour valider une requête HTTP entrante (toutes
|
|
4
|
+
// frameworks : Fastify, Express, Hono, Next.js route handlers, Cloudflare
|
|
5
|
+
// Workers, Bun.serve, etc.).
|
|
6
|
+
//
|
|
7
|
+
// Combine :
|
|
8
|
+
// - apikey check (délégué à @mostajs/api-keys → checkApiKey)
|
|
9
|
+
// - (futur) session check NextAuth
|
|
10
|
+
// - (futur) RBAC permission check via @mostajs/rbac
|
|
11
|
+
//
|
|
12
|
+
// Le consommateur passe un objet `request` normalisé (headers/query/ip) +
|
|
13
|
+
// les `checks` de scopes — le module retourne un verdict unifié.
|
|
14
|
+
//
|
|
15
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
16
|
+
function pickHeader(h, name) {
|
|
17
|
+
if (!h)
|
|
18
|
+
return undefined;
|
|
19
|
+
const v = h[name] ?? h[name.toLowerCase()];
|
|
20
|
+
if (v == null)
|
|
21
|
+
return undefined;
|
|
22
|
+
return Array.isArray(v) ? v[0] : String(v);
|
|
23
|
+
}
|
|
24
|
+
function pickQuery(q, name) {
|
|
25
|
+
if (!q)
|
|
26
|
+
return undefined;
|
|
27
|
+
const v = q[name];
|
|
28
|
+
if (v == null)
|
|
29
|
+
return undefined;
|
|
30
|
+
return Array.isArray(v) ? v[0] : String(v);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Single-call request authorization.
|
|
34
|
+
*
|
|
35
|
+
* Returns a verdict that the consumer maps onto its framework's response
|
|
36
|
+
* (e.g. `if (!result.ok) reply.code(result.status).send(result.body)`).
|
|
37
|
+
*/
|
|
38
|
+
export async function checkRequest(params) {
|
|
39
|
+
const { dialect, headers, query, ip, transport, checks = [], openMode = false } = params;
|
|
40
|
+
// Extract auth identifiers from the request
|
|
41
|
+
const apiKey = pickHeader(headers, 'x-api-key') ??
|
|
42
|
+
(pickHeader(headers, 'authorization')?.replace(/^Bearer\s+/i, '').trim() || undefined) ??
|
|
43
|
+
pickQuery(query, 'apikey') ??
|
|
44
|
+
pickQuery(query, 'api_key');
|
|
45
|
+
const projectName = pickHeader(headers, 'x-project') ??
|
|
46
|
+
pickQuery(query, 'project');
|
|
47
|
+
const resolvedIp = ip
|
|
48
|
+
?? pickHeader(headers, 'x-forwarded-for')?.split(',')[0]?.trim()
|
|
49
|
+
?? pickHeader(headers, 'x-real-ip');
|
|
50
|
+
const baseCtx = {
|
|
51
|
+
transport,
|
|
52
|
+
apiKey,
|
|
53
|
+
projectName,
|
|
54
|
+
meta: { ip: resolvedIp },
|
|
55
|
+
};
|
|
56
|
+
// No apikey → either openMode passes through, or 401
|
|
57
|
+
if (!apiKey) {
|
|
58
|
+
if (openMode)
|
|
59
|
+
return { ok: true, status: 200, ctx: baseCtx };
|
|
60
|
+
return {
|
|
61
|
+
ok: false, status: 401,
|
|
62
|
+
body: { status: 'error', error: { code: 'UNAUTHORIZED',
|
|
63
|
+
message: 'API key required (X-API-Key header, Authorization: Bearer, or ?apikey=)' } },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// No metadata DB → can't verify
|
|
67
|
+
if (!dialect) {
|
|
68
|
+
if (openMode)
|
|
69
|
+
return { ok: true, status: 200, ctx: baseCtx };
|
|
70
|
+
return {
|
|
71
|
+
ok: false, status: 503,
|
|
72
|
+
body: { status: 'error', error: { code: 'UNAVAILABLE',
|
|
73
|
+
message: 'API key validation unavailable (no metadata DB)' } },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Delegate to @mostajs/api-keys for the actual cryptographic + scope check
|
|
77
|
+
try {
|
|
78
|
+
const { checkApiKey } = await import('@mostajs/api-keys/server');
|
|
79
|
+
const result = await checkApiKey(dialect, {
|
|
80
|
+
rawKey: apiKey,
|
|
81
|
+
checks,
|
|
82
|
+
ip: resolvedIp,
|
|
83
|
+
});
|
|
84
|
+
if (!result.ok) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
status: result.code === 'UNAUTHORIZED' ? 401 : 403,
|
|
88
|
+
body: { status: 'error', error: { code: result.code, message: result.message } },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const apikey = result.apikey;
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
status: 200,
|
|
95
|
+
apikey,
|
|
96
|
+
ctx: {
|
|
97
|
+
...baseCtx,
|
|
98
|
+
subscription: apikey?.label || apikey?.id,
|
|
99
|
+
permissions: apikey?.permissions?.scopes ?? {},
|
|
100
|
+
accountId: apikey?.account || apikey?.accountId,
|
|
101
|
+
apikeyId: apikey?.id,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false, status: 500,
|
|
108
|
+
body: { status: 'error', error: { code: 'AUTH_ERROR',
|
|
109
|
+
message: e?.message || 'auth check failed' } },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { createAuthChecks } from './lib/auth-check';
|
|
|
3
3
|
export { createAuthMiddleware } from './middleware/auth-middleware';
|
|
4
4
|
export type { AuthMiddlewareOptions } from './middleware/auth-middleware';
|
|
5
5
|
export { hashPassword, comparePassword } from './lib/password';
|
|
6
|
+
export { checkRequest } from './lib/check-request';
|
|
7
|
+
export type { CheckRequestParams, CheckRequestResult, AuthErrorCode } from './lib/check-request';
|
|
6
8
|
export { getPermissionsForRoleFromDB } from '@mostajs/rbac/lib/permissions-server';
|
|
7
9
|
export { seedRBAC } from '@mostajs/rbac/lib/rbac-seed';
|
|
8
10
|
export type { SeedRBACOptions } from '@mostajs/rbac/lib/rbac-seed';
|
package/dist/server.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
3
|
// Import from '@mostajs/auth/server' in API routes and server code
|
|
4
4
|
// Auth handler factories (depend on ORM via rbac repos)
|
|
5
|
-
export { createAuthHandlers } from './lib/auth';
|
|
6
|
-
export { createAuthChecks } from './lib/auth-check';
|
|
7
|
-
export { createAuthMiddleware } from './middleware/auth-middleware';
|
|
5
|
+
export { createAuthHandlers } from './lib/auth.js';
|
|
6
|
+
export { createAuthChecks } from './lib/auth-check.js';
|
|
7
|
+
export { createAuthMiddleware } from './middleware/auth-middleware.js';
|
|
8
8
|
// Password utils
|
|
9
|
-
export { hashPassword, comparePassword } from './lib/password';
|
|
9
|
+
export { hashPassword, comparePassword } from './lib/password.js';
|
|
10
|
+
// Framework-agnostic request authorization (apikey + scope checks)
|
|
11
|
+
export { checkRequest } from './lib/check-request.js';
|
|
10
12
|
// Server-side permission DB lookup (re-export from rbac/server)
|
|
11
13
|
export { getPermissionsForRoleFromDB } from '@mostajs/rbac/lib/permissions-server';
|
|
12
14
|
// RBAC seed (re-export from rbac/server)
|
|
@@ -18,12 +20,12 @@ export { getSchemas, moduleInfo } from '@mostajs/rbac/lib/module-info';
|
|
|
18
20
|
// Repositories (re-export from rbac/server)
|
|
19
21
|
export { UserRepository, RoleRepository, PermissionRepository, PermissionCategoryRepository } from '@mostajs/rbac/server';
|
|
20
22
|
// Registration
|
|
21
|
-
export { createRegistrationHandler } from './lib/registration';
|
|
23
|
+
export { createRegistrationHandler } from './lib/registration.js';
|
|
22
24
|
// Email verification
|
|
23
|
-
export { createVerificationHandlers, generateVerifyToken } from './lib/email-verification';
|
|
25
|
+
export { createVerificationHandlers, generateVerifyToken } from './lib/email-verification.js';
|
|
24
26
|
// Password reset
|
|
25
|
-
export { createPasswordResetHandlers, generateResetToken } from './lib/password-reset';
|
|
27
|
+
export { createPasswordResetHandlers, generateResetToken } from './lib/password-reset.js';
|
|
26
28
|
// API key provider
|
|
27
|
-
export { createApiKeyProvider } from './lib/apikey-provider';
|
|
29
|
+
export { createApiKeyProvider } from './lib/apikey-provider.js';
|
|
28
30
|
// Session enrichment
|
|
29
|
-
export { enrichTokenWithPlan, enrichSessionWithPlan } from './lib/session-enrichment';
|
|
31
|
+
export { enrichTokenWithPlan, enrichSessionWithPlan } from './lib/session-enrichment.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Authentication — NextAuth, password hashing, session management",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -114,13 +114,13 @@
|
|
|
114
114
|
"node": ">=18.0.0"
|
|
115
115
|
},
|
|
116
116
|
"scripts": {
|
|
117
|
-
"build": "tsc",
|
|
117
|
+
"build": "tsc && npm run fix-esm",
|
|
118
|
+
"fix-esm": "find dist -name '*.js' -exec sed -i -E \"s|from '(\\\\.{1,2}/[^']+)'(;?)|from '\\\\1.js'\\\\2|g; s|from \\\"(\\\\.{1,2}/[^\\\"]+)\\\"(;?)|from \\\"\\\\1.js\\\"\\\\2|g\" {} \\; && find dist -name '*.js' -exec sed -i -E \"s|\\\\.js\\\\.js|.js|g\" {} \\;",
|
|
118
119
|
"prepublishOnly": "npm run build"
|
|
119
120
|
},
|
|
120
121
|
"dependencies": {
|
|
121
122
|
"@mostajs/config": "^1.0.0",
|
|
122
123
|
"@mostajs/net": "^2.0.0",
|
|
123
|
-
"@mostajs/orm": "^1.7.0",
|
|
124
124
|
"bcryptjs": "^2.4.3"
|
|
125
125
|
},
|
|
126
126
|
"peerDependencies": {
|
|
@@ -130,6 +130,8 @@
|
|
|
130
130
|
"react": ">=18"
|
|
131
131
|
},
|
|
132
132
|
"devDependencies": {
|
|
133
|
+
"@mostajs/api-keys": "^0.2.0",
|
|
134
|
+
"@mostajs/orm": "^1.13.1",
|
|
133
135
|
"@mostajs/rbac": "^2.0.3",
|
|
134
136
|
"@types/bcryptjs": "^2.4.0",
|
|
135
137
|
"@types/node": "^25.3.3",
|
|
@@ -1,9 +0,0 @@
|
|
|
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[];
|
package/dist/lib/permissions.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// @mosta/auth — Permission helpers (client-safe)
|
|
2
|
-
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
/**
|
|
4
|
-
* Check if a user has a specific permission.
|
|
5
|
-
* Supports wildcard '*' for full access.
|
|
6
|
-
*/
|
|
7
|
-
export function hasPermission(userPermissions, requiredPermission) {
|
|
8
|
-
if (!userPermissions || userPermissions.length === 0)
|
|
9
|
-
return false;
|
|
10
|
-
if (userPermissions.includes('*'))
|
|
11
|
-
return true;
|
|
12
|
-
return userPermissions.includes(requiredPermission);
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Get permissions for a role from a static map.
|
|
16
|
-
*/
|
|
17
|
-
export function getPermissionsForRole(rolePermissions, role) {
|
|
18
|
-
return rolePermissions[role] || [];
|
|
19
|
-
}
|
package/dist/lib/rbac-seed.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { CategoryDefinition, PermissionDefinition, RoleDefinition } from '../types/index';
|
|
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
|
-
}>;
|
package/dist/lib/rbac-seed.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// @mosta/auth — RBAC Seed function
|
|
2
|
-
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { getDialect } from '@mostajs/orm';
|
|
4
|
-
import { PermissionCategoryRepository } from '../repositories/permission-category.repository';
|
|
5
|
-
import { PermissionRepository } from '../repositories/permission.repository';
|
|
6
|
-
import { RoleRepository } from '../repositories/role.repository';
|
|
7
|
-
/**
|
|
8
|
-
* Idempotent seed of categories, permissions and roles.
|
|
9
|
-
* Uses upsert — safe to call multiple times.
|
|
10
|
-
*/
|
|
11
|
-
export async function seedRBAC(options) {
|
|
12
|
-
const dialect = await getDialect();
|
|
13
|
-
const catRepo = new PermissionCategoryRepository(dialect);
|
|
14
|
-
const permRepo = new PermissionRepository(dialect);
|
|
15
|
-
const roleRepo = new RoleRepository(dialect);
|
|
16
|
-
// 1. Upsert categories
|
|
17
|
-
for (const cat of options.categories) {
|
|
18
|
-
await catRepo.upsert({ name: cat.name }, cat);
|
|
19
|
-
}
|
|
20
|
-
// 2. Upsert permissions — build code→id map
|
|
21
|
-
const permissionMap = {};
|
|
22
|
-
for (const pDef of options.permissions) {
|
|
23
|
-
const perm = await permRepo.upsert({ name: pDef.name }, { name: pDef.name, description: pDef.description, category: pDef.category });
|
|
24
|
-
permissionMap[pDef.code] = perm.id;
|
|
25
|
-
}
|
|
26
|
-
// 3. Upsert roles with permission IDs
|
|
27
|
-
for (const [, roleDef] of Object.entries(options.roles)) {
|
|
28
|
-
const permissionIds = roleDef.permissions
|
|
29
|
-
.map((code) => permissionMap[code])
|
|
30
|
-
.filter(Boolean);
|
|
31
|
-
await roleRepo.upsert({ name: roleDef.name }, { name: roleDef.name, description: roleDef.description, permissions: permissionIds });
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
categoryCount: options.categories.length,
|
|
35
|
-
permissionCount: options.permissions.length,
|
|
36
|
-
roleCount: Object.keys(options.roles).length,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { BaseRepository } from '@mostajs/orm';
|
|
2
|
-
import type { IDialect } from '@mostajs/orm';
|
|
3
|
-
import type { PermissionCategoryDTO } from '../types/index';
|
|
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
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// @mosta/auth — PermissionCategoryRepository
|
|
2
|
-
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { BaseRepository } from '@mostajs/orm';
|
|
4
|
-
import { PermissionCategorySchema } from '../schemas/permission-category.schema';
|
|
5
|
-
export class PermissionCategoryRepository extends BaseRepository {
|
|
6
|
-
constructor(dialect) {
|
|
7
|
-
super(PermissionCategorySchema, dialect);
|
|
8
|
-
}
|
|
9
|
-
/** Find all sorted by order then name */
|
|
10
|
-
async findAllOrdered() {
|
|
11
|
-
return this.findAll({}, { sort: { order: 1, name: 1 } });
|
|
12
|
-
}
|
|
13
|
-
/** Find by name (unique) */
|
|
14
|
-
async findByName(name) {
|
|
15
|
-
return this.findOne({ name });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { BaseRepository } from '@mostajs/orm';
|
|
2
|
-
import type { IDialect } from '@mostajs/orm';
|
|
3
|
-
import type { PermissionDTO } from '../types/index';
|
|
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
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// @mosta/auth — PermissionRepository
|
|
2
|
-
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { BaseRepository } from '@mostajs/orm';
|
|
4
|
-
import { PermissionSchema } from '../schemas/permission.schema';
|
|
5
|
-
export class PermissionRepository extends BaseRepository {
|
|
6
|
-
constructor(dialect) {
|
|
7
|
-
super(PermissionSchema, dialect);
|
|
8
|
-
}
|
|
9
|
-
/** Find all sorted by category then name */
|
|
10
|
-
async findAllSorted() {
|
|
11
|
-
return this.findAll({}, { sort: { category: 1, name: 1 } });
|
|
12
|
-
}
|
|
13
|
-
/** Find by name (unique) */
|
|
14
|
-
async findByName(name) {
|
|
15
|
-
return this.findOne({ name });
|
|
16
|
-
}
|
|
17
|
-
/** Count permissions in a category */
|
|
18
|
-
async countByCategory(category) {
|
|
19
|
-
return this.count({ category });
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { BaseRepository } from '@mostajs/orm';
|
|
2
|
-
import type { IDialect } from '@mostajs/orm';
|
|
3
|
-
import type { RoleDTO } from '../types/index';
|
|
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
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// @mosta/auth — RoleRepository
|
|
2
|
-
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { BaseRepository } from '@mostajs/orm';
|
|
4
|
-
import { RoleSchema } from '../schemas/role.schema';
|
|
5
|
-
export class RoleRepository extends BaseRepository {
|
|
6
|
-
constructor(dialect) {
|
|
7
|
-
super(RoleSchema, dialect);
|
|
8
|
-
}
|
|
9
|
-
/** Find all roles with permissions populated */
|
|
10
|
-
async findAllWithPermissions() {
|
|
11
|
-
return this.findWithRelations({}, ['permissions']);
|
|
12
|
-
}
|
|
13
|
-
/** Find a role by name */
|
|
14
|
-
async findByName(name) {
|
|
15
|
-
return this.findOne({ name });
|
|
16
|
-
}
|
|
17
|
-
/** Find role by ID with permissions populated */
|
|
18
|
-
async findByIdWithPermissions(id) {
|
|
19
|
-
return this.findByIdWithRelations(id, ['permissions']);
|
|
20
|
-
}
|
|
21
|
-
/** Add a permission to a role */
|
|
22
|
-
async addPermission(roleId, permissionId) {
|
|
23
|
-
return this.addToSet(roleId, 'permissions', permissionId);
|
|
24
|
-
}
|
|
25
|
-
/** Remove a permission from a role */
|
|
26
|
-
async removePermission(roleId, permissionId) {
|
|
27
|
-
return this.pull(roleId, 'permissions', permissionId);
|
|
28
|
-
}
|
|
29
|
-
/** Remove a permission from ALL roles (cascade) */
|
|
30
|
-
async removePermissionFromAll(permissionId) {
|
|
31
|
-
return this.updateMany({ permissions: permissionId }, { $pull: { permissions: permissionId } });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { BaseRepository } from '@mostajs/orm';
|
|
2
|
-
import type { IDialect, FilterQuery, QueryOptions } from '@mostajs/orm';
|
|
3
|
-
import type { UserDTO } from '../types/index';
|
|
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
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// @mosta/auth — UserRepository
|
|
2
|
-
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { BaseRepository } from '@mostajs/orm';
|
|
4
|
-
import { UserSchema } from '../schemas/user.schema';
|
|
5
|
-
export class UserRepository extends BaseRepository {
|
|
6
|
-
constructor(dialect) {
|
|
7
|
-
super(UserSchema, dialect);
|
|
8
|
-
}
|
|
9
|
-
/** List users without password field */
|
|
10
|
-
async findAllSafe(filter = {}, options) {
|
|
11
|
-
return this.findAll(filter, { ...options, exclude: ['password'] });
|
|
12
|
-
}
|
|
13
|
-
/** Find a single user by ID without password */
|
|
14
|
-
async findByIdSafe(id) {
|
|
15
|
-
return this.findById(id, { exclude: ['password'] });
|
|
16
|
-
}
|
|
17
|
-
/** Find user by email (for authentication) */
|
|
18
|
-
async findByEmail(email) {
|
|
19
|
-
return this.findOne({ email: email.toLowerCase() });
|
|
20
|
-
}
|
|
21
|
-
/** Update lastLoginAt timestamp */
|
|
22
|
-
async updateLastLogin(id) {
|
|
23
|
-
await this.update(id, { lastLoginAt: new Date() });
|
|
24
|
-
}
|
|
25
|
-
/** Find user by ID with roles populated */
|
|
26
|
-
async findByIdWithRoles(id) {
|
|
27
|
-
return this.findByIdWithRelations(id, ['roles']);
|
|
28
|
-
}
|
|
29
|
-
/** Find all users with roles populated (no password) */
|
|
30
|
-
async findAllWithRoles(filter = {}, options) {
|
|
31
|
-
return this.findWithRelations(filter, ['roles'], { ...options, exclude: ['password'] });
|
|
32
|
-
}
|
|
33
|
-
/** Count users having a specific role */
|
|
34
|
-
async countByRole(roleId) {
|
|
35
|
-
return this.count({ roles: roleId });
|
|
36
|
-
}
|
|
37
|
-
/** Add a role to a user */
|
|
38
|
-
async addRole(userId, roleId) {
|
|
39
|
-
return this.addToSet(userId, 'roles', roleId);
|
|
40
|
-
}
|
|
41
|
-
/** Remove a role from a user */
|
|
42
|
-
async removeRole(userId, roleId) {
|
|
43
|
-
return this.pull(userId, 'roles', roleId);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const PermissionCategorySchema = {
|
|
2
|
-
name: 'PermissionCategory',
|
|
3
|
-
collection: 'permission_categories',
|
|
4
|
-
timestamps: true,
|
|
5
|
-
fields: {
|
|
6
|
-
name: { type: 'string', required: true, unique: true, lowercase: true, trim: true },
|
|
7
|
-
label: { type: 'string', required: true, trim: true },
|
|
8
|
-
description: { type: 'string' },
|
|
9
|
-
icon: { type: 'string' },
|
|
10
|
-
order: { type: 'number', default: 0 },
|
|
11
|
-
system: { type: 'boolean', default: false },
|
|
12
|
-
},
|
|
13
|
-
relations: {},
|
|
14
|
-
indexes: [
|
|
15
|
-
{ fields: { order: 'asc', name: 'asc' } },
|
|
16
|
-
],
|
|
17
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const PermissionSchema = {
|
|
2
|
-
name: 'Permission',
|
|
3
|
-
collection: 'permissions',
|
|
4
|
-
timestamps: true,
|
|
5
|
-
fields: {
|
|
6
|
-
name: { type: 'string', required: true, unique: true },
|
|
7
|
-
description: { type: 'string' },
|
|
8
|
-
category: { type: 'string' },
|
|
9
|
-
},
|
|
10
|
-
relations: {},
|
|
11
|
-
indexes: [],
|
|
12
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export const RoleSchema = {
|
|
2
|
-
name: 'Role',
|
|
3
|
-
collection: 'roles',
|
|
4
|
-
timestamps: true,
|
|
5
|
-
fields: {
|
|
6
|
-
name: { type: 'string', required: true, unique: true },
|
|
7
|
-
description: { type: 'string' },
|
|
8
|
-
},
|
|
9
|
-
relations: {
|
|
10
|
-
permissions: { target: 'Permission', type: 'many-to-many', through: 'role_permissions' },
|
|
11
|
-
},
|
|
12
|
-
indexes: [],
|
|
13
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export const UserSchema = {
|
|
2
|
-
name: 'User',
|
|
3
|
-
collection: 'users',
|
|
4
|
-
timestamps: true,
|
|
5
|
-
fields: {
|
|
6
|
-
email: { type: 'string', required: true, unique: true, lowercase: true, trim: true },
|
|
7
|
-
password: { type: 'string', required: true },
|
|
8
|
-
firstName: { type: 'string', required: true, trim: true },
|
|
9
|
-
lastName: { type: 'string', required: true, trim: true },
|
|
10
|
-
phone: { type: 'string', trim: true },
|
|
11
|
-
status: { type: 'string', enum: ['active', 'locked', 'disabled'], default: 'active' },
|
|
12
|
-
lastLoginAt: { type: 'date' },
|
|
13
|
-
},
|
|
14
|
-
relations: {
|
|
15
|
-
roles: { target: 'Role', type: 'many-to-many', through: 'user_roles' },
|
|
16
|
-
},
|
|
17
|
-
indexes: [
|
|
18
|
-
{ fields: { status: 'asc' } },
|
|
19
|
-
],
|
|
20
|
-
};
|