exguard-backend 1.0.39 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -1196
- package/dist/index.cjs +16 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +16 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/setup-nestjs-module.cjs +37 -0
- package/scripts/setup-nestjs-simple.cjs +199 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExGuard NestJS Guards - Simple Implementation
|
|
3
|
+
* Copy this file to your project's src/exguard/exguard.guard.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';
|
|
7
|
+
import { Reflector } from '@nestjs/core';
|
|
8
|
+
import { ExGuardBackend } from 'exguard-backend';
|
|
9
|
+
|
|
10
|
+
export const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';
|
|
11
|
+
export const EXGUARD_ROLES_KEY = 'exguard_roles';
|
|
12
|
+
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class ExGuardAuthGuard implements CanActivate {
|
|
15
|
+
constructor(
|
|
16
|
+
@Optional() @Inject('EXGUARD_INSTANCE') private exGuard: ExGuardBackend,
|
|
17
|
+
private reflector: Reflector,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
21
|
+
const request = context.switchToHttp().getRequest();
|
|
22
|
+
const token = this.extractToken(request);
|
|
23
|
+
|
|
24
|
+
if (!token) {
|
|
25
|
+
throw new UnauthorizedException('No token provided');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!this.exGuard) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result = await this.exGuard.authenticate({ token, request });
|
|
33
|
+
|
|
34
|
+
if (!result.allowed) {
|
|
35
|
+
throw new ForbiddenException(result.error || 'Access denied');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
request.user = result.user;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private extractToken(request: any): string | null {
|
|
43
|
+
const auth = request.headers?.authorization;
|
|
44
|
+
if (auth?.startsWith('Bearer ')) {
|
|
45
|
+
return auth.substring(7);
|
|
46
|
+
}
|
|
47
|
+
return request.headers?.['x-access-token'] || null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Injectable()
|
|
52
|
+
export class ExGuardPermissionGuard implements CanActivate {
|
|
53
|
+
constructor(
|
|
54
|
+
@Optional() @Inject('EXGUARD_INSTANCE') private exGuard: ExGuardBackend,
|
|
55
|
+
private reflector: Reflector,
|
|
56
|
+
) {}
|
|
57
|
+
|
|
58
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
59
|
+
const request = context.switchToHttp().getRequest();
|
|
60
|
+
const token = this.extractToken(request);
|
|
61
|
+
|
|
62
|
+
if (!token) {
|
|
63
|
+
throw new UnauthorizedException('No token provided');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!this.exGuard) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const authResult = await this.exGuard.authenticate({ token, request });
|
|
71
|
+
|
|
72
|
+
if (!authResult.allowed) {
|
|
73
|
+
throw new ForbiddenException(authResult.error || 'Access denied');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!authResult.user) {
|
|
77
|
+
throw new ForbiddenException('User not found');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const handler = context.getHandler();
|
|
81
|
+
|
|
82
|
+
const permMeta = this.reflector.get<{ permissions: string[]; requireAll?: boolean }>(
|
|
83
|
+
EXGUARD_PERMISSIONS_KEY,
|
|
84
|
+
handler,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (permMeta) {
|
|
88
|
+
const { permissions, requireAll } = permMeta;
|
|
89
|
+
const userPermissions = authResult.user.modules?.flatMap(m => m.permissions) || [];
|
|
90
|
+
|
|
91
|
+
if (requireAll) {
|
|
92
|
+
if (!permissions.every(p => userPermissions.includes(p))) {
|
|
93
|
+
throw new ForbiddenException('Insufficient permissions');
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
if (!permissions.some(p => userPermissions.includes(p))) {
|
|
97
|
+
throw new ForbiddenException('Insufficient permissions');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const roleMeta = this.reflector.get<{ roles: string[]; requireAll?: boolean }>(
|
|
103
|
+
EXGUARD_ROLES_KEY,
|
|
104
|
+
handler,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (roleMeta) {
|
|
108
|
+
const { roles, requireAll } = roleMeta;
|
|
109
|
+
const userRoles = authResult.user.roles || [];
|
|
110
|
+
|
|
111
|
+
if (requireAll) {
|
|
112
|
+
if (!roles.every(r => userRoles.includes(r))) {
|
|
113
|
+
throw new ForbiddenException('Insufficient roles');
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
if (!roles.some(r => userRoles.includes(r))) {
|
|
117
|
+
throw new ForbiddenException('Insufficient roles');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
request.user = authResult.user;
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private extractToken(request: any): string | null {
|
|
127
|
+
const auth = request.headers?.authorization;
|
|
128
|
+
if (auth?.startsWith('Bearer ')) {
|
|
129
|
+
return auth.substring(7);
|
|
130
|
+
}
|
|
131
|
+
return request.headers?.['x-access-token'] || null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function RequirePermissions(permissions: string[], requireAll = false) {
|
|
136
|
+
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
137
|
+
Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function RequireRoles(roles: string[], requireAll = false) {
|
|
142
|
+
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
143
|
+
Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function createPermissionGuard(permissions: string[], requireAll = false) {
|
|
148
|
+
return new (class PermissionGuard implements CanActivate {
|
|
149
|
+
private exGuard: ExGuardBackend;
|
|
150
|
+
|
|
151
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
152
|
+
const request = context.switchToHttp().getRequest();
|
|
153
|
+
const token = this.extractToken(request);
|
|
154
|
+
|
|
155
|
+
if (!token) {
|
|
156
|
+
throw new UnauthorizedException('No token provided');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Get ExGuard instance from request
|
|
160
|
+
const exGuardInstance = request.exGuard || (this as any).exGuard;
|
|
161
|
+
if (!exGuardInstance) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const authResult = await exGuardInstance.authenticate({ token, request });
|
|
166
|
+
|
|
167
|
+
if (!authResult.allowed) {
|
|
168
|
+
throw new ForbiddenException(authResult.error || 'Access denied');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!authResult.user) {
|
|
172
|
+
throw new ForbiddenException('User not found');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const userPermissions = authResult.user.modules?.flatMap(m => m.permissions) || [];
|
|
176
|
+
|
|
177
|
+
if (requireAll) {
|
|
178
|
+
if (!permissions.every(p => userPermissions.includes(p))) {
|
|
179
|
+
throw new ForbiddenException('Insufficient permissions');
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
if (!permissions.some(p => userPermissions.includes(p))) {
|
|
183
|
+
throw new ForbiddenException('Insufficient permissions');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
request.user = authResult.user;
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private extractToken(request: any): string | null {
|
|
192
|
+
const auth = request.headers?.authorization;
|
|
193
|
+
if (auth?.startsWith('Bearer ')) {
|
|
194
|
+
return auth.substring(7);
|
|
195
|
+
}
|
|
196
|
+
return request.headers?.['x-access-token'] || null;
|
|
197
|
+
}
|
|
198
|
+
})();
|
|
199
|
+
}
|