From 82d9ed2395a855e92cde391d802055d53d51e5a1 Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Wed, 23 Oct 2024 16:24:09 +0200 Subject: [PATCH 01/13] [Fix] Product Name Not Displayed During Estimate Editing --- .../pages/invoices/invoice-edit/invoice-edit.component.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/gauzy/src/app/pages/invoices/invoice-edit/invoice-edit.component.ts b/apps/gauzy/src/app/pages/invoices/invoice-edit/invoice-edit.component.ts index e7ee22c543a..2905ed19d55 100644 --- a/apps/gauzy/src/app/pages/invoices/invoice-edit/invoice-edit.component.ts +++ b/apps/gauzy/src/app/pages/invoices/invoice-edit/invoice-edit.component.ts @@ -311,9 +311,8 @@ export class InvoiceEditComponent extends PaginationFilterBaseComponent implemen component: InvoiceProductsSelectorComponent }, valuePrepareFunction: (product: IProduct) => { - return product?.name - ? `${this.translatableService.getTranslatedProperty(product, 'name')}` - : ''; + const translatedName = this.translatableService.getTranslatedProperty(product, 'name'); + return translatedName || ''; } }; break; From e9b031f1af607dc2fa330fec96222565f904f7d2 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 12:58:13 +0530 Subject: [PATCH 02/13] feat: cookie validation to allow access from all subdomains --- apps/gauzy/src/app/app.module.guard.ts | 4 +- .../ui-core/core/src/lib/auth/auth.guard.ts | 40 ++++++++-------- .../ui-core/core/src/lib/auth/auth.module.ts | 3 ++ .../core/src/lib/auth/cookie-helper.ts | 48 +++++++++++++++++++ .../core/src/lib/auth/no-auth.guard.ts | 17 +++---- 5 files changed, 83 insertions(+), 29 deletions(-) create mode 100644 packages/ui-core/core/src/lib/auth/cookie-helper.ts diff --git a/apps/gauzy/src/app/app.module.guard.ts b/apps/gauzy/src/app/app.module.guard.ts index 915c37794ac..aef68709acf 100644 --- a/apps/gauzy/src/app/app.module.guard.ts +++ b/apps/gauzy/src/app/app.module.guard.ts @@ -1,10 +1,10 @@ import { Injectable } from '@angular/core'; -import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router'; +import { Router, ActivatedRouteSnapshot } from '@angular/router'; import { environment } from '@gauzy/ui-config'; import { Store } from '@gauzy/ui-core/core'; @Injectable() -export class AppModuleGuard implements CanActivate { +export class AppModuleGuard { constructor(private readonly router: Router, private readonly store: Store) {} /** diff --git a/packages/ui-core/core/src/lib/auth/auth.guard.ts b/packages/ui-core/core/src/lib/auth/auth.guard.ts index 071cd7f8562..9bca42b5fe0 100644 --- a/packages/ui-core/core/src/lib/auth/auth.guard.ts +++ b/packages/ui-core/core/src/lib/auth/auth.guard.ts @@ -1,16 +1,17 @@ import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { firstValueFrom } from 'rxjs'; import { AuthService, AuthStrategy, ElectronService, Store } from '../services'; +import { getCookie } from './cookie-helper'; @Injectable() -export class AuthGuard implements CanActivate { +export class AuthGuard { constructor( - private readonly router: Router, - private readonly authService: AuthService, - private readonly authStrategy: AuthStrategy, - private readonly store: Store, - private readonly electronService: ElectronService + private readonly _router: Router, + private readonly _authService: AuthService, + private readonly _authStrategy: AuthStrategy, + private readonly _store: Store, + private readonly _electronService: ElectronService ) {} /** @@ -21,20 +22,21 @@ export class AuthGuard implements CanActivate { * @return {Promise} A promise that resolves to true if the user is authenticated, false otherwise. */ async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const token = route.queryParamMap.get('token'); - const userId = route.queryParamMap.get('userId'); + const token = route.queryParamMap.get('token') || getCookie('token'); + const userId = route.queryParamMap.get('userId') || getCookie('userId'); + // If token and userId exist, store them if (token && userId) { - this.store.token = token; - this.store.userId = userId; + this._store.token = token; + this._store.userId = userId; } - if (await this.authService.isAuthenticated()) { - // Logged in, so allow navigation - return true; + // Check if the user is authenticated + if (await this._authService.isAuthenticated()) { + return true; // Allow navigation } - // Not logged in, handle the logout process + // Not authenticated, handle logout await this.handleLogout(state.url); return false; } @@ -45,15 +47,15 @@ export class AuthGuard implements CanActivate { * @param {string} returnUrl - The URL to return to after logging in. */ private async handleLogout(returnUrl: string): Promise { - if (this.electronService.isElectron) { + if (this._electronService.isElectron) { try { - this.electronService.ipcRenderer.send('logout'); + this._electronService.ipcRenderer.send('logout'); } catch (error) { console.error('Error sending logout message to Electron:', error); } } - await firstValueFrom(this.authStrategy.logout()); - await this.router.navigate(['/auth/login'], { queryParams: { returnUrl } }); + await firstValueFrom(this._authStrategy.logout()); + await this._router.navigate(['/auth/login'], { queryParams: { returnUrl } }); } } diff --git a/packages/ui-core/core/src/lib/auth/auth.module.ts b/packages/ui-core/core/src/lib/auth/auth.module.ts index be9ed870a50..d80b7c158f3 100644 --- a/packages/ui-core/core/src/lib/auth/auth.module.ts +++ b/packages/ui-core/core/src/lib/auth/auth.module.ts @@ -6,6 +6,9 @@ import { AuthGuard } from './auth.guard'; import { NoAuthGuard } from './no-auth.guard'; import { AuthService, AuthStrategy, ElectronService, Store } from '../services'; +/** + * Social links for auth + */ const socialLinks = [ { url: environment.GOOGLE_AUTH_LINK, diff --git a/packages/ui-core/core/src/lib/auth/cookie-helper.ts b/packages/ui-core/core/src/lib/auth/cookie-helper.ts new file mode 100644 index 00000000000..4ae89d95f3a --- /dev/null +++ b/packages/ui-core/core/src/lib/auth/cookie-helper.ts @@ -0,0 +1,48 @@ +/** + * Retrieves the value of a cookie by its name for the current domain and its subdomains. + * + * @param {string} name - The name of the cookie to retrieve. + * @return {string | null} - The value of the cookie if found, or null if not found. + */ +export function getCookie(name: string): string | null { + const value = `; ${document.cookie}`; // Get all cookies as a string and add a leading semicolon + const parts = value.split(`; ${name}=`); // Split the string by the desired cookie name + + // If the cookie is found, split to isolate its value and return it + if (parts.length === 2) { + const cookie = parts.pop()?.split(';').shift() || null; // Get the cookie value + + // Check if the cookie is set for the current domain or its subdomains + if (isCookieForValidDomain(cookie)) { + return cookie; // Return the cookie value if it's for a valid domain + } + } + + // Return null if the cookie is not found + return null; +} + +/** + * Checks if the cookie is set for the current domain, its subdomains, or localhost. + * + * @param {string} cookie - The value of the cookie to check. + * @return {boolean} - True if the cookie is considered valid, otherwise false. + */ +function isCookieForValidDomain(cookie: string | null): boolean { + // Check if the cookie is not null + if (cookie === null) { + return false; // Not valid if cookie does not exist + } + + // Get the current hostname + const hostname = window.location.hostname; // e.g., "demo.gauzy.co" or "gauzy.co" + + // Define the base domain + const mainDomain = 'gauzy.co'; + + // Check if the hostname is localhost or a subdomain of gauzy.co + const isLocalhost = hostname === 'localhost'; + const isSubdomain = hostname.endsWith(`.${mainDomain}`) || hostname === mainDomain; + + return isLocalhost || isSubdomain; // Return true if valid, false otherwise +} diff --git a/packages/ui-core/core/src/lib/auth/no-auth.guard.ts b/packages/ui-core/core/src/lib/auth/no-auth.guard.ts index e95f4a41b77..d7299f5de4b 100644 --- a/packages/ui-core/core/src/lib/auth/no-auth.guard.ts +++ b/packages/ui-core/core/src/lib/auth/no-auth.guard.ts @@ -1,15 +1,16 @@ import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { AuthService, Store } from '../services'; + /** * Use for routes which only need to be displayed if user is NOT logged in */ @Injectable() -export class NoAuthGuard implements CanActivate { +export class NoAuthGuard { constructor( - private readonly router: Router, - private readonly authService: AuthService, - private readonly store: Store + private readonly _router: Router, + private readonly _authService: AuthService, + private readonly _store: Store ) {} /** @@ -20,18 +21,18 @@ export class NoAuthGuard implements CanActivate { * @return {Promise} A promise that resolves to true if the user is authenticated, false otherwise. */ async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - if (!this.store.token) { + if (!this._store.token) { // not logged in so return true return true; } - if (!(await this.authService.isAuthenticated())) { + if (!(await this._authService.isAuthenticated())) { // not logged in so return true return true; } // logged in so redirect to dashboard - this.router.navigate(['/pages/dashboard']); + this._router.navigate(['/pages/dashboard']); return false; } From 657d4cc128dcddbc76405d1b9e7290de09d4ee5a Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 13:25:12 +0530 Subject: [PATCH 03/13] fix: retrieve missing refresh token from cookie --- packages/ui-core/core/src/lib/auth/auth.guard.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui-core/core/src/lib/auth/auth.guard.ts b/packages/ui-core/core/src/lib/auth/auth.guard.ts index 9bca42b5fe0..32cd716cc8e 100644 --- a/packages/ui-core/core/src/lib/auth/auth.guard.ts +++ b/packages/ui-core/core/src/lib/auth/auth.guard.ts @@ -24,11 +24,13 @@ export class AuthGuard { async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { const token = route.queryParamMap.get('token') || getCookie('token'); const userId = route.queryParamMap.get('userId') || getCookie('userId'); + const refreshToken = route.queryParamMap.get('refresh_token') || getCookie('refresh_token'); // If token and userId exist, store them if (token && userId) { this._store.token = token; this._store.userId = userId; + this._store.refresh_token = refreshToken; } // Check if the user is authenticated From 1e220ff7a74419c6c87a7865b7c3861d27359855 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 15:07:18 +0530 Subject: [PATCH 04/13] fix: #8480 apply code rabbit suggestions --- .../ui-core/common/src/lib/constants/index.ts | 1 + .../src/lib/constants/route.constant.ts | 4 ++ .../core/src/lib/auth/cookie-helper.ts | 40 ++++++++++++++----- .../core/src/lib/auth/no-auth.guard.ts | 3 +- 4 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 packages/ui-core/common/src/lib/constants/route.constant.ts diff --git a/packages/ui-core/common/src/lib/constants/index.ts b/packages/ui-core/common/src/lib/constants/index.ts index fff2f7aa02a..98f36d229c5 100644 --- a/packages/ui-core/common/src/lib/constants/index.ts +++ b/packages/ui-core/common/src/lib/constants/index.ts @@ -1,3 +1,4 @@ export * from './app.constants'; export * from './layout.constants'; +export * from './route.constant'; export * from './timesheet.constants'; diff --git a/packages/ui-core/common/src/lib/constants/route.constant.ts b/packages/ui-core/common/src/lib/constants/route.constant.ts new file mode 100644 index 00000000000..6bd97cfca14 --- /dev/null +++ b/packages/ui-core/common/src/lib/constants/route.constant.ts @@ -0,0 +1,4 @@ +// In a constants file or configuration service +export const ROUTES = { + DASHBOARD: '/pages/dashboard' +} as const; diff --git a/packages/ui-core/core/src/lib/auth/cookie-helper.ts b/packages/ui-core/core/src/lib/auth/cookie-helper.ts index 4ae89d95f3a..45be9e521f9 100644 --- a/packages/ui-core/core/src/lib/auth/cookie-helper.ts +++ b/packages/ui-core/core/src/lib/auth/cookie-helper.ts @@ -1,3 +1,11 @@ +// Define allowed domains configuration +const DOMAIN_CONFIG = { + production: ['gauzy.co'], + demo: ['demo.gauzy.co'], + staging: ['staging.gauzy.co'], + development: ['localhost', '127.0.0.1'] +} as const; + /** * Retrieves the value of a cookie by its name for the current domain and its subdomains. * @@ -5,8 +13,14 @@ * @return {string | null} - The value of the cookie if found, or null if not found. */ export function getCookie(name: string): string | null { + if (!name || typeof name !== 'string') { + return null; + } + + // Sanitize the cookie name + const sanitizedName = encodeURIComponent(name); const value = `; ${document.cookie}`; // Get all cookies as a string and add a leading semicolon - const parts = value.split(`; ${name}=`); // Split the string by the desired cookie name + const parts = value.split(`; ${sanitizedName}=`); // Split the string by the desired cookie name // If the cookie is found, split to isolate its value and return it if (parts.length === 2) { @@ -14,7 +28,7 @@ export function getCookie(name: string): string | null { // Check if the cookie is set for the current domain or its subdomains if (isCookieForValidDomain(cookie)) { - return cookie; // Return the cookie value if it's for a valid domain + return decodeURIComponent(cookie); // Return the cookie value if it's for a valid domain } } @@ -35,14 +49,22 @@ function isCookieForValidDomain(cookie: string | null): boolean { } // Get the current hostname - const hostname = window.location.hostname; // e.g., "demo.gauzy.co" or "gauzy.co" + const hostname = window.location.hostname; // e.g., "demo.gauzy.co" or "app.gauzy.co" - // Define the base domain - const mainDomain = 'gauzy.co'; + // Get environment-specific domains + const validDomains = [ + ...DOMAIN_CONFIG.production, + ...DOMAIN_CONFIG.demo, + ...DOMAIN_CONFIG.staging, + ...DOMAIN_CONFIG.development + ]; - // Check if the hostname is localhost or a subdomain of gauzy.co - const isLocalhost = hostname === 'localhost'; - const isSubdomain = hostname.endsWith(`.${mainDomain}`) || hostname === mainDomain; + // More robust domain validation + const isValidDomain = validDomains.some((domain) => { + if (domain === hostname) return true; + if (domain.startsWith('.')) return hostname.endsWith(domain); + return hostname.endsWith(`.${domain}`) || hostname === domain; + }); - return isLocalhost || isSubdomain; // Return true if valid, false otherwise + return isValidDomain; // Return true if valid, false otherwise } diff --git a/packages/ui-core/core/src/lib/auth/no-auth.guard.ts b/packages/ui-core/core/src/lib/auth/no-auth.guard.ts index d7299f5de4b..33f079f28aa 100644 --- a/packages/ui-core/core/src/lib/auth/no-auth.guard.ts +++ b/packages/ui-core/core/src/lib/auth/no-auth.guard.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; +import { ROUTES } from '@gauzy/ui-core/common'; import { AuthService, Store } from '../services'; /** @@ -32,7 +33,7 @@ export class NoAuthGuard { } // logged in so redirect to dashboard - this._router.navigate(['/pages/dashboard']); + this._router.navigate([ROUTES.DASHBOARD]); return false; } From 16bbb9d8bb2c06fddda2046225a88554c2cf98b0 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 15:25:05 +0530 Subject: [PATCH 05/13] fix: #8480 apply code rabbit suggestions --- .../ui-core/core/src/lib/auth/cookie-helper.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/ui-core/core/src/lib/auth/cookie-helper.ts b/packages/ui-core/core/src/lib/auth/cookie-helper.ts index 45be9e521f9..32dd17451e9 100644 --- a/packages/ui-core/core/src/lib/auth/cookie-helper.ts +++ b/packages/ui-core/core/src/lib/auth/cookie-helper.ts @@ -1,4 +1,4 @@ -// Define allowed domains configuration +// Define allowed domains for each environment const DOMAIN_CONFIG = { production: ['gauzy.co'], demo: ['demo.gauzy.co'], @@ -26,8 +26,8 @@ export function getCookie(name: string): string | null { if (parts.length === 2) { const cookie = parts.pop()?.split(';').shift() || null; // Get the cookie value - // Check if the cookie is set for the current domain or its subdomains - if (isCookieForValidDomain(cookie)) { + // Validate if the cookie is set for the current domain or its subdomains + if (isCookieForValidDomain(sanitizedName)) { return decodeURIComponent(cookie); // Return the cookie value if it's for a valid domain } } @@ -39,7 +39,7 @@ export function getCookie(name: string): string | null { /** * Checks if the cookie is set for the current domain, its subdomains, or localhost. * - * @param {string} cookie - The value of the cookie to check. + * @param {string} cookie - The name of the cookie to check. * @return {boolean} - True if the cookie is considered valid, otherwise false. */ function isCookieForValidDomain(cookie: string | null): boolean { @@ -49,7 +49,7 @@ function isCookieForValidDomain(cookie: string | null): boolean { } // Get the current hostname - const hostname = window.location.hostname; // e.g., "demo.gauzy.co" or "app.gauzy.co" + const hostname = window.location.hostname; // e.g., "demo.gauzy.co" or "localhost" // Get environment-specific domains const validDomains = [ @@ -59,12 +59,10 @@ function isCookieForValidDomain(cookie: string | null): boolean { ...DOMAIN_CONFIG.development ]; - // More robust domain validation - const isValidDomain = validDomains.some((domain) => { + // Check if the cookie's domain is valid + return validDomains.some((domain) => { if (domain === hostname) return true; if (domain.startsWith('.')) return hostname.endsWith(domain); return hostname.endsWith(`.${domain}`) || hostname === domain; }); - - return isValidDomain; // Return true if valid, false otherwise } From 289fd62f00e167f6b30968c38cb28717e85f23f4 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 16:13:54 +0530 Subject: [PATCH 06/13] fiix: after logout delete stored cookie --- .../core/src/lib/auth/cookie-helper.ts | 124 ++++++++++++++++-- .../services/auth/auth-strategy.service.ts | 6 + 2 files changed, 116 insertions(+), 14 deletions(-) diff --git a/packages/ui-core/core/src/lib/auth/cookie-helper.ts b/packages/ui-core/core/src/lib/auth/cookie-helper.ts index 32dd17451e9..bc022c5ab4b 100644 --- a/packages/ui-core/core/src/lib/auth/cookie-helper.ts +++ b/packages/ui-core/core/src/lib/auth/cookie-helper.ts @@ -39,7 +39,7 @@ export function getCookie(name: string): string | null { /** * Checks if the cookie is set for the current domain, its subdomains, or localhost. * - * @param {string} cookie - The name of the cookie to check. + * @param {string} cookie - The value of the cookie to check. * @return {boolean} - True if the cookie is considered valid, otherwise false. */ function isCookieForValidDomain(cookie: string | null): boolean { @@ -49,20 +49,116 @@ function isCookieForValidDomain(cookie: string | null): boolean { } // Get the current hostname - const hostname = window.location.hostname; // e.g., "demo.gauzy.co" or "localhost" + const hostname = window.location.hostname; // e.g., "demo.gauzy.co" or "app.gauzy.co" + + // Check for development environments + if (DOMAIN_CONFIG.development.includes(hostname as 'localhost' | '127.0.0.1')) { + return true; // Allow cookies for localhost and 127.0.0.1 + } // Get environment-specific domains - const validDomains = [ - ...DOMAIN_CONFIG.production, - ...DOMAIN_CONFIG.demo, - ...DOMAIN_CONFIG.staging, - ...DOMAIN_CONFIG.development - ]; - - // Check if the cookie's domain is valid - return validDomains.some((domain) => { - if (domain === hostname) return true; - if (domain.startsWith('.')) return hostname.endsWith(domain); - return hostname.endsWith(`.${domain}`) || hostname === domain; + const validDomains = [...DOMAIN_CONFIG.production, ...DOMAIN_CONFIG.demo, ...DOMAIN_CONFIG.staging]; + + // More robust domain validation + return validDomains.some((domain: string) => { + // Convert hostname and domain to lowercase for case-insensitive comparison + const normalizedHostname = hostname.toLowerCase(); + const normalizedDomain = domain.toLowerCase(); + + // Check for exact match + if (normalizedHostname === normalizedDomain) { + return true; + } + + // Check if the hostname ends with the domain and ensure proper boundaries + if (normalizedHostname.endsWith(`.${normalizedDomain}`)) { + // Ensure there are no additional dots to prevent attacks + const subdomain = normalizedHostname.slice(0, -normalizedDomain.length - 1); + return !subdomain.includes('.'); + } + + // Prevent direct domain spoofing by checking if it matches the exact domain + if (normalizedHostname === `www.${normalizedDomain}`) { + return true; + } + + return false; // Invalid if none of the checks pass }); } + +/** + * Sets a cookie with the specified name, value, and options. + * + * @param {string} name - The name of the cookie. + * @param {string} value - The value of the cookie. + * @param {Object} options - Additional options for the cookie. + */ +export function setCookie(name: string, value: string, options: { [key: string]: any } = {}) { + if (!name || typeof value === 'undefined') { + return; // Ensure valid inputs + } + + // Prepare cookie options with defaults + const cookieOptions = { + path: '/', + SameSite: 'Lax', + Secure: window.location.protocol === 'https:', + ...options + }; + + // Remove domain option for localhost or 127.0.0.1 + const hostname = window.location.hostname; + if (hostname === 'localhost' || hostname === '127.0.0.1') { + delete cookieOptions['domain']; + } else { + cookieOptions['domain'] = cookieOptions['domain'] || '.gauzy.co'; + } + + // Build the cookie string + const cookieString = + `${encodeURIComponent(name)}=${encodeURIComponent(value)}; ` + + Object.entries(cookieOptions) + .map(([key, val]) => `${key}=${val}`) + .join('; '); + + // Set the cookie + document.cookie = cookieString; +} + +/** + * Deletes a cookie by setting its expiration date to a time in the past. + * + * @param {string} name - The name of the cookie to delete. + * @param {Object} options - Additional options for the cookie. + */ +export function deleteCookie(name: string, options: { [key: string]: any } = {}) { + if (!name) { + return; // Invalid name, exit function + } + + // Prepare cookie options with defaults + const cookieOptions = { + path: '/', + SameSite: 'Lax', + Secure: window.location.protocol === 'https:', + ...options + }; + + // Remove domain option for localhost or 127.0.0.1 + const hostname = window.location.hostname; + if (hostname === 'localhost' || hostname === '127.0.0.1') { + delete cookieOptions['domain']; + } else { + cookieOptions['domain'] = cookieOptions['domain'] || '.gauzy.co'; + } + + // Build the cookie string for deletion + const cookieString = + `${encodeURIComponent(name)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; ` + + Object.entries(cookieOptions) + .map(([key, val]) => `${key}=${val}`) + .join('; '); + + // Set the cookie to delete it + document.cookie = cookieString; +} diff --git a/packages/ui-core/core/src/lib/services/auth/auth-strategy.service.ts b/packages/ui-core/core/src/lib/services/auth/auth-strategy.service.ts index 3216f0954de..026313b20d5 100644 --- a/packages/ui-core/core/src/lib/services/auth/auth-strategy.service.ts +++ b/packages/ui-core/core/src/lib/services/auth/auth-strategy.service.ts @@ -12,6 +12,7 @@ import { AuthService } from './auth.service'; import { TimesheetFilterService } from '../timesheet/timesheet-filter.service'; import { TimeTrackerService } from '../time-tracker/time-tracker.service'; import { Store } from '../store/store.service'; +import { deleteCookie } from '../../auth/cookie-helper'; @Injectable() export class AuthStrategy extends NbAuthStrategy { @@ -289,6 +290,11 @@ export class AuthStrategy extends NbAuthStrategy { this.timeTrackerService.clearTimeTracker(); this.timesheetFilterService.clear(); } + + // Delete cookies + deleteCookie('userId', { SameSite: 'None', Secure: true }); // Default path + deleteCookie('token', { SameSite: 'None', Secure: true }); // Default path + deleteCookie('refresh_token', { SameSite: 'None', Secure: true }); // Default path } public login(loginInput: IUserLoginInput): Observable { From 2e25ee3fbbf1ea75fdea6e6b3408462bddb663ef Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 16:15:44 +0530 Subject: [PATCH 07/13] fix: #8480 apply code rabbit suggestions --- packages/ui-core/core/src/lib/auth/cookie-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-core/core/src/lib/auth/cookie-helper.ts b/packages/ui-core/core/src/lib/auth/cookie-helper.ts index bc022c5ab4b..70df39e7dfa 100644 --- a/packages/ui-core/core/src/lib/auth/cookie-helper.ts +++ b/packages/ui-core/core/src/lib/auth/cookie-helper.ts @@ -27,7 +27,7 @@ export function getCookie(name: string): string | null { const cookie = parts.pop()?.split(';').shift() || null; // Get the cookie value // Validate if the cookie is set for the current domain or its subdomains - if (isCookieForValidDomain(sanitizedName)) { + if (isCookieForValidDomain(cookie)) { return decodeURIComponent(cookie); // Return the cookie value if it's for a valid domain } } From cec5e81d01c98ada618d26cafed66bb831db3786 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 16:26:13 +0530 Subject: [PATCH 08/13] fix: security enhance token validation and cookie security. --- .../ui-core/core/src/lib/auth/auth.guard.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/ui-core/core/src/lib/auth/auth.guard.ts b/packages/ui-core/core/src/lib/auth/auth.guard.ts index 32cd716cc8e..14802e45f08 100644 --- a/packages/ui-core/core/src/lib/auth/auth.guard.ts +++ b/packages/ui-core/core/src/lib/auth/auth.guard.ts @@ -33,6 +33,13 @@ export class AuthGuard { this._store.refresh_token = refreshToken; } + // Validate the token before proceeding + if (token && !this.validateToken(token)) { + console.error('Invalid token, redirecting to login page...'); + await this.handleLogout(state.url); // Handle invalid token + return false; // Prevent navigation + } + // Check if the user is authenticated if (await this._authService.isAuthenticated()) { return true; // Allow navigation @@ -60,4 +67,14 @@ export class AuthGuard { await firstValueFrom(this._authStrategy.logout()); await this._router.navigate(['/auth/login'], { queryParams: { returnUrl } }); } + + /** + * Validates the format of a JWT token. + * + * @param {string} token - The JWT token to validate. + * @returns {boolean} - Returns true if the token is valid, otherwise false. + */ + private validateToken(token: string): boolean { + return typeof token === 'string' && token.trim().length > 0 && token.split('.').length === 3; + } } From f9a5ff671b41061251592e284e0d869812e2ba36 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 16:38:48 +0530 Subject: [PATCH 09/13] fix: #8480 apply code rabbit suggestions The cookie options setup is duplicated between setCookie and deleteCookie functions. Consider extracting this logic into a shared helper function. --- .../core/src/lib/auth/cookie-helper.ts | 84 ++++++++++--------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/packages/ui-core/core/src/lib/auth/cookie-helper.ts b/packages/ui-core/core/src/lib/auth/cookie-helper.ts index 70df39e7dfa..722cd5dad46 100644 --- a/packages/ui-core/core/src/lib/auth/cookie-helper.ts +++ b/packages/ui-core/core/src/lib/auth/cookie-helper.ts @@ -86,6 +86,42 @@ function isCookieForValidDomain(cookie: string | null): boolean { }); } +/** + * Prepares cookie options with default values and overrides. + * + * @param {Object} [options={}] - Additional options to customize the cookie settings. + * @param {string} [options.path='/'] - The path where the cookie is accessible. + * @param {string} [options.SameSite='Lax'] - SameSite attribute to control cookie sharing across sites. + * @param {boolean} [options.Secure] - If true, the cookie will only be sent over HTTPS. + * @param {string} [options.domain] - The domain for which the cookie is valid. + * @returns {Object} The final cookie options object with defaults applied. + */ +function prepareCookieOptions(options: { [key: string]: any } = {}): { [key: string]: any } { + // Prepare cookie options with defaults + const cookieOptions = { + path: '/', // Default path for all cookies + SameSite: 'Lax', // Prevent CSRF attacks + Secure: window.location.protocol === 'https:', // Send only over HTTPS + ...options // Spread existing options + }; + + // Cache hostname lookup to avoid repeated access to window.location.hostname + const getCurrentHostname = (() => { + let hostname: string; + return () => (hostname ??= window.location.hostname); + })(); + + // Get current host name + const hostname = getCurrentHostname(); + if (hostname === 'localhost' || hostname === '127.0.0.1') { + cookieOptions['domain'] = undefined; // Don't set the domain for localhost + } else { + cookieOptions['domain'] = cookieOptions['domain'] || '.gauzy.co'; // Default domain for production + } + + return cookieOptions; // Return the final cookie options +} + /** * Sets a cookie with the specified name, value, and options. * @@ -99,27 +135,13 @@ export function setCookie(name: string, value: string, options: { [key: string]: } // Prepare cookie options with defaults - const cookieOptions = { - path: '/', - SameSite: 'Lax', - Secure: window.location.protocol === 'https:', - ...options - }; - - // Remove domain option for localhost or 127.0.0.1 - const hostname = window.location.hostname; - if (hostname === 'localhost' || hostname === '127.0.0.1') { - delete cookieOptions['domain']; - } else { - cookieOptions['domain'] = cookieOptions['domain'] || '.gauzy.co'; - } + const cookieOptions = prepareCookieOptions(options); // Build the cookie string - const cookieString = - `${encodeURIComponent(name)}=${encodeURIComponent(value)}; ` + - Object.entries(cookieOptions) - .map(([key, val]) => `${key}=${val}`) - .join('; '); + const cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; `; + Object.entries(cookieOptions) + .map(([key, val]) => `${key}=${val}`) + .join('; '); // Set the cookie document.cookie = cookieString; @@ -137,27 +159,13 @@ export function deleteCookie(name: string, options: { [key: string]: any } = {}) } // Prepare cookie options with defaults - const cookieOptions = { - path: '/', - SameSite: 'Lax', - Secure: window.location.protocol === 'https:', - ...options - }; - - // Remove domain option for localhost or 127.0.0.1 - const hostname = window.location.hostname; - if (hostname === 'localhost' || hostname === '127.0.0.1') { - delete cookieOptions['domain']; - } else { - cookieOptions['domain'] = cookieOptions['domain'] || '.gauzy.co'; - } + const cookieOptions = prepareCookieOptions(options); // Build the cookie string for deletion - const cookieString = - `${encodeURIComponent(name)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; ` + - Object.entries(cookieOptions) - .map(([key, val]) => `${key}=${val}`) - .join('; '); + const cookieString = `${encodeURIComponent(name)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; `; + Object.entries(cookieOptions) + .map(([key, val]) => `${key}=${val}`) + .join('; '); // Set the cookie to delete it document.cookie = cookieString; From 23eb9277e94df0e11236343c8a794a17cdc8b3cf Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 16:57:59 +0530 Subject: [PATCH 10/13] fix: use environment configuration for default domain. --- .scripts/configure.ts | 2 ++ .scripts/env.ts | 4 ++++ .../ui-config/src/lib/environments/model.ts | 1 + .../ui-core/core/src/lib/auth/cookie-helper.ts | 18 ++++++++++-------- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/.scripts/configure.ts b/.scripts/configure.ts index ddc676d7deb..3604eb520a6 100644 --- a/.scripts/configure.ts +++ b/.scripts/configure.ts @@ -78,6 +78,7 @@ if (!isDocker) { API_BASE_URL: API_BASE_URL, CLIENT_BASE_URL: CLIENT_BASE_URL, + COOKIE_DOMAIN: '${env.COOKIE_DOMAIN}', PLATFORM_WEBSITE_URL: '${env.PLATFORM_WEBSITE_URL}', PLATFORM_WEBSITE_DOWNLOAD_URL: '${env.PLATFORM_WEBSITE_DOWNLOAD_URL}', @@ -221,6 +222,7 @@ if (!isDocker) { API_BASE_URL: API_BASE_URL, CLIENT_BASE_URL: CLIENT_BASE_URL, + COOKIE_DOMAIN: '${env.COOKIE_DOMAIN}', PLATFORM_WEBSITE_URL: 'DOCKER_PLATFORM_WEBSITE_URL', PLATFORM_WEBSITE_DOWNLOAD_URL: 'DOCKER_PLATFORM_WEBSITE_DOWNLOAD_URL', diff --git a/.scripts/env.ts b/.scripts/env.ts index 4303ff0a472..d010c380ec7 100644 --- a/.scripts/env.ts +++ b/.scripts/env.ts @@ -10,6 +10,8 @@ export type Env = Readonly<{ // Set to true if build / runs in Docker IS_DOCKER: boolean; + COOKIE_DOMAIN: string; + // Base URL of Gauzy UI website CLIENT_BASE_URL: string; @@ -140,6 +142,8 @@ export const env: Env = cleanEnv( IS_DOCKER: bool({ default: false }), + COOKIE_DOMAIN: str({ default: '.gauzy.co' }), + CLIENT_BASE_URL: str({ default: 'http://localhost:4200' }), API_BASE_URL: str({ default: 'http://localhost:3000' }), diff --git a/packages/ui-config/src/lib/environments/model.ts b/packages/ui-config/src/lib/environments/model.ts index bece11decb0..3387202aefc 100644 --- a/packages/ui-config/src/lib/environments/model.ts +++ b/packages/ui-config/src/lib/environments/model.ts @@ -3,6 +3,7 @@ export interface Environment { API_BASE_URL: string; CLIENT_BASE_URL: string; + COOKIE_DOMAIN: string; PLATFORM_WEBSITE_URL?: string; PLATFORM_WEBSITE_DOWNLOAD_URL?: string; diff --git a/packages/ui-core/core/src/lib/auth/cookie-helper.ts b/packages/ui-core/core/src/lib/auth/cookie-helper.ts index 722cd5dad46..be0a3cc28d9 100644 --- a/packages/ui-core/core/src/lib/auth/cookie-helper.ts +++ b/packages/ui-core/core/src/lib/auth/cookie-helper.ts @@ -1,10 +1,4 @@ -// Define allowed domains for each environment -const DOMAIN_CONFIG = { - production: ['gauzy.co'], - demo: ['demo.gauzy.co'], - staging: ['staging.gauzy.co'], - development: ['localhost', '127.0.0.1'] -} as const; +import { environment } from '@gauzy/ui-config'; /** * Retrieves the value of a cookie by its name for the current domain and its subdomains. @@ -51,6 +45,14 @@ function isCookieForValidDomain(cookie: string | null): boolean { // Get the current hostname const hostname = window.location.hostname; // e.g., "demo.gauzy.co" or "app.gauzy.co" + // Define allowed domains for each environment + const DOMAIN_CONFIG = { + production: ['gauzy.co', 'app.gauzy.co'], + demo: ['demo.gauzy.co'], + staging: ['staging.gauzy.co'], + development: ['localhost', '127.0.0.1'] + } as const; + // Check for development environments if (DOMAIN_CONFIG.development.includes(hostname as 'localhost' | '127.0.0.1')) { return true; // Allow cookies for localhost and 127.0.0.1 @@ -116,7 +118,7 @@ function prepareCookieOptions(options: { [key: string]: any } = {}): { [key: str if (hostname === 'localhost' || hostname === '127.0.0.1') { cookieOptions['domain'] = undefined; // Don't set the domain for localhost } else { - cookieOptions['domain'] = cookieOptions['domain'] || '.gauzy.co'; // Default domain for production + cookieOptions['domain'] = cookieOptions['domain'] || environment.COOKIE_DOMAIN; // Default domain for production } return cookieOptions; // Return the final cookie options From f6bd376268e434f2624bed97ca1455a38fd7f9eb Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 17:04:04 +0530 Subject: [PATCH 11/13] Update packages/ui-config/src/lib/environments/model.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/ui-config/src/lib/environments/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-config/src/lib/environments/model.ts b/packages/ui-config/src/lib/environments/model.ts index 3387202aefc..41960b5ebd2 100644 --- a/packages/ui-config/src/lib/environments/model.ts +++ b/packages/ui-config/src/lib/environments/model.ts @@ -3,7 +3,7 @@ export interface Environment { API_BASE_URL: string; CLIENT_BASE_URL: string; - COOKIE_DOMAIN: string; + COOKIE_DOMAIN?: string; PLATFORM_WEBSITE_URL?: string; PLATFORM_WEBSITE_DOWNLOAD_URL?: string; From 17518f5c05e4d4defe5eceb6e33bb6beab23cd99 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 18:03:23 +0530 Subject: [PATCH 12/13] fix: reports menu should be show/hide after organization change --- .scripts/configure.ts | 2 +- apps/gauzy/src/app/pages/pages.component.ts | 136 +++++++++--------- packages/ui-core/core/src/index.ts | 1 + .../ui-core/core/src/lib/extension/index.ts | 1 + .../nav-builder/nav-menu-builder.service.ts | 26 +++- 5 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 packages/ui-core/core/src/lib/extension/index.ts diff --git a/.scripts/configure.ts b/.scripts/configure.ts index 3604eb520a6..d9df0da12b5 100644 --- a/.scripts/configure.ts +++ b/.scripts/configure.ts @@ -222,7 +222,7 @@ if (!isDocker) { API_BASE_URL: API_BASE_URL, CLIENT_BASE_URL: CLIENT_BASE_URL, - COOKIE_DOMAIN: '${env.COOKIE_DOMAIN}', + COOKIE_DOMAIN: 'DOCKER_COOKIE_DOMAIN', PLATFORM_WEBSITE_URL: 'DOCKER_PLATFORM_WEBSITE_URL', PLATFORM_WEBSITE_DOWNLOAD_URL: 'DOCKER_PLATFORM_WEBSITE_DOWNLOAD_URL', diff --git a/apps/gauzy/src/app/pages/pages.component.ts b/apps/gauzy/src/app/pages/pages.component.ts index cc12f8a93ca..b8c2facdba0 100644 --- a/apps/gauzy/src/app/pages/pages.component.ts +++ b/apps/gauzy/src/app/pages/pages.component.ts @@ -2,12 +2,10 @@ import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Data, Router } from '@angular/router'; import { NbMenuItem } from '@nebular/theme'; import { TranslateService } from '@ngx-translate/core'; -import { merge, pairwise } from 'rxjs'; -import { filter, map, take, tap } from 'rxjs/operators'; +import { filter, map, merge, pairwise, take, tap } from 'rxjs'; import { NgxPermissionsService } from 'ngx-permissions'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { chain } from 'underscore'; -import { TranslationBaseComponent } from '@gauzy/ui-core/i18n'; +import { FeatureEnum, IOrganization, IRolePermission, IUser, IntegrationEnum, PermissionsEnum } from '@gauzy/contracts'; import { AuthStrategy, IJobMatchingEntity, @@ -19,8 +17,8 @@ import { Store, UsersService } from '@gauzy/ui-core/core'; -import { FeatureEnum, IOrganization, IRolePermission, IUser, IntegrationEnum, PermissionsEnum } from '@gauzy/contracts'; import { distinctUntilChange, isNotEmpty } from '@gauzy/ui-core/common'; +import { TranslationBaseComponent } from '@gauzy/ui-core/i18n'; import { ReportService } from './reports/all-report/report.service'; @UntilDestroy({ checkProperties: true }) @@ -80,15 +78,9 @@ export class PagesComponent extends TranslationBaseComponent implements AfterVie filter((organization: IOrganization) => !!organization), distinctUntilChange(), pairwise(), // Pair each emitted value with the previous one - tap(([organization]: [IOrganization, IOrganization]) => { - const { id: organizationId, tenantId } = organization; - + tap(([previousOrganization]: [IOrganization, IOrganization]) => { // Remove the specified menu items for previous selected organization - this._navMenuBuilderService.removeNavMenuItems( - // Define the base item IDs - this.getReportMenuBaseItemIds().map((itemId) => `${itemId}-${organizationId}-${tenantId}`), - 'reports' - ); + this.removeOrganizationReportsMenuItems(previousOrganization); }), untilDestroyed(this) ) @@ -118,25 +110,21 @@ export class PagesComponent extends TranslationBaseComponent implements AfterVie .subscribe(); this.reportService.menuItems$.pipe(distinctUntilChange(), untilDestroyed(this)).subscribe((menuItems) => { - if (menuItems) { - this.reportMenuItems = chain(menuItems) - .values() - .map((item) => { - return { - id: item.slug + `-${this.organization?.id}`, - title: item.name, - link: `/pages/reports/${item.slug}`, - icon: item.iconClass, - data: { - translationKey: `${item.name}` - } - }; - }) - .value(); - } else { - this.reportMenuItems = []; - } - this.addOrganizationReportsMenuItems(); + // Convert the menuItems object to an array + const reportItems = menuItems ? Object.values(menuItems) : []; + + this.reportMenuItems = reportItems.map((item) => ({ + id: item.slug, + title: item.name, + link: `/pages/reports/${item.slug}`, + icon: item.iconClass, + data: { + translationKey: item.name + } + })); + + // Add the report menu items to the navigation menu + this.addOrRemoveOrganizationReportsMenuItems(); }); } @@ -176,49 +164,62 @@ export class PagesComponent extends TranslationBaseComponent implements AfterVie } /** - * Adds report menu items to the organization's navigation menu. + * Removes the report menu items associated with the current organization. + * + * This function checks if the organization is defined. If not, it logs a warning and exits early. + * If the organization is defined, it constructs item IDs based on the organization and tenant ID + * and removes these items from the navigation menu. + * + * @returns {void} This function does not return a value. */ - private addOrganizationReportsMenuItems() { - if (!this.organization) { - // Handle the case where this.organization is not defined - console.warn('Organization not defined. Unable to add/remove menu items.'); + private removeOrganizationReportsMenuItems(organization: IOrganization): void { + // Return early if the organization is not defined, logging a warning + if (!organization) { + console.warn(`Organization not defined. Unable to remove menu items.`); return; } - const { id: organizationId, tenantId } = this.organization; - // Remove the specified menu items for current selected organization - // Note: We need to remove old menus before constructing new menus for the organization. - this._navMenuBuilderService.removeNavMenuItems( - // Define the base item IDs - this.getReportMenuBaseItemIds().map((itemId) => `${itemId}-${organizationId}-${tenantId}`), - 'reports' + // Destructure organization properties + const { id: organizationId, tenantId } = organization; + + // Generate the item IDs to remove and call the service method + const itemIdsToRemove = this.getReportMenuBaseItemIds().map( + (itemId) => `${itemId}-${organizationId}-${tenantId}` ); - // Validate if reportMenuItems is an array and has elements - if (!Array.isArray(this.reportMenuItems) || this.reportMenuItems.length === 0) { + this._navMenuBuilderService.removeNavMenuItems(itemIdsToRemove, 'reports'); + } + + /** + * Adds report menu items to the organization's navigation menu. + */ + private addOrRemoveOrganizationReportsMenuItems() { + if (!this.organization) { + console.warn('Organization not defined. Unable to add/remove menu items.'); return; } + const { id: organizationId, tenantId } = this.organization; + + // Remove old menu items before constructing new ones for the organization + this.removeOrganizationReportsMenuItems(this.organization); + // Iterate over each report and add it to the navigation menu - try { - this.reportMenuItems.forEach((report: NavMenuSectionItem) => { - // Validate the structure of each report item - if (report && report.id && report.title) { - this._navMenuBuilderService.addNavMenuItem( - { - id: report.id, // Unique identifier for the menu item - title: report.title, // The title of the menu item - icon: report.icon, // The icon class for the menu item, using FontAwesome in this case - link: report.link, // The link where the menu item directs - data: report.data - }, - 'reports' - ); // The id of the section where this item should be added - } - }); - } catch (error) { - console.error('Error adding report menu items', error); - } + this.reportMenuItems.forEach((report: NavMenuSectionItem) => { + // Validate the structure of each report item + if (report?.id && report?.title) { + this._navMenuBuilderService.addNavMenuItem( + { + id: `${report.id}-${organizationId}-${tenantId}`, // Unique identifier for the menu item + title: report.title, // The title of the menu item + icon: report.icon, // The icon class for the menu item + link: report.link, // The link where the menu item directs + data: report.data // The data associated with the menu item + }, + 'reports' // The id of the section where this item should be added + ); + } + }); } /** @@ -402,5 +403,8 @@ export class PagesComponent extends TranslationBaseComponent implements AfterVie this.store.featureTenant = tenant.featureOrganizations.filter((item) => !item.organizationId); } - ngOnDestroy() {} + ngOnDestroy() { + // Remove the report menu items associated with the current organization before destroying the component + this.removeOrganizationReportsMenuItems(this.organization); + } } diff --git a/packages/ui-core/core/src/index.ts b/packages/ui-core/core/src/index.ts index b6be0396738..38d20e147f1 100644 --- a/packages/ui-core/core/src/index.ts +++ b/packages/ui-core/core/src/index.ts @@ -5,6 +5,7 @@ export * from './lib/auth'; export * from './lib/common/component-registry.types'; export * from './lib/components'; export * from './lib/core.module'; +export * from './lib/extension'; export * from './lib/guards'; export * from './lib/interceptors'; export * from './lib/module-import-guard'; diff --git a/packages/ui-core/core/src/lib/extension/index.ts b/packages/ui-core/core/src/lib/extension/index.ts new file mode 100644 index 00000000000..91ade2b9f0a --- /dev/null +++ b/packages/ui-core/core/src/lib/extension/index.ts @@ -0,0 +1 @@ +export * from './add-nav-menu-item'; diff --git a/packages/ui-core/core/src/lib/services/nav-builder/nav-menu-builder.service.ts b/packages/ui-core/core/src/lib/services/nav-builder/nav-menu-builder.service.ts index fb97a0df9f9..fdee0366576 100644 --- a/packages/ui-core/core/src/lib/services/nav-builder/nav-menu-builder.service.ts +++ b/packages/ui-core/core/src/lib/services/nav-builder/nav-menu-builder.service.ts @@ -229,9 +229,7 @@ export class NavMenuBuilderService { } } } else { - console.error( - `Could not add menu item "${item.config.id}", section "${item.sectionId}" does not exist` - ); + this.logMenuWarning(item.config.id, item.sectionId); } }); @@ -239,4 +237,26 @@ export class NavMenuBuilderService { }) ); } + + /** + * Logs a warning message about an inability to add a menu item. + * + * @param itemId - The ID of the menu item that could not be added. + * @param sectionId - The ID of the section where the item was to be added. + * @param level - Optional logging level; defaults to 'warn'. + */ + logMenuWarning(itemId: string, sectionId: string, level: 'warn' | 'info' | 'error' = 'warn') { + const message = `Warning: Unable to add menu item "${itemId}" because the specified section "${sectionId}" does not exist.`; + + switch (level) { + case 'info': + console.info(message); + break; + case 'error': + console.error(message); + break; + default: + console.warn(message); + } + } } From b9f6ec1a068cf53d04abc3b2880cce3fc25922f2 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 24 Oct 2024 18:18:12 +0530 Subject: [PATCH 13/13] fix: #8482 apply code rabbit suggestions --- .../nav-builder/nav-menu-builder.service.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/ui-core/core/src/lib/services/nav-builder/nav-menu-builder.service.ts b/packages/ui-core/core/src/lib/services/nav-builder/nav-menu-builder.service.ts index fdee0366576..d1b2bd3ceb1 100644 --- a/packages/ui-core/core/src/lib/services/nav-builder/nav-menu-builder.service.ts +++ b/packages/ui-core/core/src/lib/services/nav-builder/nav-menu-builder.service.ts @@ -245,18 +245,9 @@ export class NavMenuBuilderService { * @param sectionId - The ID of the section where the item was to be added. * @param level - Optional logging level; defaults to 'warn'. */ - logMenuWarning(itemId: string, sectionId: string, level: 'warn' | 'info' | 'error' = 'warn') { - const message = `Warning: Unable to add menu item "${itemId}" because the specified section "${sectionId}" does not exist.`; - - switch (level) { - case 'info': - console.info(message); - break; - case 'error': - console.error(message); - break; - default: - console.warn(message); - } + private logMenuWarning(itemId: string, sectionId: string, level: 'warn' | 'info' | 'error' = 'warn') { + const message = `Unable to add menu item "${itemId}". Section "${sectionId}" does not exist. Please ensure the section is defined before adding items.`; + const logFn = console[level]; + logFn(message); } }