Skip to content

Commit

Permalink
Id of credential (#92)
Browse files Browse the repository at this point in the history
* Id of credential
Fixes #90

Signed-off-by: Mirko Mollik <[email protected]>

* add a task to start all required tasks

Signed-off-by: Mirko Mollik <[email protected]>

* recorder tasks

Signed-off-by: Mirko Mollik <[email protected]>

* Id of credential
Fixes #90

Signed-off-by: Mirko Mollik <[email protected]>

* add patch

Signed-off-by: Mirko Mollik <[email protected]>

---------

Signed-off-by: Mirko Mollik <[email protected]>
  • Loading branch information
cre8 committed Jul 30, 2024
1 parent 99b5b19 commit 6a9d6bb
Show file tree
Hide file tree
Showing 18 changed files with 1,044 additions and 843 deletions.
51 changes: 51 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Start issuer, holder, verifier",
"dependsOrder": "parallel",
"dependsOn": [
"nx run issuer-frontend:serve",
"nx run issuer-backend:serve",
"nx run holder-app:serve",
"nx run holder-backend:serve",
"nx run verifier-frontend:serve",
"nx run verifier-backend:serve"
]
},

{
"label": "nx run issuer-frontend:serve",
"type": "shell",
"command": "pnpm exec nx run issuer-frontend:serve"
},
{
"label": "nx run issuer-backend:serve",
"type": "shell",
"command": "pnpm exec nx run issuer-backend:serve"
},
{
"label": "nx run holder-app:serve",
"type": "shell",
"command": "pnpm exec nx run holder-app:serve"
},
{
"label": "nx run holder-backend:serve",
"type": "shell",
"command": "pnpm exec nx run holder-backend:serve"
},

{
"label": "nx run verifier-frontend:serve",
"type": "shell",
"command": "pnpm exec nx run verifier-frontend:serve"
},
{
"label": "nx run verifier-backend:serve",
"type": "shell",
"command": "pnpm exec nx run verifier-backend:serve"
}
]
}
26 changes: 18 additions & 8 deletions apps/holder-backend/src/app/credentials/credentials.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CredentialResponse } from './dto/credential-response.dto';
import { OnEvent } from '@nestjs/event-emitter';
import { USER_DELETED_EVENT, UserDeletedEvent } from '../auth/auth.service';
import { Interval } from '@nestjs/schedule';
import { createHash } from 'crypto';

type DateKey = 'exp' | 'nbf';
@Injectable()
Expand All @@ -41,20 +42,29 @@ export class CredentialsService {
}

async create(createCredentialDto: CreateCredentialDto, user: string) {
const credential = new Credential();
credential.id = createCredentialDto.id;
credential.user = user;
credential.value = createCredentialDto.value;
credential.metaData = createCredentialDto.metaData;
credential.issuer = createCredentialDto.issuer;
credential.nbf = await this.getDate(createCredentialDto.value, 'nbf');
credential.exp = await this.getDate(createCredentialDto.value, 'exp');
const credential = this.credentialRepository.create({
...createCredentialDto,
user,
id: this.getCredentialId(createCredentialDto.value),
nbf: await this.getDate(createCredentialDto.value, 'nbf'),
exp: await this.getDate(createCredentialDto.value, 'exp'),
});
await this.credentialRepository.save(credential);
return {
id: credential.id,
};
}

/**
* Create the id of the credential based on the hash of the value.
* @param value
* @returns
*/
getCredentialId(value: string): string {
//just use the first part since not all credentials have the disclosed values.
return createHash('sha256').update(value.split('~')[0]).digest('hex');
}

