diff --git a/src/front/Components/Layouts/LoginCallback/index.tsx b/src/front/Components/Layouts/LoginCallback/index.tsx index dcd31de4..fff5c755 100644 --- a/src/front/Components/Layouts/LoginCallback/index.tsx +++ b/src/front/Components/Layouts/LoginCallback/index.tsx @@ -7,6 +7,7 @@ import HelpBox from "@Front/Components/Elements/HelpBox"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import Module from "@Front/Config/Module"; import UserStore from "@Front/Stores/UserStore"; +import CookieService from "@Front/Services/CookieService/CookieService"; import Image from "next/image"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; @@ -21,16 +22,10 @@ import MessageBus from "src/sdk/MessageBus"; import Iframe from "src/sdk/Iframe"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; -import ImportData, { ProgressInfo } from "src/common/Api/LeCoffreApi/sdk/ImportData"; -import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService"; -import OfficeService from "src/common/Api/LeCoffreApi/sdk/OfficeService"; -import OfficeRoleService from "src/common/Api/LeCoffreApi/sdk/OfficeRoleService"; -import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService"; -import { DEFAULT_STORAGE_URLS, DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants"; +import { ProgressInfo } from "src/common/Api/LeCoffreApi/sdk/ImportData"; export default function LoginCallBack() { const router = useRouter(); - const [idNotUser, setIdNotUser] = useState(null); const [isAuthModalOpen, setIsAuthModalOpen] = useState(false); const [isConnected, setIsConnected] = useState(false); @@ -43,169 +38,26 @@ export default function LoginCallBack() { description: '' }); - const getOffice = async (idNotUser: any) => { - return await new Promise((resolve: (office: any) => void) => { - OfficeService.getOffices().then((processes: any[]) => { - const officeFound: any = processes.length > 0 ? processes.find((office: any) => office.processData.idNot === idNotUser.office.idNot) : null; - if (officeFound) { - resolve(officeFound); - } else { - // Some info must be here or have some value, just to be sure - if (!idNotUser.office.office_status || idNotUser.office.office_status !== 'ACTIVATED') { - console.error(`[LoginCallback] office_status is not ACTIVATED for idNot ${idNotUser.office.idNot}`); - return; - } - - // I guess if we don't have crpcen that's also a big problem - if (!idNotUser.office.crpcen) { - console.error(`[LoginCallback] crpcen is not set for idNot ${idNotUser.office.idNot}`); - return; - } - - // We create office - const officeData: any = { - idNot: idNotUser.office.idNot, - name: idNotUser.office.name, - crpcen: idNotUser.office.crpcen, - address: { - create: { - address: idNotUser.office.address.address, - zip_code: idNotUser.office.address.zip_code, - city: idNotUser.office.address.city - } - }, - office_status: idNotUser.office.office_status // must be ACTIVATED though - }; - - Auth.getInstance().getIdNotUserForOffice(idNotUser.office.idNot).then((users: any) => { - console.log('users : ', users); - const activeUsers = users.result.filter((user: any) => user.activite === 'En exercice'); - let officeCollaborators: any[] = []; - for (const user of activeUsers) { - CollaboratorService.getCollaboratorByUid(user.uid).then((collaborator: any) => { - console.log('collaborator : ', collaborator); - officeCollaborators.push(collaborator); - }); - } - - OfficeService.createOffice(officeData, officeCollaborators, DEFAULT_VALIDATOR_ID, [...DEFAULT_STORAGE_URLS]).then((process: any) => { - if (process) { - const office: any = process.processData; - resolve(office); - } - }); - }); - } + const waitForUserInfo = (): Promise => { + return new Promise((resolve, reject) => { + if (UserStore.instance.getUser()) { + resolve(UserStore.instance.getUser()); return; - }); - }); - }; - - const getCollaborator = async (idNotUser: any) => { - return await new Promise(async (resolve: (role: any) => void) => { - const processFound: any | null = await CollaboratorService.getCollaboratorBy({ idNot: idNotUser.idNot }); - if (processFound) { - console.log('Found a collaborator for idNot', idNotUser.idNot); - // TODO: check if the collaborator is in the office process - const office: any = await getOffice(idNotUser); - // Take the role of the collaborator - MessageBus.getInstance().getRolesForProcess(processFound.processId).then((roles: any) => { - console.log('roles : ', roles); - // We should find one pairing id in the role 'owner' - const owners = roles['owner'].members; - if (owners.length !== 1) { - console.error('[LoginCallback] owner should have 1 member'); - return; - } - const ownerPairingId = owners[0]; - // Now we can check if the owner pairing id is in the office roles - MessageBus.getInstance().getRolesForProcess(office.processId).then((officeRoles: any) => { - const officeOwners = officeRoles['owner'].members; - if (!officeOwners.includes(ownerPairingId)) { - // We add the newly created collaborator to the office roles - OfficeService.addCollaborators(office, officeRoles, [ownerPairingId]).then((process: any) => { - resolve(processFound); - }); - } else { - // Nothing to do - resolve(processFound); - } - }); - }); - } else { - console.log('No collaborator found for idNot', idNotUser.idNot); - const office: any = await getOffice(idNotUser); - - const role: any = (await RoleService.getRoles()) - .map((process: any) => process.processData) - .find((role: any) => role.name === idNotUser.role.name); - - const officeRole: any = (await OfficeRoleService.getOfficeRoles()) - .map((process: any) => process.processData) - .filter((officeRole: any) => officeRole.office.uid === office.processData.uid) - .find((officeRole: any) => officeRole.name === idNotUser.office_role.name); - - if (!office || !role || !officeRole) { - LoaderService.getInstance().hide(); - setShowProgress(true); - - await ImportData.import(office, DEFAULT_VALIDATOR_ID, (info: ProgressInfo) => { - setProgressInfo(info); - }); - - setShowProgress(false); - LoaderService.getInstance().show(); - } - - const collaboratorData: any = { - idNot: idNotUser.idNot, - contact: idNotUser.contact, - office: { - uid: office.uid - }, - role: { - uid: role.uid - }, - office_role: { - uid: officeRole.uid - } - }; - - CollaboratorService.createCollaborator(collaboratorData, DEFAULT_VALIDATOR_ID).then((newCollaborator: any) => { - if (newCollaborator) { - // Now that we created the collaborator, we must check that it's in the office roles (probably not) - MessageBus.getInstance().getRolesForProcess(newCollaborator.processId).then((roles: any) => { - console.log('roles : ', roles); - // We should have our own pairing id in roles['owner'] - const owner = roles['owner'].members; - if (owner.length !== 1) { - console.error('[LoginCallback] owner should have 1 member'); - return; - } - const ownerPairingId = owner[0]; - if (ownerPairingId !== newCollaborator.processData.uid) { - console.error('[LoginCallback] owner pairing id is not the same as the collaborator uid'); - return; - } - - // is ownerPairingId in roles for the office process? - MessageBus.getInstance().getRolesForProcess(office.processId).then((officeRoles: any) => { - const officeOwners = officeRoles['owner'].members; - if (!officeOwners.includes(ownerPairingId)) { - // We add the newly created collaborator to the office roles - OfficeService.addCollaborators(office, officeRoles, [ownerPairingId]).then((process: any) => { - resolve(newCollaborator); - }); - } else { - // Nothing to do - resolve(newCollaborator); - } - }); - - }); - } - }); } + + // Poll for userInfo every 100ms + const checkInterval = setInterval(() => { + if (UserStore.instance.getUser()) { + clearInterval(checkInterval); + resolve(UserStore.instance.getUser()); + } + }, 100); + + // Timeout after 60 seconds + setTimeout(() => { + clearInterval(checkInterval); + reject(new Error('Timeout waiting for user info')); + }, 60000); }); }; @@ -216,8 +68,8 @@ export default function LoginCallBack() { // TODO: review // HACK: If start with http://local.lecoffreio.4nkweb:3000/authorized-client // Replace with http://localhost:3000/authorized-client - if (window.location.href.startsWith('http://local.4nkweb.com')) { - window.location.href = window.location.href.replace('http://local.4nkweb.com:3000/authorized-client', 'http://localhost:3000/authorized-client'); + if (window.location.href.startsWith('http://local.lecoffreio.4nkweb')) { + window.location.href = window.location.href.replace('http://local.lecoffreio.4nkweb:3000/authorized-client', 'http://localhost:3000/authorized-client'); return; } @@ -230,22 +82,34 @@ export default function LoginCallBack() { window.history.replaceState({}, document.title, rootUrl); } - const user: any = await Auth.getInstance().getIdNotUser(code as string); - setIdNotUser(user.idNotUser); - setIsAuthModalOpen(true); - console.log('[LoginCallback] idNotUser', idNotUser); - /* - const token: any = null; - if (!token) return router.push(Module.getInstance().get().modules.pages.Login.props.path); - await UserStore.instance.connect(token.accessToken, token.refreshToken); - const jwt = JwtService.getInstance().decodeJwt(); - if (!jwt) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); - if (jwt.rules && !jwt.rules.includes("GET folders")) { - return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path); + const user: any = await Auth.getInstance().idNotAuth(code as string); + + // Extract both user data and auth token from the response + const { idNotUser, authToken } = user; + + if (!authToken) { + console.error('[LoginCallback] No authToken received from backend'); + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); } + + // Store the auth token for API authentication + // TODO The authToken is just a uuid for now, it's very broken + CookieService.getInstance().setCookie("leCoffreAccessToken", authToken); + + // Test that we can get user info and the authToken works + // TODO test that what's returned is identical to what we got before + const userInfoResponse = await Auth.getInstance().getIdNotUser(); + console.log('[LoginCallback] userInfoResponse:', userInfoResponse); + if (!userInfoResponse || !userInfoResponse.success || !userInfoResponse.data) { + console.error('[LoginCallback] No userInfo received from backend'); + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + } + + // Store user info as a cookie + CookieService.getInstance().setCookie("leCoffreUserInfo", JSON.stringify(userInfoResponse.data)); setIsAuthModalOpen(true); - //return router.push(Module.getInstance().get().modules.pages.Folder.props.path); - */ + console.log('[LoginCallback] authToken stored successfully'); + return; } catch (e: any) { if (e.http_status === 401 && e.message === "Email not found") { @@ -257,21 +121,6 @@ export default function LoginCallBack() { return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); } } - /* - const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); - if (!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); - const isTokenRefreshed = await JwtService.getInstance().refreshToken(refreshToken); - const jwt = JwtService.getInstance().decodeJwt(); - if (!jwt) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); - if (!jwt.rules.includes("GET folders")) { - return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path); - } - if (isTokenRefreshed) { - //setIsAuthModalOpen(true); - //return router.push(Module.getInstance().get().modules.pages.Folder.props.path); - return; - } - */ return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=2"); } getUser(); @@ -354,23 +203,110 @@ export default function LoginCallBack() { LoaderService.getInstance().show(); MessageBus.getInstance().initMessageListener(); MessageBus.getInstance().isReady().then(async () => { - const collaborator: any = await getCollaborator(idNotUser); - if (!UserStore.instance.connect(collaborator)) { - console.error('[LoginCallback] collaborator not connected'); + try { + const userInfo = await waitForUserInfo(); + if (!userInfo) { + console.error('[LoginCallback] No userInfo received from backend'); + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + } + // console.log('userInfo : ', userInfo); + + // Here we are now authenticated with idNot, we have our idnot user info + // We also have a device and it should be paired + // What we may not have yet is a collaborator process + // Office may not have a process too + let collaboratorProcess: { processId: string, processData: { [key: string]: any } } | null = null; + let officeProcess: { processId: string, processData: { [key: string]: any } } | null = null; + + // Initialize collaborator process + try { + // Wait for pairing ID to be available before proceeding + const pairingId = await MessageBus.getInstance().getPairingId(); + console.log('[LoginCallback] Pairing ID obtained:', pairingId); + // Check if we are part of the right collaborator process + const myCollaboratorProcessesData = await MessageBus.getInstance().getProcessesDecoded((processId: string, values: { [key: string]: any }) => { + return values['utype'] === 'collaborator' + && values['idNot'] === userInfo.idNot + && values['isDeleted'] === 'false'; + }); + if (myCollaboratorProcessesData && Object.keys(myCollaboratorProcessesData).length !== 0) { + collaboratorProcess = { processId: Object.keys(myCollaboratorProcessesData)[0]!, processData: Object.values(myCollaboratorProcessesData)[0]! }; + } else { + const res = await Auth.getInstance().getUserProcessByIdNot(pairingId); + if (res.success) { + collaboratorProcess = res.data; + } else { + console.error('[LoginCallback] Error getting collaborator process'); + router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + return; + } + // await new Promise(resolve => setTimeout(resolve, 100)); + } + // If we're on a new device, signer should notice and add us to the process + // TODO check that we're part of the collaborator process + } catch (error: any) { + console.error('[LoginCallback] Error getting collaborator process:', error); + if (error.message === 'Timeout waiting for pairing ID') { + console.error('[LoginCallback] Pairing ID not available after timeout'); + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + } + // If we can't get collaborator process, we can't proceed + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + } + + // Initialize office process + try { + // Wait for pairing ID to be available before proceeding + const pairingId = await MessageBus.getInstance().getPairingId(); + console.log('[LoginCallback] Pairing ID obtained:', pairingId); + // Now we need to check for office process + const myOfficeProcessesData = await MessageBus.getInstance().getProcessesDecoded((processId: string, values: { [key: string]: any }) => { + return values['utype'] === 'office' + && values['idNot'] === userInfo.office.idNot + && values['isDeleted'] === 'false'; + }); + if (myOfficeProcessesData && Object.keys(myOfficeProcessesData).length !== 0) { + officeProcess = { processId: Object.keys(myOfficeProcessesData)[0]!, processData: Object.values(myOfficeProcessesData)[0]! }; + } else { + const res = await Auth.getInstance().getUserProcessByIdNot(pairingId); + if (res.success) { + officeProcess = res.data; + } else { + console.error('[LoginCallback] Error getting collaborator process'); + router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + return; + } + } + + // TODO Check that we're part of the office process + // For now we rely on the signer to get office data, for the sake of simplicity + } catch (error: any) { + console.error('[LoginCallback] Error getting office process:', error); + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + } + + // Verify both processes are initialized before proceeding + if (!collaboratorProcess || !officeProcess) { + console.error('[LoginCallback] Failed to initialize required processes'); + router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + return; + } + + if (!UserStore.instance.connect(collaboratorProcess.processData, officeProcess.processData)) { + console.error('[LoginCallback] collaborator not connected'); + router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + return; + } + + window.location.href = Module.getInstance().get().modules.pages.Folder.props.path; + } catch (error) { + console.error('[LoginCallback] Error waiting for user info or processing collaborator:', error); router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); - return; + } finally { + MessageBus.getInstance().destroyMessageListener(); + LoaderService.getInstance().hide(); } - - MessageBus.getInstance().destroyMessageListener(); - LoaderService.getInstance().hide(); - - /* - if (jwt.rules && !jwt.rules.includes("GET folders")) { - router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path); - } - */ - - window.location.href = Module.getInstance().get().modules.pages.Folder.props.path; + return; }); }, 100); }}