Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
improve history management
Browse files Browse the repository at this point in the history
Signed-off-by: Mirko Mollik <mollik@trustcerts.de>
  • Loading branch information
Mirko Mollik committed Apr 28, 2024
1 parent f76bbe0 commit bd5f171
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 25 deletions.
15 changes: 15 additions & 0 deletions apps/backend/src/history/dto/history-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApiProperty } from '@nestjs/swagger';
import { History } from '../entities/history.entity';

class Disclosed {
key: string;
value: string;
}

export class HistoryResponse extends History {
/**
* The values that were presented
*/
@ApiProperty({ type: [Disclosed] })
disclosed: Disclosed[];
}
6 changes: 6 additions & 0 deletions apps/backend/src/history/entities/history.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export class History {
@Column()
status: HistoryStatus;

@Column({ nullable: true })
credentialType: string;

@Column({ nullable: true })
presentation: string;

/**
* Date of creation
*/
Expand Down
6 changes: 5 additions & 1 deletion apps/backend/src/history/history.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ApiOAuth2, ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger';
import { AuthGuard, AuthenticatedUser } from 'nest-keycloak-connect';
import { HistoryService } from './history.service';
import { KeycloakUser } from 'src/auth/user';
import { HistoryResponse } from './dto/history-response.dto';

@UseGuards(AuthGuard)
@ApiOAuth2([])
Expand All @@ -19,7 +20,10 @@ export class HistoryController {

@ApiOperation({ summary: 'get one element' })
@Get(':id')
getOne(@AuthenticatedUser() user: KeycloakUser, @Param('id') id: string) {
getOne(
@AuthenticatedUser() user: KeycloakUser,
@Param('id') id: string
): Promise<HistoryResponse> {
return this.historyService.getOne(id, user.sub);
}

Expand Down
51 changes: 46 additions & 5 deletions apps/backend/src/history/history.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,54 @@ import { Injectable } from '@nestjs/common';
import { History } from './entities/history.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CompactSdJwtVc } from '@sphereon/ssi-types';
import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc';
import { digest } from '@sd-jwt/crypto-nodejs';
import { HistoryResponse } from './dto/history-response.dto';

@Injectable()
export class HistoryService {
instance: SDJwtVcInstance;

constructor(
@InjectRepository(History)
private historyRepository: Repository<History>
) {}
) {
this.instance = new SDJwtVcInstance({ hasher: digest });
}

all(user: string) {
return this.historyRepository.find({
where: { user },
order: { created_at: 'DESC' },
// we only the id, relyingParty, created_at, and status fields to represent it as a list
select: ['id', 'relyingParty', 'created_at', 'status'],
select: [
'id',
'relyingParty',
'relyingPartyLogo',
'credentialType',
'created_at',
'status',
],
});
}

getOne(id: string, user: string) {
return this.historyRepository.findOne({ where: { id, user } });
//TODO: decode the presentation to return the values that got presented
return this.historyRepository
.findOne({ where: { id, user } })
.then(async (element: HistoryResponse) => {
if (element.presentation) {
const pres = await this.instance.decode(element.presentation);
element.disclosed = pres.disclosures.map((d) => ({
key: d.key,
value: d.value as string,
}));
}
element.user = undefined;
element.presentation = undefined;
return element;
});
}

add(
Expand All @@ -40,8 +69,20 @@ export class HistoryService {
return this.historyRepository.save(history);
}

setStatus(id: string, status: 'accepted' | 'declined') {
return this.historyRepository.update({ id }, { status });
async accept(id: string, presentation: CompactSdJwtVc) {
const pres = await this.instance.decode(presentation);
return this.historyRepository.update(
{ id },
{
presentation,
status: 'accepted',
credentialType: pres.jwt.payload.vct as string,
}
);
}

decline(id: string) {
return this.historyRepository.update({ id }, { status: 'declined' });
}

delete(sub: string) {
Expand Down
7 changes: 5 additions & 2 deletions apps/backend/src/oid4vc/oid4vp/oid4vp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { SubmissionRequest } from './dto/submission-request.dto';
import { HistoryService } from 'src/history/history.service';
import { Oid4vpParseRequest } from './dto/parse-request.dto';
import { Session } from './session';
import { CompactSdJwtVc } from '@sphereon/ssi-types';

@Injectable()
export class Oid4vpService {
Expand Down Expand Up @@ -199,7 +200,9 @@ export class Oid4vpService {
const res = await session.op
.submitAuthorizationResponse(authenticationResponseWithJWT)
.catch(() => '');
await this.historyService.setStatus(sessionId, 'accepted');
const response = authenticationResponseWithJWT.response.payload
.vp_token as CompactSdJwtVc;
await this.historyService.accept(sessionId, response);
this.sessions.delete(sessionId);
}

Expand All @@ -214,7 +217,7 @@ export class Oid4vpService {
if (!session || session.user !== user) {
throw new ConflictException('Session not found');
}
await this.historyService.setStatus(id, 'declined');
await this.historyService.decline(id);
this.sessions.delete(id);
}

Expand Down
2 changes: 2 additions & 0 deletions apps/holder/projects/shared/api/kms/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ model/createKey.ts
model/cred.ts
model/credentialIssuer.ts
model/credentialResponse.ts
model/disclosed.ts
model/history.ts
model/historyResponse.ts
model/issuerMetadataLogo.ts
model/keyResponse.ts
model/keyType.ts
Expand Down
10 changes: 6 additions & 4 deletions apps/holder/projects/shared/api/kms/api/history.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { Observable } from 'rxjs';

// @ts-ignore
import { History } from '../model/history';
// @ts-ignore
import { HistoryResponse } from '../model/historyResponse';

// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
Expand Down Expand Up @@ -230,9 +232,9 @@ export class HistoryApiService {
* @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 historyControllerGetOne(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<History>;
public historyControllerGetOne(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<History>>;
public historyControllerGetOne(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<History>>;
public historyControllerGetOne(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HistoryResponse>;
public historyControllerGetOne(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<HistoryResponse>>;
public historyControllerGetOne(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<HistoryResponse>>;
public historyControllerGetOne(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 historyControllerGetOne.');
Expand Down Expand Up @@ -282,7 +284,7 @@ export class HistoryApiService {
}

let localVarPath = `/history/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`;
return this.httpClient.request<History>('get', `${this.configuration.basePath}${localVarPath}`,
return this.httpClient.request<HistoryResponse>('get', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
Expand Down
18 changes: 18 additions & 0 deletions apps/holder/projects/shared/api/kms/model/disclosed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/


export interface Disclosed {
key: string;
value: string;
}

2 changes: 2 additions & 0 deletions apps/holder/projects/shared/api/kms/model/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export interface History {
*/
relyingParty: string;
relyingPartyLogo: string;
credentialType: string;
presentation: string;
/**
* Date of creation
*/
Expand Down
57 changes: 57 additions & 0 deletions apps/holder/projects/shared/api/kms/model/historyResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { Disclosed } from './disclosed';


export interface HistoryResponse {
/**
* Status of the history entry
*/
status: HistoryResponse.StatusEnum;
/**
* The values that were presented
*/
disclosed: Array<Disclosed>;
/**
* Unique ID of the history entry
*/
id: string;
/**
* The user that owns the key
*/
user: string;
/**
* Values
*/
value: string;
/**
* Relying party
*/
relyingParty: string;
relyingPartyLogo: string;
credentialType: string;
presentation: string;
/**
* Date of creation
*/
created_at: string;
}
export namespace HistoryResponse {
export type StatusEnum = 'pending' | 'accepted' | 'declined';
export const StatusEnum = {
pending: 'pending' as StatusEnum,
accepted: 'accepted' as StatusEnum,
declined: 'declined' as StatusEnum
};
}


2 changes: 2 additions & 0 deletions apps/holder/projects/shared/api/kms/model/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ export * from './createKey';
export * from './cred';
export * from './credentialIssuer';
export * from './credentialResponse';
export * from './disclosed';
export * from './history';
export * from './historyResponse';
export * from './issuerMetadataLogo';
export * from './keyResponse';
export * from './keyType';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,13 @@ <h3>History</h3>
<mat-nav-list>
@for (history of values; track history) {
<a mat-list-item [routerLink]="history.id">
<!-- <img matListAvatar [src]="history.relyingPartyLogo" alt="logo" /> -->
<mat-icon
matListItemIcon
[ngClass]="{
success: history.status === 'accepted',
declined: history.status === 'declined'
}"
>call_made</mat-icon
>
<h3 matListItemTitle>{{ history.relyingParty }}</h3>
<p matListItemLine>{{ history.created_at | date: 'medium' }}</p>
<img matListItemAvatar [src]="history.relyingPartyLogo" alt="logo" />
<h3 matListItemTitle *ngIf="history.credentialType">
<span>{{ history.credentialType }}</span>
</h3>
<h3 matListItemTitle *ngIf="history.status === 'declined'">Declined</h3>
<span matListItemLine>{{ history.relyingParty }}</span>
<span matListItemLine>{{ history.created_at | date: 'medium' }}</span>
</a>
}
</mat-nav-list>
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ <h3>{{ element.relyingParty }}</h3>
<div matListItemTitle>Action</div>
<div matListItemLine>{{ element.status }}</div>
</mat-list-item>
<!-- in case the user declined, should we show what they requested? -->
<mat-list-item *ngIf="element.credentialType">
<div matListItemTitle>Credential</div>
<div matListItemLine>{{ element.credentialType }}</div>
</mat-list-item>
<div *ngIf="element.disclosed">
<mat-divider></mat-divider>
<div mat-subheader>Presented values</div>
@for (disclosed of element.disclosed; track disclosed) {
<mat-list-item>
<div matListItemTitle>{{ disclosed.key }}</div>
<div matListItemLine>{{ disclosed.value }}</div>
</mat-list-item>
}
</div>
</mat-list>
</mat-card-content>
</mat-card>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { History, HistoryApiService } from '../../api/kms';
import { History, HistoryApiService, HistoryResponse } from '../../api/kms';
import { firstValueFrom } from 'rxjs';
import { MatCardModule } from '@angular/material/card';
import { CommonModule } from '@angular/common';
Expand All @@ -25,7 +25,7 @@ import { MatListModule } from '@angular/material/list';
styleUrl: './history-show.component.scss',
})
export class HistoryShowComponent implements OnInit {
element!: History;
element!: HistoryResponse;

constructor(
private route: ActivatedRoute,
Expand Down

0 comments on commit bd5f171

Please sign in to comment.