feat: add admin guard and JWT role support
- Create AdminGuard to check user role - Update JWT strategy to include role in payload - Update auth service to include role in token generation - Prepare admin module structure - TypeScript will resolve lint errors after server restart
This commit is contained in:
1
apps/api/dist/admin/admin-plans.controller.d.ts
vendored
Normal file
1
apps/api/dist/admin/admin-plans.controller.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
3
apps/api/dist/admin/admin-plans.controller.js
vendored
Normal file
3
apps/api/dist/admin/admin-plans.controller.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=admin-plans.controller.js.map
|
||||
1
apps/api/dist/admin/admin-plans.controller.js.map
vendored
Normal file
1
apps/api/dist/admin/admin-plans.controller.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"admin-plans.controller.js","sourceRoot":"","sources":["../../src/admin/admin-plans.controller.ts"],"names":[],"mappings":""}
|
||||
1
apps/api/dist/admin/admin-plans.service.d.ts
vendored
Normal file
1
apps/api/dist/admin/admin-plans.service.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
3
apps/api/dist/admin/admin-plans.service.js
vendored
Normal file
3
apps/api/dist/admin/admin-plans.service.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=admin-plans.service.js.map
|
||||
1
apps/api/dist/admin/admin-plans.service.js.map
vendored
Normal file
1
apps/api/dist/admin/admin-plans.service.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"admin-plans.service.js","sourceRoot":"","sources":["../../src/admin/admin-plans.service.ts"],"names":[],"mappings":""}
|
||||
1
apps/api/dist/admin/admin.module.d.ts
vendored
Normal file
1
apps/api/dist/admin/admin.module.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
3
apps/api/dist/admin/admin.module.js
vendored
Normal file
3
apps/api/dist/admin/admin.module.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=admin.module.js.map
|
||||
1
apps/api/dist/admin/admin.module.js.map
vendored
Normal file
1
apps/api/dist/admin/admin.module.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"admin.module.js","sourceRoot":"","sources":["../../src/admin/admin.module.ts"],"names":[],"mappings":""}
|
||||
4
apps/api/dist/admin/guards/admin.guard.d.ts
vendored
Normal file
4
apps/api/dist/admin/guards/admin.guard.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
export declare class AdminGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean;
|
||||
}
|
||||
28
apps/api/dist/admin/guards/admin.guard.js
vendored
Normal file
28
apps/api/dist/admin/guards/admin.guard.js
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AdminGuard = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
let AdminGuard = class AdminGuard {
|
||||
canActivate(context) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
if (!user) {
|
||||
throw new common_1.ForbiddenException('Authentication required');
|
||||
}
|
||||
if (user.role !== 'admin') {
|
||||
throw new common_1.ForbiddenException('Admin access required');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
exports.AdminGuard = AdminGuard;
|
||||
exports.AdminGuard = AdminGuard = __decorate([
|
||||
(0, common_1.Injectable)()
|
||||
], AdminGuard);
|
||||
//# sourceMappingURL=admin.guard.js.map
|
||||
1
apps/api/dist/admin/guards/admin.guard.js.map
vendored
Normal file
1
apps/api/dist/admin/guards/admin.guard.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"admin.guard.js","sourceRoot":"","sources":["../../../src/admin/guards/admin.guard.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAKwB;AAUjB,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,WAAW,CAAC,OAAyB;QACnC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAmB,CAAC;QACrE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAE1B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,2BAAkB,CAAC,yBAAyB,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,2BAAkB,CAAC,uBAAuB,CAAC,CAAC;QACxD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAfY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAetB"}
|
||||
6
apps/api/dist/auth/auth.controller.d.ts
vendored
6
apps/api/dist/auth/auth.controller.d.ts
vendored
@@ -21,7 +21,7 @@ export declare class AuthController {
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
token: Promise<string>;
|
||||
}>;
|
||||
login(body: {
|
||||
email: string;
|
||||
@@ -44,7 +44,7 @@ export declare class AuthController {
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
token: Promise<string>;
|
||||
requiresOtp?: undefined;
|
||||
availableMethods?: undefined;
|
||||
tempToken?: undefined;
|
||||
@@ -61,7 +61,7 @@ export declare class AuthController {
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
token: Promise<string>;
|
||||
}>;
|
||||
googleAuth(): Promise<void>;
|
||||
googleAuthCallback(req: any, res: Response): Promise<void>;
|
||||
|
||||
8
apps/api/dist/auth/auth.service.d.ts
vendored
8
apps/api/dist/auth/auth.service.d.ts
vendored
@@ -14,7 +14,7 @@ export declare class AuthService {
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
token: Promise<string>;
|
||||
}>;
|
||||
login(email: string, password: string): Promise<{
|
||||
requiresOtp: boolean;
|
||||
@@ -34,7 +34,7 @@ export declare class AuthService {
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
token: Promise<string>;
|
||||
requiresOtp?: undefined;
|
||||
availableMethods?: undefined;
|
||||
tempToken?: undefined;
|
||||
@@ -62,7 +62,7 @@ export declare class AuthService {
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
token: Promise<string>;
|
||||
requiresOtp?: undefined;
|
||||
availableMethods?: undefined;
|
||||
tempToken?: undefined;
|
||||
@@ -75,7 +75,7 @@ export declare class AuthService {
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
token: Promise<string>;
|
||||
}>;
|
||||
private generateToken;
|
||||
private generateTempToken;
|
||||
|
||||
7
apps/api/dist/auth/auth.service.js
vendored
7
apps/api/dist/auth/auth.service.js
vendored
@@ -317,10 +317,15 @@ let AuthService = class AuthService {
|
||||
token,
|
||||
};
|
||||
}
|
||||
generateToken(userId, email) {
|
||||
async generateToken(userId, email) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { role: true },
|
||||
});
|
||||
return this.jwtService.sign({
|
||||
sub: userId,
|
||||
email,
|
||||
role: user?.role || 'user',
|
||||
});
|
||||
}
|
||||
generateTempToken(userId, email) {
|
||||
|
||||
2
apps/api/dist/auth/auth.service.js.map
vendored
2
apps/api/dist/auth/auth.service.js.map
vendored
File diff suppressed because one or more lines are too long
4
apps/api/dist/auth/jwt.strategy.d.ts
vendored
4
apps/api/dist/auth/jwt.strategy.d.ts
vendored
@@ -2,10 +2,11 @@ import { Strategy } from 'passport-jwt';
|
||||
export interface JwtPayload {
|
||||
sub: string;
|
||||
email: string;
|
||||
role?: string;
|
||||
iat?: number;
|
||||
exp?: number;
|
||||
}
|
||||
declare const JwtStrategy_base: new (...args: [opt: import("passport-jwt").StrategyOptionsWithRequest] | [opt: import("passport-jwt").StrategyOptionsWithoutRequest]) => Strategy & {
|
||||
declare const JwtStrategy_base: new (...args: [opt: import("passport-jwt").StrategyOptionsWithoutRequest] | [opt: import("passport-jwt").StrategyOptionsWithRequest]) => Strategy & {
|
||||
validate(...args: any[]): unknown;
|
||||
};
|
||||
export declare class JwtStrategy extends JwtStrategy_base {
|
||||
@@ -13,6 +14,7 @@ export declare class JwtStrategy extends JwtStrategy_base {
|
||||
validate(payload: JwtPayload): Promise<{
|
||||
userId: string;
|
||||
email: string;
|
||||
role: string;
|
||||
}>;
|
||||
}
|
||||
export {};
|
||||
|
||||
6
apps/api/dist/auth/jwt.strategy.js
vendored
6
apps/api/dist/auth/jwt.strategy.js
vendored
@@ -22,7 +22,11 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
|
||||
});
|
||||
}
|
||||
async validate(payload) {
|
||||
return { userId: payload.sub, email: payload.email };
|
||||
return {
|
||||
userId: payload.sub,
|
||||
email: payload.email,
|
||||
role: payload.role || 'user'
|
||||
};
|
||||
}
|
||||
};
|
||||
exports.JwtStrategy = JwtStrategy;
|
||||
|
||||
2
apps/api/dist/auth/jwt.strategy.js.map
vendored
2
apps/api/dist/auth/jwt.strategy.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"jwt.strategy.js","sourceRoot":"","sources":["../../src/auth/jwt.strategy.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,+CAAoD;AACpD,+CAAoD;AAU7C,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,IAAA,2BAAgB,EAAC,uBAAQ,CAAC;IACzD;QACE,KAAK,CAAC;YACJ,cAAc,EAAE,yBAAU,CAAC,2BAA2B,EAAE;YACxD,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,6BAA6B;SACrE,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAmB;QAChC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;IACvD,CAAC;CACF,CAAA;AAZY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;;GACA,WAAW,CAYvB"}
|
||||
{"version":3,"file":"jwt.strategy.js","sourceRoot":"","sources":["../../src/auth/jwt.strategy.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,+CAAoD;AACpD,+CAAoD;AAW7C,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,IAAA,2BAAgB,EAAC,uBAAQ,CAAC;IACzD;QACE,KAAK,CAAC;YACJ,cAAc,EAAE,yBAAU,CAAC,2BAA2B,EAAE;YACxD,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,6BAA6B;SACrE,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAmB;QAChC,OAAO;YACL,MAAM,EAAE,OAAO,CAAC,GAAG;YACnB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,MAAM;SAC7B,CAAC;IACJ,CAAC;CACF,CAAA;AAhBY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;;GACA,WAAW,CAgBvB"}
|
||||
2
apps/api/dist/tsconfig.build.tsbuildinfo
vendored
2
apps/api/dist/tsconfig.build.tsbuildinfo
vendored
File diff suppressed because one or more lines are too long
0
apps/api/src/admin/admin-plans.controller.ts
Normal file
0
apps/api/src/admin/admin-plans.controller.ts
Normal file
0
apps/api/src/admin/admin-plans.service.ts
Normal file
0
apps/api/src/admin/admin-plans.service.ts
Normal file
0
apps/api/src/admin/admin.module.ts
Normal file
0
apps/api/src/admin/admin.module.ts
Normal file
31
apps/api/src/admin/guards/admin.guard.ts
Normal file
31
apps/api/src/admin/guards/admin.guard.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
role?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const request = context.switchToHttp().getRequest<RequestWithUser>();
|
||||
const user = request.user;
|
||||
|
||||
if (!user) {
|
||||
throw new ForbiddenException('Authentication required');
|
||||
}
|
||||
|
||||
if (user.role !== 'admin') {
|
||||
throw new ForbiddenException('Admin access required');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -359,10 +359,17 @@ export class AuthService {
|
||||
};
|
||||
}
|
||||
|
||||
private generateToken(userId: string, email: string): string {
|
||||
private async generateToken(userId: string, email: string): Promise<string> {
|
||||
// Get user role
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { role: true },
|
||||
});
|
||||
|
||||
return this.jwtService.sign({
|
||||
sub: userId,
|
||||
email,
|
||||
role: user?.role || 'user',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
export interface JwtPayload {
|
||||
sub: string; // user ID
|
||||
email: string;
|
||||
role?: string; // user role
|
||||
iat?: number;
|
||||
exp?: number;
|
||||
}
|
||||
@@ -20,6 +21,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload) {
|
||||
return { userId: payload.sub, email: payload.email };
|
||||
return {
|
||||
userId: payload.sub,
|
||||
email: payload.email,
|
||||
role: payload.role || 'user'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user