Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revised OAuth2 for First Party Application implementation #487

Merged
merged 15 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package_lock.json
/build

.env
.env.prod

# Logs
logs
Expand Down
101 changes: 48 additions & 53 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { I18nextProvider } from 'react-i18next';

import i18n from './i18n';
import useCheckURL from './hooks/useCheckURL';
import { CredentialsProvider } from './context/CredentialsContext';
import { withSessionContext } from './context/SessionContext';
import { checkForUpdates } from './offlineRegistrationSW';

Expand All @@ -15,6 +14,7 @@ import Snowfalling from './components/ChristmasAnimation/Snowfalling';
import Spinner from './components/Shared/Spinner';

import { withContainerContext } from './context/ContainerContext';
import { withCredentialsContext } from './context/CredentialsContext';
import StatusContext from './context/StatusContext';

import UpdateNotification from './components/Notifications/UpdateNotification';
Expand Down Expand Up @@ -136,59 +136,54 @@ function App() {

return (
<I18nextProvider i18n={i18n}>
<CredentialsProvider>
<Snowfalling />
<Suspense fallback={<Spinner />}>
<HandlerNotification />
<UpdateNotification />
<Routes>
<Route element={
<PrivateRoute>
<Layout>
<Suspense fallback={<Spinner size='small' />}>
<PrivateRoute.NotificationPermissionWarning />
<FadeInContentTransition appear reanimateKey={location.pathname}>
<Outlet />
</FadeInContentTransition>
</Suspense>
</Layout>
</PrivateRoute>
}>
<Route path="/settings" element={<Settings />} />
<Route path="/" element={<Home />} />
<Route path="/credential/:credentialId" element={<Credential />} />
<Route path="/credential/:credentialId/history" element={<CredentialHistory />} />
<Route path="/credential/:credentialId/details" element={<CredentialDetails />} />
<Route path="/history" element={<History />} />
<Route path="/history/:historyId" element={<HistoryDetail />} />
<Route path="/add" element={<AddCredentials />} />
<Route path="/send" element={<SendCredentials />} />
<Route path="/verification/result" element={<VerificationResult />} />
<Route path="/cb/*" element={<Home />} />
</Route>
<Route element={
<FadeInContentTransition reanimateKey={location.pathname}>
<Outlet />
</FadeInContentTransition>
}>
<Route path="/login" element={<Login />} />
<Route path="/login-state" element={<LoginState />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
{showSelectCredentialsPopup &&
<SelectCredentialsPopup isOpen={showSelectCredentialsPopup} setIsOpen={setShowSelectCredentialsPopup} setSelectionMap={setSelectionMap} conformantCredentialsMap={conformantCredentialsMap} verifierDomainName={verifierDomainName} />
}
{showPinInputPopup &&
<PinInputPopup isOpen={showPinInputPopup} setIsOpen={setShowPinInputPopup} />
}
{showMessagePopup &&
<MessagePopup type={typeMessagePopup} message={textMessagePopup} onClose={() => setMessagePopup(false)} />
}
</Suspense>
</CredentialsProvider>
<Snowfalling />
<Suspense fallback={<Spinner />}>
<HandlerNotification />
<UpdateNotification />
<Routes>
<Route element={
<PrivateRoute>
<Layout>
<Suspense fallback={<Spinner size='small' />}>
<PrivateRoute.NotificationPermissionWarning />
<FadeInContentTransition appear reanimateKey={location.pathname}>
<Outlet />
</FadeInContentTransition>
</Suspense>
</Layout>
</PrivateRoute>
}>
<Route path="/settings" element={<Settings />} />
<Route path="/" element={<Home />} />
<Route path="/credential/:credentialId" element={<Credential />} />
<Route path="/credential/:credentialId/history" element={<CredentialHistory />} />
<Route path="/credential/:credentialId/details" element={<CredentialDetails />} />
<Route path="/history" element={<History />} />
<Route path="/history/:historyId" element={<HistoryDetail />} />
<Route path="/add" element={<AddCredentials />} />
<Route path="/send" element={<SendCredentials />} />
<Route path="/verification/result" element={<VerificationResult />} />
<Route path="/cb/*" element={<Home />} />
</Route>
<Route element={
<FadeInContentTransition reanimateKey={location.pathname}>
<Outlet />
</FadeInContentTransition>
}>
<Route path="/login" element={<Login />} />
<Route path="/login-state" element={<LoginState />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
{showPinInputPopup &&
<PinInputPopup isOpen={showPinInputPopup} setIsOpen={setShowPinInputPopup} />
}
{showMessagePopup &&
<MessagePopup type={typeMessagePopup} message={textMessagePopup} onClose={() => setMessagePopup(false)} />
}
</Suspense>
</I18nextProvider>
);
}

export default withSessionContext(withContainerContext(App));
export default withSessionContext(withCredentialsContext(withContainerContext(App)));
66 changes: 40 additions & 26 deletions src/components/Popups/SelectCredentialsPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,28 +58,27 @@ const StepBar = ({ totalSteps, currentStep, stepTitles }) => {
);
};

function SelectCredentialsPopup({ isOpen, setIsOpen, setSelectionMap, conformantCredentialsMap, verifierDomainName }) {
function SelectCredentialsPopup({ popupState, setPopupState, showPopup, hidePopup, vcEntityList, vcEntityListInstances, container }) {

const { api } = useContext(SessionContext);
const [vcEntities, setVcEntities] = useState([]);
const { vcEntityList, vcEntityListInstances } = useContext(CredentialsContext);
const navigate = useNavigate();
// const { vcEntityList, vcEntityListInstances } = useContext(CredentialsContext);
const { t } = useTranslation();
const keys = useMemo(() => Object.keys(conformantCredentialsMap), [conformantCredentialsMap]);
const stepTitles = useMemo(() => Object.keys(conformantCredentialsMap).map(key => key), [conformantCredentialsMap]);
const keys = useMemo(() => popupState?.options ? Object.keys(popupState.options.conformantCredentialsMap) : null, [popupState]);
const stepTitles = useMemo(() => popupState?.options ? Object.keys(popupState.options.conformantCredentialsMap).map(key => key) : null, [popupState]);
const [currentIndex, setCurrentIndex] = useState(0);
const [currentSelectionMap, setCurrentSelectionMap] = useState({});
const [requestedFields, setRequestedFields] = useState([]);
const [showAllFields, setShowAllFields] = useState(false);
const [selectedCredential, setSelectedCredential] = useState(null);
const container = useContext(ContainerContext);
const screenType = useScreenType();
const [currentSlide, setCurrentSlide] = useState(1);

useEffect(() => {
const getData = async () => {
if (currentIndex === Object.keys(conformantCredentialsMap).length) {
setSelectionMap(currentSelectionMap);
setIsOpen(false);
if (currentIndex === Object.keys(popupState.options.conformantCredentialsMap).length) {
reinitialize();
popupState.resolve(new Map(Object.entries(currentSelectionMap)));
return;
}

Expand All @@ -96,34 +95,38 @@ function SelectCredentialsPopup({ isOpen, setIsOpen, setSelectionMap, conformant
);

const filteredVcEntities = vcEntities.filter(vcEntity =>
conformantCredentialsMap[keys[currentIndex]].credentials.includes(vcEntity.credentialIdentifier)
popupState.options.conformantCredentialsMap[keys[currentIndex]].credentials.includes(vcEntity.credentialIdentifier)
);

setRequestedFields(conformantCredentialsMap[keys[currentIndex]].requestedFields);
setRequestedFields(popupState.options.conformantCredentialsMap[keys[currentIndex]].requestedFields);
setVcEntities(filteredVcEntities);
} catch (error) {
console.error('Failed to fetch data', error);
}
};

getData();
if (popupState?.options && container && vcEntityList) {
console.log("opts = ", popupState.options)
getData();
}
}, [
api,
conformantCredentialsMap,
currentIndex,
currentSelectionMap,
keys,
container,
popupState,
vcEntityList,
setSelectionMap,
setIsOpen,
container.credentialParserRegistry,
]);


useEffect(() => {
const currentKey = keys[currentIndex];
const selectedId = currentSelectionMap[currentKey];
setSelectedCredential(selectedId);
}, [currentIndex, currentSelectionMap, keys]);
if (popupState?.options) {
const currentKey = keys[currentIndex];
const selectedId = currentSelectionMap[currentKey];
setSelectedCredential(selectedId);
}
}, [currentIndex, currentSelectionMap, keys, popupState]);

const goToNextSelection = () => {
setShowAllFields(false);
Expand All @@ -148,12 +151,23 @@ function SelectCredentialsPopup({ isOpen, setIsOpen, setSelectionMap, conformant
}
};

const reinitialize = () => {
setCurrentIndex(0);
setCurrentSlide(1);
setCurrentSelectionMap({});
setRequestedFields([]);
setSelectedCredential(null);
setPopupState({ isOpen: false });
}

const onClose = () => {
setIsOpen(false);
navigate('/');
// setIsOpen(false);
popupState.reject();
reinitialize();
// navigate('/');
}

if (!isOpen) {
if (!popupState?.isOpen) {
return null;
};

Expand Down Expand Up @@ -200,7 +214,7 @@ function SelectCredentialsPopup({ isOpen, setIsOpen, setSelectionMap, conformant
})();

return (
<PopupLayout isOpen={isOpen} onClose={onClose} loading={false} fullScreen={screenType !== 'desktop'}>
<PopupLayout isOpen={popupState?.isOpen} onClose={onClose} loading={false} fullScreen={screenType !== 'desktop'}>
<div className={`${screenType !== 'desktop' && 'pb-16'}`}>
<div>
{stepTitles && (
Expand All @@ -214,13 +228,13 @@ function SelectCredentialsPopup({ isOpen, setIsOpen, setSelectionMap, conformant
)}
<hr className="mb-2 border-t border-primary/80 dark:border-white/80" />

{requestedFieldsText && requestedFields.length > 0 && verifierDomainName && (
{requestedFieldsText && requestedFields.length > 0 && popupState.options.verifierDomainName && (
<>
<p className="pd-2 text-gray-700 text-sm dark:text-white">
<span>
<Trans
i18nKey={requestedFields.length === 1 ? "selectCredentialPopup.descriptionFieldsSingle" : "selectCredentialPopup.descriptionFieldsMultiple"}
values={{ verifierDomainName }}
values={{ verifierDomainName: popupState.options.verifierDomainName }}
components={{ strong: <strong /> }}
/>
</span>
Expand Down
40 changes: 38 additions & 2 deletions src/context/ContainerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import defaultCredentialImage from "../assets/images/cred.png";
import renderSvgTemplate from "../components/Credentials/RenderSvgTemplate";
import renderCustomSvgTemplate from "../components/Credentials/RenderCustomSvgTemplate";
import StatusContext from "./StatusContext";
import SelectCredentialsPopup from "../components/Popups/SelectCredentialsPopup";
import { getSdJwtVcMetadata } from "../lib/utils/getSdJwtVcMetadata";
import { CredentialBatchHelper } from "../lib/services/CredentialBatchHelper";
import CredentialsContext from '../context/CredentialsContext';

export type ContainerContextValue = {
httpProxy: IHttpProxy,
Expand All @@ -47,10 +49,38 @@ const defaultLocale = 'en-US';
export const ContainerContextProvider = ({ children }) => {
const { isOnline } = useContext(StatusContext);
const { isLoggedIn, api, keystore } = useContext(SessionContext);

const [container, setContainer] = useState<ContainerContextValue>(null);
const [isInitialized, setIsInitialized] = useState(false); // New flag
const [shouldUseCache, setShouldUseCache] = useState(true)
const { vcEntityList, vcEntityListInstances, latestCredentials, getData, currentSlide, setCurrentSlide } = useContext<any>(CredentialsContext);

useEffect(() => {
getData();
}, [getData]);

const [popupState, setPopupState] = useState({
isOpen: false,
options: null,
resolve: (value: unknown) => { },
reject: () => { },
});

const showPopup = (options): Promise<Map<string, string>> =>
new Promise((resolve, reject) => {
setPopupState({
isOpen: true,
options,
resolve,
reject,
});
});

const hidePopup = () => {
setPopupState((prevState) => ({
...prevState,
isOpen: false,
}));
};

useEffect(() => {
window.addEventListener('generatedProof', (e) => {
Expand Down Expand Up @@ -226,12 +256,17 @@ export const ContainerContextProvider = ({ children }) => {
audience,
issuanceDate: new Date().toISOString(),
});
},

async function showCredentialSelectionPopup(conformantCredentialsMap: Map<string, string[]>, verifierDomainName: string): Promise<Map<string, string>> {
return showPopup({ conformantCredentialsMap, verifierDomainName });
}
);

cont.register<OpenID4VCIClientFactory>('OpenID4VCIClientFactory', OpenID4VCIClientFactory,
cont.resolve<IHttpProxy>('HttpProxy'),
cont.resolve<IOpenID4VCIClientStateRepository>('OpenID4VCIClientStateRepository'),
cont.resolve<IOpenID4VPRelyingParty>('OpenID4VPRelyingParty'),
async (requests: { nonce: string, audience: string, issuer: string }[]): Promise<{ proof_jwts: string[] }> => {
const [{ proof_jwts }, newPrivateData, keystoreCommit] = await keystore.generateOpenid4vciProofs(requests);
await api.updatePrivateData(newPrivateData);
Expand Down Expand Up @@ -295,11 +330,12 @@ export const ContainerContextProvider = ({ children }) => {
};

initialize();
}, [isLoggedIn, api, container, isInitialized, keystore, isOnline, shouldUseCache]);
}, [isLoggedIn, api, container, isInitialized, keystore, isOnline, shouldUseCache, vcEntityList]);

return (
<ContainerContext.Provider value={container}>
{children}
<SelectCredentialsPopup popupState={popupState} setPopupState={setPopupState} showPopup={showPopup} hidePopup={hidePopup} container={container} vcEntityList={vcEntityList} vcEntityListInstances={vcEntityListInstances} />
</ContainerContext.Provider>
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/context/CredentialsContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,10 @@ export const CredentialsProvider = ({ children }) => {
);
};

export const withCredentialsContext = (Component) =>
(props) => (
<CredentialsProvider>
<Component {...props} />
</CredentialsProvider>
);
export default CredentialsContext;
Loading
Loading