From be38dbf53086f98a701773e322547faa34211ad7 Mon Sep 17 00:00:00 2001 From: Mirko Mollik Date: Fri, 12 Jul 2024 15:11:36 +0200 Subject: [PATCH] Feat/templates (#84) * feat: manage templates for issuer via gui Signed-off-by: Mirko Mollik * add template management to verifier Signed-off-by: Mirko Mollik * use pnpm v9 Signed-off-by: Mirko Mollik * fix: demo call Signed-off-by: Mirko Mollik --------- Signed-off-by: Mirko Mollik --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- apps/demo/src/app/eid/eid.component.ts | 10 +- .../src/app/oid4vc/oid4vci/oid4vci.service.ts | 12 + .../dto/credential-offer-session.dto.ts | 19 + .../src/app/issuer/issuer.controller.ts | 12 + .../src/app/issuer/issuer.service.ts | 5 +- apps/issuer-backend/src/app/issuer/state.ts | 88 + ...tusListAccept.ts => status-list-accept.ts} | 0 .../src/app/status/status.controller.ts | 2 +- .../src/app/templates/templates.controller.ts | 6 +- apps/issuer-frontend/project.json | 2 +- .../src/app/app.component.html | 62 +- .../src/app/app.component.scss | 9 - apps/issuer-frontend/src/app/app.component.ts | 64 +- apps/issuer-frontend/src/app/app.config.ts | 3 + apps/issuer-frontend/src/app/app.routes.ts | 39 + .../sessions-list.component.html | 9 + .../sessions-list.component.scss | 0 .../sessions-list/sessions-list.component.ts | 33 + .../templates-create.component.html | 27 + .../templates-create.component.scss | 3 + .../templates-create.component.ts | 69 + .../templates-edit.component.html | 32 + .../templates-edit.component.scss | 3 + .../templates-edit.component.ts | 87 + .../templates-issue.component.html | 76 + .../templates-issue.component.scss | 9 + .../templates-issue.component.ts | 92 + .../templates-list.component.html | 21 + .../templates-list.component.scss | 0 .../templates-list.component.ts | 32 + .../templates-show.component.html | 18 + .../templates-show.component.scss | 0 .../templates-show.component.ts | 67 + .../src/app/verifier/session-manager.ts | 4 + .../src/app/verifier/siop.controller.ts | 11 +- apps/verifier-frontend/project.json | 2 +- .../src/app/app.component.html | 24 +- .../src/app/app.component.scss | 9 - .../src/app/app.component.ts | 45 +- apps/verifier-frontend/src/app/app.config.ts | 3 + apps/verifier-frontend/src/app/app.routes.ts | 39 + .../sessions-list.component.html | 9 + .../sessions-list.component.scss | 0 .../sessions-list/sessions-list.component.ts | 35 + .../templates-create.component.html | 27 + .../templates-create.component.scss | 3 + .../templates-create.component.ts | 68 + .../templates-edit.component.html | 32 + .../templates-edit.component.scss | 3 + .../templates-edit.component.ts | 87 + .../templates-issue.component.html | 46 + .../templates-issue.component.scss | 9 + .../templates-issue.component.ts | 76 + .../templates-list.component.html | 21 + .../templates-list.component.scss | 0 .../templates-list.component.ts | 32 + .../templates-show.component.html | 18 + .../templates-show.component.scss | 0 .../templates-show.component.ts | 67 + .../src/lib/api/.openapi-generator/FILES | 12 +- libs/issuer-shared/src/lib/api/api.module.ts | 52 +- libs/issuer-shared/src/lib/api/api/api.ts | 6 +- .../src/lib/api/api/metadata.service.ts | 242 + .../src/lib/api/api/sessions.service.ts | 69 + .../src/lib/api/api/templates.service.ts | 467 + .../credentialConfigurationSupportedV1013.ts | 28 + .../api/model/credentialDefinitionV1013.ts | 18 + .../lib/api/model/credentialOfferSession.ts | 40 + .../api/model/credentialsSupportedDisplay.ts | 24 + .../src/lib/api/model/imageInfo.ts | 18 + .../src/lib/api/model/metadata.ts | 18 + .../src/lib/api/model/metadataDisplay.ts | 23 + .../issuer-shared/src/lib/api/model/models.ts | 8 + .../src/lib/api/model/template.ts | 20 + .../src/lib/api/.openapi-generator/FILES | 9 + libs/verifier-shared/src/lib/api/api/api.ts | 4 +- .../src/lib/api/api/siop.service.ts | 128 +- .../src/lib/api/api/templates.service.ts | 467 + .../src/lib/api/model/constraints.ts | 19 + .../src/lib/api/model/field.ts | 19 + .../src/lib/api/model/filter.ts | 18 + .../src/lib/api/model/format.ts | 17 + .../src/lib/api/model/inputDescriptor.ts | 21 + .../src/lib/api/model/metadata.ts | 19 + .../src/lib/api/model/models.ts | 8 + .../src/lib/api/model/request.ts | 22 + .../src/lib/api/model/template.ts | 20 + .../src/lib/verifier.service.ts | 8 +- package.json | 2 +- pnpm-lock.yaml | 23333 +++++++++------- 92 files changed, 15634 insertions(+), 11110 deletions(-) create mode 100644 apps/issuer-backend/src/app/issuer/dto/credential-offer-session.dto.ts create mode 100644 apps/issuer-backend/src/app/issuer/state.ts rename apps/issuer-backend/src/app/status/{StatusListAccept.ts => status-list-accept.ts} (100%) create mode 100644 apps/issuer-frontend/src/app/app.routes.ts create mode 100644 apps/issuer-frontend/src/app/sessions-list/sessions-list.component.html create mode 100644 apps/issuer-frontend/src/app/sessions-list/sessions-list.component.scss create mode 100644 apps/issuer-frontend/src/app/sessions-list/sessions-list.component.ts create mode 100644 apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.html create mode 100644 apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.scss create mode 100644 apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.ts create mode 100644 apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.html create mode 100644 apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.scss create mode 100644 apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.ts create mode 100644 apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.html create mode 100644 apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.scss create mode 100644 apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.ts create mode 100644 apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.html create mode 100644 apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.scss create mode 100644 apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.ts create mode 100644 apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.html create mode 100644 apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.scss create mode 100644 apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.ts create mode 100644 apps/verifier-frontend/src/app/app.routes.ts create mode 100644 apps/verifier-frontend/src/app/sessions-list/sessions-list.component.html create mode 100644 apps/verifier-frontend/src/app/sessions-list/sessions-list.component.scss create mode 100644 apps/verifier-frontend/src/app/sessions-list/sessions-list.component.ts create mode 100644 apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.html create mode 100644 apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.scss create mode 100644 apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.ts create mode 100644 apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.html create mode 100644 apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.scss create mode 100644 apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.ts create mode 100644 apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.html create mode 100644 apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.scss create mode 100644 apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.ts create mode 100644 apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.html create mode 100644 apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.scss create mode 100644 apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.ts create mode 100644 apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.html create mode 100644 apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.scss create mode 100644 apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.ts create mode 100644 libs/issuer-shared/src/lib/api/api/metadata.service.ts create mode 100644 libs/issuer-shared/src/lib/api/api/templates.service.ts create mode 100644 libs/issuer-shared/src/lib/api/model/credentialConfigurationSupportedV1013.ts create mode 100644 libs/issuer-shared/src/lib/api/model/credentialDefinitionV1013.ts create mode 100644 libs/issuer-shared/src/lib/api/model/credentialOfferSession.ts create mode 100644 libs/issuer-shared/src/lib/api/model/credentialsSupportedDisplay.ts create mode 100644 libs/issuer-shared/src/lib/api/model/imageInfo.ts create mode 100644 libs/issuer-shared/src/lib/api/model/metadata.ts create mode 100644 libs/issuer-shared/src/lib/api/model/metadataDisplay.ts create mode 100644 libs/issuer-shared/src/lib/api/model/template.ts create mode 100644 libs/verifier-shared/src/lib/api/api/templates.service.ts create mode 100644 libs/verifier-shared/src/lib/api/model/constraints.ts create mode 100644 libs/verifier-shared/src/lib/api/model/field.ts create mode 100644 libs/verifier-shared/src/lib/api/model/filter.ts create mode 100644 libs/verifier-shared/src/lib/api/model/format.ts create mode 100644 libs/verifier-shared/src/lib/api/model/inputDescriptor.ts create mode 100644 libs/verifier-shared/src/lib/api/model/metadata.ts create mode 100644 libs/verifier-shared/src/lib/api/model/request.ts create mode 100644 libs/verifier-shared/src/lib/api/model/template.ts diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6dce49e5..d5585cad 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -19,7 +19,7 @@ jobs: - uses: pnpm/action-setup@v3 with: - version: 8 + version: 9 # Cache node_modules - uses: actions/setup-node@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9742197..a691d0e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - uses: pnpm/action-setup@v3 with: - version: 8 + version: 9 # Cache node_modules - uses: actions/setup-node@v4 diff --git a/apps/demo/src/app/eid/eid.component.ts b/apps/demo/src/app/eid/eid.component.ts index 16c5760a..073c47ce 100644 --- a/apps/demo/src/app/eid/eid.component.ts +++ b/apps/demo/src/app/eid/eid.component.ts @@ -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'; @@ -59,6 +62,7 @@ export class EidComponent implements OnDestroy { constructor( public issuerService: IssuerService, public verifierService: VerifierService, + private verifierConfigService: VerifierConfigService, private snackBar: MatSnackBar ) {} @@ -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) => { diff --git a/apps/holder-backend/src/app/oid4vc/oid4vci/oid4vci.service.ts b/apps/holder-backend/src/app/oid4vc/oid4vci/oid4vci.service.ts index 36e4d85f..cc7c4c36 100644 --- a/apps/holder-backend/src/app/oid4vc/oid4vci/oid4vci.service.ts +++ b/apps/holder-backend/src/app/oid4vc/oid4vci/oid4vci.service.ts @@ -125,6 +125,7 @@ export class Oid4vciService { alg: Alg.ES256, format: credential.format, }); + const sdjwtvc = await this.sdjwt.decode( credentialResponse.credential as string ); @@ -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 }; diff --git a/apps/issuer-backend/src/app/issuer/dto/credential-offer-session.dto.ts b/apps/issuer-backend/src/app/issuer/dto/credential-offer-session.dto.ts new file mode 100644 index 00000000..bf01bbc3 --- /dev/null +++ b/apps/issuer-backend/src/app/issuer/dto/credential-offer-session.dto.ts @@ -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; +} diff --git a/apps/issuer-backend/src/app/issuer/issuer.controller.ts b/apps/issuer-backend/src/app/issuer/issuer.controller.ts index 11fcfbfe..ba5c1976 100644 --- a/apps/issuer-backend/src/app/issuer/issuer.controller.ts +++ b/apps/issuer-backend/src/app/issuer/issuer.controller.ts @@ -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([]) @@ -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 { + return ( + this.issuerService.vcIssuer + .credentialOfferSessions as CustomStates + ).all(); + } + @ApiOperation({ summary: 'Returns the status for a session' }) @Get(':id') async getSession(@Param('id') id: string): Promise { diff --git a/apps/issuer-backend/src/app/issuer/issuer.service.ts b/apps/issuer-backend/src/app/issuer/issuer.service.ts index 55b6c8d8..d4acfc92 100644 --- a/apps/issuer-backend/src/app/issuer/issuer.service.ts +++ b/apps/issuer-backend/src/app/issuer/issuer.service.ts @@ -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; @@ -264,7 +265,7 @@ export class IssuerService implements OnModuleInit { { cNonceExpiresIn: 300, //TODO: use persistant session managements in production - credentialOfferSessions: new MemoryStates(), + credentialOfferSessions: new CustomStates(), cNonces: new MemoryStates(), uris: new MemoryStates(), jwtVerifyCallback, @@ -286,6 +287,8 @@ export class IssuerService implements OnModuleInit { preAuthorizedCodeExpirationDuration: 1000 * 60 * 10, tokenExpiresIn: 300, }, + //TODO: not implemented yet + //notificationOpts: {}, }, }); } diff --git a/apps/issuer-backend/src/app/issuer/state.ts b/apps/issuer-backend/src/app/issuer/state.ts new file mode 100644 index 00000000..5c6bde1a --- /dev/null +++ b/apps/issuer-backend/src/app/issuer/state.ts @@ -0,0 +1,88 @@ +import { + IStateManager, + STATE_MISSING_ERROR, + StateType, +} from '@sphereon/oid4vci-common'; + +export class CustomStates implements IStateManager { + private readonly expiresInMS: number; + private readonly states: Map; + 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 { + this.states.clear(); + } + + async clearExpired(timestamp?: number): Promise { + 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 { + if (!id) { + throw Error('No id supplied'); + } + return this.states.delete(id); + } + + async getAsserted(id: string): Promise { + 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 { + return Array.from(this.states.values()); + } + + async get(id: string): Promise { + return this.states.get(id); + } + + async has(id: string): Promise { + if (!id) { + throw Error('No id supplied'); + } + return this.states.has(id); + } + + async set(id: string, stateValue: T): Promise { + if (!id) { + throw Error('No id supplied'); + } + this.states.set(id, stateValue); + } + + async startCleanupRoutine(timeout?: number): Promise { + if (!this.cleanupIntervalId) { + this.cleanupIntervalId = setInterval( + () => this.clearExpired(), + timeout ?? 30000 + ); + } + } + + async stopCleanupRoutine(): Promise { + if (this.cleanupIntervalId) { + clearInterval(this.cleanupIntervalId); + } + } +} diff --git a/apps/issuer-backend/src/app/status/StatusListAccept.ts b/apps/issuer-backend/src/app/status/status-list-accept.ts similarity index 100% rename from apps/issuer-backend/src/app/status/StatusListAccept.ts rename to apps/issuer-backend/src/app/status/status-list-accept.ts diff --git a/apps/issuer-backend/src/app/status/status.controller.ts b/apps/issuer-backend/src/app/status/status.controller.ts index 231e1ed7..6666f447 100644 --- a/apps/issuer-backend/src/app/status/status.controller.ts +++ b/apps/issuer-backend/src/app/status/status.controller.ts @@ -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([]) diff --git a/apps/issuer-backend/src/app/templates/templates.controller.ts b/apps/issuer-backend/src/app/templates/templates.controller.ts index 6677b7b7..d744fd33 100644 --- a/apps/issuer-backend/src/app/templates/templates.controller.ts +++ b/apps/issuer-backend/src/app/templates/templates.controller.ts @@ -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 { + return Object.values( + Object.fromEntries(await this.templatesService.listAll()) + ); } @ApiOperation({ summary: 'Get one template' }) diff --git a/apps/issuer-frontend/project.json b/apps/issuer-frontend/project.json index e9c551a3..d3d50ada 100644 --- a/apps/issuer-frontend/project.json +++ b/apps/issuer-frontend/project.json @@ -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": [], diff --git a/apps/issuer-frontend/src/app/app.component.html b/apps/issuer-frontend/src/app/app.component.html index ab153b24..a33319db 100644 --- a/apps/issuer-frontend/src/app/app.component.html +++ b/apps/issuer-frontend/src/app/app.component.html @@ -1,58 +1,4 @@ -
-
- Require PIN - -
-
- QR-Code - - QR-Code Url - - - - - Pin - - - -

- Status: {{ issuerService.statusEvent.value }} -

-
-
+ + Templates + + diff --git a/apps/issuer-frontend/src/app/app.component.scss b/apps/issuer-frontend/src/app/app.component.scss index e9245874..e69de29b 100644 --- a/apps/issuer-frontend/src/app/app.component.scss +++ b/apps/issuer-frontend/src/app/app.component.scss @@ -1,9 +0,0 @@ -#content { - max-width: 300px; - margin: auto; -} - -img { - width: 300px; - height: 300px; -} diff --git a/apps/issuer-frontend/src/app/app.component.ts b/apps/issuer-frontend/src/app/app.component.ts index 0cd161cf..6c911cda 100644 --- a/apps/issuer-frontend/src/app/app.component.ts +++ b/apps/issuer-frontend/src/app/app.component.ts @@ -1,70 +1,14 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar'; import { CommonModule } from '@angular/common'; -import qrcode from 'qrcode'; -import { FlexLayoutModule } from 'ng-flex-layout'; -import { IssuerService } from '@credhub/issuer-shared'; -import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatButtonModule } from '@angular/material/button'; @Component({ standalone: true, - imports: [ - CommonModule, - RouterModule, - MatButtonModule, - MatIconModule, - MatInputModule, - MatSnackBarModule, - ReactiveFormsModule, - FlexLayoutModule, - ReactiveFormsModule, - MatSlideToggleModule, - ], + imports: [CommonModule, RouterModule, MatToolbarModule, MatButtonModule], selector: 'app-root', templateUrl: './app.component.html', styleUrl: './app.component.scss', }) -export class AppComponent { - form: FormGroup; - qrCodeField = new FormControl(''); - qrCodeImage?: string; - pinField = new FormControl(''); - - constructor( - public issuerService: IssuerService, - private snackBar: MatSnackBar - ) { - this.form = new FormGroup({ - pin: new FormControl(false), - }); - } - - async generate() { - this.pinField.setValue(''); - - const response = await this.issuerService.getUrl( - undefined, - { - prename: 'Max', - surname: 'Mustermann', - }, - this.form.value - ); - this.qrCodeField.setValue(response.uri); - if (response.userPin) { - this.pinField.setValue(response.userPin); - } - this.qrCodeImage = await qrcode.toDataURL(response.uri); - this.copyValue(response.uri); - } - - copyValue(value: string) { - navigator.clipboard.writeText(value); - this.snackBar.open('Copied to clipboard', 'Close', { duration: 2000 }); - } -} +export class AppComponent {} diff --git a/apps/issuer-frontend/src/app/app.config.ts b/apps/issuer-frontend/src/app/app.config.ts index 90c5bf39..4ac86784 100644 --- a/apps/issuer-frontend/src/app/app.config.ts +++ b/apps/issuer-frontend/src/app/app.config.ts @@ -10,10 +10,13 @@ import { Configuration, IssuerConfigService, } from '@credhub/issuer-shared'; +import { provideRouter } from '@angular/router'; +import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideHttpClient(), + provideRouter(routes), { provide: APP_INITIALIZER, useFactory: ( diff --git a/apps/issuer-frontend/src/app/app.routes.ts b/apps/issuer-frontend/src/app/app.routes.ts new file mode 100644 index 00000000..d21c6f69 --- /dev/null +++ b/apps/issuer-frontend/src/app/app.routes.ts @@ -0,0 +1,39 @@ +import { Routes } from '@angular/router'; +import { TemplatesListComponent } from './templates/templates-list/templates-list.component'; +import { TemplatesCreateComponent } from './templates/templates-create/templates-create.component'; +import { TemplatesShowComponent } from './templates/templates-show/templates-show.component'; +import { TemplatesIssueComponent } from './templates/templates-issue/templates-issue.component'; +import { TemplatesEditComponent } from './templates/templates-edit/templates-edit.component'; + +export const routes: Routes = [ + { + path: '', + redirectTo: 'templates', + pathMatch: 'full', + }, + { + path: 'templates', + children: [ + { + path: '', + component: TemplatesListComponent, + }, + { + path: 'new', + component: TemplatesCreateComponent, + }, + { + path: ':id', + component: TemplatesShowComponent, + }, + { + path: ':id/edit', + component: TemplatesEditComponent, + }, + { + path: ':id/issue', + component: TemplatesIssueComponent, + }, + ], + }, +]; diff --git a/apps/issuer-frontend/src/app/sessions-list/sessions-list.component.html b/apps/issuer-frontend/src/app/sessions-list/sessions-list.component.html new file mode 100644 index 00000000..5d3c4eb1 --- /dev/null +++ b/apps/issuer-frontend/src/app/sessions-list/sessions-list.component.html @@ -0,0 +1,9 @@ + + @for(session of sessions; track session) { + + {{ session.preAuthorizedCode }} + {{ session.status }} + {{ session.createdAt | date }} + + } + diff --git a/apps/issuer-frontend/src/app/sessions-list/sessions-list.component.scss b/apps/issuer-frontend/src/app/sessions-list/sessions-list.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/apps/issuer-frontend/src/app/sessions-list/sessions-list.component.ts b/apps/issuer-frontend/src/app/sessions-list/sessions-list.component.ts new file mode 100644 index 00000000..ea7f6515 --- /dev/null +++ b/apps/issuer-frontend/src/app/sessions-list/sessions-list.component.ts @@ -0,0 +1,33 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { + CredentialOfferSession, + SessionsApiService, +} from '@credhub/issuer-shared'; +import { firstValueFrom } from 'rxjs'; +import { MatListModule } from '@angular/material/list'; + +@Component({ + selector: 'app-sessions-list', + standalone: true, + imports: [CommonModule, MatListModule], + templateUrl: './sessions-list.component.html', + styleUrl: './sessions-list.component.scss', +}) +export class SessionsListComponent implements OnInit, OnDestroy { + sessions: CredentialOfferSession[] = []; + interval!: NodeJS.Timeout; + + constructor(private sessionsApiService: SessionsApiService) {} + ngOnDestroy(): void { + clearInterval(this.interval); + } + + async ngOnInit(): Promise { + this.interval = setInterval(() => { + firstValueFrom(this.sessionsApiService.issuerControllerListAll()).then( + (sessions) => (this.sessions = sessions) + ); + }, 1000); + } +} diff --git a/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.html b/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.html new file mode 100644 index 00000000..db480ce1 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.html @@ -0,0 +1,27 @@ + + + Create template + + + + Template + + Input is invalid + + + + + + diff --git a/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.scss b/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.scss new file mode 100644 index 00000000..89ff0c2b --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.scss @@ -0,0 +1,3 @@ +#field { + width: 100vw; +} diff --git a/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.ts b/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.ts new file mode 100644 index 00000000..d20430d4 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-create/templates-create.component.ts @@ -0,0 +1,69 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { + AbstractControl, + FormControl, + ReactiveFormsModule, + ValidationErrors, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { ActivatedRoute } from '@angular/router'; +import { Template, TemplatesApiService } from '@credhub/issuer-shared'; +import { firstValueFrom } from 'rxjs'; +import { Router } from '@angular/router'; +import { MatCardModule } from '@angular/material/card'; + +@Component({ + selector: 'app-templates-create', + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatButtonModule, + MatSnackBarModule, + MatCardModule, + ], + templateUrl: './templates-create.component.html', + styleUrl: './templates-create.component.scss', +}) +export class TemplatesCreateComponent implements OnInit { + control!: FormControl; + + constructor( + private templatesApiService: TemplatesApiService, + private router: Router, + private snackBar: MatSnackBar + ) {} + + async ngOnInit(): Promise { + this.control = new FormControl('{}', this.isValidJson); + } + + isValidJson(control: AbstractControl): ValidationErrors | null { + try { + JSON.parse(control.value); + } catch (e) { + return { invalidJson: true }; + } + return null; + } + + save(): void { + const content: Template = JSON.parse(this.control.value); + firstValueFrom( + this.templatesApiService.templatesControllerUpdate( + content.schema.id, + content + ) + ).then(() => + this.router + .navigate(['/templates']) + .then(() => + this.snackBar.open('Template created', 'Dismiss', { duration: 3000 }) + ) + ); + } +} diff --git a/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.html b/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.html new file mode 100644 index 00000000..7e9c120e --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.html @@ -0,0 +1,32 @@ + + + + + {{ template.schema.id }} + + + + + Template + + Input is invalid + + + + + + diff --git a/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.scss b/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.scss new file mode 100644 index 00000000..89ff0c2b --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.scss @@ -0,0 +1,3 @@ +#field { + width: 100vw; +} diff --git a/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.ts b/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.ts new file mode 100644 index 00000000..ca42f24a --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-edit/templates-edit.component.ts @@ -0,0 +1,87 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Template, TemplatesApiService } from '@credhub/issuer-shared'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { firstValueFrom } from 'rxjs'; +import { + AbstractControl, + FormControl, + ReactiveFormsModule, + ValidationErrors, +} from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatCardModule } from '@angular/material/card'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatIconModule } from '@angular/material/icon'; +import { FlexLayoutModule } from 'ng-flex-layout'; + +@Component({ + selector: 'app-templates-edit', + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatButtonModule, + MatSnackBarModule, + MatCardModule, + MatTabsModule, + MatIconModule, + RouterModule, + FlexLayoutModule, + ], + templateUrl: './templates-edit.component.html', + styleUrl: './templates-edit.component.scss', +}) +export class TemplatesEditComponent implements OnInit { + template!: Template; + + control!: FormControl; + + constructor( + private templatesApiService: TemplatesApiService, + private route: ActivatedRoute, + private router: Router, + private snackBar: MatSnackBar + ) {} + + async ngOnInit(): Promise { + const id = this.route.snapshot.paramMap.get('id'); + if (!id) { + return; + } + this.template = await firstValueFrom( + this.templatesApiService.templatesControllerGetOne(id) + ); + this.control = new FormControl(JSON.stringify(this.template, null, 2), [ + this.isValidJson, + ]); + } + + isValidJson(control: AbstractControl): ValidationErrors | null { + try { + JSON.parse(control.value); + } catch (e) { + return { invalidJson: true }; + } + return null; + } + + save(): void { + const content: Template = JSON.parse(this.control.value); + firstValueFrom( + this.templatesApiService.templatesControllerUpdate( + content.schema.id, + content + ) + ).then(() => + this.router + .navigate(['/templates']) + .then(() => + this.snackBar.open('Template saved', 'Dismiss', { duration: 3000 }) + ) + ); + } +} diff --git a/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.html b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.html new file mode 100644 index 00000000..9688da35 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.html @@ -0,0 +1,76 @@ +
+ + + + + {{ template.schema.id }} + + +
+ + @for(field of form.controls | keyvalue; track field) { + + {{ field.key }} + + + } + Require PIN + + + + +
+
+
+ QR-Code + + QR-Code Url + + + + + Pin + + + +

+ Status: {{ issuerService.statusEvent.value }} +

+
+
diff --git a/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.scss b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.scss new file mode 100644 index 00000000..e9245874 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.scss @@ -0,0 +1,9 @@ +#content { + max-width: 300px; + margin: auto; +} + +img { + width: 300px; + height: 300px; +} diff --git a/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.ts b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.ts new file mode 100644 index 00000000..4e72eb36 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.ts @@ -0,0 +1,92 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { + IssuerService, + Template, + TemplatesApiService, +} from '@credhub/issuer-shared'; +import qrcode from 'qrcode'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { FlexLayoutModule } from 'ng-flex-layout'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { firstValueFrom } from 'rxjs'; +import { MatCardModule } from '@angular/material/card'; + +@Component({ + selector: 'app-templates-issue', + standalone: true, + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + MatInputModule, + MatSnackBarModule, + ReactiveFormsModule, + FlexLayoutModule, + ReactiveFormsModule, + MatSlideToggleModule, + MatCardModule, + RouterModule, + ], + templateUrl: './templates-issue.component.html', + styleUrl: './templates-issue.component.scss', +}) +export class TemplatesIssueComponent implements OnInit, OnDestroy { + form: FormGroup; + qrCodeField = new FormControl(''); + qrCodeImage?: string; + pinRequired = new FormControl(false); + pinField = new FormControl(''); + id!: string; + template!: Template; + + constructor( + public issuerService: IssuerService, + private templatesApiService: TemplatesApiService, + private snackBar: MatSnackBar, + private route: ActivatedRoute + ) { + this.form = new FormGroup({}); + } + async ngOnInit(): Promise { + this.id = this.route.snapshot.paramMap.get('id') as string; + this.template = await firstValueFrom( + this.templatesApiService.templatesControllerGetOne(this.id) + ); + this.generateForm(); + } + + ngOnDestroy(): void { + this.issuerService.stop(); + } + + generateForm() { + for (const key in this.template.schema.claims) { + this.form.addControl(key, new FormControl('')); + } + } + + async generate() { + this.pinField.setValue(''); + + const response = await this.issuerService.getUrl(this.id, this.form.value, { + pin: this.pinRequired.value as boolean, + }); + this.qrCodeField.setValue(response.uri); + if (response.userPin) { + this.pinField.setValue(response.userPin); + } + this.qrCodeImage = await qrcode.toDataURL(response.uri); + this.copyValue(response.uri); + } + + copyValue(value: string) { + navigator.clipboard.writeText(value); + this.snackBar.open('Copied to clipboard', 'Close', { duration: 2000 }); + } +} diff --git a/apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.html b/apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.html new file mode 100644 index 00000000..486df198 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.html @@ -0,0 +1,21 @@ + + + Templates + + + + @for(template of templates; track template) { + {{ template.schema.id }} + + } + + + + + + diff --git a/apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.scss b/apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.ts b/apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.ts new file mode 100644 index 00000000..9b868350 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-list/templates-list.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Template, TemplatesApiService } from '@credhub/issuer-shared'; +import { firstValueFrom } from 'rxjs'; +import { MatListModule } from '@angular/material/list'; +import { RouterModule } from '@angular/router'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCard, MatCardModule } from '@angular/material/card'; + +@Component({ + selector: 'app-templates-list', + standalone: true, + imports: [ + CommonModule, + MatListModule, + RouterModule, + MatButtonModule, + MatCardModule, + ], + templateUrl: './templates-list.component.html', + styleUrl: './templates-list.component.scss', +}) +export class TemplatesListComponent implements OnInit { + templates: Template[] = []; + constructor(private templatesApiService: TemplatesApiService) {} + + async ngOnInit(): Promise { + this.templates = await firstValueFrom( + this.templatesApiService.templatesControllerListAll() + ); + } +} diff --git a/apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.html b/apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.html new file mode 100644 index 00000000..e770cf20 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.html @@ -0,0 +1,18 @@ + + + + + {{ template.schema.id }} + + + + + + + + + + + diff --git a/apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.scss b/apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.ts b/apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.ts new file mode 100644 index 00000000..2ddd2371 --- /dev/null +++ b/apps/issuer-frontend/src/app/templates/templates-show/templates-show.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Template, TemplatesApiService } from '@credhub/issuer-shared'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { firstValueFrom } from 'rxjs'; +import { FormControl } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatCardModule } from '@angular/material/card'; +import { FlexLayoutModule } from 'ng-flex-layout'; +import { MatIconModule } from '@angular/material/icon'; +import { SessionsListComponent } from '../../sessions-list/sessions-list.component'; + +@Component({ + selector: 'app-templates-show', + standalone: true, + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + RouterModule, + MatCardModule, + FlexLayoutModule, + SessionsListComponent, + ], + templateUrl: './templates-show.component.html', + styleUrl: './templates-show.component.scss', +}) +export class TemplatesShowComponent implements OnInit { + template!: Template; + + control!: FormControl; + + constructor( + private templatesApiService: TemplatesApiService, + private route: ActivatedRoute, + private router: Router, + private snackBar: MatSnackBar + ) {} + + async ngOnInit(): Promise { + const id = this.route.snapshot.paramMap.get('id'); + if (!id) { + return; + } + this.template = await firstValueFrom( + this.templatesApiService.templatesControllerGetOne(id) + ); + } + + delete() { + if (!confirm('Are you sure you want to delete this template?')) { + return; + } + firstValueFrom( + this.templatesApiService.templatesControllerDelete( + this.template.schema.id + ) + ).then(() => + this.router + .navigate(['/templates']) + .then(() => + this.snackBar.open('Template deleted', 'Dismiss', { duration: 3000 }) + ) + ); + } +} diff --git a/apps/verifier-backend/src/app/verifier/session-manager.ts b/apps/verifier-backend/src/app/verifier/session-manager.ts index 0644d44b..4d4af351 100644 --- a/apps/verifier-backend/src/app/verifier/session-manager.ts +++ b/apps/verifier-backend/src/app/verifier/session-manager.ts @@ -99,6 +99,10 @@ export class InMemoryRPSessionManager implements IRPSessionManager { ); } + getAllRequestStates(): Promise { + return Promise.resolve(Object.values(this.authorizationRequests)); + } + async getRequestStateByCorrelationId( correlationId: string, errorOnNotFound?: boolean diff --git a/apps/verifier-backend/src/app/verifier/siop.controller.ts b/apps/verifier-backend/src/app/verifier/siop.controller.ts index 1d396490..ff8bb1b2 100644 --- a/apps/verifier-backend/src/app/verifier/siop.controller.ts +++ b/apps/verifier-backend/src/app/verifier/siop.controller.ts @@ -24,6 +24,7 @@ import { import { ConfigService } from '@nestjs/config'; import { v4 } from 'uuid'; import { AuthResponseRequestDto } from './dto/auth-repsonse-request.dto'; +import { InMemoryRPSessionManager } from './session-manager'; @ApiTags('siop') @UseGuards(AuthGuard) @@ -79,10 +80,18 @@ export class SiopController { return await request?.request.requestObject?.toJwt(); } + @ApiOperation({ summary: 'Get all auth request' }) + @Get(':rp/auth-request') + async getAllAuthRequest(@Param('rp') rp: string) { + const instance = await this.relyingPartyManagerService.getOrCreate(rp); + return ( + instance.rp.sessionManager as InMemoryRPSessionManager + ).getAllRequestStates(); + } + /** * Add the route to get the status of the request */ - @Public() @ApiOperation({ summary: 'Get the status of the auth request' }) @Get(':rp/auth-request/:correlationId/status') async getAuthRequestStatus( diff --git a/apps/verifier-frontend/project.json b/apps/verifier-frontend/project.json index 028b59a2..52906b24 100644 --- a/apps/verifier-frontend/project.json +++ b/apps/verifier-frontend/project.json @@ -29,7 +29,7 @@ "apps/verifier-frontend/src/assets" ], "styles": [ - "@angular/material/prebuilt-themes/indigo-pink.css", + "@angular/material/prebuilt-themes/azure-blue.css", "apps/verifier-frontend/src/styles.scss" ], "scripts": [], diff --git a/apps/verifier-frontend/src/app/app.component.html b/apps/verifier-frontend/src/app/app.component.html index 95e4feaf..a33319db 100644 --- a/apps/verifier-frontend/src/app/app.component.html +++ b/apps/verifier-frontend/src/app/app.component.html @@ -1,20 +1,4 @@ -
- - QR-Code - - - - -

- Status: {{ verifierService.statusEvent.value }} -

-
+ + Templates + + diff --git a/apps/verifier-frontend/src/app/app.component.scss b/apps/verifier-frontend/src/app/app.component.scss index e9245874..e69de29b 100644 --- a/apps/verifier-frontend/src/app/app.component.scss +++ b/apps/verifier-frontend/src/app/app.component.scss @@ -1,9 +0,0 @@ -#content { - max-width: 300px; - margin: auto; -} - -img { - width: 300px; - height: 300px; -} diff --git a/apps/verifier-frontend/src/app/app.component.ts b/apps/verifier-frontend/src/app/app.component.ts index c491c0d9..6c911cda 100644 --- a/apps/verifier-frontend/src/app/app.component.ts +++ b/apps/verifier-frontend/src/app/app.component.ts @@ -1,51 +1,14 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar'; import { CommonModule } from '@angular/common'; -import qrcode from 'qrcode'; -import { FlexLayoutModule } from 'ng-flex-layout'; -import { VerifierService } from '@credhub/verifier-shared'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatButtonModule } from '@angular/material/button'; @Component({ standalone: true, - imports: [ - CommonModule, - RouterModule, - MatButtonModule, - MatIconModule, - MatInputModule, - MatSnackBarModule, - ReactiveFormsModule, - FlexLayoutModule, - ], + imports: [CommonModule, RouterModule, MatToolbarModule, MatButtonModule], selector: 'app-root', templateUrl: './app.component.html', styleUrl: './app.component.scss', }) -export class AppComponent { - qrCodeField = new FormControl(''); - qrCodeImage?: string; - url?: string; - - constructor( - public verifierService: VerifierService, - private snackBar: MatSnackBar - ) {} - - async generate() { - this.url = await this.verifierService.getUrl(); - this.qrCodeField.setValue(this.url); - this.qrCodeImage = await qrcode.toDataURL(this.url); - this.copyValue(); - } - - copyValue() { - if (!this.url) return; - navigator.clipboard.writeText(this.url); - this.snackBar.open('URL copied to clipboard', 'Close', { duration: 3000 }); - } -} +export class AppComponent {} diff --git a/apps/verifier-frontend/src/app/app.config.ts b/apps/verifier-frontend/src/app/app.config.ts index b6eeeecd..bddae971 100644 --- a/apps/verifier-frontend/src/app/app.config.ts +++ b/apps/verifier-frontend/src/app/app.config.ts @@ -10,10 +10,13 @@ import { Configuration, VerifierConfigService, } from '@credhub/verifier-shared'; +import { provideRouter } from '@angular/router'; +import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideHttpClient(), + provideRouter(routes), { provide: APP_INITIALIZER, useFactory: ( diff --git a/apps/verifier-frontend/src/app/app.routes.ts b/apps/verifier-frontend/src/app/app.routes.ts new file mode 100644 index 00000000..d21c6f69 --- /dev/null +++ b/apps/verifier-frontend/src/app/app.routes.ts @@ -0,0 +1,39 @@ +import { Routes } from '@angular/router'; +import { TemplatesListComponent } from './templates/templates-list/templates-list.component'; +import { TemplatesCreateComponent } from './templates/templates-create/templates-create.component'; +import { TemplatesShowComponent } from './templates/templates-show/templates-show.component'; +import { TemplatesIssueComponent } from './templates/templates-issue/templates-issue.component'; +import { TemplatesEditComponent } from './templates/templates-edit/templates-edit.component'; + +export const routes: Routes = [ + { + path: '', + redirectTo: 'templates', + pathMatch: 'full', + }, + { + path: 'templates', + children: [ + { + path: '', + component: TemplatesListComponent, + }, + { + path: 'new', + component: TemplatesCreateComponent, + }, + { + path: ':id', + component: TemplatesShowComponent, + }, + { + path: ':id/edit', + component: TemplatesEditComponent, + }, + { + path: ':id/issue', + component: TemplatesIssueComponent, + }, + ], + }, +]; diff --git a/apps/verifier-frontend/src/app/sessions-list/sessions-list.component.html b/apps/verifier-frontend/src/app/sessions-list/sessions-list.component.html new file mode 100644 index 00000000..765668d1 --- /dev/null +++ b/apps/verifier-frontend/src/app/sessions-list/sessions-list.component.html @@ -0,0 +1,9 @@ + + @for(session of sessions; track session) { + + {{ session.correlationId }} + {{ session.status }} + {{ session.timestamp | date }} + + } + diff --git a/apps/verifier-frontend/src/app/sessions-list/sessions-list.component.scss b/apps/verifier-frontend/src/app/sessions-list/sessions-list.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/apps/verifier-frontend/src/app/sessions-list/sessions-list.component.ts b/apps/verifier-frontend/src/app/sessions-list/sessions-list.component.ts new file mode 100644 index 00000000..86d59ab7 --- /dev/null +++ b/apps/verifier-frontend/src/app/sessions-list/sessions-list.component.ts @@ -0,0 +1,35 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { firstValueFrom } from 'rxjs'; +import { MatListModule } from '@angular/material/list'; +import { SiopApiService } from '@credhub/verifier-shared'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'app-sessions-list', + standalone: true, + imports: [CommonModule, MatListModule], + templateUrl: './sessions-list.component.html', + styleUrl: './sessions-list.component.scss', +}) +export class SessionsListComponent implements OnInit, OnDestroy { + sessions: any[] = []; + interval!: NodeJS.Timeout; + + constructor( + private templatesApiService: SiopApiService, + private route: ActivatedRoute + ) {} + ngOnDestroy(): void { + clearInterval(this.interval); + } + + async ngOnInit(): Promise { + const id = this.route.snapshot.paramMap.get('id') as string; + this.interval = setInterval(() => { + firstValueFrom( + this.templatesApiService.siopControllerGetAllAuthRequest(id) + ).then((sessions) => (this.sessions = sessions)); + }, 1000); + } +} diff --git a/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.html b/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.html new file mode 100644 index 00000000..db480ce1 --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.html @@ -0,0 +1,27 @@ + + + Create template + + + + Template + + Input is invalid + + + + + + diff --git a/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.scss b/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.scss new file mode 100644 index 00000000..89ff0c2b --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.scss @@ -0,0 +1,3 @@ +#field { + width: 100vw; +} diff --git a/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.ts b/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.ts new file mode 100644 index 00000000..c8f93e0b --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-create/templates-create.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { + AbstractControl, + FormControl, + ReactiveFormsModule, + ValidationErrors, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { Template, TemplatesApiService } from '@credhub/verifier-shared'; +import { firstValueFrom } from 'rxjs'; +import { Router } from '@angular/router'; +import { MatCardModule } from '@angular/material/card'; + +@Component({ + selector: 'app-templates-create', + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatButtonModule, + MatSnackBarModule, + MatCardModule, + ], + templateUrl: './templates-create.component.html', + styleUrl: './templates-create.component.scss', +}) +export class TemplatesCreateComponent implements OnInit { + control!: FormControl; + + constructor( + private templatesApiService: TemplatesApiService, + private router: Router, + private snackBar: MatSnackBar + ) {} + + async ngOnInit(): Promise { + this.control = new FormControl('{}', this.isValidJson); + } + + isValidJson(control: AbstractControl): ValidationErrors | null { + try { + JSON.parse(control.value); + } catch (e) { + return { invalidJson: true }; + } + return null; + } + + save(): void { + const content: Template = JSON.parse(this.control.value); + firstValueFrom( + this.templatesApiService.templatesControllerUpdate( + content.request.id, + content + ) + ).then(() => + this.router + .navigate(['/templates']) + .then(() => + this.snackBar.open('Template created', 'Dismiss', { duration: 3000 }) + ) + ); + } +} diff --git a/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.html b/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.html new file mode 100644 index 00000000..f2c72b21 --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.html @@ -0,0 +1,32 @@ + + + + + {{ template.request.id }} + + + + + Template + + Input is invalid + + + + + + diff --git a/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.scss b/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.scss new file mode 100644 index 00000000..89ff0c2b --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.scss @@ -0,0 +1,3 @@ +#field { + width: 100vw; +} diff --git a/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.ts b/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.ts new file mode 100644 index 00000000..389aaaef --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-edit/templates-edit.component.ts @@ -0,0 +1,87 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Template, TemplatesApiService } from '@credhub/verifier-shared'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { firstValueFrom } from 'rxjs'; +import { + AbstractControl, + FormControl, + ReactiveFormsModule, + ValidationErrors, +} from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatCardModule } from '@angular/material/card'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatIconModule } from '@angular/material/icon'; +import { FlexLayoutModule } from 'ng-flex-layout'; + +@Component({ + selector: 'app-templates-edit', + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatButtonModule, + MatSnackBarModule, + MatCardModule, + MatTabsModule, + MatIconModule, + RouterModule, + FlexLayoutModule, + ], + templateUrl: './templates-edit.component.html', + styleUrl: './templates-edit.component.scss', +}) +export class TemplatesEditComponent implements OnInit { + template!: Template; + + control!: FormControl; + + constructor( + private templatesApiService: TemplatesApiService, + private route: ActivatedRoute, + private router: Router, + private snackBar: MatSnackBar + ) {} + + async ngOnInit(): Promise { + const id = this.route.snapshot.paramMap.get('id'); + if (!id) { + return; + } + this.template = await firstValueFrom( + this.templatesApiService.templatesControllerGetOne(id) + ); + this.control = new FormControl(JSON.stringify(this.template, null, 2), [ + this.isValidJson, + ]); + } + + isValidJson(control: AbstractControl): ValidationErrors | null { + try { + JSON.parse(control.value); + } catch (e) { + return { invalidJson: true }; + } + return null; + } + + save(): void { + const content: Template = JSON.parse(this.control.value); + firstValueFrom( + this.templatesApiService.templatesControllerUpdate( + content.request.id, + content + ) + ).then(() => + this.router + .navigate(['/templates']) + .then(() => + this.snackBar.open('Template saved', 'Dismiss', { duration: 3000 }) + ) + ); + } +} diff --git a/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.html b/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.html new file mode 100644 index 00000000..096ffe34 --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.html @@ -0,0 +1,46 @@ +
+ + + + + {{ template.request.id }} + + +
+ + + + +
+
+
+ QR-Code + + QR-Code Url + + + +

+ Status: {{ verifierService.statusEvent.value }} +

+
+
diff --git a/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.scss b/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.scss new file mode 100644 index 00000000..e9245874 --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.scss @@ -0,0 +1,9 @@ +#content { + max-width: 300px; + margin: auto; +} + +img { + width: 300px; + height: 300px; +} diff --git a/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.ts b/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.ts new file mode 100644 index 00000000..bcd845d4 --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-issue/templates-issue.component.ts @@ -0,0 +1,76 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { + VerifierService, + Template, + TemplatesApiService, +} from '@credhub/verifier-shared'; +import qrcode from 'qrcode'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { FlexLayoutModule } from 'ng-flex-layout'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { firstValueFrom } from 'rxjs'; +import { MatCardModule } from '@angular/material/card'; + +@Component({ + selector: 'app-templates-issue', + standalone: true, + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + MatInputModule, + MatSnackBarModule, + ReactiveFormsModule, + FlexLayoutModule, + ReactiveFormsModule, + MatSlideToggleModule, + MatCardModule, + RouterModule, + ], + templateUrl: './templates-issue.component.html', + styleUrl: './templates-issue.component.scss', +}) +export class TemplatesIssueComponent implements OnInit, OnDestroy { + form: FormGroup; + qrCodeField = new FormControl(''); + qrCodeImage?: string; + id!: string; + template!: Template; + + constructor( + public verifierService: VerifierService, + private templatesApiService: TemplatesApiService, + private snackBar: MatSnackBar, + private route: ActivatedRoute + ) { + this.form = new FormGroup({}); + } + async ngOnInit(): Promise { + this.id = this.route.snapshot.paramMap.get('id') as string; + this.template = await firstValueFrom( + this.templatesApiService.templatesControllerGetOne(this.id) + ); + } + + ngOnDestroy(): void { + this.verifierService.stop(); + } + + async generate() { + const response = await this.verifierService.getUrl(this.id); + this.qrCodeField.setValue(response); + this.qrCodeImage = await qrcode.toDataURL(response); + this.copyValue(response); + } + + copyValue(value: string) { + navigator.clipboard.writeText(value); + this.snackBar.open('Copied to clipboard', 'Close', { duration: 2000 }); + } +} diff --git a/apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.html b/apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.html new file mode 100644 index 00000000..2b13b61d --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.html @@ -0,0 +1,21 @@ + + + Templates + + + + @for(template of templates; track template) { + {{ template.request.id }} + + } + + + + + + diff --git a/apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.scss b/apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.ts b/apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.ts new file mode 100644 index 00000000..6554761d --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-list/templates-list.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Template, TemplatesApiService } from '@credhub/verifier-shared'; +import { firstValueFrom } from 'rxjs'; +import { MatListModule } from '@angular/material/list'; +import { RouterModule } from '@angular/router'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; + +@Component({ + selector: 'app-templates-list', + standalone: true, + imports: [ + CommonModule, + MatListModule, + RouterModule, + MatButtonModule, + MatCardModule, + ], + templateUrl: './templates-list.component.html', + styleUrl: './templates-list.component.scss', +}) +export class TemplatesListComponent implements OnInit { + templates: Template[] = []; + constructor(private templatesApiService: TemplatesApiService) {} + + async ngOnInit(): Promise { + this.templates = await firstValueFrom( + this.templatesApiService.templatesControllerListAll() + ); + } +} diff --git a/apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.html b/apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.html new file mode 100644 index 00000000..b62e5d70 --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.html @@ -0,0 +1,18 @@ + + + + + {{ template.request.id }} + + + + + + + + + + + diff --git a/apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.scss b/apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.ts b/apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.ts new file mode 100644 index 00000000..64ed0b51 --- /dev/null +++ b/apps/verifier-frontend/src/app/templates/templates-show/templates-show.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Template, TemplatesApiService } from '@credhub/verifier-shared'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { firstValueFrom } from 'rxjs'; +import { FormControl } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatCardModule } from '@angular/material/card'; +import { FlexLayoutModule } from 'ng-flex-layout'; +import { MatIconModule } from '@angular/material/icon'; +import { SessionsListComponent } from '../../sessions-list/sessions-list.component'; + +@Component({ + selector: 'app-templates-show', + standalone: true, + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + RouterModule, + MatCardModule, + FlexLayoutModule, + SessionsListComponent, + ], + templateUrl: './templates-show.component.html', + styleUrl: './templates-show.component.scss', +}) +export class TemplatesShowComponent implements OnInit { + template!: Template; + + control!: FormControl; + + constructor( + private templatesApiService: TemplatesApiService, + private route: ActivatedRoute, + private router: Router, + private snackBar: MatSnackBar + ) {} + + async ngOnInit(): Promise { + const id = this.route.snapshot.paramMap.get('id'); + if (!id) { + return; + } + this.template = await firstValueFrom( + this.templatesApiService.templatesControllerGetOne(id) + ); + } + + delete() { + if (!confirm('Are you sure you want to delete this template?')) { + return; + } + firstValueFrom( + this.templatesApiService.templatesControllerDelete( + this.template.request.id + ) + ).then(() => + this.router + .navigate(['/templates']) + .then(() => + this.snackBar.open('Template deleted', 'Dismiss', { duration: 3000 }) + ) + ); + } +} diff --git a/libs/issuer-shared/src/lib/api/.openapi-generator/FILES b/libs/issuer-shared/src/lib/api/.openapi-generator/FILES index fb742561..ec6d2346 100644 --- a/libs/issuer-shared/src/lib/api/.openapi-generator/FILES +++ b/libs/issuer-shared/src/lib/api/.openapi-generator/FILES @@ -1,12 +1,12 @@ -.gitignore -.openapi-generator-ignore README.md api.module.ts api/api.ts api/credentials.service.ts api/default.service.ts +api/metadata.service.ts api/sessions.service.ts api/status.service.ts +api/templates.service.ts api/wellKnown.service.ts configuration.ts encoder.ts @@ -15,10 +15,18 @@ index.ts model/changeStatusDto.ts model/createListDto.ts model/credential.ts +model/credentialConfigurationSupportedV1013.ts +model/credentialDefinitionV1013.ts +model/credentialOfferSession.ts +model/credentialsSupportedDisplay.ts +model/imageInfo.ts +model/metadata.ts +model/metadataDisplay.ts model/models.ts model/sessionRequestDto.ts model/sessionResponseDto.ts model/sessionStatus.ts model/statusList.ts +model/template.ts param.ts variables.ts diff --git a/libs/issuer-shared/src/lib/api/api.module.ts b/libs/issuer-shared/src/lib/api/api.module.ts index 73acf3d4..58d341fb 100644 --- a/libs/issuer-shared/src/lib/api/api.module.ts +++ b/libs/issuer-shared/src/lib/api/api.module.ts @@ -1,42 +1,30 @@ -import { - NgModule, - ModuleWithProviders, - SkipSelf, - Optional, -} from '@angular/core'; +import { NgModule, ModuleWithProviders, SkipSelf, Optional } from '@angular/core'; import { Configuration } from './configuration'; import { HttpClient } from '@angular/common/http'; + @NgModule({ - imports: [], + imports: [], declarations: [], - exports: [], - providers: [], + exports: [], + providers: [] }) export class ApiModule { - public static forRoot( - configurationFactory: () => Configuration - ): ModuleWithProviders { - return { - ngModule: ApiModule, - providers: [{ provide: Configuration, useFactory: configurationFactory }], - }; - } - - constructor( - @Optional() @SkipSelf() parentModule: ApiModule, - @Optional() http: HttpClient - ) { - if (parentModule) { - throw new Error( - 'ApiModule is already loaded. Import in your base AppModule only.' - ); + public static forRoot(configurationFactory: () => Configuration): ModuleWithProviders { + return { + ngModule: ApiModule, + providers: [ { provide: Configuration, useFactory: configurationFactory } ] + }; } - if (!http) { - throw new Error( - 'You need to import the HttpClientModule in your AppModule! \n' + - 'See also https://github.com/angular/angular/issues/20575' - ); + + constructor( @Optional() @SkipSelf() parentModule: ApiModule, + @Optional() http: HttpClient) { + if (parentModule) { + throw new Error('ApiModule is already loaded. Import in your base AppModule only.'); + } + if (!http) { + throw new Error('You need to import the HttpClientModule in your AppModule! \n' + + 'See also https://github.com/angular/angular/issues/20575'); + } } - } } diff --git a/libs/issuer-shared/src/lib/api/api/api.ts b/libs/issuer-shared/src/lib/api/api/api.ts index 18c319fa..a1990671 100644 --- a/libs/issuer-shared/src/lib/api/api/api.ts +++ b/libs/issuer-shared/src/lib/api/api/api.ts @@ -2,10 +2,14 @@ export * from './credentials.service'; import { CredentialsApiService } from './credentials.service'; export * from './default.service'; import { DefaultApiService } from './default.service'; +export * from './metadata.service'; +import { MetadataApiService } from './metadata.service'; export * from './sessions.service'; import { SessionsApiService } from './sessions.service'; export * from './status.service'; import { StatusApiService } from './status.service'; +export * from './templates.service'; +import { TemplatesApiService } from './templates.service'; export * from './wellKnown.service'; import { WellKnownApiService } from './wellKnown.service'; -export const APIS = [CredentialsApiService, DefaultApiService, SessionsApiService, StatusApiService, WellKnownApiService]; +export const APIS = [CredentialsApiService, DefaultApiService, MetadataApiService, SessionsApiService, StatusApiService, TemplatesApiService, WellKnownApiService]; diff --git a/libs/issuer-shared/src/lib/api/api/metadata.service.ts b/libs/issuer-shared/src/lib/api/api/metadata.service.ts new file mode 100644 index 00000000..e8fd8d1b --- /dev/null +++ b/libs/issuer-shared/src/lib/api/api/metadata.service.ts @@ -0,0 +1,242 @@ +/** + * 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. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpParameterCodec, HttpContext + } from '@angular/common/http'; +import { CustomHttpParameterCodec } from '../encoder'; +import { Observable } from 'rxjs'; + +// @ts-ignore +import { Metadata } from '../model/metadata'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; + + + +@Injectable({ + providedIn: 'root' +}) +export class MetadataApiService { + + protected basePath = 'http://localhost'; + public defaultHeaders = new HttpHeaders(); + public configuration = new Configuration(); + public encoder: HttpParameterCodec; + + constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: Configuration) { + if (configuration) { + this.configuration = configuration; + } + if (typeof this.configuration.basePath !== 'string') { + const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined; + if (firstBasePath != undefined) { + basePath = firstBasePath; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + + // @ts-ignore + private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { + if (typeof value === "object" && value instanceof Date === false) { + httpParams = this.addToHttpParamsRecursive(httpParams, value); + } else { + httpParams = this.addToHttpParamsRecursive(httpParams, value, key); + } + return httpParams; + } + + private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { + if (value == null) { + return httpParams; + } + + if (typeof value === "object") { + if (Array.isArray(value)) { + (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)); + } else if (value instanceof Date) { + if (key != null) { + httpParams = httpParams.append(key, (value as Date).toISOString().substring(0, 10)); + } else { + throw Error("key may not be null if value is Date"); + } + } else { + Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive( + httpParams, value[k], key != null ? `${key}.${k}` : k)); + } + } else if (key != null) { + httpParams = httpParams.append(key, value); + } else { + throw Error("key may not be null if value is not object or array"); + } + return httpParams; + } + + /** + * Get the metadata for the credential issuer + * @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 metadataControllerGetMetadata(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public metadataControllerGetMetadata(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public metadataControllerGetMetadata(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public metadataControllerGetMetadata(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (oauth2) required + localVarCredential = this.configuration.lookupCredential('oauth2'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/metadata`; + return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * Set the metadata for the credential issuer + * @param metadata + * @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 metadataControllerSetMetadata(metadata: Metadata, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable; + public metadataControllerSetMetadata(metadata: Metadata, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public metadataControllerSetMetadata(metadata: Metadata, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public metadataControllerSetMetadata(metadata: Metadata, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable { + if (metadata === null || metadata === undefined) { + throw new Error('Required parameter metadata was null or undefined when calling metadataControllerSetMetadata.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (oauth2) required + localVarCredential = this.configuration.lookupCredential('oauth2'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/metadata`; + return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: metadata, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + +} diff --git a/libs/issuer-shared/src/lib/api/api/sessions.service.ts b/libs/issuer-shared/src/lib/api/api/sessions.service.ts index f03d5333..099490fd 100644 --- a/libs/issuer-shared/src/lib/api/api/sessions.service.ts +++ b/libs/issuer-shared/src/lib/api/api/sessions.service.ts @@ -18,6 +18,8 @@ import { HttpClient, HttpHeaders, HttpParams, import { CustomHttpParameterCodec } from '../encoder'; import { Observable } from 'rxjs'; +// @ts-ignore +import { CredentialOfferSession } from '../model/credentialOfferSession'; // @ts-ignore import { SessionRequestDto } from '../model/sessionRequestDto'; // @ts-ignore @@ -238,6 +240,73 @@ export class SessionsApiService { ); } + /** + * Lists all sessions + * @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 issuerControllerListAll(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public issuerControllerListAll(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public issuerControllerListAll(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public issuerControllerListAll(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (oauth2) required + localVarCredential = this.configuration.lookupCredential('oauth2'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/sessions`; + return this.httpClient.request>('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + /** * Creates a new session request * @param sessionRequestDto diff --git a/libs/issuer-shared/src/lib/api/api/templates.service.ts b/libs/issuer-shared/src/lib/api/api/templates.service.ts new file mode 100644 index 00000000..1946f7e7 --- /dev/null +++ b/libs/issuer-shared/src/lib/api/api/templates.service.ts @@ -0,0 +1,467 @@ +/** + * 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. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpParameterCodec, HttpContext + } from '@angular/common/http'; +import { CustomHttpParameterCodec } from '../encoder'; +import { Observable } from 'rxjs'; + +// @ts-ignore +import { Template } from '../model/template'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; + + + +@Injectable({ + providedIn: 'root' +}) +export class TemplatesApiService { + + protected basePath = 'http://localhost'; + public defaultHeaders = new HttpHeaders(); + public configuration = new Configuration(); + public encoder: HttpParameterCodec; + + constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: Configuration) { + if (configuration) { + this.configuration = configuration; + } + if (typeof this.configuration.basePath !== 'string') { + const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined; + if (firstBasePath != undefined) { + basePath = firstBasePath; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + + // @ts-ignore + private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { + if (typeof value === "object" && value instanceof Date === false) { + httpParams = this.addToHttpParamsRecursive(httpParams, value); + } else { + httpParams = this.addToHttpParamsRecursive(httpParams, value, key); + } + return httpParams; + } + + private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { + if (value == null) { + return httpParams; + } + + if (typeof value === "object") { + if (Array.isArray(value)) { + (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)); + } else if (value instanceof Date) { + if (key != null) { + httpParams = httpParams.append(key, (value as Date).toISOString().substring(0, 10)); + } else { + throw Error("key may not be null if value is Date"); + } + } else { + Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive( + httpParams, value[k], key != null ? `${key}.${k}` : k)); + } + } else if (key != null) { + httpParams = httpParams.append(key, value); + } else { + throw Error("key may not be null if value is not object or array"); + } + return httpParams; + } + + /** + * Create a new template + * @param template + * @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 templatesControllerCreate(template: Template, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable; + public templatesControllerCreate(template: Template, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public templatesControllerCreate(template: Template, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public templatesControllerCreate(template: Template, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable { + if (template === null || template === undefined) { + throw new Error('Required parameter template was null or undefined when calling templatesControllerCreate.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (oauth2) required + localVarCredential = this.configuration.lookupCredential('oauth2'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/templates`; + return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: template, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * Delete a template + * @param id + * @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 templatesControllerDelete(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable; + public templatesControllerDelete(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public templatesControllerDelete(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public templatesControllerDelete(id: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable { + if (id === null || id === undefined) { + throw new Error('Required parameter id was null or undefined when calling templatesControllerDelete.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (oauth2) required + localVarCredential = this.configuration.lookupCredential('oauth2'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/templates/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`; + return this.httpClient.request('delete', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * Get one template + * @param id + * @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 templatesControllerGetOne(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable