Files
inji-wallet/machines/Issuers/IssuersService.ts
KiruthikaJeyashankar 5305e7d7ea [INJIMOB-3392] add token request logic in wallet for vci flow (#2014)
* [INJIMOB-3392] add token request logic in wallet for vci flow

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] chore: update integration of VCIClient native module

Changes are updated as per new changes in the library

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3390] refactor: event structure of token request

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3392] fix tokenEndpoint method and refactorings

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] cnonce decode from accesstoken and credential response destructuring fix

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3390] add: getIssuerMetadata in kotlin NativeModule

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3393] fix: auth callback in android

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3390] fix: proofJwt issue in download flow

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3392] fix credentialofferflow

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392]fix format issues in bridge layer

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392]fix activity log texts on application reopen

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392]cache issuer metadata by key: issuerhost

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] fix error scenarios and cleanup issuermachine

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] refactor request method to handle missing error scenarios

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] fix max lines for txcode description to 2

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] rename credentialissueruri to credentialissuer

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] take cnonce from outside accesstoken

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] declare random-values at entry file

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] set fallback keytype to user priority first

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] add locales for network request failed error

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] remove console log

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3392] refactor and clean up code

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

---------

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>
Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>
Co-authored-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>
2025-07-24 11:42:00 +05:30

383 lines
12 KiB
TypeScript

