[INJIMOB-3193]add preauth and credential offer support (#1949)

[INJIMOB-3058]temp commit2



[INJIMOB-3058]temp commit2



[INJIMOB-3058] add support for pre-auth flow by credential-offer



[INJIMOB-3187] animo working chcekpoint

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>
This commit is contained in:
abhip2565
2025-06-04 14:46:07 +05:30
committed by GitHub
parent ddf5244f32
commit bd90b342e0
63 changed files with 3510 additions and 2063 deletions

View File

@@ -41,6 +41,7 @@ export const API_URLS: ApiUrls = {
buildURL: (authorizationServerUrl: string): string =>
`${authorizationServerUrl}/.well-known/oauth-authorization-server`,
},
allProperties: {
method: 'GET',
buildURL: (): `/${string}` => '/v1/mimoto/allProperties',
@@ -115,6 +116,14 @@ export const API = {
);
return response.response.issuers || [];
},
fetchIssuerConfig: async (issuerId: string) => {
const response = await request(
API_URLS.issuerConfig.method,
API_URLS.issuerConfig.buildURL(issuerId),
);
return response.response;
},
fetchIssuerWellknownConfig: async (credentialIssuer: string) => {
const response = await request(
API_URLS.issuerWellknownConfig.method,
@@ -122,17 +131,7 @@ export const API = {
);
return response;
},
fetchAuthorizationServerMetadata: async (authorizationServerUrl: string) => {
const response = await request(
API_URLS.authorizationServerMetadataConfig.method,
API_URLS.authorizationServerMetadataConfig.buildURL(
authorizationServerUrl,
),
undefined,
'',
);
return response;
},
fetchAllProperties: async () => {
const response = await request(
API_URLS.allProperties.method,

View File

@@ -81,6 +81,12 @@ export const SUPPORTED_KEY_TYPES = {
'ECC R1': KeyTypes.ES256,
RSA: KeyTypes.RS256,
};
export const KEY_TYPE_TO_JWT_ALG = {
[KeyTypes.ED25519]: 'EdDSA',
[KeyTypes.ES256K]: 'ES256K',
[KeyTypes.ES256]: 'ES256',
[KeyTypes.RS256]: 'RS256',
};
export function isAndroid(): boolean {
return Platform.OS === 'android';

View File

@@ -19,7 +19,7 @@ import {
VerifiableCredential,
} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
import getAllConfigurations, {CACHED_API} from '../api';
import {isAndroid, isIOS} from '../constants';
import {isAndroid, isIOS, KEY_TYPE_TO_JWT_ALG} from '../constants';
import {getJWT} from '../cryptoutil/cryptoUtil';
import {isMockVC} from '../Utils';
import {
@@ -34,7 +34,6 @@ import {KeyTypes} from '../cryptoutil/KeyTypes';
import {VCFormat} from '../VCFormat';
import {UnsupportedVcFormat} from '../error/UnsupportedVCFormat';
import {VCMetadata} from '../VCMetadata';
import {UUID} from '../Utils';
export const Protocols = {
OpenId4VCI: 'OpenId4VCI',
@@ -71,7 +70,7 @@ export const isActivationNeeded = (issuer: string) => {
export const Issuers_Key_Ref = 'OpenId4VCI_KeyPair';
export const updateCredentialInformation = async (
context,
context: any,
credential: VerifiableCredential,
): Promise<CredentialWrapper> => {
let processedCredential;
@@ -81,14 +80,23 @@ export const updateCredentialInformation = async (
context.selectedCredentialType.format,
);
}
const verifiableCredential = {
...credential,
credentialConfigurationId: context.selectedCredentialType.id,
issuerLogo: getDisplayObjectForCurrentLanguage(
context.selectedIssuer.display,
)?.logo,
processedCredential,
};
let verifiableCredential;
try {
verifiableCredential = {
...credential,
credentialConfigurationId: context.selectedCredentialType.id,
issuerLogo: getDisplayObjectForCurrentLanguage(
context.selectedIssuer.display,
)?.logo,
processedCredential,
};
} catch (e) {
console.error(
'Error occurred while processing credential for rendering',
e,
);
}
return {
verifiableCredential,
format: context.selectedCredentialType.format,
@@ -111,28 +119,14 @@ export const getDisplayObjectForCurrentLanguage = (
obj => obj[languageKey] == currentLanguage,
)[0];
if (!displayType) {
displayType = display.filter(obj => obj[languageKey] === 'en')[0];
displayType =
display.filter(obj => obj[languageKey] === 'en')[0] ||
display.filter(obj => obj[languageKey] === 'en-US')[0] ||
display[0];
}
return displayType;
};
export const constructAuthorizationConfiguration = (
selectedIssuer: issuerType,
supportedScope: string,
) => {
return {
issuer: selectedIssuer.issuer_id,
clientId: selectedIssuer.client_id,
scopes: [supportedScope],
redirectUrl: selectedIssuer.redirect_uri,
additionalParameters: {ui_locales: i18n.language},
serviceConfiguration: {
authorizationEndpoint: selectedIssuer.authorizationEndpoint,
tokenEndpoint: selectedIssuer.token_endpoint,
},
};
};
export const getCredentialIssuersWellKnownConfig = async (
issuer: string | undefined,
defaultFields: string[],
@@ -141,6 +135,7 @@ export const getCredentialIssuersWellKnownConfig = async (
issuerHost: string,
) => {
let fields: string[] = defaultFields;
let wellknownFieldsFlag = false;
let matchingWellknownDetails: any;
const wellknownResponse = await CACHED_API.fetchIssuerWellknownConfig(
issuer!,
@@ -169,9 +164,13 @@ export const getCredentialIssuersWellKnownConfig = async (
);
});
} else if (format === VCFormat.ldp_vc) {
fields = Object.keys(
const ldpFields = Object.keys(
matchingWellknownDetails.credential_definition.credentialSubject,
);
if (ldpFields.length > 0) {
fields = ldpFields;
wellknownFieldsFlag = true;
}
} else {
console.error(`Unsupported credential format - ${format} found`);
throw new UnsupportedVcFormat(format);
@@ -186,12 +185,15 @@ export const getCredentialIssuersWellKnownConfig = async (
return {
matchingCredentialIssuerMetadata: matchingWellknownDetails,
fields: fields,
wellknownFieldsFlag: false,
};
}
return {
matchingCredentialIssuerMetadata: matchingWellknownDetails,
wellknownResponse,
fields: fields,
wellknownFieldsFlag:
wellknownFieldsFlag || matchingWellknownDetails?.order?.length > 0,
};
};
@@ -217,6 +219,7 @@ export const getDetailedViewFields = async (
return {
matchingCredentialIssuerMetadata: response.matchingCredentialIssuerMetadata,
fields: updatedFieldsList,
wellknownFieldsFlag: response.wellknownFieldsFlag,
wellknownResponse: response.wellknownResponse,
};
};
@@ -245,7 +248,8 @@ export const OIDCErrors = {
AUTHORIZATION_ENDPOINT_DISCOVERY: {
GRANT_TYPE_NOT_SUPPORTED: 'Grant type not supported by Wallet',
FAILED_TO_FETCH_AUTHORIZATION_ENDPOINT: 'Failed to fetch authorization endpoint or grant type not supported by wallet',
FAILED_TO_FETCH_AUTHORIZATION_ENDPOINT:
'Failed to fetch authorization endpoint or grant type not supported by wallet',
},
};
@@ -267,17 +271,24 @@ export async function constructProofJWT(
accessToken: string,
selectedIssuer: issuerType,
keyType: string,
isCredentialOfferFlow: boolean,
cNonce?: string,
): Promise<string> {
const jwk = await getJWK(publicKey, keyType);
const jwtHeader = {
alg: keyType,
jwk: await getJWK(publicKey, keyType),
alg: KEY_TYPE_TO_JWT_ALG[keyType],
typ: 'openid4vci-proof+jwt',
};
if (isCredentialOfferFlow) {
jwtHeader['kid'] = `did:jwk:${base64url(JSON.stringify(jwk))}#0`;
} else {
jwtHeader['jwk'] = jwk;
}
const decodedToken = jwtDecode(accessToken);
const jwtPayload = {
iss: selectedIssuer.client_id,
nonce: decodedToken.c_nonce,
aud: selectedIssuer.credential_audience,
nonce: cNonce ?? decodedToken.c_nonce,
aud: selectedIssuer.credential_audience ?? selectedIssuer.credential_issuer,
iat: Math.floor(new Date().getTime() / 1000),
exp: Math.floor(new Date().getTime() / 1000) + 18000,
};
@@ -312,7 +323,7 @@ export const getJWK = async (publicKey, keyType) => {
}
return {
...publicKeyJWK,
alg: keyType,
alg: KEY_TYPE_TO_JWT_ALG[keyType],
use: 'sig',
};
} catch (e) {
@@ -387,23 +398,30 @@ export function selectCredentialRequestKey(
return keyOrder[index];
}
}
return '';
return KeyTypes.ED25519;
}
export const constructIssuerMetaData = (
selectedIssuer: issuerType,
selectedCredentialType: CredentialTypes,
downloadTimeout: Number,
scope: string,
): Object => {
const issuerMeta: Object = {
credentialAudience: selectedIssuer.credential_audience,
credentialEndpoint: selectedIssuer.credential_endpoint,
downloadTimeoutInMilliSeconds: downloadTimeout,
credentialFormat: selectedCredentialType.format,
credentialFormat: isIOS()
? selectedCredentialType.format
: selectedCredentialType.format.toUpperCase(),
authorizationServers: selectedIssuer['authorization_servers'],
tokenEndpoint: selectedIssuer.token_endpoint,
scope: scope,
};
if (selectedCredentialType.format === VCFormat.ldp_vc) {
issuerMeta['credentialType'] = selectedCredentialType?.credential_definition
?.type ?? ['VerifiableCredential'];
if (selectedCredentialType?.credential_definition['@context'])
issuerMeta['context'] =
selectedCredentialType?.credential_definition['@context'];
} else if (selectedCredentialType.format === VCFormat.mso_mdoc) {
issuerMeta['doctype'] = selectedCredentialType.doctype;
issuerMeta['claims'] = selectedCredentialType.claims;

View File

@@ -1,19 +1,126 @@
import {NativeModules} from 'react-native';
import {NativeModules, NativeEventEmitter} from 'react-native';
import {__AppId} from '../GlobalVariables';
import {constructProofJWT} from '../openId4VCI/Utils';
import {issuerType} from '../../machines/Issuers/IssuersMachine';
import {VerifiableCredential} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
const emitter = new NativeEventEmitter(NativeModules.InjiVciClient);
export class VciClient {
static async downloadCredential(
issuerMetaData: Object,
jwtProof: string,
accessToken: string,
static get client() {
const nativeClient = NativeModules.InjiVciClient;
nativeClient.init(__AppId.getValue());
return nativeClient;
}
static async requestCredentialByOffer(
credentialOffer: string,
getTxCode: (
inputMode: string | undefined,
description: string | undefined,
length: number | undefined,
) => void,
getProofJwt: (
accesToken: string,
cNonce: string | null,
issuerMetadata: object,
credentialConfigurationId: string,
) => void,
navigateToAuthView: (authorizationEndpoint: string) => void,
requestTrustIssuerConsent: (issuerMetadata: object) => void,
) {
const InjiVciClient = NativeModules.InjiVciClient;
InjiVciClient.init(__AppId.getValue());
const credentialResponse = await InjiVciClient.requestCredential(
issuerMetaData,
jwtProof,
accessToken,
const proofListener = emitter.addListener(
'onRequestProof',
async ({
accessToken,
cNonce,
issuerMetadata,
credentialConfigurationId,
}) => {
getProofJwt(
accessToken,
cNonce,
JSON.parse(issuerMetadata),
credentialConfigurationId,
);
},
);
return JSON.parse(credentialResponse);
const authListener = emitter.addListener(
'onRequestAuthCode',
async ({authorizationEndpoint}) => {
navigateToAuthView(authorizationEndpoint);
},
);
const txCodeListener = emitter.addListener(
'onRequestTxCode',
async ({inputMode, description, length}) => {
getTxCode(inputMode, description, length);
},
);
const trustIssuerListener = emitter.addListener(
'onCheckIssuerTrust',
async ({issuerMetadata}) => {
const issuerMetadataObject = JSON.parse(issuerMetadata);
requestTrustIssuerConsent(issuerMetadataObject);
},
);
let response = '';
try {
response = await VciClient.client.requestCredentialByOffer(
credentialOffer,
JSON.stringify({
clientId: 'wallet',
redirectUri: 'io.mosip.residentapp.inji://oauthredirect',
}),
);
} catch (error) {
console.error('Error requesting credential by offer:', error);
throw error;
}
proofListener.remove();
authListener.remove();
txCodeListener.remove();
trustIssuerListener.remove();
return JSON.parse(response) as VerifiableCredential;
}
static async requestCredentialFromTrustedIssuer(
resolvedIssuerMetaData: Object,
clientMetadata: Object,
getProofJwt: (accessToken: string, cNonce: string) => Promise<void>,
navigateToAuthView: (authorizationEndpoint: string) => void,
) {
const proofListener = emitter.addListener(
'onRequestProof',
async ({accessToken, cNonce}) => {
getProofJwt(accessToken, cNonce);
proofListener.remove();
},
);
const authListener = emitter.addListener(
'onRequestAuthCode',
({authorizationEndpoint}) => {
navigateToAuthView(authorizationEndpoint);
},
);
let response = '';
try {
response = await VciClient.client.requestCredentialFromTrustedIssuer(
JSON.stringify(resolvedIssuerMetaData),
JSON.stringify(clientMetadata),
);
} catch (error) {
console.error('Error requesting credential from trusted issuer:', error);
throw error;
}
proofListener.remove();
authListener.remove();
return JSON.parse(response);
}
}