From b4b7c643b842c5fb7dde04c7ffb7389d4673a4a0 Mon Sep 17 00:00:00 2001 From: Zafar Shaikh Date: Mon, 9 Feb 2026 13:32:44 +0530 Subject: [PATCH 1/4] feat(auth|frontend): set up an auth guard for the route /dashboard --- apps/backend/src/auth/auth.service.ts | 7 +++++ apps/frontend/src/app/app.routes.ts | 7 +++++ .../src/app/auth/components/login/login.ts | 8 +++-- .../auth/components/register/register.spec.ts | 1 + .../app/auth/components/register/register.ts | 7 +++-- .../src/app/auth/guards/auth-guard.spec.ts | 17 +++++++++++ .../src/app/auth/guards/auth-guard.ts | 15 ++++++++++ apps/frontend/src/app/auth/services/auth.ts | 29 +++++++++++++++++-- .../src/app/pages/dashboard/dashboard.css | 0 .../src/app/pages/dashboard/dashboard.html | 1 + .../src/app/pages/dashboard/dashboard.spec.ts | 22 ++++++++++++++ .../src/app/pages/dashboard/dashboard.ts | 9 ++++++ .../shared-dtos/src/user/user-response.dto.ts | 1 + 13 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 apps/frontend/src/app/auth/guards/auth-guard.spec.ts create mode 100644 apps/frontend/src/app/auth/guards/auth-guard.ts create mode 100644 apps/frontend/src/app/pages/dashboard/dashboard.css create mode 100644 apps/frontend/src/app/pages/dashboard/dashboard.html create mode 100644 apps/frontend/src/app/pages/dashboard/dashboard.spec.ts create mode 100644 apps/frontend/src/app/pages/dashboard/dashboard.ts diff --git a/apps/backend/src/auth/auth.service.ts b/apps/backend/src/auth/auth.service.ts index b2237c2..1f61e37 100644 --- a/apps/backend/src/auth/auth.service.ts +++ b/apps/backend/src/auth/auth.service.ts @@ -45,10 +45,17 @@ export class AuthService { savedUser = await queryRunner.manager.save(user); await queryRunner.commitTransaction(); + const payload = { + sub: savedUser.id, + email: savedUser.email, + }; + const accessToken = await this.jwtService.signAsync(payload); + const response: UserResponseDto = { id: savedUser.id, name: savedUser.name, email: savedUser.email, + access_token: accessToken, }; return response; diff --git a/apps/frontend/src/app/app.routes.ts b/apps/frontend/src/app/app.routes.ts index 4b77712..3a9e8a4 100644 --- a/apps/frontend/src/app/app.routes.ts +++ b/apps/frontend/src/app/app.routes.ts @@ -1,8 +1,15 @@ import { Routes } from '@angular/router'; +import { Dashboard } from '@/app/pages/dashboard/dashboard'; +import { authGuard } from '@/app/auth/guards/auth-guard'; export const routes: Routes = [ { path: 'auth', loadChildren: () => import('./auth/auth-module').then((m) => m.AuthModule), }, + { + path: 'dashboard', + component: Dashboard, + canActivate: [authGuard], + }, ]; diff --git a/apps/frontend/src/app/auth/components/login/login.ts b/apps/frontend/src/app/auth/components/login/login.ts index dcabfab..bfb500d 100644 --- a/apps/frontend/src/app/auth/components/login/login.ts +++ b/apps/frontend/src/app/auth/components/login/login.ts @@ -8,6 +8,7 @@ import { import { EMAIL_REGEX_STRING } from '@shared/validation/email.constants'; import { LoginUserDto } from '@shared/dtos/auth/login-user.dto'; import { CommonModule } from '@angular/common'; +import { Router } from '@angular/router'; @Component({ selector: 'app-login', @@ -18,6 +19,7 @@ import { CommonModule } from '@angular/common'; export class Login { private formBuilder = inject(FormBuilder); private authService = inject(Auth); + private router = inject(Router); loginForm = this.formBuilder.group({ email: ['', [Validators.required, Validators.pattern(new RegExp(EMAIL_REGEX_STRING))]], @@ -43,10 +45,10 @@ export class Login { const userData = this.loginForm.value as LoginUserDto; this.authService.login(userData).subscribe({ - next: (response) => { + next: () => { this.errorMessage = null; - localStorage.setItem('access_token', response.access_token); - console.log('Login successful!', response); + this.loginForm.reset(); + this.router.navigate(['/dashboard']); }, error: (err) => { if (err.error && err.error.message) { diff --git a/apps/frontend/src/app/auth/components/register/register.spec.ts b/apps/frontend/src/app/auth/components/register/register.spec.ts index 723431e..334fd11 100644 --- a/apps/frontend/src/app/auth/components/register/register.spec.ts +++ b/apps/frontend/src/app/auth/components/register/register.spec.ts @@ -89,6 +89,7 @@ describe('Register', () => { id: 1, name: 'Test', email: 'email@test.com', + access_token: 'access_token', }; authService.register.and.returnValue(of(mockResponse)); diff --git a/apps/frontend/src/app/auth/components/register/register.ts b/apps/frontend/src/app/auth/components/register/register.ts index 0b92791..0f99e79 100644 --- a/apps/frontend/src/app/auth/components/register/register.ts +++ b/apps/frontend/src/app/auth/components/register/register.ts @@ -8,6 +8,7 @@ import { import { EMAIL_REGEX_STRING } from '@shared/validation/email.constants'; import { Auth } from '@/app/auth/services/auth'; import { RegisterUserDto } from '@shared/dtos/auth/register-user.dto'; +import { Router } from '@angular/router'; @Component({ selector: 'app-register', @@ -18,6 +19,7 @@ import { RegisterUserDto } from '@shared/dtos/auth/register-user.dto'; export class Register { private formBuilder = inject(FormBuilder); private authService = inject(Auth); + private router = inject(Router); registerForm = this.formBuilder.group({ name: ['', [Validators.required]], @@ -48,9 +50,10 @@ export class Register { const userData = this.registerForm.value as RegisterUserDto; this.authService.register(userData).subscribe({ - next: (response) => { + next: () => { this.errorMessage = null; - console.log('Registration successful!', response); + this.registerForm.reset(); + this.router.navigate(['/dashboard']); }, error: (err) => { if (err.error && err.error.message) { diff --git a/apps/frontend/src/app/auth/guards/auth-guard.spec.ts b/apps/frontend/src/app/auth/guards/auth-guard.spec.ts new file mode 100644 index 0000000..0f21aa3 --- /dev/null +++ b/apps/frontend/src/app/auth/guards/auth-guard.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; +import { CanActivateFn } from '@angular/router'; + +import { authGuard } from './auth-guard'; + +describe('authGuard', () => { + const executeGuard: CanActivateFn = (...guardParameters) => + TestBed.runInInjectionContext(() => authGuard(...guardParameters)); + + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + + it('should be created', () => { + expect(executeGuard).toBeTruthy(); + }); +}); diff --git a/apps/frontend/src/app/auth/guards/auth-guard.ts b/apps/frontend/src/app/auth/guards/auth-guard.ts new file mode 100644 index 0000000..582aad5 --- /dev/null +++ b/apps/frontend/src/app/auth/guards/auth-guard.ts @@ -0,0 +1,15 @@ +import { CanActivateFn, Router } from '@angular/router'; +import { Auth } from '@/app/auth/services/auth'; +import { inject } from '@angular/core'; + +export const authGuard: CanActivateFn = () => { + const authService = inject(Auth); + const router = inject(Router); + + if (authService.isAuthenticated()) { + return true; + } + + router.navigate(['/auth/login']); + return false; +}; diff --git a/apps/frontend/src/app/auth/services/auth.ts b/apps/frontend/src/app/auth/services/auth.ts index 47ec881..f755419 100644 --- a/apps/frontend/src/app/auth/services/auth.ts +++ b/apps/frontend/src/app/auth/services/auth.ts @@ -5,7 +5,7 @@ import { RegisterUserDto } from '@shared/dtos/auth/register-user.dto'; import { UserResponseDto } from '@shared/dtos/user/user-response.dto'; import { LoginUserDto } from '@shared/dtos/auth/login-user.dto'; import { AccessTokenDto } from '@shared/dtos/auth/access-token.dto'; -import { Observable } from 'rxjs'; +import { Observable, tap } from 'rxjs'; @Injectable({ providedIn: 'root', @@ -13,12 +13,35 @@ import { Observable } from 'rxjs'; export class Auth { private httpClient = inject(HttpClient); private apiUrl = `${environment.apiUrl}/auth`; + private readonly TOKEN_KEY = 'access_token'; register(userData: RegisterUserDto): Observable { - return this.httpClient.post(`${this.apiUrl}/register`, userData); + return this.httpClient + .post(`${this.apiUrl}/register`, userData) + .pipe(tap((response: UserResponseDto) => this.setSession(response))); } login(userData: LoginUserDto): Observable { - return this.httpClient.post(`${this.apiUrl}/login`, userData); + return this.httpClient + .post(`${this.apiUrl}/login`, userData) + .pipe(tap((response: AccessTokenDto) => this.setSession(response))); + } + + logout() { + localStorage.removeItem(this.TOKEN_KEY); + } + + isAuthenticated(): boolean { + return !!localStorage.getItem(this.TOKEN_KEY); + } + + getToken(): string | null { + return localStorage.getItem(this.TOKEN_KEY); + } + + private setSession(authResult: UserResponseDto | AccessTokenDto) { + if (authResult.access_token) { + localStorage.setItem(this.TOKEN_KEY, authResult.access_token); + } } } diff --git a/apps/frontend/src/app/pages/dashboard/dashboard.css b/apps/frontend/src/app/pages/dashboard/dashboard.css new file mode 100644 index 0000000..e69de29 diff --git a/apps/frontend/src/app/pages/dashboard/dashboard.html b/apps/frontend/src/app/pages/dashboard/dashboard.html new file mode 100644 index 0000000..9c5fce9 --- /dev/null +++ b/apps/frontend/src/app/pages/dashboard/dashboard.html @@ -0,0 +1 @@ +

dashboard works!

diff --git a/apps/frontend/src/app/pages/dashboard/dashboard.spec.ts b/apps/frontend/src/app/pages/dashboard/dashboard.spec.ts new file mode 100644 index 0000000..25fda72 --- /dev/null +++ b/apps/frontend/src/app/pages/dashboard/dashboard.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Dashboard } from './dashboard'; + +describe('Dashboard', () => { + let component: Dashboard; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Dashboard], + }).compileComponents(); + + fixture = TestBed.createComponent(Dashboard); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/frontend/src/app/pages/dashboard/dashboard.ts b/apps/frontend/src/app/pages/dashboard/dashboard.ts new file mode 100644 index 0000000..8d3a6d5 --- /dev/null +++ b/apps/frontend/src/app/pages/dashboard/dashboard.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-dashboard', + imports: [], + templateUrl: './dashboard.html', + styleUrl: './dashboard.css', +}) +export class Dashboard {} diff --git a/libs/shared-dtos/src/user/user-response.dto.ts b/libs/shared-dtos/src/user/user-response.dto.ts index dbbbf17..5c3d3b8 100644 --- a/libs/shared-dtos/src/user/user-response.dto.ts +++ b/libs/shared-dtos/src/user/user-response.dto.ts @@ -6,4 +6,5 @@ export class UserResponseDto { readonly id!: number; readonly name!: string; readonly email!: string; + readonly access_token!: string; } From 572274f3327640911f94096a35450f1d44cd7788 Mon Sep 17 00:00:00 2001 From: Zafar Shaikh Date: Mon, 9 Feb 2026 13:33:47 +0530 Subject: [PATCH 2/4] feat(auth|backend): install package passport to setup backend route guards --- apps/backend/package.json | 3 +++ package-lock.json | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/apps/backend/package.json b/apps/backend/package.json index ea3679f..9cfb65c 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -24,11 +24,13 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", "@nestjs/jwt": "^11.0.1", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/typeorm": "^11.0.0", "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", + "passport": "^0.7.0", "passport-jwt": "^4.0.1", "pg": "^8.16.3", "reflect-metadata": "^0.2.2", @@ -45,6 +47,7 @@ "@types/express": "^5.0.0", "@types/jest": "^30.0.0", "@types/node": "^22.10.7", + "@types/passport": "^1.0.17", "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", "eslint": "^9.18.0", diff --git a/package-lock.json b/package-lock.json index a68b11d..0f09ec5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,11 +22,13 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", "@nestjs/jwt": "^11.0.1", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/typeorm": "^11.0.0", "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", + "passport": "^0.7.0", "passport-jwt": "^4.0.1", "pg": "^8.16.3", "reflect-metadata": "^0.2.2", @@ -43,6 +45,7 @@ "@types/express": "^5.0.0", "@types/jest": "^30.0.0", "@types/node": "^22.10.7", + "@types/passport": "^1.0.17", "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", "eslint": "^9.18.0", @@ -5123,6 +5126,16 @@ "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" } }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "11.1.6", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.6.tgz", @@ -16654,6 +16667,24 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, "node_modules/passport-jwt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", @@ -16754,6 +16785,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/pg": { "version": "8.16.3", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", @@ -20136,7 +20172,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4.0" From 4a5da1a0bea8ea5f0db437efb985bc626bf15578 Mon Sep 17 00:00:00 2001 From: Zafar7645 Date: Sun, 22 Feb 2026 16:58:37 +0530 Subject: [PATCH 3/4] feat(auth): implement route guards, jwt strategy, and token interceptor --- apps/backend/src/auth/auth.controller.ts | 16 ++++++++++++- apps/backend/src/auth/auth.module.ts | 5 +++- .../backend/src/auth/guards/jwt-auth.guard.ts | 5 ++++ apps/backend/src/auth/jwt.strategy.ts | 19 +++++++++++++++ apps/frontend/src/app/app.config.ts | 7 +++--- .../interceptors/token-interceptor.spec.ts | 17 +++++++++++++ .../auth/interceptors/token-interceptor.ts | 18 ++++++++++++++ .../src/app/pages/dashboard/dashboard.html | 6 ++++- .../src/app/pages/dashboard/dashboard.ts | 24 ++++++++++++++++--- 9 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 apps/backend/src/auth/guards/jwt-auth.guard.ts create mode 100644 apps/backend/src/auth/jwt.strategy.ts create mode 100644 apps/frontend/src/app/auth/interceptors/token-interceptor.spec.ts create mode 100644 apps/frontend/src/app/auth/interceptors/token-interceptor.ts diff --git a/apps/backend/src/auth/auth.controller.ts b/apps/backend/src/auth/auth.controller.ts index f08822d..624d65a 100644 --- a/apps/backend/src/auth/auth.controller.ts +++ b/apps/backend/src/auth/auth.controller.ts @@ -1,9 +1,17 @@ -import { Body, Controller, Post } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Post, + UseGuards, + Request, +} from '@nestjs/common'; import { RegisterUserDto } from '@shared/dtos/auth/register-user.dto'; import { AuthService } from '@/auth/auth.service'; import { UserResponseDto } from '@shared/dtos/user/user-response.dto'; import { LoginUserDto } from '@shared/dtos/auth/login-user.dto'; import { AccessTokenDto } from '@shared/dtos/auth/access-token.dto'; +import { JwtAuthGuard } from './guards/jwt-auth.guard'; @Controller('auth') export class AuthController { @@ -20,4 +28,10 @@ export class AuthController { async login(@Body() loginUserDto: LoginUserDto): Promise { return this.authService.login(loginUserDto); } + + @UseGuards(JwtAuthGuard) + @Get('profile') + getProfile(@Request() req) { + return req.user; + } } diff --git a/apps/backend/src/auth/auth.module.ts b/apps/backend/src/auth/auth.module.ts index 01788fd..e1217fa 100644 --- a/apps/backend/src/auth/auth.module.ts +++ b/apps/backend/src/auth/auth.module.ts @@ -4,10 +4,13 @@ import { AuthService } from '@/auth/auth.service'; import { UsersModule } from '@/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { PassportModule } from '@nestjs/passport'; +import { JwtStrategy } from '@/auth/jwt.strategy'; @Module({ imports: [ UsersModule, + PassportModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ @@ -21,6 +24,6 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; }), ], controllers: [AuthController], - providers: [AuthService], + providers: [AuthService, JwtStrategy], }) export class AuthModule {} diff --git a/apps/backend/src/auth/guards/jwt-auth.guard.ts b/apps/backend/src/auth/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..2155290 --- /dev/null +++ b/apps/backend/src/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/apps/backend/src/auth/jwt.strategy.ts b/apps/backend/src/auth/jwt.strategy.ts new file mode 100644 index 0000000..910f579 --- /dev/null +++ b/apps/backend/src/auth/jwt.strategy.ts @@ -0,0 +1,19 @@ +import { PassportStrategy } from '@nestjs/passport'; +import { ConfigService } from '@nestjs/config'; +import { Strategy, ExtractJwt } from 'passport-jwt'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET') || 'secretKey', + }); + } + + async validate(payload: any) { + return { userId: payload.sub, username: payload.username }; + } +} diff --git a/apps/frontend/src/app/app.config.ts b/apps/frontend/src/app/app.config.ts index 1e7350e..7dd3797 100644 --- a/apps/frontend/src/app/app.config.ts +++ b/apps/frontend/src/app/app.config.ts @@ -5,14 +5,15 @@ import { } from '@angular/core'; import { provideRouter } from '@angular/router'; -import { routes } from './app.routes'; -import { provideHttpClient } from '@angular/common/http'; +import { routes } from '@/app/app.routes'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { tokenInterceptor } from '@/app/auth/interceptors/token-interceptor'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), - provideHttpClient(), + provideHttpClient(withInterceptors([tokenInterceptor])), ], }; diff --git a/apps/frontend/src/app/auth/interceptors/token-interceptor.spec.ts b/apps/frontend/src/app/auth/interceptors/token-interceptor.spec.ts new file mode 100644 index 0000000..7860833 --- /dev/null +++ b/apps/frontend/src/app/auth/interceptors/token-interceptor.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpInterceptorFn } from '@angular/common/http'; + +import { tokenInterceptor } from './token-interceptor'; + +describe('tokenInterceptor', () => { + const interceptor: HttpInterceptorFn = (req, next) => + TestBed.runInInjectionContext(() => tokenInterceptor(req, next)); + + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + + it('should be created', () => { + expect(interceptor).toBeTruthy(); + }); +}); diff --git a/apps/frontend/src/app/auth/interceptors/token-interceptor.ts b/apps/frontend/src/app/auth/interceptors/token-interceptor.ts new file mode 100644 index 0000000..5d4a72b --- /dev/null +++ b/apps/frontend/src/app/auth/interceptors/token-interceptor.ts @@ -0,0 +1,18 @@ +import { HttpInterceptorFn } from '@angular/common/http'; +import { Auth } from '@/app/auth/services/auth'; +import { inject } from '@angular/core'; + +export const tokenInterceptor: HttpInterceptorFn = (req, next) => { + const authService = inject(Auth); + const token = authService.getToken(); + + if (token) { + const clonedRequest = req.clone({ + setHeaders: { + Authorization: `Bearer ${token}`, + }, + }); + return next(clonedRequest); + } + return next(req); +}; diff --git a/apps/frontend/src/app/pages/dashboard/dashboard.html b/apps/frontend/src/app/pages/dashboard/dashboard.html index 9c5fce9..c4f600c 100644 --- a/apps/frontend/src/app/pages/dashboard/dashboard.html +++ b/apps/frontend/src/app/pages/dashboard/dashboard.html @@ -1 +1,5 @@ -

dashboard works!

+
+

Welcome to Dashboard

+

If you see your profile data below, the Interceptor works! 🎉

+
{{ profileData | json }}
+
diff --git a/apps/frontend/src/app/pages/dashboard/dashboard.ts b/apps/frontend/src/app/pages/dashboard/dashboard.ts index 8d3a6d5..4c515a1 100644 --- a/apps/frontend/src/app/pages/dashboard/dashboard.ts +++ b/apps/frontend/src/app/pages/dashboard/dashboard.ts @@ -1,9 +1,27 @@ -import { Component } from '@angular/core'; +import { environment } from '@/environments/environment.development'; +import { CommonModule } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { Component, inject } from '@angular/core'; @Component({ selector: 'app-dashboard', - imports: [], + imports: [CommonModule], templateUrl: './dashboard.html', styleUrl: './dashboard.css', }) -export class Dashboard {} +export class Dashboard { + private httpClient = inject(HttpClient); + profileData: any = null; + + ngOnInit() { + this.httpClient.get(`${environment.apiUrl}/auth/profile`).subscribe({ + next: (data) => { + console.log('Profile Data received:', data); + this.profileData = data; + }, + error: (err) => { + console.error('Error fetching profile data:', err); + }, + }); + } +} From 1bdcd8958ad228914088e4013e39ab23df5f96bb Mon Sep 17 00:00:00 2001 From: Zafar7645 Date: Tue, 3 Mar 2026 16:35:05 +0530 Subject: [PATCH 4/4] feat(auth): apply suggestions from automated code review --- apps/backend/src/auth/auth.controller.spec.ts | 1 + apps/backend/src/auth/auth.controller.ts | 2 +- apps/backend/src/auth/jwt.strategy.ts | 6 +++--- apps/frontend/src/app/auth/services/auth.spec.ts | 1 + .../src/app/pages/dashboard/dashboard.spec.ts | 5 +++++ .../src/app/pages/dashboard/dashboard.ts | 16 ++++++++++++---- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/apps/backend/src/auth/auth.controller.spec.ts b/apps/backend/src/auth/auth.controller.spec.ts index a99d13d..c30b8eb 100644 --- a/apps/backend/src/auth/auth.controller.spec.ts +++ b/apps/backend/src/auth/auth.controller.spec.ts @@ -49,6 +49,7 @@ describe('AuthController', () => { id: 1, name: 'Test User', email: 'test.user@test.com', + access_token: 'SomeReallyLongAccessTokenText', }; (authService.register as jest.Mock).mockResolvedValue( mockUserResponseDto, diff --git a/apps/backend/src/auth/auth.controller.ts b/apps/backend/src/auth/auth.controller.ts index 624d65a..1f3b903 100644 --- a/apps/backend/src/auth/auth.controller.ts +++ b/apps/backend/src/auth/auth.controller.ts @@ -31,7 +31,7 @@ export class AuthController { @UseGuards(JwtAuthGuard) @Get('profile') - getProfile(@Request() req) { + getProfile(@Request() req: { user: { userId: number; email: string } }) { return req.user; } } diff --git a/apps/backend/src/auth/jwt.strategy.ts b/apps/backend/src/auth/jwt.strategy.ts index 910f579..ecf189a 100644 --- a/apps/backend/src/auth/jwt.strategy.ts +++ b/apps/backend/src/auth/jwt.strategy.ts @@ -9,11 +9,11 @@ export class JwtStrategy extends PassportStrategy(Strategy) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: configService.get('JWT_SECRET') || 'secretKey', + secretOrKey: configService.getOrThrow('JWT_SECRET'), }); } - async validate(payload: any) { - return { userId: payload.sub, username: payload.username }; + validate(payload: { sub: number; email: string }) { + return { userId: payload.sub, email: payload.email }; } } diff --git a/apps/frontend/src/app/auth/services/auth.spec.ts b/apps/frontend/src/app/auth/services/auth.spec.ts index c2034d1..262bf7b 100644 --- a/apps/frontend/src/app/auth/services/auth.spec.ts +++ b/apps/frontend/src/app/auth/services/auth.spec.ts @@ -42,6 +42,7 @@ describe('Auth', () => { id: 1, name: 'Test', email: 'email@test.com', + access_token: 'access_token', }; service.register(mockRegisterData).subscribe((response) => { diff --git a/apps/frontend/src/app/pages/dashboard/dashboard.spec.ts b/apps/frontend/src/app/pages/dashboard/dashboard.spec.ts index 25fda72..56585e9 100644 --- a/apps/frontend/src/app/pages/dashboard/dashboard.spec.ts +++ b/apps/frontend/src/app/pages/dashboard/dashboard.spec.ts @@ -1,22 +1,27 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Dashboard } from './dashboard'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; describe('Dashboard', () => { let component: Dashboard; let fixture: ComponentFixture; + let httpMock: HttpTestingController; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [Dashboard], + providers: [provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(Dashboard); component = fixture.componentInstance; + httpMock = TestBed.inject(HttpTestingController); fixture.detectChanges(); }); it('should create', () => { + httpMock.expectOne('/auth/profile').flush({ id: 1, name: 'Test', email: 'test@test.com' }); expect(component).toBeTruthy(); }); }); diff --git a/apps/frontend/src/app/pages/dashboard/dashboard.ts b/apps/frontend/src/app/pages/dashboard/dashboard.ts index 4c515a1..90b5567 100644 --- a/apps/frontend/src/app/pages/dashboard/dashboard.ts +++ b/apps/frontend/src/app/pages/dashboard/dashboard.ts @@ -1,7 +1,13 @@ import { environment } from '@/environments/environment.development'; import { CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; -import { Component, inject } from '@angular/core'; +import { Component, inject, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +interface UserProfile { + userId: number; + email: string; +} @Component({ selector: 'app-dashboard', @@ -9,18 +15,20 @@ import { Component, inject } from '@angular/core'; templateUrl: './dashboard.html', styleUrl: './dashboard.css', }) -export class Dashboard { +export class Dashboard implements OnInit { private httpClient = inject(HttpClient); - profileData: any = null; + private router = inject(Router); + profileData: UserProfile | null = null; ngOnInit() { - this.httpClient.get(`${environment.apiUrl}/auth/profile`).subscribe({ + this.httpClient.get(`${environment.apiUrl}/auth/profile`).subscribe({ next: (data) => { console.log('Profile Data received:', data); this.profileData = data; }, error: (err) => { console.error('Error fetching profile data:', err); + this.router.navigate(['/auth/login']); }, }); }