Commit 6937fd65 authored by Hidde-Jan Jongsma's avatar Hidde-Jan Jongsma

Handle jolocom isuance and verification

parent 5e093829
......@@ -46,4 +46,14 @@ export interface ConnectorService {
* @returns All data needed for the front-end to set up a verifying session.
*/
handleVerifyCredentialRequest(request: CredentialVerifyRequest): Promise<any>;
/**
* Handle the disclosure of credentials, i.e. a reponse to a verify request.
*
* @returns All data needed to forward to the client.
*/
handleVerifyCredentialDisclosure(
request: CredentialVerifyRequest,
body: unknown,
): Promise<any>;
}
......@@ -142,9 +142,14 @@ export class IrmaService implements ConnectorService {
};
}
handleIrmaDisclosure(verifyRequest: CredentialVerifyRequest, jwt: string) {
async handleVerifyCredentialDisclosure(
verifyRequest: CredentialVerifyRequest,
body: { jwt: string },
) {
const publicKey = IRMASERVER_PUBLIC_KEY;
const { jwt } = body;
const decoded = verify(jwt, publicKey, {
issuer: 'irmaserver',
subject: 'disclosing_result',
......
......@@ -22,7 +22,19 @@ import { CredentialVerifyRequest } from 'src/requests/credential-verify-request.
import { JolocomCredentialRequestToken } from './jolocom-credential-request-token.entity';
import { keyIdToDid } from 'jolocom-lib/js/utils/helper';
const DEFAULT_EXPIRY_MS = 60 * 60 * 1000;
// const DEFAULT_EXPIRY_MS = 60 * 60 * 1000;
function omitId<T>(obj: T): Omit<T, 'id'> {
const newObj = {};
Object.keys(obj)
.filter(key => key !== 'id')
.forEach(key => {
newObj[key] = obj[key];
});
return newObj as Omit<T, 'id'>;
}
@Injectable()
export class JolocomService implements ConnectorService {
......@@ -79,6 +91,15 @@ export class JolocomService implements ConnectorService {
};
}
async handleIssueCredential(request: CredentialIssueRequest, jwt: string) {
const cred = await this.createCredentialToken(request, jwt);
const token = cred.encode();
this.logger.debug('Jolocom issue token', token);
return { token };
}
async handleVerifyCredentialRequest(request: CredentialVerifyRequest) {
const credRequest = await this.createCredentialRequestToken(request);
const token = credRequest.encode();
......@@ -90,6 +111,80 @@ export class JolocomService implements ConnectorService {
};
}
public async handleVerifyCredentialDisclosure(
verifyRequest: CredentialVerifyRequest,
body: { token: string },
): Promise<any> {
const { token } = body;
this.logger.debug('Jolocom disclosure token', token);
const jolocomCredentialResponse = JolocomLib.parse.interactionToken.fromJWT<
JolocomCredentialResponse
>(token);
if (!JolocomLib.util.validateDigestable(jolocomCredentialResponse)) {
throw new Error('Invalid signature');
}
const identityWallet = await this.getIdentityWallet(
verifyRequest.verifier.jolocomWallet,
);
const jolocomToken = await this.tokenRepository.findOneOrFail({
verifyRequest,
nonce: jolocomCredentialResponse.nonce,
});
const jolocomCredentialRequest: JSONWebToken<JolocomCredentialRequest> = JolocomLib.parse.interactionToken.fromJWT(
jolocomToken.token,
);
// The validate method will ensure the response contains a valid signature, is not expired,
// lists our did in the aud (audience) section, and contains the same jti (nonce) as the request.
await identityWallet.validateJWT(
jolocomCredentialResponse,
jolocomCredentialRequest,
);
const credentialResponse = jolocomCredentialResponse.interactionToken;
// We check against the request we created in a previous step
const validResponse = credentialResponse.satisfiesRequest(
jolocomCredentialRequest.interactionToken,
);
if (!validResponse) {
throw new Error('Incorrect credential received');
}
this.logger.debug(JSON.stringify(credentialResponse));
// Validate the provided credentials
const providedCredentials = credentialResponse.suppliedCredentials;
const signatureValidationResults = await JolocomLib.util.validateDigestables(
providedCredentials,
);
if (signatureValidationResults.every(result => result === true)) {
let data = {};
providedCredentials.forEach(credential => {
data = {
...data,
...omitId(credential.claim),
};
});
this.logger.debug('Got data');
this.logger.debug(JSON.stringify(data));
// Handle the data in the provided credentials
return data;
} else {
throw new Error('Not all provided credentials are valid');
}
}
/* JolocomService specific */
/* JolocomCredentialType methods */
......@@ -175,14 +270,14 @@ export class JolocomService implements ConnectorService {
// Return a Jolocom CredentialOffert interaction token
return identityWallet.create.interactionTokens.request.offer(
{
expires: new Date(Date.now() + DEFAULT_EXPIRY_MS), // FIXME Is this necessary?
// expires: new Date(Date.now() + DEFAULT_EXPIRY_MS), // FIXME Is this necessary?
callbackURL: this.configService.getUrl(
`/api/issue/jolocom/recieve?issueRequestId=${issueRequest.requestId}`,
`/api/issue/jolocom/receive?issueRequestId=${issueRequest.requestId}`,
),
offeredCredentials: [
{
type: jolocomType.type,
...jolocomType.offerMetadata,
// ...jolocomType.offerMetadata,
},
],
},
......@@ -252,9 +347,9 @@ export class JolocomService implements ConnectorService {
const credentialRequestToken = await identityWallet.create.interactionTokens.request.share(
{
expires: new Date(Date.now() + DEFAULT_EXPIRY_MS), // FIXME Is this necessary?
// expires: new Date(Date.now() + DEFAULT_EXPIRY_MS), // FIXME Is this necessary?
callbackURL: this.configService.getUrl(
`/verify/jolocom/disclose?verifyRequestId=${verifyRequest.requestId}`,
`/api/verify/jolocom/disclose?verifyRequestId=${verifyRequest.requestId}`,
),
credentialRequirements: [
{
......@@ -283,66 +378,4 @@ export class JolocomService implements ConnectorService {
return credentialRequestToken;
}
public async handleJolocomDisclosure(
verifyRequest: CredentialVerifyRequest,
token: string,
): Promise<any> {
this.logger.debug('Jolocom disclosure token', token);
const jolocomCredentialResponse = JolocomLib.parse.interactionToken.fromJWT<
JolocomCredentialResponse
>(token);
if (!JolocomLib.util.validateDigestable(jolocomCredentialResponse)) {
throw new Error('Invalid signature');
}
const identityWallet = await this.getIdentityWallet(
verifyRequest.verifier.jolocomWallet,
);
const jolocomToken = await this.tokenRepository.findOneOrFail({
verifyRequest,
nonce: jolocomCredentialResponse.nonce,
});
const jolocomCredentialRequest: JSONWebToken<JolocomCredentialRequest> = JolocomLib.parse.interactionToken.fromJWT(
jolocomToken.token,
);
// The validate method will ensure the response contains a valid signature, is not expired,
// lists our did in the aud (audience) section, and contains the same jti (nonce) as the request.
await identityWallet.validateJWT(
jolocomCredentialResponse,
jolocomCredentialRequest,
);
const credentialResponse = jolocomCredentialResponse.interactionToken;
// We check against the request we created in a previous step
const validResponse = credentialResponse.satisfiesRequest(
jolocomCredentialRequest.interactionToken,
);
if (!validResponse) {
throw new Error('Incorrect credential received');
}
// Validate the provided credentials
const providedCredentials = credentialResponse.suppliedCredentials;
const signatureValidationResults = await JolocomLib.util.validateDigestables(
providedCredentials,
);
if (signatureValidationResults.every(result => result === true)) {
// The credentials can be used
const data = providedCredentials.map(credential => credential.toJSON());
// Handle the data in the provided credentials
return data;
} else {
throw new Error('Not all provided credentials are valid');
}
}
}
import { Controller, Get, Query, Param } from '@nestjs/common';
import { Controller, Get, Query, Param, Body, Post } from '@nestjs/common';
import {
DecodeIssueRequestPipe,
......@@ -8,10 +8,14 @@ import { CredentialIssueRequest } from '../requests/credential-issue-request.ent
import { ConnectorsService } from '../connectors/connectors.service';
import { GetConnectorPipe } from '../connectors/get-connector.pipe';
import { ConnectorService } from '../connectors/connector-service.interface';
import { JolocomService } from 'src/connectors/jolocom/jolocom.service';
@Controller('api/issue')
export class IssueController {
constructor(private connectorsService: ConnectorsService) {}
constructor(
private connectorsService: ConnectorsService,
private jolocomService: JolocomService,
) {}
@Get()
async receiveCredentialIssueRequest(
......@@ -34,4 +38,14 @@ export class IssueController {
) {
return connectorService.handleIssueCredentialRequest(issueRequest);
}
@Post('jolocom/receive')
async handleCredentialIssueReceive(
@Query('issueRequestId', GetIssueRequestPipe)
issueRequest: CredentialIssueRequest,
@Body('token')
token: string,
) {
return this.jolocomService.handleIssueCredential(issueRequest, token);
}
}
......@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
import { IssueController } from './issue.controller';
import { ConnectorsModule } from 'src/connectors/connectors.module';
import { RequestsModule } from 'src/requests/requests.module';
import { JolocomModule } from 'src/connectors/jolocom/jolocom.module';
@Module({
imports: [ConnectorsModule, RequestsModule],
imports: [ConnectorsModule, RequestsModule, JolocomModule],
controllers: [IssueController],
})
export class IssueModule {}
......@@ -8,14 +8,21 @@ import {
import { Socket, Server } from 'socket.io';
import { CredentialVerifyRequest } from './credential-verify-request.entity';
import { DecodeVerifyRequestPipe } from './requests.pipe';
import {
DecodeVerifyRequestPipe,
DecodeIssueRequestPipe,
} from './requests.pipe';
import { ResponseStatus } from 'src/connectors/response-status.enum';
import { CredentialIssueRequest } from './credential-issue-request.entity';
import { RequestsService } from './requests.service';
@WebSocketGateway()
export class RequestsGateway {
@WebSocketServer()
server: Server;
constructor(private requestsService: RequestsService) {}
@SubscribeMessage('message')
handleMessage(@MessageBody() message: string): string {
return `${message.toLocaleUpperCase()}!`;
......@@ -29,6 +36,14 @@ export class RequestsGateway {
return verifyRequest;
}
@SubscribeMessage('issue-request')
handleIssueRequest(
@MessageBody(DecodeIssueRequestPipe)
issueRequest: CredentialIssueRequest,
): CredentialIssueRequest {
return issueRequest;
}
@SubscribeMessage('request-started')
handleRequestStarted(
@MessageBody()
......@@ -40,6 +55,67 @@ export class RequestsGateway {
client.join(requestId);
}
@SubscribeMessage('request-success')
async handleRequestDone(
@MessageBody()
{ requestId, connector }: { requestId: string; connector: string },
) {
console.log('Done with request', requestId);
const request = await this.requestsService.findRequestByRequestId(
requestId,
);
if (request instanceof CredentialIssueRequest) {
const responseToken = this.requestsService.encodeIssueRequestResponse(
request,
ResponseStatus.success,
connector,
);
this.sendRedirectResponse(
requestId,
ResponseStatus.success,
`${request.callbackUrl}${responseToken}`,
);
} else {
// FIXME: Raise error? We should probably only allow issue request to be
// done manually.
}
}
@SubscribeMessage('request-cancelled')
async handleRequestCancelled(
@MessageBody()
requestId: string,
) {
console.log('Done with request', requestId);
const request = await this.requestsService.findRequestByRequestId(
requestId,
);
let responseToken: string;
if (request instanceof CredentialIssueRequest) {
responseToken = this.requestsService.encodeIssueRequestResponse(
request,
ResponseStatus.cancelled,
);
} else {
responseToken = this.requestsService.encodeVerifyRequestResponse(
request,
ResponseStatus.cancelled,
);
}
this.sendRedirectResponse(
requestId,
ResponseStatus.cancelled,
`${request.callbackUrl}${responseToken}`,
);
}
sendRedirectResponse(
requestId: string,
status: ResponseStatus,
......
......@@ -63,10 +63,7 @@ export class RequestsService {
async findVerifyRequestByRequestId(requestId: string) {
const [type, uuid] = requestId.split(':');
console.log(type, CredentialVerifyRequest.requestType, uuid);
if (type !== CredentialVerifyRequest.requestType || !uuid) {
console.log(type !== CredentialVerifyRequest.requestType, !uuid);
return null;
}
......@@ -237,8 +234,8 @@ export class RequestsService {
encodeVerifyRequestResponse(
verifyRequest: CredentialVerifyRequest,
status: ResponseStatus,
connectorName: string,
data: any,
connectorName?: string,
data?: any,
) {
return this.encodeResponse(
{
......@@ -258,7 +255,7 @@ export class RequestsService {
encodeIssueRequestResponse(
issueRequest: CredentialIssueRequest,
status: ResponseStatus,
connectorName: string,
connectorName?: string,
) {
return this.encodeResponse(
{
......
......@@ -10,8 +10,6 @@ import {
} from '../requests/requests.pipe';
import { CredentialVerifyRequest } from '../requests/credential-verify-request.entity';
import { RequestsGateway } from '../requests/requests.gateway';
import { IrmaService } from 'src/connectors/irma/irma.service';
import { JolocomService } from 'src/connectors/jolocom/jolocom.service';
import { RequestsService } from 'src/requests/requests.service';
import { ResponseStatus } from 'src/connectors/response-status.enum';
......@@ -20,12 +18,8 @@ export class VerifyController {
constructor(
private gateway: RequestsGateway,
private connectorsService: ConnectorsService,
private irmaService: IrmaService,
private jolocomService: JolocomService,
private requestsService: RequestsService,
) {
console.log(this.gateway);
}
) {}
@Get()
async receiveCredentialVerifyRequest(
......@@ -46,69 +40,97 @@ export class VerifyController {
@Query('verifyRequestId', GetVerifyRequestPipe)
verifyRequest: CredentialVerifyRequest,
) {
console.log(verifyRequest, connectorService);
return connectorService.handleVerifyCredentialRequest(verifyRequest);
}
@Post('irma/disclose')
handleIrmaVerifyDisclosure(
@Post(':connector/disclose')
async handleCredentialVerifyDisclosure(
@Param('connector', GetConnectorPipe) connectorService: ConnectorService,
@Query('verifyRequestId', GetVerifyRequestPipe)
verifyRequest: CredentialVerifyRequest,
@Body('jwt')
irmaJwt: string,
@Body()
body: unknown,
) {
try {
// TODO: Abstract this properly (also for jolocom)
const result = this.irmaService.handleIrmaDisclosure(
verifyRequest,
irmaJwt,
);
const data = await connectorService.handleVerifyCredentialDisclosure(
verifyRequest,
body,
);
const responseToken = this.requestsService.encodeVerifyRequestResponse(
verifyRequest,
ResponseStatus.success,
'irma',
result,
);
const responseToken = this.requestsService.encodeVerifyRequestResponse(
verifyRequest,
ResponseStatus.success,
connectorService.name,
data,
);
this.gateway.sendRedirectResponse(
verifyRequest.requestId,
ResponseStatus.success,
`${verifyRequest.callbackUrl}${responseToken}`,
);
} catch {
// TODO: handle bad flow
}
this.gateway.sendRedirectResponse(
verifyRequest.requestId,
ResponseStatus.success,
`${verifyRequest.callbackUrl}${responseToken}`,
);
}
@Post('jolocom/disclose')
handleJolocomVerifyDisclosure(
@Query('verifyRequestId', GetVerifyRequestPipe)
verifyRequest: CredentialVerifyRequest,
@Body('token')
jolocomJwt: string,
) {
try {
// TODO: Abstract this properly (also for jolocom)
const result = this.jolocomService.handleJolocomDisclosure(
verifyRequest,
jolocomJwt,
);
// @Post('irma/disclose')
// handleIrmaVerifyDisclosure(
// @Query('verifyRequestId', GetVerifyRequestPipe)
// verifyRequest: CredentialVerifyRequest,
// @Body('jwt')
// irmaJwt: string,
// ) {
// try {
// // TODO: Abstract this properly (also for jolocom)
// const result = this.irmaService.handleIrmaDisclosure(
// verifyRequest,
// irmaJwt,
// );
const responseToken = this.requestsService.encodeVerifyRequestResponse(
verifyRequest,
ResponseStatus.success,
'jolocom',
result,
);
// const responseToken = this.requestsService.encodeVerifyRequestResponse(
// verifyRequest,
// ResponseStatus.success,
// 'irma',
// result,
// );
this.gateway.sendRedirectResponse(
verifyRequest.requestId,
ResponseStatus.success,
`${verifyRequest.callbackUrl}${responseToken}`,
);
} catch {
// TODO: handle bad flow
}
}
// this.gateway.sendRedirectResponse(
// verifyRequest.requestId,
// ResponseStatus.success,
// `${verifyRequest.callbackUrl}${responseToken}`,
// );
// } catch {
// // TODO: handle bad flow
// }
// }
// @Post('jolocom/disclose')
// async handleJolocomVerifyDisclosure(
// @Query('verifyRequestId', GetVerifyRequestPipe)
// verifyRequest: CredentialVerifyRequest,
// @Body('token')
// jolocomJwt: string,
// ) {
// try {
// // TODO: Abstract this properly (also for jolocom)
// const result = await this.jolocomService.handleJolocomDisclosure(
// verifyRequest,
// jolocomJwt,
// );
// console.log('Got result', result);
// const responseToken = this.requestsService.encodeVerifyRequestResponse(
// verifyRequest,
// ResponseStatus.success,
// 'jolocom',
// result,
// );
// this.gateway.sendRedirectResponse(
// verifyRequest.requestId,
// ResponseStatus.success,
// `${verifyRequest.callbackUrl}${responseToken}`,
// );
// } catch {
// // TODO: handle bad flow
// }
// }
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment