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;
|
avatarUrl: string | null;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
};
|
};
|
||||||
token: string;
|
token: Promise<string>;
|
||||||
}>;
|
}>;
|
||||||
login(body: {
|
login(body: {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -44,7 +44,7 @@ export declare class AuthController {
|
|||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
};
|
};
|
||||||
token: string;
|
token: Promise<string>;
|
||||||
requiresOtp?: undefined;
|
requiresOtp?: undefined;
|
||||||
availableMethods?: undefined;
|
availableMethods?: undefined;
|
||||||
tempToken?: undefined;
|
tempToken?: undefined;
|
||||||
@@ -61,7 +61,7 @@ export declare class AuthController {
|
|||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
};
|
};
|
||||||
token: string;
|
token: Promise<string>;
|
||||||
}>;
|
}>;
|
||||||
googleAuth(): Promise<void>;
|
googleAuth(): Promise<void>;
|
||||||
googleAuthCallback(req: any, res: Response): 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;
|
avatarUrl: string | null;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
};
|
};
|
||||||
token: string;
|
token: Promise<string>;
|
||||||
}>;
|
}>;
|
||||||
login(email: string, password: string): Promise<{
|
login(email: string, password: string): Promise<{
|
||||||
requiresOtp: boolean;
|
requiresOtp: boolean;
|
||||||
@@ -34,7 +34,7 @@ export declare class AuthService {
|
|||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
};
|
};
|
||||||
token: string;
|
token: Promise<string>;
|
||||||
requiresOtp?: undefined;
|
requiresOtp?: undefined;
|
||||||
availableMethods?: undefined;
|
availableMethods?: undefined;
|
||||||
tempToken?: undefined;
|
tempToken?: undefined;
|
||||||
@@ -62,7 +62,7 @@ export declare class AuthService {
|
|||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
};
|
};
|
||||||
token: string;
|
token: Promise<string>;
|
||||||
requiresOtp?: undefined;
|
requiresOtp?: undefined;
|
||||||
availableMethods?: undefined;
|
availableMethods?: undefined;
|
||||||
tempToken?: undefined;
|
tempToken?: undefined;
|
||||||
@@ -75,7 +75,7 @@ export declare class AuthService {
|
|||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
};
|
};
|
||||||
token: string;
|
token: Promise<string>;
|
||||||
}>;
|
}>;
|
||||||
private generateToken;
|
private generateToken;
|
||||||
private generateTempToken;
|
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,
|
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({
|
return this.jwtService.sign({
|
||||||
sub: userId,
|
sub: userId,
|
||||||
email,
|
email,
|
||||||
|
role: user?.role || 'user',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
generateTempToken(userId, email) {
|
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 {
|
export interface JwtPayload {
|
||||||
sub: string;
|
sub: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
role?: string;
|
||||||
iat?: number;
|
iat?: number;
|
||||||
exp?: 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;
|
validate(...args: any[]): unknown;
|
||||||
};
|
};
|
||||||
export declare class JwtStrategy extends JwtStrategy_base {
|
export declare class JwtStrategy extends JwtStrategy_base {
|
||||||
@@ -13,6 +14,7 @@ export declare class JwtStrategy extends JwtStrategy_base {
|
|||||||
validate(payload: JwtPayload): Promise<{
|
validate(payload: JwtPayload): Promise<{
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
role: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
export {};
|
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) {
|
async validate(payload) {
|
||||||
return { userId: payload.sub, email: payload.email };
|
return {
|
||||||
|
userId: payload.sub,
|
||||||
|
email: payload.email,
|
||||||
|
role: payload.role || 'user'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
exports.JwtStrategy = JwtStrategy;
|
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({
|
return this.jwtService.sign({
|
||||||
sub: userId,
|
sub: userId,
|
||||||
email,
|
email,
|
||||||
|
role: user?.role || 'user',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ExtractJwt, Strategy } from 'passport-jwt';
|
|||||||
export interface JwtPayload {
|
export interface JwtPayload {
|
||||||
sub: string; // user ID
|
sub: string; // user ID
|
||||||
email: string;
|
email: string;
|
||||||
|
role?: string; // user role
|
||||||
iat?: number;
|
iat?: number;
|
||||||
exp?: number;
|
exp?: number;
|
||||||
}
|
}
|
||||||
@@ -20,6 +21,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async validate(payload: JwtPayload) {
|
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