import NetInfo from '@react-native-community/netinfo';
import { NativeModules } from 'react-native';
import Cloud from '../../shared/CloudBackupAndRestoreUtils';
import getAllConfigurations, { CACHED_API } from '../../shared/api';
import {
fetchKeyPair,
generateKeyPair,
} from '../../shared/cryptoutil/cryptoUtil';
import {
constructProofJWT,
hasKeyPair,
updateCredentialInformation,
verifyCredentialData,
} from '../../shared/openId4VCI/Utils';
import VciClient from '../../shared/vciClient/VciClient';
import { displayType, issuerType } from './IssuersMachine';
import { setItem } from '../store';
import { API_CACHED_STORAGE_KEYS } from '../../shared/constants';
import { createCacheObject } from '../../shared/Utils';
import { VerificationResult } from '../../shared/vcjs/verifyCredential';
export const IssuersService = () => {
return {
isUserSignedAlready: () => async () => {
return await Cloud.isSignedInAlready();
},
downloadIssuersList: async () => {
const trustedIssuersList = await CACHED_API.fetchIssuers();
return trustedIssuersList;
},
checkInternet: async () => await NetInfo.fetch(),
downloadIssuerWellknown: async (context: any) => {
const wellknownResponse = (await VciClient.getInstance().getIssuerMetadata(
context.selectedIssuer.credential_issuer_host,
)) as issuerType;
const wellknownCacheObject = createCacheObject(wellknownResponse);
await setItem(
API_CACHED_STORAGE_KEYS.fetchIssuerWellknownConfig(
context.selectedIssuer.credential_issuer_host,
),
wellknownCacheObject,
'',
);
return wellknownResponse;
},
getCredentialTypes: async (context: any) => {
const credentialTypes: Array<{id: string; [key: string]: any}> = [];
const selectedIssuer = context.selectedIssuer;
const keys = Object.keys(
selectedIssuer.credential_configurations_supported,
);
for (const key of keys) {
if (selectedIssuer.credential_configurations_supported[key]) {
credentialTypes.push({
id: key,
...selectedIssuer.credential_configurations_supported[key],
});
}
}
if (credentialTypes.length === 0) {
throw new Error(
`No credential type found for issuer ${selectedIssuer.issuer_id}`,
);
}
return credentialTypes;
},
downloadCredential: (context: any) => async (sendBack: any) => {
const navigateToAuthView = (authorizationEndpoint: string) => {
sendBack({
type: 'AUTH_ENDPOINT_RECEIVED',
authEndpoint: authorizationEndpoint,
});
};
const getProofJwt = async (
credentialIssuer: string,
cNonce: string | null,
proofSigningAlgosSupported: string[] | null,
) => {
sendBack({
type: 'PROOF_REQUEST',
credentialIssuer: credentialIssuer,
cNonce: cNonce,
proofSigningAlgosSupported: proofSigningAlgosSupported,
});
};
const getTokenResponse = (tokenRequest: object) => {
sendBack({
type: 'TOKEN_REQUEST',
tokenRequest: tokenRequest,
});
};
const {credential} =
await VciClient.getInstance().requestCredentialFromTrustedIssuer(
context.selectedIssuer.credential_issuer_host,
context.selectedCredentialType.id,
{
clientId: context.selectedIssuer.client_id,
redirectUri: context.selectedIssuer.redirect_uri,
},
getProofJwt,
navigateToAuthView,
getTokenResponse,
);
return updateCredentialInformation(context, credential);
},
sendTxCode: async (context: any) => {
await VciClient.getInstance().sendTxCode(context.txCode);
},
sendConsentGiven: async () => {
await VciClient.getInstance().sendIssuerConsent(true);
},
sendConsentNotGiven: async () => {
await VciClient.getInstance().sendIssuerConsent(false);
},
checkIssuerIdInStoredTrustedIssuers: async (context: any) => {
const {RNSecureKeystoreModule} = NativeModules;
try {
return await RNSecureKeystoreModule.hasAlias(
context.credentialOfferCredentialIssuer,
);
} catch (error) {
console.error(
`Error while checking issuer ID in trusted issuers:`,
error,
);
return false;
}
},
addIssuerToTrustedIssuers: async (context: any) => {
const {RNSecureKeystoreModule} = NativeModules;
try {
await RNSecureKeystoreModule.storeData(
context.credentialOfferCredentialIssuer,
'trusted',
);
} catch {
console.error('Error updating issuer trust in keystore');
}
},
downloadCredentialFromOffer: (context: any) => async (sendBack: any) => {
const navigateToAuthView = (authorizationEndpoint: string) => {
sendBack({
type: 'AUTH_ENDPOINT_RECEIVED',
authEndpoint: authorizationEndpoint,
});
};
const getSignedProofJwt = async (
credentialIssuer: string,
cNonce: string | null,
proofSigningAlgosSupported: string[] | null,
) => {
sendBack({
type: 'PROOF_REQUEST',
cNonce: cNonce,
issuer: credentialIssuer,
proofSigningAlgosSupported: proofSigningAlgosSupported,
});
};
const getTxCode = async (
inputMode: string | undefined,
description: string | undefined,
length: number | undefined,
) => {
sendBack({
type: 'TX_CODE_REQUEST',
inputMode: inputMode,
description: description,
length: length,
});
};
const requesTrustIssuerConsent = async (
credentialIssuer: string,
issuerDisplay: object[],
) => {
const issuerDisplayObject = issuerDisplay as displayType[];
sendBack({
type: 'TRUST_ISSUER_CONSENT_REQUEST',
issuerDisplay: issuerDisplayObject,
issuer: credentialIssuer,
});
};
const getTokenResponse = (tokenRequest: object) => {
sendBack({
type: 'TOKEN_REQUEST',
tokenRequest: tokenRequest,
});
};
const credentialResponse =
await VciClient.getInstance().requestCredentialByOffer(
context.qrData,
getTxCode,
getSignedProofJwt,
navigateToAuthView,
getTokenResponse,
requesTrustIssuerConsent,
);
return credentialResponse;
},
sendTokenRequest: async (context: any) => {
const tokenRequestObject = context.tokenRequestObject;
return await sendTokenRequest(
tokenRequestObject,
context.selectedIssuer?.token_endpoint,
);
},
sendTokenResponse: async (context: any) => {
const tokenResponse = context.tokenResponse;
if (!tokenResponse) {
throw new Error(
'Could not send token response, tokenResponse is undefined or null',
);
}
return await VciClient.getInstance().sendTokenResponse(
JSON.stringify(tokenResponse),
);
},
updateCredential: async (context: any) => {
const credential = await updateCredentialInformation(
context,
context.credential,
);
return credential;
},
cacheIssuerWellknown: async (context: any) => {
const credentialIssuer = context.credentialOfferCredentialIssuer;
const issuerMetadata = (await VciClient.getInstance().getIssuerMetadata(
credentialIssuer,
)) as issuerType;
const wellknownCacheObject = createCacheObject(issuerMetadata);
await setItem(
API_CACHED_STORAGE_KEYS.fetchIssuerWellknownConfig(credentialIssuer),
wellknownCacheObject,
'',
);
return issuerMetadata;
},
constructProof: async (context: any) => {
const proofJWT = await constructProofJWT(
context.publicKey,
context.privateKey,
context.credentialOfferCredentialIssuer,
null,
context.keyType,
context.wellknownKeyTypes,
true,
context.cNonce,
);
await VciClient.getInstance().sendProof(proofJWT);
return proofJWT;
},
constructAndSendProofForTrustedIssuers: async (context: any) => {
const issuerMeta = context.selectedIssuer;
const proofJWT = await constructProofJWT(
context.publicKey,
context.privateKey,
context.selectedIssuer.credential_issuer_host,
context.selectedIssuer.client_id,
context.keyType,
context.wellknownKeyTypes,
false,
context.cNonce,
);
await VciClient.getInstance().sendProof(proofJWT);
return proofJWT;
},
getKeyOrderList: async () => {
const {RNSecureKeystoreModule} = NativeModules;
const keyOrder = JSON.parse(
(await RNSecureKeystoreModule.getData('keyPreference'))[1],
);
return keyOrder;
},
generateKeyPair: async (context: any) => {
const keypair = await generateKeyPair(context.keyType);
return keypair;
},
getKeyPair: async (context: any) => {
if (context.keyType === '') {
throw new Error('key type not found');
} else if (!!(await hasKeyPair(context.keyType))) {
return await fetchKeyPair(context.keyType);
}
},
getSelectedKey: async (context: any) => {
return context.keyType;
},
verifyCredential: async (context: any): Promise<VerificationResult> => {
const { isCredentialOfferFlow, verifiableCredential, selectedCredentialType } = context;
if (isCredentialOfferFlow) {
const configurations = await getAllConfigurations();
if (configurations.disableCredentialOfferVcVerification) {
return {
isVerified: true,
verificationMessage: '',
verificationErrorCode: '',
};
}
}
const verificationResult = await verifyCredentialData(
verifiableCredential?.credential,
selectedCredentialType.format,
);
if (!verificationResult.isVerified) {
throw new Error(verificationResult.verificationErrorCode);
}
return verificationResult;
}
}
}
async function sendTokenRequest(
tokenRequestObject: any,
proxyTokenEndpoint: any = null,
) {
if (proxyTokenEndpoint) {
tokenRequestObject.tokenEndpoint = proxyTokenEndpoint;
}
if (!tokenRequestObject?.tokenEndpoint) {
console.error('tokenEndpoint is not provided in tokenRequestObject');
throw new Error('tokenEndpoint is required');
}
const formBody = new URLSearchParams();
formBody.append('grant_type', tokenRequestObject.grantType);
if (tokenRequestObject.authCode) {
formBody.append('code', tokenRequestObject.authCode);
}
if (tokenRequestObject.preAuthCode) {
formBody.append('pre-authorized_code', tokenRequestObject.preAuthCode);
}
if (tokenRequestObject.txCode) {
formBody.append('tx_code', tokenRequestObject.txCode);
}
if (tokenRequestObject.clientId) {
formBody.append('client_id', tokenRequestObject.clientId);
}
if (tokenRequestObject.redirectUri) {
formBody.append('redirect_uri', tokenRequestObject.redirectUri);
}
if (tokenRequestObject.codeVerifier) {
formBody.append('code_verifier', tokenRequestObject.codeVerifier);
}
const response = await fetch(tokenRequestObject.tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formBody.toString(),
});
if (!response.ok) {
const errorText = await response.text();
console.error(
'Token request failed with status:',
response.status,
errorText,
);
throw new Error(`Token request failed: ${response.status} ${errorText}`);
}
const tokenResponse = await response.json();
return tokenResponse;
}