Skip to content

Commit

Permalink
Feat/templates (#84)
Browse files Browse the repository at this point in the history
* feat: manage templates for issuer via gui

Signed-off-by: Mirko Mollik <mirkomollik@gmail.com>

* add template management to verifier

Signed-off-by: Mirko Mollik <mirkomollik@gmail.com>

* use pnpm v9

Signed-off-by: Mirko Mollik <mirkomollik@gmail.com>

* fix: demo call

Signed-off-by: Mirko Mollik <mirkomollik@gmail.com>

---------

Signed-off-by: Mirko Mollik <mirkomollik@gmail.com>
  • Loading branch information
cre8 authored Jul 12, 2024
1 parent 85dee2c commit be38dbf
Show file tree
Hide file tree
Showing 92 changed files with 15,634 additions and 11,110 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

- uses: pnpm/action-setup@v3
with:
version: 8
version: 9

# Cache node_modules
- uses: actions/setup-node@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

- uses: pnpm/action-setup@v3
with:
version: 8
version: 9

# Cache node_modules
- uses: actions/setup-node@v4
Expand Down
10 changes: 8 additions & 2 deletions apps/demo/src/app/eid/eid.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
Validators,
} from '@angular/forms';
import { IssuerService } from '@credhub/issuer-shared';
import { VerifierService } from '@credhub/verifier-shared';
import {
VerifierConfigService,
VerifierService,
} from '@credhub/verifier-shared';
import qrcode from 'qrcode';
import { MatInputModule } from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon';
Expand Down Expand Up @@ -59,6 +62,7 @@ export class EidComponent implements OnDestroy {
constructor(
public issuerService: IssuerService,
public verifierService: VerifierService,
private verifierConfigService: VerifierConfigService,
private snackBar: MatSnackBar
) {}

Expand Down Expand Up @@ -111,7 +115,9 @@ export class EidComponent implements OnDestroy {
* Starts the verification process
*/
async startVerification() {
this.verifierUrl = await this.verifierService.getUrl();
this.verifierUrl = await this.verifierService.getUrl(
this.verifierConfigService.getConfig('credentialId')
);
this.qrCodeVerifyField.setValue(this.verifierUrl);
this.qrCodeVerifyImage = await qrcode.toDataURL(this.verifierUrl);
this.verifierService.statusEvent.subscribe((status) => {
Expand Down
12 changes: 12 additions & 0 deletions apps/holder-backend/src/app/oid4vc/oid4vci/oid4vci.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class Oid4vciService {
alg: Alg.ES256,
format: credential.format,
});

const sdjwtvc = await this.sdjwt.decode(
credentialResponse.credential as string
);
Expand All @@ -138,6 +139,17 @@ export class Oid4vciService {
},
user
);
/* if (credentialResponse.notification_id) {
const res = await data.client.sendNotification(
{},
{
notification_id: credentialResponse.notification_id,
event: 'credential_accepted',
}
);
console.log(res);
} */

//remove the old session
this.sessions.delete(accept.id);
return { id: credentialEntry.id };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
CredentialOfferSession as ICredentialOfferSession,
AssertedUniformCredentialOffer,
IssueStatus,
} from '@sphereon/oid4vci-common';

export class CredentialOfferSession implements ICredentialOfferSession {
clientId?: string;
credentialOffer: AssertedUniformCredentialOffer;
credentialDataSupplierInput?: any;
userPin?: string;
status: IssueStatus;
error?: string;
lastUpdatedAt: number;
notification_id: string;
issuerState?: string;
preAuthorizedCode?: string;
createdAt: number;
}
12 changes: 12 additions & 0 deletions apps/issuer-backend/src/app/issuer/issuer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { ApiOAuth2, ApiOperation, ApiTags } from '@nestjs/swagger';
import { AuthGuard } from 'nest-keycloak-connect';
import { SessionResponseDto } from './dto/session-response.dto';
import { SessionStatus } from './dto/session-status.dto';
import { CustomStates } from './state';
import { CredentialOfferSession as ICredentialOfferSession } from '@sphereon/oid4vci-common';
import { CredentialOfferSession } from './dto/credential-offer-session.dto';

@UseGuards(AuthGuard)
@ApiOAuth2([])
Expand All @@ -22,6 +25,15 @@ import { SessionStatus } from './dto/session-status.dto';
export class IssuerController {
constructor(private issuerService: IssuerService) {}

@ApiOperation({ summary: 'Lists all sessions' })
@Get()
async listAll(): Promise<CredentialOfferSession[]> {
return (
this.issuerService.vcIssuer
.credentialOfferSessions as CustomStates<ICredentialOfferSession>
).all();
}

@ApiOperation({ summary: 'Returns the status for a session' })
@Get(':id')
async getSession(@Param('id') id: string): Promise<SessionStatus> {
Expand Down
5 changes: 4 additions & 1 deletion apps/issuer-backend/src/app/issuer/issuer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { IssuerMetadata } from './types';
import { StatusService } from '../status/status.service';
import { SessionResponseDto } from './dto/session-response.dto';
import { ConfigService } from '@nestjs/config';
import { CustomStates } from './state';

interface CredentialDataSupplierInput {
credentialSubject: Record<string, unknown>;
Expand Down Expand Up @@ -264,7 +265,7 @@ export class IssuerService implements OnModuleInit {
{
cNonceExpiresIn: 300,
//TODO: use persistant session managements in production
credentialOfferSessions: new MemoryStates<CredentialOfferSession>(),
credentialOfferSessions: new CustomStates<CredentialOfferSession>(),
cNonces: new MemoryStates<CNonceState>(),
uris: new MemoryStates<URIState>(),
jwtVerifyCallback,
Expand All @@ -286,6 +287,8 @@ export class IssuerService implements OnModuleInit {
preAuthorizedCodeExpirationDuration: 1000 * 60 * 10,
tokenExpiresIn: 300,
},
//TODO: not implemented yet
//notificationOpts: {},
},
});
}
Expand Down
88 changes: 88 additions & 0 deletions apps/issuer-backend/src/app/issuer/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
IStateManager,
STATE_MISSING_ERROR,
StateType,
} from '@sphereon/oid4vci-common';

export class CustomStates<T extends StateType> implements IStateManager<T> {
private readonly expiresInMS: number;
private readonly states: Map<string, T>;
private cleanupIntervalId?: number | NodeJS.Timeout;

constructor(opts?: { expiresInSec?: number }) {
this.expiresInMS =
opts?.expiresInSec !== undefined ? opts?.expiresInSec * 1000 : 180000;
this.states = new Map();
}
async clearAll(): Promise<void> {
this.states.clear();
}

async clearExpired(timestamp?: number): Promise<void> {
const states = Array.from(this.states.entries());
const ts = timestamp ?? +new Date();
for (const [id, state] of states) {
if (state.createdAt + this.expiresInMS < ts) {
this.states.delete(id);
}
}
}

async delete(id: string): Promise<boolean> {
if (!id) {
throw Error('No id supplied');
}
return this.states.delete(id);
}

async getAsserted(id: string): Promise<T> {
if (!id) {
throw Error('No id supplied');
}
let result: T | undefined;
if (await this.has(id)) {
result = (await this.get(id)) as T;
}
if (!result) {
throw new Error(STATE_MISSING_ERROR + ` (${id})`);
}
return result;
}

async all(): Promise<T[]> {
return Array.from(this.states.values());
}

async get(id: string): Promise<T | undefined> {
return this.states.get(id);
}

async has(id: string): Promise<boolean> {
if (!id) {
throw Error('No id supplied');
}
return this.states.has(id);
}

async set(id: string, stateValue: T): Promise<void> {
if (!id) {
throw Error('No id supplied');
}
this.states.set(id, stateValue);
}

async startCleanupRoutine(timeout?: number): Promise<void> {
if (!this.cleanupIntervalId) {
this.cleanupIntervalId = setInterval(
() => this.clearExpired(),
timeout ?? 30000
);
}
}

async stopCleanupRoutine(): Promise<void> {
if (this.cleanupIntervalId) {
clearInterval(this.cleanupIntervalId);
}
}
}
2 changes: 1 addition & 1 deletion apps/issuer-backend/src/app/status/status.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { StatusService } from './status.service';
import { AuthGuard, Public } from 'nest-keycloak-connect';
import { CreateListDto } from './dto/create-list.dto';
import { ChangeStatusDto } from './dto/change-status.dto';
import { StatusListAccept } from './StatusListAccept';
import { StatusListAccept } from './status-list-accept';

@UseGuards(AuthGuard)
@ApiOAuth2([])
Expand Down
6 changes: 4 additions & 2 deletions apps/issuer-backend/src/app/templates/templates.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ export class TemplatesController {

@ApiOperation({ summary: 'List all templates' })
@Get()
async listAll() {
return Object.fromEntries(await this.templatesService.listAll());
async listAll(): Promise<Template[]> {
return Object.values(
Object.fromEntries(await this.templatesService.listAll())
);
}

@ApiOperation({ summary: 'Get one template' })
Expand Down
2 changes: 1 addition & 1 deletion apps/issuer-frontend/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"apps/issuer-frontend/src/assets"
],
"styles": [
"@angular/material/prebuilt-themes/indigo-pink.css",
"@angular/material/prebuilt-themes/azure-blue.css",
"apps/issuer-frontend/src/styles.scss"
],
"scripts": [],
Expand Down
62 changes: 4 additions & 58 deletions apps/issuer-frontend/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,58 +1,4 @@
<div fxLayout="column" fxLayoutGap="16px" id="content">
<form
[formGroup]="form"
(submit)="generate()"
fxLayout="column"
fxLayoutGap="16px"
>
<mat-slide-toggle formControlName="pin" color="primary"
>Require PIN</mat-slide-toggle
>
<button type="submit" mat-raised-button color="primary">
Generate QR-Code
</button>
</form>
<div *ngIf="qrCodeImage" fxLayout="column" fxLayoutGap="16px">
<img [src]="qrCodeImage" alt="QR-Code" />
<mat-form-field [appearance]="'outline'">
<mat-label>QR-Code Url</mat-label>
<input
placeholder="QR-Code Url"
matInput
readonly
[formControl]="qrCodeField"
/>
<button
mat-icon-button
matSuffix
(click)="copyValue(qrCodeField.value!)"
*ngIf="qrCodeImage"
>
<mat-icon>content_copy</mat-icon>
</button>
</mat-form-field>
<mat-form-field
[appearance]="'outline'"
*ngIf="pinField.value && pinField.value.length > 0"
>
<mat-label>Pin</mat-label>
<input
placeholder="Issuer Name"
matInput
[formControl]="pinField"
readonly
/>
<button
mat-icon-button
matSuffix
(click)="copyValue(pinField.value!)"
*ngIf="qrCodeImage"
>
<mat-icon>content_copy</mat-icon>
</button>
</mat-form-field>
<p *ngIf="issuerService.statusEvent.value">
Status: {{ issuerService.statusEvent.value }}
</p>
</div>
</div>
<mat-toolbar>
<a mat-button routerLink="/">Templates</a>
</mat-toolbar>
<router-outlet></router-outlet>
9 changes: 0 additions & 9 deletions apps/issuer-frontend/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,9 +0,0 @@
#content {
max-width: 300px;
margin: auto;
}

img {
width: 300px;
height: 300px;
}
Loading

0 comments on commit be38dbf

Please sign in to comment.