diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml deleted file mode 100644 index d97cb754..00000000 --- a/.github/workflows/demo.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Demo - Build & Deploy to Scaleway - -on: - push: - - [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 - - IMAGE_NAME: front - CONTAINER_NAME: front - -jobs: - build-and-push-image-lecoffre: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: cicd - - name: Setup SSH - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts - env: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - - name: Copy SSH - run: cp ~/.ssh/id_rsa id_rsa - - name: Login to Scaleway Container Registry - uses: docker/login-action@v3 - with: - username: nologin - password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} - registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} - - name: Build the Docker Image - run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - - name: Push the Docker Image to Scaleway Container Registry - run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - deploy-to-scaleway-lecoffre: - needs: build-and-push-image-lecoffre - runs-on: ubuntu-latest - environment: demo - steps: - - name: Install CLI - uses: scaleway/action-scw@v0 - - name: Get container ID - run: | - echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV - env: - SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} - SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} - SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} - SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} - - name: Deploy the container based on the new image - run: | - env_string="" - while IFS= read -r line; do - if [[ "$line" == *"="* ]]; then - key=$(echo "$line" | cut -d '=' -f 1) - value=$(echo "$line" | cut -d '=' -f 2-) - if [[ -n "$key" ]]; then - env_string+="environment-variables.$key=$value " - fi - fi - done <<< "$ENV_VARS" - env_string=$(echo $env_string | sed 's/ $//') - scw container container update ${{ env.CONTAINER_ID }} $env_string - env: - ENV_VARS: ${{ secrets.ENV }} - SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} - SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} - SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} - SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 69bbed27..6791a39e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,77 +2,73 @@ name: Test - Build & Deploy to Scaleway on: push: - branches: [test] + branches: [legacy_dev] -env: - PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a - NAMESPACE_ID_LECOFFRE: e2036b1d-b5c1-4cdd-b168-d895c498f679 - CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffretestfhhn6rcl - - IMAGE_NAME: front - CONTAINER_NAME: front +env: + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: 3829c5cd-9fb0-4871-97a1-eb33e4bc1114 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffretestouylprmj + + IMAGE_NAME: front + CONTAINER_NAME: front jobs: - build-and-push-image-lecoffre: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup SSH - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts - env: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - - name: Copy SSH - run: cp ~/.ssh/id_rsa id_rsa - - name: Login to Scaleway Container Registry - uses: docker/login-action@v3 - with: - username: nologin - password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} - registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} - - name: Get Git Commit SHA - id: vars - run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - name: Build the Docker Image - run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - - name: Push the Docker Image to Scaleway Container Registry - run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - deploy-to-scaleway-lecoffre: - needs: build-and-push-image-lecoffre - runs-on: ubuntu-latest - environment: preprod - steps: - - name: Install CLI - uses: scaleway/action-scw@v0 - - name: Get container ID - run: | - echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV - env: - SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} - SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} - SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} - SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} - - name: Deploy the container based on the new image - run: | - env_string="" - while IFS= read -r line; do - if [[ "$line" == *"="* ]]; then - key=$(echo "$line" | cut -d '=' -f 1) - value=$(echo "$line" | cut -d '=' -f 2-) - if [[ -n "$key" ]]; then - env_string+="environment-variables.$key=$value " - fi - fi - done <<< "$ENV_VARS" - env_string=$(echo $env_string | sed 's/ $//') - scw container container update ${{ env.CONTAINER_ID }} $env_string - env: - ENV_VARS: ${{ secrets.ENV }} - SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} - SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} - SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} - SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} - \ No newline at end of file + build-and-push-image-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Docker Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Docker Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + deploy-to-scaleway-lecoffre: + needs: build-and-push-image-lecoffre + runs-on: ubuntu-latest + environment: test + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} 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);