Skip to content

Commit

Permalink
fix: optimize plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Mirko Mollik <mirko.mollik@fit.fraunhofer.de>
  • Loading branch information
cre8 committed Apr 21, 2024
1 parent 1cbfc57 commit 4b196b3
Show file tree
Hide file tree
Showing 21 changed files with 230 additions and 63 deletions.
10 changes: 8 additions & 2 deletions apps/backend/src/history/history.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { ApiOAuth2, ApiOperation, ApiTags } from '@nestjs/swagger';
import { Controller, Delete, Get, Param, UseGuards } from '@nestjs/common';
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';
Expand All @@ -22,4 +22,10 @@ export class HistoryController {
getOne(@AuthenticatedUser() user: KeycloakUser, @Param('id') id: string) {
return this.historyService.getOne(id, user.sub);
}

@ApiOperation({ summary: 'delete all entries' })
@Delete()
async delete(@AuthenticatedUser() user: KeycloakUser) {
await this.historyService.delete(user.sub);
}
}
4 changes: 4 additions & 0 deletions apps/backend/src/history/history.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ export class HistoryService {
setStatus(id: string, status: 'accepted' | 'declined') {
return this.historyRepository.update({ id }, { status });
}

delete(sub: string) {
return this.historyRepository.delete({ user: sub });
}
}
1 change: 0 additions & 1 deletion apps/backend/src/keys/keys.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ export class KeysService {
where: { id: kid, user },
});
const jwk = await importJWK<JoseKeyLike>(key.privateKey, 'ES256');
console.log(kbJwt.payload);
const jwt = await new SignJWT({ ...kbJwt.payload, aud })
.setProtectedHeader({ typ: kbJwt.header.typ, alg: 'ES256', kid })
.sign(jwk);
Expand Down
6 changes: 5 additions & 1 deletion apps/backend/src/oid4vc/oid4vci/dto/parse-request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { IsString } from 'class-validator';
import { IsBoolean, IsOptional, IsString } from 'class-validator';

