Skip to content
This repository was archived by the owner on Dec 18, 2025. It is now read-only.
Draft
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
34 changes: 34 additions & 0 deletions config.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
urls: &urls
backend_url: &backend_url "http://backend.test"
wallet_url: &wallet_url "http://wallet.test"
wallet_callback_url: &wallet_callback_url "http://wallet.test/cb"
ws_url: &ws_url "ws://ws.wallet.test"

wallet:
environment: development
log_level: info
ws_url: *ws_url
wallet_backend_url: *backend_url
login_with_password: false
did_key_version: jwk_jcs-pub
app_version: $npm_package_version
generate_sourcemap: false
display_console: true
webauthn_rpid: localhost
openid4vci_redirect_uri: *wallet_callback_url
openid4vci_proof_type_precedence: jwt,attestation
openid4vp_san_dns_check: false
openid4vp_san_dns_check_ssl_certs: false
validate_credentials_with_trust_anchors: true
multi_language_display: true
static_public_url: *wallet_url
static_name: wwWallet
core_configuration:
wallet_url: *wallet_callback_url
wallet_callback_url: *wallet_callback_url
dpop_ttl_seconds: 60
static_clients:
- issuer: "http://issuer.test"
client_id: "client_id"
client_secret: "client_secret"
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"eslint-config-react-app": "^7.0.1",
"jsdom": "^26.0.0",
"sharp": "^0.34.5",
"nock": "^14.0.10",
"vite": "^6.1.0",
"vite-plugin-checker": "^0.11.0",
"vite-plugin-svgr": "^4.3.0",
Expand Down
5 changes: 5 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import axios, { AxiosResponse } from 'axios';
import { Err, Ok, Result } from 'ts-results';
import { useDispatch } from 'react-redux';

import * as config from '../config';
import { logger } from '@/logger';
import { setLoggedIn } from '@/store';
import { fromBase64Url, jsonParseTaggedBinary, jsonStringifyTaggedBinary, toBase64Url } from '../util';
import { EncryptedContainer, makeAssertionPrfExtensionInputs, parsePrivateData, serializePrivateData } from '../services/keystore';
import { CachedUser, LocalStorageKeystore } from '../services/LocalStorageKeystore';
Expand Down Expand Up @@ -98,6 +100,7 @@ export interface BackendApi {
}

export function useApi(isOnlineProp: boolean = true): BackendApi {
const dispatch = useDispatch();
const isOnline = useMemo(() => isOnlineProp === null ? true : isOnlineProp, [isOnlineProp]);
const [appToken, setAppToken, clearAppToken] = useSessionStorage<string | null>("appToken", null);
const [userHandle,] = useSessionStorage<string | null>("userHandle", null);
Expand Down Expand Up @@ -326,6 +329,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {

const clearSession = useCallback((): void => {
clearSessionStorage();
dispatch(setLoggedIn(false));
events.dispatchEvent(new CustomEvent<ClearSessionEvent>(CLEAR_SESSION_EVENT));
}, [clearSessionStorage]);

Expand All @@ -343,6 +347,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
authenticationType,
showWelcome: authenticationType === 'signup',
});
dispatch(setLoggedIn(true));

await addItem('users', response.data.uuid, response.data);
if (isOnline) {
Expand Down
4 changes: 2 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ export type DidKeyVersion = "p256-pub" | "jwk_jcs-pub";
export const APP_VERSION = import.meta.env.VITE_APP_VERSION;

export const LOG_LEVEL = import.meta.env.VITE_LOG_LEVEL;
export const BACKEND_URL = import.meta.env.VITE_WALLET_BACKEND_URL;
export const BACKEND_URL = import.meta.env.VITE_WALLET_BACKEND_URL.startsWith('"') ? JSON.parse(import.meta.env.VITE_WALLET_BACKEND_URL) : import.meta.env.VITE_WALLET_BACKEND_URL;
export const DID_KEY_VERSION: DidKeyVersion = import.meta.env.VITE_DID_KEY_VERSION as DidKeyVersion;
export const DISPLAY_CONSOLE = import.meta.env.VITE_DISPLAY_CONSOLE;
export const CORE_CONFIGURATION = import.meta.env.VITE_CORE_CONFIGURATION;
export const CORE_CONFIGURATION = typeof import.meta.env.VITE_CORE_CONFIGURATION === 'object' ? import.meta.env.VITE_CORE_CONFIGURATION : JSON.parse(import.meta.env.VITE_CORE_CONFIGURATION);
export const MULTI_LANGUAGE_DISPLAY: boolean = import.meta.env.VITE_MULTI_LANGUAGE_DISPLAY ? JSON.parse(import.meta.env.VITE_MULTI_LANGUAGE_DISPLAY) : false;
export const I18N_WALLET_NAME_OVERRIDE: string | undefined = import.meta.env.VITE_I18N_WALLET_NAME_OVERRIDE;
export const INACTIVE_LOGOUT_MILLIS = (import.meta.env.VITE_INACTIVE_LOGOUT_SECONDS ? parseInt(import.meta.env.VITE_INACTIVE_LOGOUT_SECONDS, 10) : 60 * 15) * 1000
Expand Down
7 changes: 7 additions & 0 deletions src/context/SessionContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useContext, useEffect, useCallback, useRef, useState, useMemo } from 'react';
import { useDispatch } from 'react-redux';

import StatusContext from './StatusContext';
import { useApi } from '../api';
import { logger } from '@/logger';
import { setLoggedIn } from '@/store';
import { KeystoreEvent, useLocalStorageKeystore } from '../services/LocalStorageKeystore';
import keystoreEvents from '../services/keystoreEvents';
import SessionContext, { SessionContextValue } from './SessionContext';
Expand All @@ -13,6 +15,7 @@ import { fetchKeyConfig, HpkeConfig } from '@/lib/utils/ohttpHelpers';
import { OHTTP_KEY_CONFIG } from '@/config';

export const SessionContextProvider = ({ children }) => {
const dispatch = useDispatch();
const { isOnline } = useContext(StatusContext);
const api = useApi(isOnline);
const keystore = useLocalStorageKeystore(keystoreEvents);
Expand Down Expand Up @@ -44,6 +47,10 @@ export const SessionContextProvider = ({ children }) => {
clearSessionRef.current = clearSession;
}, [clearSession]);

useEffect(() => {
dispatch(setLoggedIn(isLoggedIn));
}, [isLoggedIn]);

