mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-09 13:38:01 -05:00
* perf(INJI-458): cache fixed result of custom keystore presence Each call over the RN bridge can take significant user-visible time, and since the result is used multiple times and cannot change for a device in runtime, it can be computed once stored for later use. Signed-off-by: Harsh Vardhan <harsh59v@gmail.com> * feat(INJI-458): show loading for android h/w keystore check & generation Signed-off-by: Harsh Vardhan <harsh59v@gmail.com> * feat(INJI-472): show loader on issuer select Co-authored-by: KiruthikaJeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com> Signed-off-by: Harsh Vardhan <harsh59v@gmail.com> * refactor(INJI-458): rename const name to isHardwareKeystoreExists Signed-off-by: Harsh Vardhan <harsh59v@gmail.com> --------- Signed-off-by: Harsh Vardhan <harsh59v@gmail.com> Co-authored-by: KiruthikaJeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com>
545 lines
17 KiB
TypeScript
545 lines
17 KiB
TypeScript
import {
|
|
ActorRefFrom,
|
|
assign,
|
|
EventFrom,
|
|
send,
|
|
sendParent,
|
|
StateFrom,
|
|
} from 'xstate';
|
|
import {createModel} from 'xstate/lib/model';
|
|
import {AppServices} from '../shared/GlobalContext';
|
|
import {MY_VCS_STORE_KEY, ESIGNET_BASE_URL} from '../shared/constants';
|
|
import {StoreEvents} from './store';
|
|
import {linkTransactionResponse, VC} from '../types/VC/ExistingMosipVC/vc';
|
|
import {request} from '../shared/request';
|
|
import {
|
|
getJwt,
|
|
isHardwareKeystoreExists,
|
|
} from '../shared/cryptoutil/cryptoUtil';
|
|
import {
|
|
getBindingCertificateConstant,
|
|
getPrivateKey,
|
|
} from '../shared/keystore/SecureKeystore';
|
|
import i18n from '../i18n';
|
|
import {parseMetadatas, VCMetadata} from '../shared/VCMetadata';
|
|
import {
|
|
getEndEventData,
|
|
sendEndEvent,
|
|
} from '../shared/telemetry/TelemetryUtils';
|
|
|
|
const model = createModel(
|
|
{
|
|
serviceRefs: {} as AppServices,
|
|
selectedVc: {} as VC,
|
|
linkCode: '',
|
|
myVcs: [] as VCMetadata[],
|
|
thumbprint: '',
|
|
linkTransactionResponse: {} as linkTransactionResponse,
|
|
authFactors: [],
|
|
authorizeScopes: null,
|
|
clientName: {},
|
|
configs: {},
|
|
essentialClaims: [],
|
|
linkTransactionId: '',
|
|
logoUrl: '',
|
|
voluntaryClaims: [],
|
|
selectedVoluntaryClaims: [],
|
|
errorMessage: '',
|
|
domainName: '',
|
|
consentClaims: ['name', 'picture'],
|
|
isSharing: {},
|
|
linkedTransactionId: '',
|
|
},
|
|
{
|
|
events: {
|
|
SELECT_VC: (vc: VC) => ({vc}),
|
|
SCANNING_DONE: (params: string) => ({params}),
|
|
STORE_RESPONSE: (response: unknown) => ({response}),
|
|
STORE_ERROR: (error: Error) => ({error}),
|
|
TOGGLE_CONSENT_CLAIM: (enable: boolean, claim: string) => ({
|
|
enable,
|
|
claim,
|
|
}),
|
|
DISMISS: () => ({}),
|
|
CONFIRM: () => ({}),
|
|
GET: (value: string) => ({value}),
|
|
VERIFY: () => ({}),
|
|
CANCEL: () => ({}),
|
|
FACE_VALID: () => ({}),
|
|
FACE_INVALID: () => ({}),
|
|
RETRY_VERIFICATION: () => ({}),
|
|
},
|
|
},
|
|
);
|
|
|
|
export const QrLoginEvents = model.events;
|
|
export type QrLoginRef = ActorRefFrom<typeof qrLoginMachine>;
|
|
|
|
export const qrLoginMachine =
|
|
/** @xstate-layout N4IgpgJg5mDOIC5QEUBOAZA9lAlgOwDocIAbMAYgGUBhAQQDl6BJegcQH0ARAeXoFEA2gAYAuolAAHTLBwAXHJjziQAD0QBGACzqATAQCs+nZv0BmABzqL50zoA0IAJ6IA7KaEFNATnPmv6oUMhHUMAX1CHNCxcQmIyck4mSgBZJMphMSQQKRl5RWU1BF1zFwIvHQrTFxdNCxcANgdnBH16j1NbdT8vfW8q9XDIjGx8AhJ8AGsAFVQAQzxYWYBjPLxyCEUwIjwAN0wJraiRwnG8abmF5dWEfD2l2dWMjOUcuQUlLMKtXQMjEwsrL5bE1EF4XOYCEJ6iEXF4ekIOppzIMQEcYmNJjN5osVu9yGBUKhMKgCBISA8AGbEgC2BDRo1O52xV3eN12mHuj1Ezyyr1WBQ0JQhmlhotMZmM+hcIIQNX0nnUhh65h06hcOhKKPphFgAAtMAB3ADqs1QeHwUHI1F4ADEmAAlZI8yTSN75T6IEIeHT+eo1Lyaeriwwy+pggiwtXw9SKjU6LXDdF6w0ms0Wq0Mah8dDO7Ku-kehBegg+9R+7yB4P6GV+dQRlwBIRqoTqjX1BPRUaUfUGviE4kZ+hZnOiF7594Coo9TSQtyqqUx9zApyINpeCNIoVaYzlUwd45jTCzCDJRwANSWsCoU249r47DvlAACrxKIJR7zx+7QIU-fLofoMaATYWjaKGLjylCOjQi4gSmJoOgNPuSY9jsSzoDgsCyFQ2Z8NQUzsGe1C5nyE6Fr4M4BkI3j6DYtEttWK4tDGEb1H6DbmEYAZschozJgaaEYVh5Bnnw9pMDaACaJFfh8P6uLRZRIoqKl0UI5gyiK9SQtobjUZu0KaLxOqoehmHYYkKRpDJuRkfJRTqRCpj1PoTYBj01QVKGPwVIBHT6AGqq1MZBAUssYC0AArrIurkDatBZoRtDoEwnA2W6cmqJ6rklr6-qVoYjHNP4kI2Jo1ENr01EBiFYVLBF0WxfFiUsGeyWpelBb2cWpbltxVahiKuVqmGXT1IG6heLV4VRTFg7Dp1dlZUWOW9flQaFTW2glrBYK0SqZaqiFtyzOMEBMBAYB4PIsiOAkSSpJQ6Qfi6tnfstf4EABQFdKYoHqKG1EECUrnmEIcJ+FC7YRKiiajCdZ0XVdN13XeUz2pJhFiRJTB0FMTC8It72FD1eUVhtIZMQF2mRjUHT1FYPTQ0MnaEKgYAAI6RXAsjUIosDI1atoOk6L15m9mW-hBX0hD9IGaGBTEIfKvTNoGOhVF4zOw6zBDs1zPN8wsgt0EO2ZE5Lq7S99ql-QrANMe4M6RqNDa6NC5hGTD2oEBseAUJZj3PZkr0ZZOAS+AQzmuZNNGefYSshAYunUdCmvaz7+vc1hFpGwL13rJs2x7AcdJw2znPZ-IeBQHnyNsncDzvE8YukcTq42GUYYtuNgFsQGMqqnWNjwSNiFeACe7e+XeuVzzuf84LBJEiSZKUjSZe61n8813X10NxyTeKC3Ifi2HhYR05LluXHrY1qYO2OanY1sciKJ4Jgl3wFk2pjhLk4AFpGhMQAfKOE4CIGQKnizA8cQwB-3PvZYoEJVT+FVCUaC-hNDeXlDYCoipwRCCIeKEKjIsSXFxO9NulsiguWdpYIMQZoK9F6DKOmX0FYQSITHHoU1p6634qmc0NcEFdWWghNh4MCBlnqGDLQ7EAxvxgeibsho+wr1EUtEmLYIQqmjvbeCKoiqrgCCWTc4JtzeA1qQo8J5zyXk0e3BAIph7jTYvBQIwQLDgWdv4Bo5UkTqjMCFfiglzKOJoX4bS4oGyIR9FUIhCdmgMz0EEqw4pApaGgTrA8dUGoxQiZOGoX0Si1CDFYAKCEkkaChKVUeHQNYNGosdXYp1iBI2unIZoocxGFCRHoKU40VT+H2n4QG8oqig3Bj4LwUIXAhW3lhPeshCmFiROucqhlYK6AXA7Zoxg6wTwgv09JHRfAhT9vAz8-9Cz+HXJUxUCIzBIiRJpaCOkRoNlMGCME8EFlzxzrvRe11Vn2RBtI1oDM-pGAsF4GsdZXJEI8n8Go41wjhCAA */
|
|
model.createMachine(
|
|
{
|
|
predictableActionArguments: true,
|
|
preserveActionOrder: true,
|
|
tsTypes: {} as import('./QrLoginMachine.typegen').Typegen0,
|
|
schema: {
|
|
context: model.initialContext,
|
|
events: {} as EventFrom<typeof model>,
|
|
},
|
|
id: 'QrLogin',
|
|
initial: 'waitingForData',
|
|
states: {
|
|
waitingForData: {
|
|
on: {
|
|
GET: {
|
|
actions: [
|
|
'setScanData',
|
|
'resetLinkTransactionId',
|
|
'resetSelectedVoluntaryClaims',
|
|
],
|
|
target: 'linkTransaction',
|
|
},
|
|
},
|
|
},
|
|
linkTransaction: {
|
|
invoke: {
|
|
src: 'linkTransaction',
|
|
onDone: [
|
|
{
|
|
actions: [
|
|
'setlinkTransactionResponse',
|
|
'expandLinkTransResp',
|
|
'setClaims',
|
|
],
|
|
target: 'loadMyVcs',
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: 'SetErrorMessage',
|
|
target: 'ShowError',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
ShowError: {
|
|
on: {
|
|
DISMISS: {
|
|
actions: 'forwardToParent',
|
|
target: 'waitingForData',
|
|
},
|
|
},
|
|
},
|
|
loadMyVcs: {
|
|
entry: 'loadMyVcs',
|
|
on: {
|
|
STORE_RESPONSE: {
|
|
actions: 'setMyVcs',
|
|
target: 'showvcList',
|
|
},
|
|
},
|
|
},
|
|
showvcList: {
|
|
on: {
|
|
SELECT_VC: {
|
|
actions: 'setSelectedVc',
|
|
},
|
|
VERIFY: {
|
|
target: 'faceAuth',
|
|
},
|
|
DISMISS: {
|
|
actions: 'forwardToParent',
|
|
target: 'waitingForData',
|
|
},
|
|
},
|
|
},
|
|
faceAuth: {
|
|
on: {
|
|
FACE_VALID: {
|
|
target: 'requestConsent',
|
|
},
|
|
FACE_INVALID: {
|
|
target: 'invalidIdentity',
|
|
},
|
|
CANCEL: {
|
|
target: 'showvcList',
|
|
},
|
|
},
|
|
},
|
|
invalidIdentity: {
|
|
on: {
|
|
DISMISS: {
|
|
target: 'showvcList',
|
|
},
|
|
RETRY_VERIFICATION: {
|
|
target: 'faceAuth',
|
|
},
|
|
},
|
|
},
|
|
sendingAuthenticate: {
|
|
invoke: {
|
|
src: 'sendAuthenticate',
|
|
onDone: {
|
|
target: 'requestConsent',
|
|
actions: 'setLinkedTransactionId',
|
|
},
|
|
onError: [
|
|
{
|
|
actions: 'SetErrorMessage',
|
|
target: 'ShowError',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
requestConsent: {
|
|
on: {
|
|
CONFIRM: {
|
|
target: 'loadingThumbprint',
|
|
},
|
|
TOGGLE_CONSENT_CLAIM: {
|
|
actions: 'setConsentClaims',
|
|
target: 'requestConsent',
|
|
},
|
|
DISMISS: {
|
|
actions: 'forwardToParent',
|
|
target: 'waitingForData',
|
|
},
|
|
},
|
|
},
|
|
loadingThumbprint: {
|
|
entry: 'loadThumbprint',
|
|
on: {
|
|
STORE_RESPONSE: {
|
|
actions: 'setThumbprint',
|
|
target: 'sendingConsent',
|
|
},
|
|
},
|
|
},
|
|
sendingConsent: {
|
|
invoke: {
|
|
src: 'sendConsent',
|
|
onDone: {
|
|
target: 'success',
|
|
},
|
|
onError: [
|
|
{
|
|
actions: 'SetErrorMessage',
|
|
target: 'ShowError',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
success: {
|
|
entry: [() => sendEndEvent(getEndEventData('QR login', 'SUCCESS'))],
|
|
on: {
|
|
CONFIRM: {
|
|
target: 'done',
|
|
},
|
|
},
|
|
},
|
|
done: {
|
|
type: 'final',
|
|
data: context => context,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
actions: {
|
|
forwardToParent: sendParent('DISMISS'),
|
|
|
|
setScanData: assign({
|
|
linkCode: (context, event) => event.value,
|
|
}),
|
|
|
|
// TODO: loaded VCMetadatas are not used anywhere. remove?
|
|
loadMyVcs: send(StoreEvents.GET(MY_VCS_STORE_KEY), {
|
|
to: context => context.serviceRefs.store,
|
|
}),
|
|
|
|
setMyVcs: model.assign({
|
|
myVcs: (_context, event) =>
|
|
parseMetadatas((event.response || []) as object[]),
|
|
}),
|
|
|
|
loadThumbprint: send(
|
|
context =>
|
|
StoreEvents.GET(
|
|
getBindingCertificateConstant(
|
|
context.selectedVc.walletBindingResponse?.walletBindingId,
|
|
),
|
|
),
|
|
{to: context => context.serviceRefs.store},
|
|
),
|
|
setThumbprint: assign({
|
|
thumbprint: (_context, event) => {
|
|
return (event.response || '') as string;
|
|
},
|
|
}),
|
|
resetLinkTransactionId: model.assign({
|
|
linkTransactionId: () => '',
|
|
}),
|
|
|
|
resetSelectedVoluntaryClaims: model.assign({
|
|
selectedVoluntaryClaims: () => [],
|
|
}),
|
|
|
|
setSelectedVc: assign({
|
|
selectedVc: (context, event) => {
|
|
return {...event.vc};
|
|
},
|
|
}),
|
|
|
|
setlinkTransactionResponse: assign({
|
|
linkTransactionResponse: (context, event) =>
|
|
event.data as linkTransactionResponse,
|
|
}),
|
|
|
|
expandLinkTransResp: assign({
|
|
authFactors: context => context.linkTransactionResponse.authFactors,
|
|
|
|
authorizeScopes: context =>
|
|
context.linkTransactionResponse.authorizeScopes,
|
|
|
|
clientName: context => context.linkTransactionResponse.clientName,
|
|
|
|
configs: context => context.linkTransactionResponse.configs,
|
|
|
|
essentialClaims: context =>
|
|
context.linkTransactionResponse.essentialClaims,
|
|
|
|
linkTransactionId: context =>
|
|
context.linkTransactionResponse.linkTransactionId,
|
|
|
|
logoUrl: context => context.linkTransactionResponse.logoUrl,
|
|
|
|
voluntaryClaims: context =>
|
|
context.linkTransactionResponse.voluntaryClaims,
|
|
}),
|
|
|
|
setClaims: context => {
|
|
context.voluntaryClaims.map(claim => {
|
|
context.isSharing[claim] = false;
|
|
});
|
|
},
|
|
|
|
SetErrorMessage: assign({
|
|
errorMessage: (context, event) =>
|
|
i18n.t(`errors.genericError`, {
|
|
ns: 'common',
|
|
}),
|
|
}),
|
|
|
|
setConsentClaims: assign({
|
|
isSharing: (context, event) => {
|
|
context.isSharing[event.claim] = !event.enable;
|
|
if (!event.enable) {
|
|
context.selectedVoluntaryClaims.push(event.claim);
|
|
} else {
|
|
context.selectedVoluntaryClaims =
|
|
context.selectedVoluntaryClaims.filter(
|
|
eachClaim => eachClaim !== event.claim,
|
|
);
|
|
}
|
|
return {...context.isSharing};
|
|
},
|
|
}),
|
|
setLinkedTransactionId: assign({
|
|
linkedTransactionId: (context, event) => event.data as string,
|
|
}),
|
|
},
|
|
services: {
|
|
linkTransaction: async context => {
|
|
const response = await request(
|
|
'POST',
|
|
'/v1/esignet/linked-authorization/v2/link-transaction',
|
|
{
|
|
requestTime: String(new Date().toISOString()),
|
|
request: {
|
|
linkCode: context.linkCode,
|
|
},
|
|
},
|
|
ESIGNET_BASE_URL,
|
|
);
|
|
return response.response;
|
|
},
|
|
|
|
sendAuthenticate: async context => {
|
|
let privateKey;
|
|
const individualId = context.selectedVc.vcMetadata.id;
|
|
if (!isHardwareKeystoreExists) {
|
|
privateKey = await getPrivateKey(
|
|
context.selectedVc.walletBindingResponse?.walletBindingId,
|
|
);
|
|
}
|
|
|
|
var walletBindingResponse = context.selectedVc.walletBindingResponse;
|
|
var jwt = await getJwt(privateKey, individualId, context.thumbprint);
|
|
|
|
const response = await request(
|
|
'POST',
|
|
'/v1/esignet/linked-authorization/authenticate',
|
|
{
|
|
requestTime: String(new Date().toISOString()),
|
|
request: {
|
|
linkedTransactionId: context.linkTransactionId,
|
|
individualId: individualId,
|
|
challengeList: [
|
|
{
|
|
authFactorType: 'WLA',
|
|
challenge: jwt,
|
|
format: 'jwt',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
ESIGNET_BASE_URL,
|
|
);
|
|
return response.response.linkedTransactionId;
|
|
},
|
|
|
|
sendConsent: async context => {
|
|
let privateKey;
|
|
const individualId = context.selectedVc.vcMetadata.id;
|
|
if (!isHardwareKeystoreExists) {
|
|
privateKey = await getPrivateKey(
|
|
context.selectedVc.walletBindingResponse?.walletBindingId,
|
|
);
|
|
}
|
|
|
|
const jwt = await getJwt(
|
|
privateKey,
|
|
individualId,
|
|
context.thumbprint,
|
|
);
|
|
|
|
const response = await request(
|
|
'POST',
|
|
'/v1/esignet/linked-authorization/authenticate',
|
|
{
|
|
requestTime: String(new Date().toISOString()),
|
|
request: {
|
|
linkedTransactionId: context.linkTransactionId,
|
|
individualId: individualId,
|
|
challengeList: [
|
|
{
|
|
authFactorType: 'WLA',
|
|
challenge: jwt,
|
|
format: 'jwt',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
ESIGNET_BASE_URL,
|
|
);
|
|
var linkedTrnId = response.response.linkedTransactionId;
|
|
|
|
const resp = await request(
|
|
'POST',
|
|
'/v1/esignet/linked-authorization/consent',
|
|
{
|
|
requestTime: String(new Date().toISOString()),
|
|
request: {
|
|
linkedTransactionId: linkedTrnId,
|
|
acceptedClaims: context.essentialClaims.concat(
|
|
context.selectedVoluntaryClaims,
|
|
),
|
|
permittedAuthorizeScopes: context.authorizeScopes,
|
|
},
|
|
},
|
|
ESIGNET_BASE_URL,
|
|
);
|
|
console.log(resp.response.linkedTransactionId);
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
export function createQrLoginMachine(serviceRefs: AppServices) {
|
|
return qrLoginMachine.withContext({
|
|
...qrLoginMachine.context,
|
|
serviceRefs,
|
|
});
|
|
}
|
|
|
|
type State = StateFrom<typeof qrLoginMachine>;
|
|
|
|
export function selectMyVcs(state: State) {
|
|
return state.context.myVcs;
|
|
}
|
|
export function selectIsWaitingForData(state: State) {
|
|
return state.matches('waitingForData');
|
|
}
|
|
|
|
export function selectDomainName(state: State) {
|
|
return state.context.domainName;
|
|
}
|
|
|
|
export function selectIsLinkTransaction(state: State) {
|
|
return state.matches('linkTransaction');
|
|
}
|
|
|
|
export function selectIsloadMyVcs(state: State) {
|
|
return state.matches('loadMyVcs');
|
|
}
|
|
|
|
export function selectIsShowingVcList(state: State) {
|
|
return state.matches('showvcList');
|
|
}
|
|
|
|
export function selectIsisVerifyingIdentity(state: State) {
|
|
return state.matches('faceAuth');
|
|
}
|
|
|
|
export function selectIsInvalidIdentity(state: State) {
|
|
return state.matches('invalidIdentity');
|
|
}
|
|
|
|
export function selectIsShowError(state: State) {
|
|
return state.matches('ShowError');
|
|
}
|
|
|
|
export function selectIsRequestConsent(state: State) {
|
|
return state.matches('requestConsent');
|
|
}
|
|
export function selectIsSendingAuthenticate(state: State) {
|
|
return state.matches('sendingAuthenticate');
|
|
}
|
|
|
|
export function selectIsSendingConsent(state: State) {
|
|
return state.matches('sendingConsent');
|
|
}
|
|
|
|
export function selectIsVerifyingSuccesful(state: State) {
|
|
return state.matches('success');
|
|
}
|
|
|
|
export function selectSelectedVc(state: State) {
|
|
return state.context.selectedVc;
|
|
}
|
|
|
|
export function selectLinkTransactionResponse(state: State) {
|
|
return state.context.linkTransactionResponse;
|
|
}
|
|
export function selectEssentialClaims(state: State) {
|
|
return state.context.essentialClaims;
|
|
}
|
|
|
|
export function selectVoluntaryClaims(state: State) {
|
|
return state.context.voluntaryClaims;
|
|
}
|
|
|
|
export function selectLogoUrl(state: State) {
|
|
return state.context.logoUrl;
|
|
}
|
|
|
|
export function selectClientName(state: State) {
|
|
return state.context.clientName;
|
|
}
|
|
|
|
export function selectErrorMessage(state: State) {
|
|
return state.context.errorMessage;
|
|
}
|
|
export function selectIsSharing(state: State) {
|
|
return state.context.isSharing;
|
|
}
|