/**
* Get the date from the credential, use key to get the correct value.
* @param credential
Expand Down
11 changes: 7 additions & 4 deletions apps/holder-backend/src/app/oid4vc/oid4vp/oid4vp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { VPSessionEntity } from './entities/vp-session.entity';
import { jwtVerify, importJWK, JWK, KeyLike } from 'jose';
import { SelectResults } from '@sphereon/pex/dist/main/lib';

@Injectable()
export class Oid4vpService {
Expand Down Expand Up @@ -99,8 +100,11 @@ export class Oid4vpService {
// select the credentials for the presentation
const result = await pex
.selectVerifiableCredentialsForSubmission(pds[0].definition)
.catch((err) => {
.catch((err: SelectResults) => {
console.log(err);
if (err.errors.length > 0) {
throw new ConflictException(err.errors);
}
//instead of throwing an error, we return an empty array. This allows the user to show who sent the request for what.
return { verifiableCredential: [] };
});
Expand All @@ -109,10 +113,11 @@ export class Oid4vpService {
const creds = [];
for (const matchedCredential of result.verifiableCredential) {
const sdjwtvc = await this.sdjwt.decode(matchedCredential as string);
const id = this.credentialsService.getCredentialId(matchedCredential);
creds.push({
vct: sdjwtvc.jwt.payload.vct,
iss: sdjwtvc.jwt.payload.iss,
jti: sdjwtvc.jwt.payload.jti,
jti: id,
});
}
const requests = pds[0].definition.input_descriptors.map(
Expand Down Expand Up @@ -242,7 +247,6 @@ export class Oid4vpService {
jwtIssuer: JwtIssuerWithContext,
jwt: { header: JwtHeader; payload: JwtPayload }
) => {
console.log(jwtIssuer);
jwt.header.alg = key.publicKey.alg;
jwt.header.kid = key.id;
jwt.header.typ = 'JWT';
Expand All @@ -261,7 +265,6 @@ export class Oid4vpService {
};

const presentationSignCallback: PresentationSignCallback = async (args) => {
console.log(args);
throw Error('Not implemented');
};

Expand Down
2 changes: 0 additions & 2 deletions apps/issuer-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
"dependencies": {
"sqlite3": "^5.1.7",
"pg": "^8.11.5",
"@sphereon/ssi-express-support": "0.26.0",
"@sphereon/oid4vci-issuer": "^0.15.1"
},
"pnpm": {
"patchedDependencies": {
"@sphereon/[email protected]": "patches/@[email protected]",
"@sphereon/[email protected]": "patches/@[email protected]"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from '@sphereon/oid4vci-common';

export class CredentialOfferSession implements ICredentialOfferSession {
id: string;
clientId?: string;
credentialOffer: AssertedUniformCredentialOffer;
credentialDataSupplierInput?: unknown;
Expand Down
7 changes: 4 additions & 3 deletions apps/issuer-backend/src/app/issuer/issuer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { SessionRequestDto } from './dto/session-request.dto';
import { ApiOAuth2, ApiOperation, ApiTags } from '@nestjs/swagger';
import { AuthGuard } from 'nest-keycloak-connect';
import { SessionResponseDto } from './dto/session-response.dto';
import { CredentialOfferSession as ICredentialOfferSession } from '@sphereon/oid4vci-common';
import { CredentialOfferSession } from './dto/credential-offer-session.dto';
import { DBStates } from '@credhub/relying-party-shared';

Expand All @@ -29,15 +28,17 @@ export class IssuerController {
async listAll(): Promise<CredentialOfferSession[]> {
return (
this.issuerService.vcIssuer
.credentialOfferSessions as DBStates<ICredentialOfferSession>
.credentialOfferSessions as DBStates<CredentialOfferSession>
).all();
}

@ApiOperation({ summary: 'Returns the status for a session' })
@Get(':id')
async getSession(@Param('id') id: string): Promise<CredentialOfferSession> {
const session =
await this.issuerService.vcIssuer.credentialOfferSessions.get(id);
(await this.issuerService.vcIssuer.credentialOfferSessions.get(
id
)) as CredentialOfferSession;
if (!session) {
throw new NotFoundException(`Session with id ${id} not found`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,9 @@ export class SessionsListComponent implements OnInit, OnDestroy {
deleteSelected() {
if (!confirm('Are you sure you want to delete these sessions?')) return;
for (const session of this.selection.selected) {
/* firstValueFrom(
this.sessionsApiService.issuerControllerDelete(
//session.
)
); */
firstValueFrom(
this.sessionsApiService.issuerControllerDelete(session.id)
);
}
this.loadSessions();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import {
AuthRequestStateEntity,
SiopApiService,
} from '@credhub/verifier-shared';
import { firstValueFrom } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import {
CredentialOfferSession,
SessionsApiService,
SessionStatus,
} from '@credhub/issuer-shared';

@Component({
Expand All @@ -21,7 +16,7 @@ import {
styleUrl: './sessions-show.component.scss',
})
export class SessionsShowComponent implements OnInit, OnDestroy {
session!: SessionStatus;
session!: CredentialOfferSession;
interval!: ReturnType<typeof setInterval>;
id!: string;
sessionId!: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ export class SessionsListComponent implements OnInit, OnDestroy {
: this.dataSource.data.forEach((row) => this.selection.select(row));
}

deleteSelected() {
async deleteSelected() {
if (!confirm('Are you sure you want to delete these sessions?')) return;
for (const session of this.selection.selected) {
firstValueFrom(
await firstValueFrom(
this.templatesApiService.siopControllerDeleteAuthRequest(
this.id,
session.correlationId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@
<mat-card-subtitle>{{ issuer[0].description }}</mat-card-subtitle>
</mat-card-header>
<img
*ngIf="credentials"
[src]="credentials[0].display![0].background_image?.url"
[alt]="credentials[0].display![0].background_image?.url"
*ngIf="
credentials &&
credentials[0].display &&
credentials[0].display[0].background_image
"
[src]="credentials[0].display[0].background_image.url"
[alt]="credentials[0].display[0].background_image.url"
/>
<!-- instead of just the credential name, we could present the credential with it's attributes. In this case the parse event was already executed and we need a "accept" button to persist it into the database. Because it could be that the holder is denying the credential.-->
<mat-card-content *ngIf="credentials">
<p>wants to offer you a credential.</p>
<mat-list>
<mat-list-item>
<span matListItemTitle>{{ credentials[0].display![0].name }}</span>
<span matListItemLine>{{
credentials[0].display![0].description
}}</span>
@for(credential of credentials; track credential) {
<mat-list-item *ngIf="credential.display">
<span matListItemTitle>{{ credential.display[0].name }}</span>
<span matListItemLine>{{ credential.display[0].description }}</span>
</mat-list-item>
}
</mat-list>
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayoutAlign="space-between center">
Expand Down
2 changes: 0 additions & 2 deletions libs/issuer-shared/src/lib/api/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ model/changeStatusDto.ts
model/createListDto.ts
model/credential.ts
model/credentialConfigurationSupportedV1013.ts
model/credentialDefinitionV1013.ts
model/credentialOfferSession.ts
model/credentialsSupportedDisplay.ts
model/imageInfo.ts
Expand All @@ -25,7 +24,6 @@ model/metadataDisplay.ts
model/models.ts
model/sessionRequestDto.ts
model/sessionResponseDto.ts
model/sessionStatus.ts
model/statusList.ts
model/template.ts
param.ts
Expand Down
10 changes: 4 additions & 6 deletions libs/issuer-shared/src/lib/api/api/sessions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import { CredentialOfferSession } from '../model/credentialOfferSession';
import { SessionRequestDto } from '../model/sessionRequestDto';
// @ts-ignore
import { SessionResponseDto } from '../model/sessionResponseDto';
// @ts-ignore
import { SessionStatus } from '../model/sessionStatus';

// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
Expand Down Expand Up @@ -175,9 +173,9 @@ export class SessionsApiService {
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public issuerControllerGetSession(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<SessionStatus>;
public issuerControllerGetSession(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<SessionStatus>>;
public issuerControllerGetSession(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<SessionStatus>>;
public issuerControllerGetSession(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<CredentialOfferSession>;
public issuerControllerGetSession(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<CredentialOfferSession>>;
public issuerControllerGetSession(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<CredentialOfferSession>>;
public issuerControllerGetSession(id: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {
if (id === null || id === undefined) {
throw new Error('Required parameter id was null or undefined when calling issuerControllerGetSession.');
Expand Down Expand Up @@ -227,7 +225,7 @@ export class SessionsApiService {
}

let localVarPath = `/sessions/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`;
return this.httpClient.request<SessionStatus>('get', `${this.configuration.basePath}${localVarPath}`,
return this.httpClient.request<CredentialOfferSession>('get', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { CredentialDefinitionV1013 } from './credentialDefinitionV1013';
import { CredentialsSupportedDisplay } from './credentialsSupportedDisplay';


export interface CredentialConfigurationSupportedV1013 {
credential_definition: CredentialDefinitionV1013;
vct: string;
id: string;
claims?: object;
format: object;
format: string;
scope?: string;
cryptographic_binding_methods_supported?: Array<string>;
credential_signing_alg_values_supported?: Array<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@


export interface CredentialOfferSession {
id: string;
clientId?: string;
credentialOffer: object;
credentialDataSupplierInput?: object;
Expand Down
2 changes: 0 additions & 2 deletions libs/issuer-shared/src/lib/api/model/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ export * from './changeStatusDto';
export * from './createListDto';
export * from './credential';
export * from './credentialConfigurationSupportedV1013';
export * from './credentialDefinitionV1013';
export * from './credentialOfferSession';
export * from './credentialsSupportedDisplay';
export * from './imageInfo';
export * from './metadata';
export * from './metadataDisplay';
export * from './sessionRequestDto';
export * from './sessionResponseDto';
export * from './sessionStatus';
export * from './statusList';
export * from './template';
7 changes: 3 additions & 4 deletions patches/@[email protected]
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
diff --git a/dist/AccessTokenClient.js b/dist/AccessTokenClient.js
index 6fdd0f28e961a8c5627a21b3c802c65c2e12e9bf..0b9d5d27afd12d18b5985d1f893058c73db0b259 100644
index 6fdd0f28e961a8c5627a21b3c802c65c2e12e9bf..f72c07d67077a87c69323e61cf7cc886ad120d04 100644
--- a/dist/AccessTokenClient.js
+++ b/dist/AccessTokenClient.js
@@ -79,7 +79,7 @@ class AccessTokenClient {
yield (0, functions_1.createJwtBearerClientAssertion)(request, Object.assign(Object.assign({}, opts), { credentialIssuer }));
@@ -80,6 +80,7 @@ class AccessTokenClient {
if (credentialOfferRequest === null || credentialOfferRequest === void 0 ? void 0 : credentialOfferRequest.supportedFlows.includes(oid4vci_common_1.AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
this.assertAlphanumericPin(opts.pinMetadata, pin);
- request.user_pin = pin;
request.user_pin = pin;
+ request.tx_code = pin;
request.grant_type = oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE;
// we actually know it is there because of the isPreAuthCode call
Expand Down
Loading

0 comments on commit 6a9d6bb

Please sign in to comment.