diff --git a/.github/workflows/demo.yml b/.github/workflows/test.yml similarity index 93% rename from .github/workflows/demo.yml rename to .github/workflows/test.yml index d97cb754..6791a39e 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/test.yml @@ -1,13 +1,13 @@ -name: Demo - Build & Deploy to Scaleway +name: Test - Build & Deploy to Scaleway on: - push: - - [legacy_dev] + push: + branches: [legacy_dev] env: PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a - NAMESPACE_ID_LECOFFRE: c992c042-bdb6-4974-becf-aa5039b9ec58 - CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffredemovts5gdxg + NAMESPACE_ID_LECOFFRE: 3829c5cd-9fb0-4871-97a1-eb33e4bc1114 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffretestouylprmj IMAGE_NAME: front CONTAINER_NAME: front @@ -17,8 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - ref: cicd - name: Setup SSH run: | mkdir -p ~/.ssh @@ -42,7 +40,7 @@ jobs: deploy-to-scaleway-lecoffre: needs: build-and-push-image-lecoffre runs-on: ubuntu-latest - environment: demo + environment: test steps: - name: Install CLI uses: scaleway/action-scw@v0 diff --git a/src/front/Api/BaseApiService.ts b/src/front/Api/BaseApiService.ts index 01ad7443..6065fe35 100644 --- a/src/front/Api/BaseApiService.ts +++ b/src/front/Api/BaseApiService.ts @@ -30,6 +30,15 @@ export default abstract class BaseApiService { } protected buildHeaders(contentType: ContentType) { + // Don't try to access cookies during server-side rendering + if (typeof window === 'undefined') { + const headers = new Headers(); + if (contentType === ContentType.JSON || contentType === ContentType.PDF) { + headers.set("Content-Type", contentType); + } + return headers; + } + const token = CookieService.getInstance().getCookie("leCoffreAccessToken"); const headers = new Headers(); @@ -37,7 +46,14 @@ export default abstract class BaseApiService { if (contentType === ContentType.JSON || contentType === ContentType.PDF) { headers.set("Content-Type", contentType); } - headers.set("Authorization", `Bearer ${token}`); + + // Only set Authorization header if token exists + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } else { + console.warn("No access token found in cookies when building headers"); + } + return headers; } @@ -134,38 +150,66 @@ export default abstract class BaseApiService { } private async checkJwtToken() { + // Don't check tokens during server-side rendering + if (typeof window === 'undefined') { + return; + } + const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); - if (!accessToken) return; - - const userDecodedToken = jwt_decode(accessToken) as IUserJwtPayload; - const customerDecodedToken = jwt_decode(accessToken) as ICustomerJwtPayload; - - if (!userDecodedToken && !customerDecodedToken) return; - - const now = Math.floor(Date.now() / 1000); - if (userDecodedToken.userId && userDecodedToken.exp < now) { - const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); - if (!refreshToken) { - return; - } - const decodedRefreshToken = jwt_decode(refreshToken) as IUserJwtPayload | ICustomerJwtPayload; - if (decodedRefreshToken.exp < now) { - return; - } - await JwtService.getInstance().refreshToken(refreshToken); + if (!accessToken) { + console.warn("No access token found during JWT check"); + return; } - if (customerDecodedToken.customerId && customerDecodedToken.exp < now) { - const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); - if (!refreshToken) { + + try { + const userDecodedToken = jwt_decode(accessToken) as IUserJwtPayload; + const customerDecodedToken = jwt_decode(accessToken) as ICustomerJwtPayload; + + if (!userDecodedToken && !customerDecodedToken) { + console.warn("Invalid token format during JWT check"); return; } - const decodedRefreshToken = jwt_decode(refreshToken) as IUserJwtPayload | ICustomerJwtPayload; - if (decodedRefreshToken.exp < now) { - return; + + const now = Math.floor(Date.now() / 1000); + + if (userDecodedToken.userId && userDecodedToken.exp < now) { + const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); + if (!refreshToken) { + console.warn("Access token expired but no refresh token found"); + return; + } + const decodedRefreshToken = jwt_decode(refreshToken) as IUserJwtPayload | ICustomerJwtPayload; + if (decodedRefreshToken.exp < now) { + console.warn("Both access and refresh tokens are expired"); + return; + } + const refreshSuccess = await JwtService.getInstance().refreshToken(refreshToken); + if (!refreshSuccess) { + console.error("Failed to refresh token"); + return; + } } - await JwtService.getInstance().refreshToken(refreshToken); + + if (customerDecodedToken.customerId && customerDecodedToken.exp < now) { + const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); + if (!refreshToken) { + console.warn("Access token expired but no refresh token found"); + return; + } + const decodedRefreshToken = jwt_decode(refreshToken) as IUserJwtPayload | ICustomerJwtPayload; + if (decodedRefreshToken.exp < now) { + console.warn("Both access and refresh tokens are expired"); + return; + } + const refreshSuccess = await JwtService.getInstance().refreshToken(refreshToken); + if (!refreshSuccess) { + console.error("Failed to refresh token"); + return; + } + } + } catch (error) { + console.error("Error during JWT token check:", error); } - return; } protected async processResponse(response: Response, request: () => Promise, ref?: IRef, fileName?: string): Promise { diff --git a/src/front/Hooks/useUser.ts b/src/front/Hooks/useUser.ts index 86a583a1..296e7c11 100644 --- a/src/front/Hooks/useUser.ts +++ b/src/front/Hooks/useUser.ts @@ -1,5 +1,6 @@ import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users"; import JwtService from "@Front/Services/JwtService/JwtService"; +import UserStore from "@Front/Stores/UserStore"; import User from "le-coffre-resources/dist/Notary"; import { useEffect, useState } from "react"; @@ -7,8 +8,23 @@ export default function useUser() { const [user, setUser] = useState(); useEffect(() => { + // Don't run on server-side + if (typeof window === 'undefined') { + return; + } + + // Check if user is connected before making API calls + if (!UserStore.instance.isConnected()) { + console.warn("User not connected, skipping API call"); + return; + } + const decodedJwt = JwtService.getInstance().decodeJwt(); - if (!decodedJwt) return; + if (!decodedJwt) { + console.warn("No valid JWT found, skipping API call"); + return; + } + Users.getInstance() .getByUid(decodedJwt.userId, { q: { @@ -17,6 +33,9 @@ export default function useUser() { }) .then((user) => { setUser(user); + }) + .catch((error) => { + console.error("Failed to fetch user:", error); }); }, []); diff --git a/src/front/Stores/CustomerStore.ts b/src/front/Stores/CustomerStore.ts index 2a3ff88e..bfe1b52b 100644 --- a/src/front/Stores/CustomerStore.ts +++ b/src/front/Stores/CustomerStore.ts @@ -9,10 +9,46 @@ export default class UserStore { protected readonly event = new EventEmitter(); public accessToken: string | null = null; public refreshToken: string | null = null; + private initialized = false; - private constructor() {} + private constructor() { + // Don't initialize tokens during server-side rendering + if (typeof window !== 'undefined') { + this.initializeFromCookies(); + } + } + + private initializeFromCookies() { + if (this.initialized) return; + + try { + const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); + const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); + + if (accessToken) { + this.accessToken = accessToken; + } + if (refreshToken) { + this.refreshToken = refreshToken; + } + this.initialized = true; + } catch (error) { + console.warn("Failed to initialize tokens from cookies:", error); + } + } public isConnected(): boolean { + // Ensure initialization on client side + if (typeof window !== 'undefined' && !this.initialized) { + this.initializeFromCookies(); + } + + // Check both instance variable and cookie to ensure consistency + if (typeof window !== 'undefined') { + const cookieToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); + return !!(this.accessToken || cookieToken); + } + return !!this.accessToken; } @@ -27,6 +63,10 @@ export default class UserStore { CookieService.getInstance().setCookie("leCoffreAccessToken", accessToken); CookieService.getInstance().setCookie("leCoffreRefreshToken", refreshToken); + // Update instance variables + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.event.emit("connection", this.accessToken); } catch (error) { console.error(error); @@ -41,6 +81,10 @@ export default class UserStore { CookieService.getInstance().deleteCookie("leCoffreAccessToken"); CookieService.getInstance().deleteCookie("leCoffreRefreshToken"); + // Clear instance variables + this.accessToken = null; + this.refreshToken = null; + this.event.emit("disconnection", this.accessToken); } catch (error) { console.error(error); diff --git a/src/front/Stores/UserStore.ts b/src/front/Stores/UserStore.ts index 2a3ff88e..bfe1b52b 100644 --- a/src/front/Stores/UserStore.ts +++ b/src/front/Stores/UserStore.ts @@ -9,10 +9,46 @@ export default class UserStore { protected readonly event = new EventEmitter(); public accessToken: string | null = null; public refreshToken: string | null = null; + private initialized = false; - private constructor() {} + private constructor() { + // Don't initialize tokens during server-side rendering + if (typeof window !== 'undefined') { + this.initializeFromCookies(); + } + } + + private initializeFromCookies() { + if (this.initialized) return; + + try { + const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); + const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); + + if (accessToken) { + this.accessToken = accessToken; + } + if (refreshToken) { + this.refreshToken = refreshToken; + } + this.initialized = true; + } catch (error) { + console.warn("Failed to initialize tokens from cookies:", error); + } + } public isConnected(): boolean { + // Ensure initialization on client side + if (typeof window !== 'undefined' && !this.initialized) { + this.initializeFromCookies(); + } + + // Check both instance variable and cookie to ensure consistency + if (typeof window !== 'undefined') { + const cookieToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); + return !!(this.accessToken || cookieToken); + } + return !!this.accessToken; } @@ -27,6 +63,10 @@ export default class UserStore { CookieService.getInstance().setCookie("leCoffreAccessToken", accessToken); CookieService.getInstance().setCookie("leCoffreRefreshToken", refreshToken); + // Update instance variables + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.event.emit("connection", this.accessToken); } catch (error) { console.error(error); @@ -41,6 +81,10 @@ export default class UserStore { CookieService.getInstance().deleteCookie("leCoffreAccessToken"); CookieService.getInstance().deleteCookie("leCoffreRefreshToken"); + // Clear instance variables + this.accessToken = null; + this.refreshToken = null; + this.event.emit("disconnection", this.accessToken); } catch (error) { console.error(error);