Skip to content

Commit

Permalink
support profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
vmidyllic committed Jul 21, 2023
1 parent 3fbc333 commit 23a89d7
Show file tree
Hide file tree
Showing 22 changed files with 303 additions and 632 deletions.
13 changes: 8 additions & 5 deletions src/credentials/status/on-chain-revocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,24 @@ export class OnChainResolver implements CredentialStatusResolver {
issuer: string;
} {
const url = new URL(id);
if (!url.searchParams.has('contractAddress')) {
const contractId = url.searchParams.get('contractAddress');
const revocationNonceParam = url.searchParams.get('revocationNonce');

if (!contractId) {
throw new Error('contractAddress not found');
}
if (!url.searchParams.has('revocationNonce')) {
if (!revocationNonceParam) {
throw new Error('revocationNonce not found');
}

const issuer = id.split('/')[0];
if (!issuer) {
throw new Error('issuer not found in credentialStatus id');
}

// TODO (illia-korotia): after merging core v2 need to parse contract address from did if `contractAddress` is not present in id as param
const contractId = url.searchParams.get('contractAddress');
const revocationNonce = parseInt(url.searchParams.get('revocationNonce')!, 10);
const parts = contractId!.split(':');
const revocationNonce = parseInt(revocationNonceParam, 10);
const parts = contractId.split(':');
if (parts.length != 2) {
throw new Error('invalid contract address');
}
Expand Down
139 changes: 49 additions & 90 deletions src/iden3comm/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ import {
ZeroKnowledgeProofResponse
} from '../types';
import { DID } from '@iden3/js-iden3-core';
import { ProvingMethodAlg, proving } from '@iden3/js-jwz';
import { proving } from '@iden3/js-jwz';

import * as uuid from 'uuid';
import { ICredentialWallet } from '../../credentials';
import { ProofQuery, W3CCredential } from '../../verifiable';
import { ProofQuery } from '../../verifiable';
import { byteDecoder, byteEncoder } from '../../utils';
import { IIdentityWallet, generateProfileDID } from '../../identity';

/**
* Interface that allows the processing of the authorization request in the raw format for given identifier
Expand Down Expand Up @@ -59,16 +57,17 @@ export interface IAuthHandler {
authResponse: AuthorizationResponseMessage;
}>;
}

interface AuthHandlerOptions {
/**
*
* Options to pass to auth handler
*
* @export
* @beta
* @interface AuthHandlerOptions
*/
export interface AuthHandlerOptions {
mediaType: MediaType;
packerOptions:
| {
profileNonce: number;
provingMethodAlg: ProvingMethodAlg;
}
| JWSPackerParams;
credential?: W3CCredential;
packerOptions?: JWSPackerParams;
}

/**
Expand All @@ -86,16 +85,12 @@ export class AuthHandler implements IAuthHandler {
* Creates an instance of AuthHandler.
* @param {IPackageManager} _packerMgr - package manager to unpack message envelope
* @param {IProofService} _proofService - proof service to verify zk proofs
* @param {ICredentialWallet} _credentialWallet - wallet to search credentials
* @param {IIdentityWallet} _identityWallet - wallet to search profiles and identities
*
*
*/
constructor(
private readonly _packerMgr: IPackageManager,
private readonly _proofService: IProofService,
private readonly _credentialWallet: ICredentialWallet,
private readonly _identityWallet: IIdentityWallet
private readonly _proofService: IProofService
) {}

/**
Expand All @@ -114,6 +109,18 @@ export class AuthHandler implements IAuthHandler {
return authRequest;
}

/**
* unpacks authorization request and packs authorization response
* @export
* @beta
* @param {did} did - sender DID
* @param {Uint8Array} request - raw byte message
* @returns `Promise<{
token: string;
authRequest: AuthorizationRequestMessage;
authResponse: AuthorizationResponseMessage;
}>`
*/
async handleAuthorizationRequest(
did: DID,
request: Uint8Array,
Expand All @@ -130,37 +137,35 @@ export class AuthHandler implements IAuthHandler {
}

if (!opts) {
const zkpPackerOpts = {
profileNonce: 0,
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg,
alg: ''
};

opts = {
packerOptions: zkpPackerOpts,
mediaType: MediaType.ZKPMessage
};
}

if (opts.mediaType === MediaType.SignedMessage && !opts.packerOptions) {
throw new Error(`jws packer options are required for ${MediaType.SignedMessage}`);
}

if (authRequest.to) {
// override sender did if it's explicitly specified in the auth request
did = DID.parse(authRequest.to);
}
const guid = uuid.v4();
const senderDID =
opts!.mediaType === MediaType.SignedMessage
? did.string()
: generateProfileDID(did, opts!.packerOptions!.profileNonce).string();

const authResponse: AuthorizationResponseMessage = {
id: guid,
typ: opts!.mediaType,
typ: opts.mediaType,
type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE,
thid: authRequest.thid ?? guid,
body: {
message: authRequest?.body?.message,
scope: []
},
from: senderDID,
from: did.string(),
to: authRequest.from
};

for (const proofReq of authRequest.body!.scope) {
for (const proofReq of authRequest.body.scope) {
const zkpReq: ZeroKnowledgeProofRequest = {
id: proofReq.id,
circuitId: proofReq.circuitId as CircuitId,
Expand All @@ -169,79 +174,33 @@ export class AuthHandler implements IAuthHandler {

const query = proofReq.query as unknown as ProofQuery;

if (opts!.credential) {
proofReq.query.claimId = opts!.credential.id;
}

const { credential, nonce } = await this.findCredentialForDID(did, query);

const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof(
zkpReq,
did,
credential,
{
credentialSubjectProfileNonce: nonce,
skipRevocation: query.skipClaimRevocationCheck ?? false,
authProfileNonce: opts!.packerOptions!.profileNonce
skipRevocation: query.skipClaimRevocationCheck ?? false
}
);

authResponse.body.scope.push(zkpRes);
}

const msgBytes = byteEncoder.encode(JSON.stringify(authResponse));

const packerOpts =
opts.mediaType === MediaType.SignedMessage
? opts.packerOptions
: {
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg
};

const token = byteDecoder.decode(
await this._packerMgr.pack(opts!.mediaType, msgBytes, {
await this._packerMgr.pack(opts.mediaType, msgBytes, {
senderDID: did,
...opts!.packerOptions
...packerOpts
})
);
return { authRequest, authResponse, token };
}
async findCredentialForDID(
did: DID,
query: ProofQuery
): Promise<{ credential: W3CCredential; nonce: number }> {
const creds = await this._credentialWallet.findByQuery(query);
if (!creds.length) {
throw new Error(`no credential satisfied query`);
}

const profiles = await this._identityWallet.getProfilesByDID(did);

const ownedCreds = creds.filter((cred) => {
const credentialSubjectId = cred.credentialSubject['id'] as string; // credential subject
return (
credentialSubjectId == did.string() ||
profiles.some((p) => {
return p.id === credentialSubjectId;
})
);
});

if (!ownedCreds.length) {
throw new Error(`no credentials belong to did ot its profiles`);
}

// For EQ / IN / NIN / LT / GT operations selective if credential satisfies query - we can get any.
// TODO: choose credential for selective credentials
const cred = query.skipClaimRevocationCheck
? ownedCreds[0]
: (await this._credentialWallet.findNonRevokedCredential(ownedCreds)).cred;

// get profile nonce that was used as a part of subject in the credential

let subjectDID = cred.credentialSubject['id'];
if (!subjectDID) {
subjectDID = cred.issuer; // self credential
}
const credentialSubjectProfileNonce =
subjectDID === did.string()
? 0
: profiles.find((p) => {
return p.id === subjectDID;
})!.nonce;

return { credential: cred, nonce: credentialSubjectProfileNonce };
return { authRequest, authResponse, token };
}
}
71 changes: 30 additions & 41 deletions src/iden3comm/handlers/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,25 @@ import {
CredentialsOfferMessage,
IPackageManager,
JWSPackerParams,
MessageFetchRequestMessage,
PackerParams
MessageFetchRequestMessage
} from '../types';
import { DID } from '@iden3/js-iden3-core';

import * as uuid from 'uuid';
import { W3CCredential } from '../../verifiable';
import { byteDecoder, byteEncoder } from '../../utils';
import { ProvingMethodAlg, proving } from '@iden3/js-jwz';
import { ICredentialWallet } from '../../credentials';
import { IIdentityWallet } from '../../identity';
import { proving } from '@iden3/js-jwz';

interface FetchHandlerOptions {
/**
*
* Options to pass to fetch handler
*
* @export
* @beta
* @interface FetchHandlerOptions
*/
export interface FetchHandlerOptions {
mediaType: MediaType;
packerOptions:
| {
profileNonce: number;
provingMethodAlg: ProvingMethodAlg;
}
| JWSPackerParams;
did?: DID;
packerOptions?: JWSPackerParams;
headers?: {
[key: string]: string;
};
Expand Down Expand Up @@ -67,14 +65,8 @@ export class FetchHandler implements IFetchHandler {
/**
* Creates an instance of AuthHandler.
* @param {IPackageManager} _packerMgr - package manager to unpack message envelope
* @param {ICredentialWallet} _credentialWallet - wallet to search credentials
* @param {IIdentityWallet} _identityWallet - wallet to search profiles and identities
*/
constructor(
private readonly _packerMgr: IPackageManager,
private readonly _identityWallet: IIdentityWallet,
private readonly _credentialWallet: ICredentialWallet
) {}
constructor(private readonly _packerMgr: IPackageManager) {}

/**
* Handles only messages with credentials/1.0/offer type
Expand All @@ -90,27 +82,24 @@ export class FetchHandler implements IFetchHandler {
opts?: FetchHandlerOptions
): Promise<W3CCredential[]> {
if (!opts) {
const zkpPackerOpts = {
profileNonce: 0,
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg,
alg: ''
};

opts = {
packerOptions: zkpPackerOpts,
mediaType: MediaType.ZKPMessage
};
}

if (opts.mediaType === MediaType.SignedMessage && !opts.packerOptions) {
throw new Error(`jws packer options are required for ${MediaType.SignedMessage}`);
}

const { unpackedMessage: message } = await this._packerMgr.unpack(offer);
const offerMessage = message as unknown as CredentialsOfferMessage;
if (message.type !== PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE) {
throw new Error('Invalid media type');
}
const credentials: W3CCredential[] = [];

for (let index = 0; index < (offerMessage?.body?.credentials?.length ?? 0); index++) {
const credentialInfo = offerMessage?.body?.credentials[index];
for (let index = 0; index < offerMessage.body.credentials.length; index++) {
const credentialInfo = offerMessage.body.credentials[index];

const guid = uuid.v4();
const fetchRequest: MessageFetchRequestMessage = {
Expand All @@ -119,24 +108,25 @@ export class FetchHandler implements IFetchHandler {
type: PROTOCOL_MESSAGE_TYPE.CREDENTIAL_FETCH_REQUEST_MESSAGE_TYPE,
thid: offerMessage.thid ?? guid,
body: {
id: credentialInfo?.id || ''
id: credentialInfo.id
},
from: opts.did ? opts.did.string() : offerMessage.to,
from: offerMessage.to,
to: offerMessage.from
};

const msgBytes = byteEncoder.encode(JSON.stringify(fetchRequest));

// check if offer is for profile we need to find its nonce
// if it opts did is set, then
opts.packerOptions.profileNonce = opts.did
? 0
: this._identityWallet.getProfileNonce(DID.parse(offerMessage.to!));
const packerOpts =
opts.mediaType === MediaType.SignedMessage
? opts.packerOptions
: {
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg
};

const token = byteDecoder.decode(
await this._packerMgr.pack(opts.mediaType, msgBytes, {
senderDID: offerMessage.to,
...opts.packerOptions
...packerOpts
})
);
let message: { body: { credential: W3CCredential } };
Expand All @@ -147,7 +137,8 @@ export class FetchHandler implements IFetchHandler {
const resp = await fetch(offerMessage.body.url, {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/x-www-form-urlencoded',
...(opts.headers ?? {})
},
body: token
});
Expand All @@ -164,8 +155,6 @@ export class FetchHandler implements IFetchHandler {
);
}
}

this._credentialWallet.saveAll(credentials);
return credentials;
}
}
Loading

0 comments on commit 23a89d7

Please sign in to comment.