Skip to content

Commit

Permalink
Merge pull request #283 from docknetwork/DCKM-582-holder-should-be-ab…
Browse files Browse the repository at this point in the history
…le-to-present-open-id-credentials

Dckm 582 holder should be able to present open id credentials
  • Loading branch information
maycon-mello authored Aug 29, 2024
2 parents 04ba8c3 + 18b7a9b commit c2e5c7d
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 2 deletions.
124 changes: 124 additions & 0 deletions packages/core/src/credentials/oidvc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
acquireOpenIDCredentialFromURI,
getAuthURL,
decodeRequestJWT,
getPresentationSubmision,
} from './oidvc'; // replace with your actual file path
import {credentialServiceRPC} from '@docknetwork/wallet-sdk-wasm/src/services/credential';
import {MetadataClient} from '@sphereon/oid4vci-client';
import jwtDecode from 'jwt-decode';
import axios from 'axios';
import {pexService} from '@docknetwork/wallet-sdk-wasm/src/services/pex';

jest.mock('@docknetwork/wallet-sdk-wasm/src/services/credential');
jest.mock('@sphereon/oid4vci-client');
jest.mock('jwt-decode');
jest.mock('axios');
jest.mock('@docknetwork/wallet-sdk-wasm/src/services/pex');

describe('acquireOpenIDCredentialFromURI', () => {
const didProvider: any = {
getDIDKeyPairs: jest.fn(),
};

const uri = 'https://example.com/credential';

beforeEach(() => {
didProvider.getDIDKeyPairs.mockResolvedValue([{id: 'did:example:123'}]);
(credentialServiceRPC.acquireOIDCredential as jest.Mock).mockResolvedValue({
credential: 'fake-credential',
});
});

it('should acquire OID credential without authorization URL', async () => {
const response = await acquireOpenIDCredentialFromURI({didProvider, uri});
expect(didProvider.getDIDKeyPairs).toHaveBeenCalled();
expect(credentialServiceRPC.acquireOIDCredential).toHaveBeenCalledWith({
uri,
holderKeyDocument: {id: 'did:example:123'},
});
expect(response).toBe('credential');
});

it('should acquire OID credential with authorization URL', async () => {
const getAuthCode = jest.fn().mockResolvedValue('auth-code');
(credentialServiceRPC.acquireOIDCredential as jest.Mock).mockResolvedValueOnce({
authorizationURL: 'https://example.com/auth',
});

const response = await acquireOpenIDCredentialFromURI({
didProvider,
uri,
getAuthCode,
});
expect(getAuthCode).toHaveBeenCalledWith('https://example.com/auth');
expect(credentialServiceRPC.acquireOIDCredential).toHaveBeenCalledWith({
uri,
holderKeyDocument: {id: 'did:example:123'},
authorizationCode: 'auth-code',
});
expect(response).toBe('credential');
});
});

describe('getAuthURL', () => {
it('should generate an auth URL', async () => {
const uri = 'https://example.com?client_id=fake-client';
const metadata = {
authorizationServerMetadata: {
request_object_signing_alg_values_supported: ['RS256'],
},
authorization_endpoint: 'https://auth.example.com/authorize',
};
(MetadataClient.retrieveAllMetadata as jest.Mock).mockResolvedValue(metadata);

const result = await getAuthURL(uri);
expect(MetadataClient.retrieveAllMetadata).toHaveBeenCalledWith(
'fake-client',
);
expect(result).toContain('https://auth.example.com/authorize?');
expect(result).toContain('client_id=dock-wallet');
expect(result).toContain('redirect_uri=dockwallet://vp');
});
});

describe('decodeRequestJWT', () => {
it('should decode JWT from the request URI', async () => {
const uri = 'https://example.com?request_uri=https://example.com/jwt';
const jwt = 'some-jwt';
const decodedJWT = {sub: '1234567890', name: 'Testing', admin: true};
(axios.get as jest.Mock).mockResolvedValue({data: jwt});
(jwtDecode as jest.Mock).mockReturnValue(decodedJWT);

const result = await decodeRequestJWT(uri);
expect(axios.get).toHaveBeenCalledWith('https://example.com/jwt');
expect(jwtDecode).toHaveBeenCalledWith(jwt);
expect(result).toEqual(decodedJWT);
});
});

describe('getPresentationSubmision', () => {
it('should get presentation submission', async () => {
const credentials = [{id: 'credential-1'}];
const presentationDefinition = {id: 'presentation-definition-1'};
const holderDID = 'did:example:123';
const presentationSubmission = {definition_id: 'presentation-submission-1'};

(pexService.presentationFrom as jest.Mock).mockResolvedValue({
presentation_submission: presentationSubmission,
});

const result = await getPresentationSubmision({
credentials,
presentationDefinition,
holderDID,
});

expect(pexService.presentationFrom).toHaveBeenCalledWith({
presentationDefinition,
credentials,
holderDID,
});
expect(result).toEqual(presentationSubmission);
});
});
80 changes: 79 additions & 1 deletion packages/core/src/credentials/oidvc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@

