feature: authorization - add role guard and protect products route

This commit is contained in:
kusowl 2026-02-27 13:22:02 +05:30
parent 617053c0ee
commit 8b1b831ea2
5 changed files with 45 additions and 1 deletions

View File

@ -1,6 +1,7 @@
import { Routes } from "@angular/router"; import { Routes } from "@angular/router";
import { Home } from "./features/home/home"; import { Home } from "./features/home/home";
import { authGuard } from "./core/guards/auth-guard"; import { authGuard } from "./core/guards/auth-guard";
import { roleGuard } from "./core/guards/role-guard";
export const routes: Routes = [ export const routes: Routes = [
{ {
@ -16,6 +17,7 @@ export const routes: Routes = [
path: "products", path: "products",
loadChildren: () => loadChildren: () =>
import("./features/product/product.routes").then((routes) => routes.productRoutes), import("./features/product/product.routes").then((routes) => routes.productRoutes),
// canActivate: [authGuard] canActivate: [authGuard, roleGuard],
data: { roles: ["broker"] },
}, },
]; ];

View File

@ -0,0 +1,17 @@
import { TestBed } from "@angular/core/testing";
import { CanActivateFn } from "@angular/router";
import { roleGuard } from "./role-guard";
describe("roleGuard", () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => roleGuard(...guardParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it("should be created", () => {
expect(executeGuard).toBeTruthy();
});
});

View File

@ -0,0 +1,12 @@
import { CanActivateFn } from "@angular/router";
import { inject } from "@angular/core";
import { AuthService } from "../../features/auth/services/auth-service";
export const roleGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
// get role from route data passed in route config.
const roles = route.data["roles"] as string[];
return roles && authService.hasRoles(roles);
};

View File

@ -13,4 +13,5 @@ export interface User {
email: string; email: string;
mobileNumber: string; mobileNumber: string;
city: string; city: string;
role: string;
} }

View File

@ -27,6 +27,7 @@ export class AuthService {
// User states // User states
readonly authState: WritableSignal<AuthState>; readonly authState: WritableSignal<AuthState>;
readonly user: WritableSignal<User | null>; readonly user: WritableSignal<User | null>;
readonly userRole: Signal<string | null>;
// Computed state for easy checking // Computed state for easy checking
readonly isAuthenticated: Signal<boolean>; readonly isAuthenticated: Signal<boolean>;
@ -45,6 +46,7 @@ export class AuthService {
); );
this.user = signal<User | null>(cachedUser); this.user = signal<User | null>(cachedUser);
this.isAuthenticated = computed(() => !!this.user()); this.isAuthenticated = computed(() => !!this.user());
this.userRole = computed(() => this.user()?.role || null);
} }
register(userRequest: RegisterUserRequest) { register(userRequest: RegisterUserRequest) {
@ -74,6 +76,16 @@ export class AuthService {
); );
} }
/**
* Check if current user has the role.
* Mostly used in role guard.
*/
hasRoles(roles: string[]) {
const role = this.userRole();
if (!role) return false;
return roles.includes(role);
}
logout() { logout() {
return this.http.post(`${this.backendURL}/logout`, {}).pipe( return this.http.post(`${this.backendURL}/logout`, {}).pipe(
tap({ tap({