feat: agregar contexto de operación y mejorar la restauración de sesión en el proveedor de autenticación
This commit is contained in:
parent
1a8d63373d
commit
ce9f9029e3
@ -71,7 +71,7 @@ export type IdentityInternalDeps = {
|
||||
};
|
||||
|
||||
export const buildIdentityDependencies = (params: ModuleParams): IdentityInternalDeps => {
|
||||
const { database, config, transactionContext } = params;
|
||||
const { database, config, operationContext, transactionContext } = params;
|
||||
|
||||
const transactionManager = buildTransactionManager(database, transactionContext);
|
||||
|
||||
@ -120,6 +120,7 @@ export const buildIdentityDependencies = (params: ModuleParams): IdentityInterna
|
||||
authenticateRequest: identityAuthenticateRequest({
|
||||
accessTokenVerifier,
|
||||
accountRepository,
|
||||
operationContext,
|
||||
}),
|
||||
useCases: {
|
||||
loginWithPassword: () =>
|
||||
@ -152,6 +153,7 @@ export const buildIdentityDependencies = (params: ModuleParams): IdentityInterna
|
||||
authenticateRequest: identityAuthenticateRequest({
|
||||
accessTokenVerifier,
|
||||
accountRepository,
|
||||
operationContext,
|
||||
}),
|
||||
useCases: {
|
||||
getCurrentAccount: () =>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { isApiClientError } from "@erp/core/client";
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import type { CurrentSessionResponseDTO } from "@erp/identity/common";
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import type { LoginForm } from "../login/entities";
|
||||
import {
|
||||
@ -16,7 +18,7 @@ import {
|
||||
useRefreshSessionMutation,
|
||||
} from "../shared";
|
||||
import { getCurrentSession } from "../shared/api";
|
||||
import type { ActiveCompany, AuthSession, CompanySession } from "../shared/entities";
|
||||
import type { ActiveCompany, AuthSession, AuthTokens, CompanySession } from "../shared/entities";
|
||||
|
||||
import {
|
||||
resetIdentityAuthSessionRuntimeHandlers,
|
||||
@ -61,20 +63,25 @@ const IdentityAuthSessionContext = createContext<IdentityAuthSessionContextValue
|
||||
undefined
|
||||
);
|
||||
|
||||
const EMPTY_COMPANY_SESSION: CompanySession = {
|
||||
availableCompanies: [],
|
||||
activeCompany: null,
|
||||
};
|
||||
const EMPTY_COMPANY_SESSION: CompanySession = {
|
||||
availableCompanies: [],
|
||||
activeCompany: null,
|
||||
};
|
||||
|
||||
const isUnauthorizedError = (error: unknown) =>
|
||||
isApiClientError(error) && error.status === 401;
|
||||
|
||||
export function IdentityAuthSessionProvider({ children }: { children: React.ReactNode }) {
|
||||
const dataSource = useDataSource();
|
||||
const loginMutation = useLoginMutation();
|
||||
const logoutMutation = useLogoutMutation();
|
||||
const refreshSessionMutation = useRefreshSessionMutation();
|
||||
const loginMutation = useLoginMutation();
|
||||
const logoutMutation = useLogoutMutation();
|
||||
const refreshSessionMutation = useRefreshSessionMutation();
|
||||
const refreshSessionMutationAsync = refreshSessionMutation.mutateAsync;
|
||||
|
||||
const [session, setSession] = useState<AuthSession | null>(null);
|
||||
const [companySession, setCompanySession] = useState<CompanySession>(EMPTY_COMPANY_SESSION);
|
||||
const [isRestoringSession, setIsRestoringSession] = useState(authTokenStorage.hasAccessToken());
|
||||
const [session, setSession] = useState<AuthSession | null>(null);
|
||||
const [companySession, setCompanySession] = useState<CompanySession>(EMPTY_COMPANY_SESSION);
|
||||
const [isRestoringSession, setIsRestoringSession] = useState(authTokenStorage.hasAccessToken());
|
||||
const restoreInFlightRef = useRef<Promise<AuthSession | null> | null>(null);
|
||||
|
||||
const clearSession = useCallback(() => {
|
||||
authTokenStorage.clear();
|
||||
@ -111,35 +118,67 @@ export function IdentityAuthSessionProvider({ children }: { children: React.Reac
|
||||
[dataSource]
|
||||
);
|
||||
|
||||
const restoreSession = useCallback(async () => {
|
||||
const tokens = authTokenStorage.getTokens();
|
||||
|
||||
if (!tokens.accessToken) {
|
||||
clearSession();
|
||||
setIsRestoringSession(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
setIsRestoringSession(true);
|
||||
|
||||
try {
|
||||
const response = await getCurrentSession(dataSource, tokens.accessToken);
|
||||
const restoredSession = mapCurrentSessionToAuthSession(response, {
|
||||
accessToken: tokens.accessToken,
|
||||
refreshToken: tokens.refreshToken ?? "",
|
||||
});
|
||||
|
||||
await hydrateCompanySession(tokens.accessToken);
|
||||
setSession(restoredSession);
|
||||
|
||||
return restoredSession;
|
||||
} catch {
|
||||
clearSession();
|
||||
return null;
|
||||
} finally {
|
||||
setIsRestoringSession(false);
|
||||
}
|
||||
}, [clearSession, dataSource, hydrateCompanySession]);
|
||||
const restoreSession = useCallback(() => {
|
||||
if (restoreInFlightRef.current) {
|
||||
return restoreInFlightRef.current;
|
||||
}
|
||||
|
||||
const restorePromise = Promise.resolve().then(async () => {
|
||||
const tokens = authTokenStorage.getTokens();
|
||||
|
||||
if (!tokens.accessToken) {
|
||||
clearSession();
|
||||
setIsRestoringSession(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
setIsRestoringSession(true);
|
||||
|
||||
try {
|
||||
let activeTokens: AuthTokens = {
|
||||
accessToken: tokens.accessToken,
|
||||
refreshToken: tokens.refreshToken ?? "",
|
||||
};
|
||||
let response: CurrentSessionResponseDTO;
|
||||
|
||||
try {
|
||||
response = await getCurrentSession(dataSource, activeTokens.accessToken);
|
||||
} catch (error) {
|
||||
if (!isUnauthorizedError(error) || !activeTokens.refreshToken) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const refreshedTokens = await refreshSessionMutationAsync({
|
||||
refreshToken: activeTokens.refreshToken,
|
||||
});
|
||||
|
||||
authTokenStorage.setTokens(refreshedTokens);
|
||||
activeTokens = refreshedTokens;
|
||||
response = await getCurrentSession(dataSource, activeTokens.accessToken);
|
||||
}
|
||||
|
||||
const restoredSession = mapCurrentSessionToAuthSession(response, {
|
||||
accessToken: activeTokens.accessToken,
|
||||
refreshToken: activeTokens.refreshToken,
|
||||
});
|
||||
|
||||
await hydrateCompanySession(activeTokens.accessToken);
|
||||
setSession(restoredSession);
|
||||
|
||||
return restoredSession;
|
||||
} catch {
|
||||
clearSession();
|
||||
return null;
|
||||
} finally {
|
||||
restoreInFlightRef.current = null;
|
||||
setIsRestoringSession(false);
|
||||
}
|
||||
});
|
||||
|
||||
restoreInFlightRef.current = restorePromise;
|
||||
|
||||
return restorePromise;
|
||||
}, [clearSession, dataSource, hydrateCompanySession, refreshSessionMutationAsync]);
|
||||
|
||||
const login = useCallback(
|
||||
async (params: LoginForm) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user