// The close() will dispatch Event CloseSessionTabLocal in order to call the clearSession
const logout = useCallback(async () => {
logger.debug('[Session Context] Close Keystore');
Expand Down
19 changes: 11 additions & 8 deletions src/hocs/UriHandler/UriHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useEffect, useState, useContext, useMemo, useCallback } from "react";
import React, { useEffect, useState, useMemo } from "react";
import { useSelector } from "react-redux";
import { OauthError } from "@wwwallet/client-core";
import { jsonToLog, logger } from "@/logger";
import { AppState } from "@/store";
import { ProtocolData, ProtocolStep } from "./resources";

import SessionContext from "../../context/SessionContext";

import { useTranslation } from "react-i18next";
import useClientCore from "@/hooks/useClientCore";
import useErrorDialog from "@/hooks/useErrorDialog";
Expand All @@ -17,7 +17,6 @@ import {
PresentationSuccessHandler,
ProtocolErrorHandler
} from "./handlers";
import StatusContext from "@/context/StatusContext";

type UriHandlerProps = {
children: React.ReactNode;
Expand All @@ -30,8 +29,12 @@ export const UriHandler = (props: UriHandlerProps) => {
const [ protocolData, setProtocolData ] = useState<ProtocolData>(null);

const core = useClientCore();
const { isOnline } = useContext(StatusContext);
const { isLoggedIn } = useContext(SessionContext);
const isOnline = useSelector((state: AppState) => {
return state.status.isOnline
})
const isLoggedIn = useSelector((state: AppState) => {
return state.sessions.isLoggedIn
})
const { displayError } = useErrorDialog();
const { t } = useTranslation();

Expand Down Expand Up @@ -59,10 +62,10 @@ export const UriHandler = (props: UriHandlerProps) => {
return currentStep === "protocol_error"
}, [currentStep])

const goToStep = useCallback((step: ProtocolStep, data?: ProtocolData) => {
const goToStep = (step: ProtocolStep, data?: ProtocolData) => {
setStep(step)
setProtocolData(data)
}, [])
}

useEffect(() => {
if (currentStep || !isOnline || !isLoggedIn) return
Expand Down
9 changes: 8 additions & 1 deletion src/hocs/UriHandler/handlers/AuthorizationRequestHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import { OauthError } from "@wwwallet/client-core";
import { jsonToLog, logger } from "@/logger";
import { AppState } from "@/store";
import { ProtocolData, ProtocolStep } from "../resources";

import { useTranslation } from "react-i18next";
Expand All @@ -18,15 +20,20 @@ export const AuthorizationRequestHandler = ({ goToStep, data }: AuthorizationReq
issuer,
issuer_state
} = data
const isOnline = useSelector((state: AppState) => state.status.isOnline)

const core = useClientCore();
const { displayError } = useErrorDialog();
const { t } = useTranslation();

useEffect(() => {
if (!isOnline) return

core.authorization({
issuer: issuer,
issuer_state: issuer_state ?? 'issuer_state',
}).then(({ nextStep, data }) => {
// @ts-expect-error
return goToStep(nextStep, data)
}).catch((err) => {
if (err instanceof OauthError) {
Expand All @@ -41,7 +48,7 @@ export const AuthorizationRequestHandler = ({ goToStep, data }: AuthorizationReq

throw err;
})
}, [core, goToStep, displayError, t, issuer, issuer_state])
}, [isOnline, core, goToStep, displayError, t, issuer, issuer_state])

return (
<></>
Expand Down
3 changes: 2 additions & 1 deletion src/hocs/UriHandler/handlers/AuthorizeHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import { ProtocolData, ProtocolStep } from "../resources";

type AuthorizeHandlerProps = {
Expand All @@ -11,6 +12,6 @@ export const AuthorizeHandler = ({ data }: AuthorizeHandlerProps) => {
window.location.href = authorize_url

return (
<></>
<>redirected</>
)
}
6 changes: 3 additions & 3 deletions src/hocs/UriHandler/handlers/CredentialRequestHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useContext, useEffect } from "react";
import React, { useCallback, useContext, useEffect, useState } from "react";
import { calculateJwkThumbprint, decodeJwt, exportJWK, generateKeyPair, JWK, SignJWT } from "jose";
import { OauthError } from "@wwwallet/client-core";
import { OPENID4VCI_PROOF_TYPE_PRECEDENCE } from "@/config";
Expand Down Expand Up @@ -47,8 +47,8 @@ export const CredentialRequestHandler = ({ goToStep, data }) => {
logger.debug(err);
return null;
}
}, [api]);

}, [api]
);

useEffect(() => {
const {
Expand Down
17 changes: 7 additions & 10 deletions src/hocs/UriHandler/handlers/PresentationSuccessHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import React, { useContext } from "react";
import { logger } from "@/logger";
import { ProtocolData, ProtocolStep } from "../resources";

import OpenID4VCIContext from "@/context/OpenID4VCIContext";

export type PresentationSuccessProps = {
goToStep: (step: ProtocolStep, data: ProtocolData) => void;
data: any;
}

export const PresentationSuccessHandler = ({ goToStep: _goToStep, data: _data }) => {
const { openID4VCI } = useContext(OpenID4VCIContext);
// const { openID4VCI } = useContext(OpenID4VCIContext);

const u = new URL(window.location.href);
// const u = new URL(window.location.href);

logger.debug("Handling authorization response...");
openID4VCI.handleAuthorizationResponse(u.toString()).catch(err => {
logger.error("Error during the handling of authorization response", err);
window.history.replaceState({}, '', `${window.location.pathname}`);
})
// logger.debug("Handling authorization response...");
// openID4VCI.handleAuthorizationResponse(u.toString()).catch(err => {
// logger.error("Error during the handling of authorization response", err);
// window.history.replaceState({}, '', `${window.location.pathname}`);
// })

return (
<></>
Expand Down
27 changes: 13 additions & 14 deletions src/hocs/UriHandler/handlers/ProtocolErrorHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
import React, { useContext } from "react";
import React, { useEffect } from "react";
import { ProtocolData, ProtocolStep } from "../resources";

import SessionContext from "@/context/SessionContext";

import useErrorDialog from "@/hooks/useErrorDialog";
import MessagePopup from "@/components/Popups/MessagePopup";

export type ProtocolErrorHandlerProps = {
goToStep: (step: ProtocolStep, data: ProtocolData) => void;
data: any;
}

export const ProtocolErrorHandler = ({ goToStep, data }) => {
const { isLoggedIn } = useContext(SessionContext);
const { displayError } = useErrorDialog();
const { error, error_description } = data

const urlParams = new URLSearchParams(window.location.search);
const state = urlParams.get('state');
const error = urlParams.get('error');
// TODO verify authorization response state
// const state = urlParams.get('state');

if (isLoggedIn && state && error) {
useEffect(() => {
window.history.replaceState({}, '', `${window.location.pathname}`);
const errorDescription = urlParams.get('error_description');
displayError({ title: error, description: errorDescription })
}
}, [])

return (
<></>
<>
<MessagePopup type="error" onClose={() => {}} message={{
title: error,
description: error_description,
}} />
</>
)
}
1 change: 1 addition & 0 deletions src/hocs/UriHandler/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type ProtocolData = ProtocolResponse['data']
export type ProtocolStep =
| "authorization_request"
| "authorize"
| "authorization_challenge"
| "generate_presentation"
| "send_presentation"
| "presentation_success"
Expand Down
8 changes: 0 additions & 8 deletions src/lib/services/CoreWrappers/ClientStateStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ export function useCoreClientStateStore(): ClientStateStore {
}
)

if (!clientState) {
throw new Error("could not find client state")
}

return clientState;
},
[]
Expand All @@ -97,10 +93,6 @@ export function useCoreClientStateStore(): ClientStateStore {

const clientState = JSON.parse(rawClientStates).find(({ state: e }) => e === state)

if (!clientState) {
throw new Error("could not find client state")
}

return clientState;
},
[]
Expand Down
3 changes: 2 additions & 1 deletion src/lib/services/HttpProxy/HttpProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useContext, useMemo } from 'react';
import { useSelector } from 'react-redux';
import axios from 'axios';
import { IHttpProxy, RequestHeaders, ResponseHeaders } from '../../interfaces/IHttpProxy';
import { BACKEND_URL } from '@/config';
import { AppState } from '@/store';
import { addItem, getItem, removeItem } from '@/indexedDB';
import { encryptedHttpRequest, toArrayBuffer } from '@/lib/utils/ohttpHelpers';
Expand All @@ -10,7 +11,7 @@ import SessionContext from '@/context/SessionContext';
import { toU8 } from '@/util';

// @ts-ignore
const walletBackendServerUrl = import.meta.env.VITE_WALLET_BACKEND_URL;
const walletBackendServerUrl = BACKEND_URL;
const inFlightRequests = new Map<string, Promise<any>>();
const TIMEOUT = 100 * 1000;

Expand Down
1 change: 1 addition & 0 deletions src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export {
setCalculatedWalletState,
setStorageValue,
setApi,
setLoggedIn,
setVcEntityList,
} from "./sessionsSlice";

Expand Down
Loading
Loading