diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 2a27932..913960b 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -11,11 +11,11 @@ import { AdminService } from './admin.service'; import { SuspendCampaignDto } from './dtos/suspend-campaign.dto'; import { Roles } from '../common/decorators/roles.decorator'; import { RolesGuard } from '../common/guards/roles.guard'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; @Controller('admin') @UseGuards(JwtAuthGuard, RolesGuard) -@Roles('admin') +@Roles('ADMIN') export class AdminController { constructor(private readonly adminService: AdminService) {} diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts index fd9ba87..e017ade 100644 --- a/src/admin/admin.module.ts +++ b/src/admin/admin.module.ts @@ -4,7 +4,7 @@ import { AuthModule } from '../auth/auth.module'; import { AdminService } from './admin.service'; import { AdminController } from './admin.controller'; import { NotificationsModule } from '../notifications/notifications.module'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; import { RolesGuard } from '../common/guards/roles.guard'; /** Module providing admin campaign suspension, user moderation, and audit logging */ diff --git a/src/api-keys/api-keys.controller.ts b/src/api-keys/api-keys.controller.ts index 7a2c195..b005432 100644 --- a/src/api-keys/api-keys.controller.ts +++ b/src/api-keys/api-keys.controller.ts @@ -11,7 +11,7 @@ import { import { randomBytes, createHash } from 'crypto'; import { Request } from 'express'; import { PrismaService } from '../prisma/prisma.service'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; import { Throttle } from '@nestjs/throttler'; interface JwtUser { diff --git a/src/api-keys/api-keys.module.ts b/src/api-keys/api-keys.module.ts index e545773..b6ef27e 100644 --- a/src/api-keys/api-keys.module.ts +++ b/src/api-keys/api-keys.module.ts @@ -3,7 +3,7 @@ import { PrismaModule } from '../prisma/prisma.module'; import { AuthModule } from '../auth/auth.module'; import { ApiKeysController } from './api-keys.controller'; import { ApiKeyGuard } from './api-key.guard'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; /** Provides API key generation, revocation, and authentication middleware */ @Module({ diff --git a/src/auth/auth-logout.controller.ts b/src/auth/auth-logout.controller.ts index abb0d54..e5d7708 100644 --- a/src/auth/auth-logout.controller.ts +++ b/src/auth/auth-logout.controller.ts @@ -9,7 +9,7 @@ import { } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import type { Cache } from 'cache-manager'; -import { JwtAuthGuard } from './jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; import { ApiTags, ApiOperation, diff --git a/src/auth/jwt-auth.guard.ts b/src/auth/jwt-auth.guard.ts deleted file mode 100644 index 2ed6305..0000000 --- a/src/auth/jwt-auth.guard.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - UnauthorizedException, -} from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { ConfigService } from '@nestjs/config'; -import { Request } from 'express'; - -@Injectable() -export class JwtAuthGuard implements CanActivate { - constructor( - private readonly jwt: JwtService, - private readonly config: ConfigService, - ) {} - - canActivate(context: ExecutionContext): boolean { - const request = context - .switchToHttp() - .getRequest(); - const authHeader = request.headers['authorization']; - - if (!authHeader?.startsWith('Bearer ')) { - throw new UnauthorizedException( - 'Missing or invalid Authorization header', - ); - } - - const token = authHeader.slice(7); - - try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const payload = this.jwt.verify(token, { - secret: this.config.get( - 'JWT_SECRET', - 'orbitchain-default-secret', - ), - }); - request.user = payload as Record; - return true; - } catch { - throw new UnauthorizedException('Invalid or expired token'); - } - } -} diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts deleted file mode 100644 index 536d6a5..0000000 --- a/src/auth/jwt.strategy.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { ExtractJwt, Strategy } from 'passport-jwt'; -import { ConfigService } from '@nestjs/config'; - -/** - * Passport JWT strategy for OrbitChain. - * Validates tokens and extracts user info from the JWT payload. - */ -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(configService: ConfigService) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKey: configService.get('JWT_SECRET', 'default-secret'), - }); - } - - validate(payload: { sub: string; walletAddress: string; role?: string }) { - if (!payload?.sub) { - throw new UnauthorizedException('Invalid token'); - } - return { - userId: payload.sub, - walletAddress: payload.walletAddress, - role: payload.role, - }; - } -} diff --git a/src/campaigns/campaigns.controller.ts b/src/campaigns/campaigns.controller.ts index 7114490..b2e64b6 100644 --- a/src/campaigns/campaigns.controller.ts +++ b/src/campaigns/campaigns.controller.ts @@ -23,7 +23,7 @@ import { RolesGuard } from '../common/guards/roles.guard'; import { UpdateCampaignDto } from './dto/update-campaign.dto'; import { CreateCampaignDto } from './dto/create-campaign.dto'; import { Request } from 'express'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; import { AdminGuard } from '../users/guards/admin.guard'; import { BrowseCampaignsQueryDto, diff --git a/src/campaigns/campaigns.module.ts b/src/campaigns/campaigns.module.ts index 897ec1a..33f68c9 100644 --- a/src/campaigns/campaigns.module.ts +++ b/src/campaigns/campaigns.module.ts @@ -7,7 +7,7 @@ import { CampaignsService } from './campaigns.service'; import { PrismaModule } from '../prisma/prisma.module'; import { AuthModule } from '../auth/auth.module'; import { StellarModule } from '../stellar/stellar.module'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; import { AdminGuard } from '../users/guards/admin.guard'; import { DonationsModule } from '../donations/donations.module'; diff --git a/src/campaigns/campaigns.service.spec.ts b/src/campaigns/campaigns.service.spec.ts index 73184e8..d39510e 100644 --- a/src/campaigns/campaigns.service.spec.ts +++ b/src/campaigns/campaigns.service.spec.ts @@ -21,7 +21,7 @@ describe('CampaignsService milestone target validation', () => { }; it.each([ - ['missing', undefined], + ['missing', undefined as string | undefined], ['zero', '0'], ['zero decimal', '0.0000000'], ['below the minimum precision', '0.00000001'], @@ -34,7 +34,7 @@ describe('CampaignsService milestone target validation', () => { milestones: [ { title: 'Prototype', - targetAmount, + targetAmount: targetAmount as string, }, ], }), diff --git a/src/donations/donations.controller.ts b/src/donations/donations.controller.ts index 112e882..5d5d2d0 100644 --- a/src/donations/donations.controller.ts +++ b/src/donations/donations.controller.ts @@ -8,7 +8,7 @@ import { Req, Request, } from '@nestjs/common'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; import { DonationsService } from './donations.service'; import { CreateDonationDto } from './dto/create-donation.dto'; import { diff --git a/src/donations/donations.module.ts b/src/donations/donations.module.ts index e9cc740..3f81dc2 100644 --- a/src/donations/donations.module.ts +++ b/src/donations/donations.module.ts @@ -1,6 +1,6 @@ import { Module, forwardRef } from '@nestjs/common'; import { AuthModule } from '../auth/auth.module'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; import { CampaignsModule } from '../campaigns/campaigns.module'; import { PrismaModule } from '../prisma/prisma.module'; import { StellarModule } from '../stellar/stellar.module'; diff --git a/src/notifications/notifications.controller.ts b/src/notifications/notifications.controller.ts index d559156..51eeac4 100644 --- a/src/notifications/notifications.controller.ts +++ b/src/notifications/notifications.controller.ts @@ -9,7 +9,7 @@ import { UseGuards, } from '@nestjs/common'; import { Request } from 'express'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../users/guards/jwt-auth.guard'; import { NotificationsService } from './notifications.service'; @Controller('notifications') diff --git a/src/users/guards/jwt-auth.guard.ts b/src/users/guards/jwt-auth.guard.ts index b2df95a..e0c5fff 100644 --- a/src/users/guards/jwt-auth.guard.ts +++ b/src/users/guards/jwt-auth.guard.ts @@ -5,29 +5,46 @@ import { UnauthorizedException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import { ConfigService } from '@nestjs/config'; +import { Request } from 'express'; +/** + * Validates JWT from the Authorization header and attaches the decoded + * payload to `request.user`. The secret is read from config so this + * guard is not tied to a specific JwtModule registration. + */ @Injectable() export class JwtAuthGuard implements CanActivate { - constructor(private readonly jwtService: JwtService) {} + constructor( + private readonly jwt: JwtService, + private readonly config: ConfigService, + ) {} canActivate(context: ExecutionContext): boolean { - const request = context.switchToHttp().getRequest(); - const authHeader = request.headers.authorization; + const request = context + .switchToHttp() + .getRequest(); + const authHeader = request.headers['authorization']; - if (!authHeader || !authHeader.startsWith('Bearer ')) { + if (!authHeader?.startsWith('Bearer ')) { throw new UnauthorizedException( - 'Missing or invalid authorization header', + 'Missing or invalid Authorization header', ); } - const token = authHeader.substring(7); + const token = authHeader.slice(7); try { - const payload = this.jwtService.verify(token); + const payload = this.jwt.verify(token, { + secret: this.config.get( + 'JWT_SECRET', + 'orbitchain-default-secret', + ), + }) as Record; request.user = payload; return true; - } catch (error) { - throw new UnauthorizedException('Invalid token'); + } catch { + throw new UnauthorizedException('Invalid or expired token'); } } }