import {IWallet} from '../types';
import {IDIDProvider} from '../did-provider';
import {credentialServiceRPC} from '@docknetwork/wallet-sdk-wasm/src/services/credential';
import {MetadataClient} from '@sphereon/oid4vci-client';
import jwtDecode from 'jwt-decode';
import axios from 'axios';
import {pexService} from '@docknetwork/wallet-sdk-wasm/src/services/pex';

export async function acquireOpenIDCredentialFromURI({
didProvider,
Expand Down Expand Up @@ -30,3 +33,78 @@ export async function acquireOpenIDCredentialFromURI({

return response.credential;
}

export async function getAuthURL(
uri: string,
walletClientId: string = 'dock-wallet',
requestedRedirectURI: string = 'dockwallet://vp',
) {
function buildOID4VPRequestURL(params, prefix = 'dockwallet://') {
return `${prefix}?${Object.keys(params)
.map(
key =>
`${encodeURIComponent(key)}=${encodeURIComponent(
typeof params[key] === 'object'
? JSON.stringify(params[key])
: params[key],
)}`,
)
.join('&')}`;
}

const searchParams = new URL(uri).searchParams;
const params = new URLSearchParams(searchParams);
const clientId = params.get('client_id');
const metadata = await MetadataClient.retrieveAllMetadata(clientId);
const requestedAlg =
metadata?.authorizationServerMetadata
?.request_object_signing_alg_values_supported[0];
const requestParams = {
scope: 'openid vp_token',
redirect_uri: requestedRedirectURI,
client_metadata:
requestedAlg && requestedAlg !== 'EdDSA'
? JSON.stringify({
vp_formats_supported: {
vc_json: {
alg_values_supported: [requestedAlg],
},
},
})
: ['EdDSA'],
};

return buildOID4VPRequestURL(
{
...requestParams,
client_id: walletClientId,
},
metadata.authorization_endpoint,
);
}

export async function decodeRequestJWT(uri: string) {
const searchParams = new URL(uri).searchParams;
const params = new URLSearchParams(searchParams);
const requestUri = params.get('request_uri');
const jwt = await axios.get(requestUri).then(res => res.data);
const decoded = jwtDecode(jwt);

return decoded;
}

export async function getPresentationSubmision({
credentials,
presentationDefinition,
holderDID,
}) {
const presentation = await pexService.presentationFrom({
presentationDefinition,
credentials,
holderDID,
});

return presentation.presentation_submission;
}

pexService.evaluatePresentation;
6 changes: 6 additions & 0 deletions packages/wasm/src/services/pex/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export type FilterCredentialsParams = {
holderDIDs: string[];
};

export type CreatePresentationParams = {
credentials: any[];
presentationDefinition: any;
holderDID: string;
};

export type EvaluatePresentationParams = {
presentation: any;
presentationDefinition: any;
Expand Down
5 changes: 5 additions & 0 deletions packages/wasm/src/services/pex/service-rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FilterCredentialsParams,
validation,
EvaluatePresentationParams,
CreatePresentationParams,
} from './config';

export class PEXServiceRPC extends RpcService {
Expand All @@ -19,4 +20,8 @@ export class PEXServiceRPC extends RpcService {
validation.evaluatePresentation(params);
return this.call('evaluatePresentation', params);
}

async presentationFrom(params: CreatePresentationParams) {
return this.call('presentationFrom', params);
}
}
15 changes: 14 additions & 1 deletion packages/wasm/src/services/pex/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {
validation,
EvaluatePresentationParams,
FilterCredentialsParams,
CreatePresentationParams,
} from './config';
import {PEX} from '@sphereon/pex';
import {IPresentationDefinition, PEX} from '@sphereon/pex';

const pex: PEX = new PEX();

Expand Down Expand Up @@ -65,6 +66,7 @@ class PEXService {
rpcMethods = [
PEXService.prototype.filterCredentials,
PEXService.prototype.evaluatePresentation,
PEXService.prototype.presentationFrom,
];

filterCredentials(params: FilterCredentialsParams) {
Expand All @@ -89,6 +91,17 @@ class PEXService {

return result;
}

presentationFrom(params: CreatePresentationParams) {
const {credentials, presentationDefinition, holderDID} = params;
const result: IPresentation = pex.presentationFrom(
removeOptionalAttribute(presentationDefinition),
credentials,
holderDID,
);

return result;
}
}

export const pexService = new PEXService();
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4775,6 +4775,13 @@ available-typed-arrays@^1.0.5:
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==

available-typed-arrays@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==
dependencies:
possible-typed-array-names "^1.0.0"

axe-core@^4.6.2:
version "4.8.1"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.1.tgz#6948854183ee7e7eae336b9877c5bafa027998ea"
Expand Down

0 comments on commit c2e5c7d

Please sign in to comment.