export class Oid4vciParseRequest {
@IsString()
url: string;

@IsBoolean()
@IsOptional()
noSession?: boolean;
}
2 changes: 1 addition & 1 deletion apps/backend/src/oid4vc/oid4vci/oid4vci.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class Oid4vciController {
@Post('parse')
@ApiCreatedResponse({ description: 'URL parsed', type: Oid4vciParseRepsonse })
parse(@Body() value: Oid4vciParseRequest): Promise<Oid4vciParseRepsonse> {
return this.oid4vciService.parse(value.url);
return this.oid4vciService.parse(value);
}

@ApiOperation({ summary: 'accept a credential' })
Expand Down
26 changes: 14 additions & 12 deletions apps/backend/src/oid4vc/oid4vci/oid4vci.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { KeyResponse } from 'src/keys/dto/key-response.dto';
import { KeysService } from 'src/keys/keys.service';
import { v4 as uuid } from 'uuid';
import { Oid4vciParseRepsonse } from './dto/parse-response.dto';
import { Oid4vciParseRequest } from './dto/parse-request.dto';

type Session = {
//instead of storing the client, we could also generate it on demand. In this case we need to store the uri
Expand All @@ -39,13 +40,12 @@ export class Oid4vciService {
this.sdjwt = new SDJwtVcInstance({ hasher: digest });
}

async parse(data: string): Promise<Oid4vciParseRepsonse> {
if (data.startsWith('openid-credential-offer')) {
async parse(data: Oid4vciParseRequest): Promise<Oid4vciParseRepsonse> {
if (data.url.startsWith('openid-credential-offer')) {
const client = await OpenID4VCIClient.fromURI({
uri: data,
uri: data.url,
retrieveServerMetadata: true,
});
console.log(client);
// get the credential offer
const metadata = await client.retrieveServerMetadata();
const supportedCredentials = metadata.credentialIssuerMetadata
Expand All @@ -59,14 +59,16 @@ export class Oid4vciService {
}
);
const id = uuid();
this.sessions.set(id, {
client,
relyingParty: client.getIssuer(),
credentials,
//allows use to remove the session after a certain time
created: new Date(),
issuer: metadata.credentialIssuerMetadata.display[0],
});
if (!data.noSession) {
this.sessions.set(id, {
client,
relyingParty: client.getIssuer(),
credentials,
//allows use to remove the session after a certain time
created: new Date(),
issuer: metadata.credentialIssuerMetadata.display[0],
});
}
return {
sessionId: id,
credentials,
Expand Down
6 changes: 5 additions & 1 deletion apps/backend/src/oid4vc/oid4vp/dto/parse-request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { IsString } from 'class-validator';
import { IsBoolean, IsOptional, IsString } from 'class-validator';

export class Oid4vpParseRequest {
@IsString()
url: string;

@IsBoolean()
@IsOptional()
noSession?: boolean;
}
2 changes: 1 addition & 1 deletion apps/backend/src/oid4vc/oid4vp/oid4vp.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class Oid4vpController {
@Body() value: Oid4vpParseRequest,
@AuthenticatedUser() user: KeycloakUser
): Promise<Oid4vpParseRepsonse> {
return this.oid4vciService.parse(value.url, user.sub);
return this.oid4vciService.parse(value, user.sub);
}

@ApiOperation({ summary: 'submit a response' })
Expand Down
32 changes: 20 additions & 12 deletions apps/backend/src/oid4vc/oid4vp/oid4vp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { v4 as uuid } from 'uuid';
import { Oid4vpParseRepsonse } from './dto/parse-response.dto';
import { SubmissionRequest } from './dto/submission-request.dto';
import { HistoryService } from 'src/history/history.service';
import { Oid4vpParseRequest } from './dto/parse-request.dto';

interface Session {
user: string;
Expand All @@ -42,12 +43,15 @@ export class Oid4vpService {
this.sdjwt = new SDJwtVcInstance({ hasher: digest });
}

async parse(url: string, user: string): Promise<Oid4vpParseRepsonse> {
async parse(
data: Oid4vpParseRequest,
user: string
): Promise<Oid4vpParseRepsonse> {
const sessionId = uuid();
const op = await this.getOp(user);

//parse the uri
const parsedAuthReqURI = await op.parseAuthorizationRequestURI(url);
const parsedAuthReqURI = await op.parseAuthorizationRequestURI(data.url);
const verifiedAuthReqWithJWT: VerifiedAuthorizationRequest =
await op.verifyAuthorizationRequest(
parsedAuthReqURI.requestObjectJwt as string
Expand All @@ -58,7 +62,9 @@ export class Oid4vpService {
.client_metadata as RPRegistrationMetadataPayload
).client_name ?? verifiedAuthReqWithJWT.issuer;
const logo = verifiedAuthReqWithJWT.registrationMetadataPayload.logo_uri;
await this.historyService.add(sessionId, user, issuer, logo, url);
if (!data.noSession) {
await this.historyService.add(sessionId, user, issuer, logo, data.url);
}

// get all credentials from the client, required for the presentation exchange
const credentials = (await this.credentialsService.findAll(user)).map(
Expand All @@ -81,15 +87,17 @@ export class Oid4vpService {
throw new Error('No matching credentials found');
}

// store the session
this.sessions.set(sessionId, {
user,
verifiedAuthReqWithJWT,
created: new Date(),
pex,
op,
pd: pds[0],
});
if (!data.noSession) {
// store the session
this.sessions.set(sessionId, {
user,
verifiedAuthReqWithJWT,
created: new Date(),
pex,
op,
pd: pds[0],
});
}
// select the credentials for the presentation
const result = await pex.selectVerifiableCredentialsForSubmission(
pds[0].definition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
>
<span class="info">credentials</span>
</div>
<div fxLayout="column" fxLayoutAlign=" center">
<a mat-icon-button routerLink="/history" routerLinkActive="active-link"
><mat-icon>history</mat-icon></a
>
<span class="info">History</span>
</div>
<div fxLayout="column" fxLayoutAlign=" center">
<a mat-icon-button routerLink="/settings" routerLinkActive="active-link"
><mat-icon>settings</mat-icon></a
Expand Down
10 changes: 10 additions & 0 deletions apps/holder/projects/browser-extension/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ScannerComponent } from './scanner/scanner.component';
import { SettingsComponent } from '../../../shared/settings/settings.component';
import { authGuard } from './auth/auth.guard';
import { LoginComponent } from './login/login.component';
import { HistoryListComponent } from '../../../shared/history/history-list/history-list.component';
import { HistoryShowComponent } from '../../../shared/history/history-show/history-show.component';
export const routes: Routes = [
{
path: '',
Expand All @@ -27,6 +29,14 @@ export const routes: Routes = [
path: 'credentials/:id',
component: CredentialsShowComponent,
},
{
path: 'history',
component: HistoryListComponent,
},
{
path: 'history/:id',
component: HistoryShowComponent,
},
{
path: 'settings',
component: SettingsComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<div fxLayout="column" fxLayoutGap="16px" fxLayoutAlign="start center">
<div
fxLayout="column"
fxLayoutGap="16px"
fxLayoutAlign="start center"
*ngIf="!action"
>
<p>Issue yourself a eID for demo purposes</p>
<button mat-button (click)="getCredential()">Issue Request</button>
<p>Generate a verify request for demo purposes</p>
Expand All @@ -10,15 +15,32 @@
</mat-form-field>
<mat-list>
@for (result of scanner.results; track result) {
<mat-list-item (click)="process(result)">
<mat-icon matListItemIcon>{{
result.action === 'issue' ? 'call_received' : 'call_made'
}}</mat-icon>
<mat-list-item
(click)="process(result)"
*ngIf="result.action === 'issue'"
>
<mat-icon matListItemIcon>{{ 'call_received' }}</mat-icon>
<span matListItemTitle>{{
result.credentials[0].display![0].name
result.credentials![0].display![0].name
}}</span>
<span matListItemLine>{{ result.relyingParty }}</span>
</mat-list-item>
<mat-list-item
(click)="process(result)"
*ngIf="result.action === 'verify'"
>
<mat-icon matListItemIcon>{{ 'call_made' }}</mat-icon>
<span matListItemTitle>{{ result.purpose }}</span>
<span matListItemLine>{{ result.relyingParty }}</span>
</mat-list-item>
}
</mat-list>
</div>
<app-issuance-request
*ngIf="action === 'issue'"
[url]="url!"
></app-issuance-request>
<app-verify-request
*ngIf="action === 'verify'"
[url]="url!"
></app-verify-request>
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { environment } from '../../environments/environment';
import { IssuanceRequestComponent } from '../../../../shared/oid4vc/issuance-request/issuance-request.component';
import { VerifyRequestComponent } from '../../../../shared/oid4vc/verify-request/verify-request.component';

@Component({
selector: 'app-scanner',
standalone: true,
templateUrl: './scanner.component.html',
styleUrl: './scanner.component.scss',
imports: [
CommonModule,
MatListModule,
Expand All @@ -22,13 +26,16 @@ import { environment } from '../../environments/environment';
MatInputModule,
MatIconModule,
ReactiveFormsModule,
IssuanceRequestComponent,
VerifyRequestComponent,
],
templateUrl: './scanner.component.html',
styleUrl: './scanner.component.scss',
})
export class ScannerComponent implements OnInit {
urlField!: FormControl;

action?: 'issue' | 'verify' | undefined;
url?: string;

constructor(
public scanner: ScannerService,
private httpClient: HttpClient
Expand All @@ -48,7 +55,12 @@ export class ScannerComponent implements OnInit {
});
}
process(result: ResultScan) {
this.scanner.accept(result);
this.url = result.url;
if (result.action === 'issue') {
this.action = 'issue';
} else {
this.action = 'verify';
}
}

async presentCredential() {
Expand Down
Loading

0 comments on commit 4b196b3

Please sign in to comment.