diff --git a/docs/Docs.md b/docs/Docs.md index f7338444f..a46cf9c5e 100644 --- a/docs/Docs.md +++ b/docs/Docs.md @@ -6,7 +6,7 @@ DAF can be used by using Typescript API directly, or by using remote GraphQL api ## Typescript -```typescript= +```typescript // We will be using 'did:ethr' identities import { IdentityProvider } from 'daf-ethr-did' @@ -56,12 +56,11 @@ import { DIDCommActionHandler } from 'daf-did-comm' import { W3cActionHandler } from 'daf-w3c' import { SdrActionHandler } from 'daf-selective-disclosure' const actionHandler = new W3cActionHandler() -actionHandler - .setNext(new SdrActionHandler()) - .setNext(new DIDCommActionHandler()) +actionHandler.setNext(new SdrActionHandler()).setNext(new DIDCommActionHandler()) -// Initializing the Core +// Initializing the Core import { Agent } from 'daf-core' +// we need defaultIdentityProvider = 'rinkeby-ethr-did' const agent = new Agent({ didResolver, identityProviders: [rinkebyIdentityProvider], @@ -82,7 +81,8 @@ await createConnection({ ``` ## GraphQL Server -```typescript= + +```typescript import { ApolloServer } from 'apollo-server' import { CoreGql, Message, EventTypes } from 'daf-core' import { W3cGql } from 'daf-w3c' @@ -97,12 +97,7 @@ const server = new ApolloServer({ W3cGql.typeDefs, SdrGql.typeDefs, ], - resolvers: merge( - CoreGql.resolvers, - CoreGql.IdentityManager.resolvers, - W3cGql.resolvers, - SdrGql.resolvers, - ), + resolvers: merge(CoreGql.resolvers, CoreGql.IdentityManager.resolvers, W3cGql.resolvers, SdrGql.resolvers), context: () => ({ agent }), introspection: true, }) @@ -145,62 +140,64 @@ mutation createIdentity($type: String!) { ## Sign JWT -``` typescript= +```typescript const data = { - iss: 'did:example:123', + issuer: 'did:example:123', replyUrl: 'https://example.com/didcomm', tag: 'session-123', claims: [ { reason: 'We are required by law to collect this information', - type: 'name', - essential: true + claimType: 'name', + essential: true, }, { reason: 'You can get %30 discount if you are a member of the club', - type: 'status', - value: 'member', - iss: [ + credentialContext: 'https://www.w3.org/2018/credentials/v1', + credentialType: 'ClubMembership', + claimType: 'status', + claimValue: 'member', + issuers: [ { did: 'did:ethr:567', - url: 'https://join-the-club.partner1.com' + url: 'https://join-the-club.partner1.com', }, { did: 'did:ethr:659', - url: 'https://ecosystem.io' - } - ] - } + url: 'https://ecosystem.io', + }, + ], + }, ], - vc: ['JWT-public-profile...'] + credentials: ['JWT-public-profile...'], } ``` - ### Typescript ```typescript const sdrJwt = await core.handleAction({ type: 'sign.sdr.jwt', - data + data, }) ``` ### GraphQL - ```graphql -mutation signSdrJwt($data: SDRInput!){ +mutation signSdrJwt($data: SDRInput!) { signSdr(data: $data) } ``` ## Show as QR Code + ```typescript const url = encodeURI('https://example.com/ssi?c_i=') + sdrJwt ``` -``` html - { - const a = 100 - it('should run a dummy test', () => { - expect(a).toEqual(100) - }) -}) diff --git a/packages/daf-selective-disclosure/src/__tests__/helper.test.ts b/packages/daf-selective-disclosure/src/__tests__/helper.test.ts new file mode 100644 index 000000000..7348e59fa --- /dev/null +++ b/packages/daf-selective-disclosure/src/__tests__/helper.test.ts @@ -0,0 +1,132 @@ +import { Identity, Presentation, Credential, Claim } from 'daf-core' +import { SelectiveDisclosureRequest } from '../action-handler' +import { validatePresentationAgainstSdr } from '../helper' + +describe('daf-selective-disclosure-helper', () => { + it('should validate presentation for sdr', () => { + const sdr: SelectiveDisclosureRequest = { + issuer: 'did:example:123', + replyUrl: 'https://example.com/didcomm', + tag: 'session-123', + claims: [ + { + reason: 'We are required by law to collect this information', + claimType: 'firstName', + essential: true, + }, + { + reason: 'You can get %30 discount if you are a member of the club', + credentialContext: 'https://www.w3.org/2018/credentials/v1', + credentialType: 'ClubMembership', + claimType: 'status', + claimValue: 'member', + issuers: [ + { + did: 'did:ethr:567', + url: 'https://join-the-club.partner1.com', + }, + { + did: 'did:ethr:659', + url: 'https://ecosystem.io', + }, + ], + }, + ], + credentials: ['JWT-public-profile...'], + } + + const identity = new Identity() + identity.did = 'did:example:555' + + const claim1 = new Claim() + claim1.issuer = identity + claim1.subject = identity + claim1.type = 'firstName' + claim1.value = 'Alice' + claim1.isObj = false + + const claim2 = new Claim() + claim2.issuer = identity + claim2.subject = identity + claim2.type = 'lastName' + claim2.value = 'Smith' + claim2.isObj = false + + const credential1 = new Credential() + credential1.issuer = identity + credential1.subject = identity + credential1.context = ['https://www.w3.org/2018/credentials/v1'] + credential1.type = ['VerifiableCredential'] + credential1.claims = [claim1, claim2] + + const identity1 = new Identity() + identity1.did = 'did:ethr:659' + + const claim3 = new Claim() + claim3.issuer = identity1 + claim3.subject = identity + claim3.type = 'status' + claim3.value = 'member' + claim3.isObj = false + + const credential3 = new Credential() + credential3.issuer = identity1 + credential3.subject = identity + credential3.context = ['https://www.w3.org/2018/credentials/v1'] + credential3.type = ['VerifiableCredential', 'ClubMembership'] + credential3.claims = [claim3] + + const presentation = new Presentation() + presentation.issuer = identity + presentation.audience = identity + presentation.context = ['https://www.w3.org/2018/credentials/v1'] + presentation.type = ['VerifiablePresentation'] + presentation.credentials = [credential1, credential3] + + const result = validatePresentationAgainstSdr(presentation, sdr) + + expect(result.claims[0].credentials[0].claims[0].type).toEqual('firstName') + expect(result.valid).toEqual(true) + }) + + it('should invalidate presentation for sdr', () => { + const sdr: SelectiveDisclosureRequest = { + issuer: 'did:example:123', + claims: [ + { + reason: 'We are required by law to collect this information', + claimType: 'firstName', + essential: true, + }, + ], + } + + const identity = new Identity() + identity.did = 'did:example:555' + + const claim2 = new Claim() + claim2.issuer = identity + claim2.subject = identity + claim2.type = 'lastName' + claim2.value = 'Smith' + claim2.isObj = false + + const credential1 = new Credential() + credential1.issuer = identity + credential1.subject = identity + credential1.context = ['https://www.w3.org/2018/credentials/v1'] + credential1.type = ['VerifiableCredential'] + credential1.claims = [claim2] + + const presentation = new Presentation() + presentation.issuer = identity + presentation.audience = identity + presentation.context = ['https://www.w3.org/2018/credentials/v1'] + presentation.type = ['VerifiablePresentation'] + presentation.credentials = [credential1] + + const result = validatePresentationAgainstSdr(presentation, sdr) + + expect(result.valid).toEqual(false) + }) +}) diff --git a/packages/daf-selective-disclosure/src/action-handler.ts b/packages/daf-selective-disclosure/src/action-handler.ts index 0efada769..0aa46a7b9 100644 --- a/packages/daf-selective-disclosure/src/action-handler.ts +++ b/packages/daf-selective-disclosure/src/action-handler.ts @@ -19,13 +19,14 @@ export interface SelectiveDisclosureRequest { replyUrl?: string tag?: string claims: CredentialRequestInput[] + credentials?: string[] } export interface CredentialRequestInput { reason?: string essential?: boolean - credentialType: string - credentialContext: string + credentialType?: string + credentialContext?: string claimType: string claimValue?: string issuers?: Issuer[] diff --git a/packages/daf-selective-disclosure/src/graphql.ts b/packages/daf-selective-disclosure/src/graphql.ts index e5f31289f..87d570d8d 100644 --- a/packages/daf-selective-disclosure/src/graphql.ts +++ b/packages/daf-selective-disclosure/src/graphql.ts @@ -1,25 +1,37 @@ -import { Agent, Message, Claim } from 'daf-core' +import { Agent, Message, Presentation } from 'daf-core' import { DataStore } from 'daf-data-store' import { ActionTypes, ActionSignSdr, SelectiveDisclosureRequest } from './action-handler' -import { findCredentialsForSdr } from './helper' +import { findCredentialsForSdr, validatePresentationAgainstSdr } from './helper' interface Context { agent: Agent dataStore: DataStore } -const signSdrJwt = async (_: any, args: { data: SelectiveDisclosureRequest }, ctx: Context) => +const signSdrJwt = async (_: any, args: { data: SelectiveDisclosureRequest }, ctx: Context) => ctx.agent.handleAction({ type: ActionTypes.signSdr, data: args.data, } as ActionSignSdr) - const sdr = async (message: Message, { did }: { did: string }) => { return findCredentialsForSdr(message.data, did) } +const validateAgainstSdr = async ( + presentation: Presentation, + { sdr }: { sdr: SelectiveDisclosureRequest }, +) => { + const fullPresentation = await Presentation.findOne(presentation.hash, { + relations: ['credentials', 'credentials.claims'], + }) + return validatePresentationAgainstSdr(fullPresentation, sdr) +} + export const resolvers = { + Presentation: { + validateAgainstSdr, + }, Message: { sdr, }, @@ -50,6 +62,7 @@ export const typeDefs = ` replyUrl: String tag: String claims: [CredentialRequestInput]! + credentials: [String] } type Issuer { @@ -67,6 +80,15 @@ export const typeDefs = ` credentials: [Credential] } + type PresentationValidation { + valid: Boolean! + claims: [CredentialRequest] + } + + extend type Presentation { + validateAgainstSdr(data: SDRInput!): PresentationValidation + } + extend type Message { sdr(did: ID): [CredentialRequest] } diff --git a/packages/daf-selective-disclosure/src/helper.ts b/packages/daf-selective-disclosure/src/helper.ts index 2f9a82e4b..964f8de6f 100644 --- a/packages/daf-selective-disclosure/src/helper.ts +++ b/packages/daf-selective-disclosure/src/helper.ts @@ -1,4 +1,4 @@ -import { Claim } from 'daf-core' +import { Claim, Presentation } from 'daf-core' import { SelectiveDisclosureRequest } from './action-handler' import { In, Like } from 'typeorm' @@ -38,3 +38,61 @@ export const findCredentialsForSdr = async (sdr: SelectiveDisclosureRequest, did } return result } + +export const validatePresentationAgainstSdr = ( + presentation: Presentation, + sdr: SelectiveDisclosureRequest, +) => { + let valid = true + let claims = [] + for (const credentialRequest of sdr.claims) { + let credentials = presentation.credentials.filter(credential => { + if ( + credentialRequest.claimType && + credentialRequest.claimValue && + !credential.claims.find( + claim => claim.type === credentialRequest.claimType && claim.value === credentialRequest.claimValue, + ) + ) { + return false + } + + if ( + credentialRequest.claimType && + !credentialRequest.claimValue && + !credential.claims.find(claim => claim.type === credentialRequest.claimType) + ) { + return false + } + + if ( + credentialRequest.issuers && + !credentialRequest.issuers.map(i => i.did).includes(credential.issuer.did) + ) { + return false + } + if ( + credentialRequest.credentialContext && + !credential.context.includes(credentialRequest.credentialContext) + ) { + return false + } + + if (credentialRequest.credentialType && !credential.type.includes(credentialRequest.credentialType)) { + return false + } + + return true + }) + + if (credentialRequest.essential === true && credentials.length == 0) { + valid = false + } + + claims.push({ + ...credentialRequest, + credentials, + }) + } + return { valid, claims } +}