Skip to content

Commit

Permalink
Improve editor for issuer and verifier (#95)
Browse files Browse the repository at this point in the history
* add editor to manage credentials

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

* setting the issuance dates to seconds and not milliseconds

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

* update verifier editor and session management

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

* chore: fix tests

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

* fix: deploy values for docker

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

* fix: test env files with containers in cicd

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

* chore: exclude generated code from openapi for sonarcloud

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

* chore: fix ci and add badges

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

* optimize keycloak build

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

* chore: fix keycloak build for cicd

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

* remove unused api endpoint

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

* fix: backend build

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

* chore: print the logs in case something did not start

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

---------

Signed-off-by: Mirko Mollik <[email protected]>
  • Loading branch information
cre8 committed Sep 1, 2024
1 parent a4885c7 commit 24c418b
Show file tree
Hide file tree
Showing 105 changed files with 1,434 additions and 629 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# build the docker image for keycloak
# build the docker image for keycloak and publish it to the GitHub Container Registry
- name: Build Keycloak Docker Image
run: cd deploys/keycloak && docker compose build keycloak && docker compose push keycloak
run: |
cd deploys/keycloak &&
docker compose pull keycloak &&
docker compose build keycloak &&
docker compose push keycloak
# add the release, build the container and release it with the information for sentry
- name: Build and push images
Expand Down
13 changes: 11 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ jobs:
run: npx playwright install --with-deps

- name: Build keycloak
run: cd deploys/keycloak && docker compose build keycloak
run: |
cd deploys/keycloak &&
docker compose pull keycloak &&
docker compose build keycloak
- name: Add entry to /etc/hosts
run: echo "127.0.0.1 host.testcontainers.internal" | sudo tee -a /etc/hosts

- name: Lint, test, build, e2e
- name: Lint, test, container, e2e
run: INPUT_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} pnpm exec nx affected -t lint test container e2e
# comment out since the current e2e tests do not produce any artifacts
# - name: Upload coverage
Expand Down Expand Up @@ -76,6 +79,12 @@ jobs:
path: tmp/logs
retention-days: 30

# validate if the .env.example files in the deploys folder are up to date and that the containers can be health checked and started
- name: Validate deploy environment
run:
cd deploys &&
./test.sh

- name: Check if testcontainer logs exist
id: check_testcontainer_logs
run: echo "exists=$(if [ -d tmp/logs ]; then echo true; else echo false; fi)" >> $GITHUB_ENV
2 changes: 2 additions & 0 deletions .sonarcloud.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Exclusions
sonar.exclusions=libs/verifier-shared/src/lib/api/** libs/issuer-shared/src/lib/api/** libs/holder-shared/src/lib/api/**
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
[![CD](https://github.com/openwallet-foundation-labs/credhub/actions/workflows/cd.yml/badge.svg)](https://github.com/openwallet-foundation-labs/credhub/actions/workflows/cd.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=openwallet-foundation-labs_credhub&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=openwallet-foundation-labs_credhub) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/openwallet-foundation/credo-ts/main/LICENSE) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](https://www.typescriptlang.org/)

# credhub

credhub is comprehensive monorepo including a cloud wallet for natural persons together with a minimal issuer and verifier service. The cloud wallet will host all credentials and key pairs, including the business logic to receive and present credentials.

# Getting Started

Documentation on how to get started with credhub can be found at [https://credhub.eu](https://credhub.eu)

# Virtual meetings

There is a bi weekly virtual meeting to discuss the progress of the project. You can get a calendar invite [here](https://zoom-lfx.platform.linuxfoundation.org/meeting/93045942637?password=2c738e22-bb7b-44a7-aab1-e98fa7fc82f6)

# Contributing

If you would like to contribute to the project, please read our [contributing guide](./CONTRIBUTING.md).

# License

This project is licensed under the [Apache License Version 2.0 (Apache-2.0).](./LICENSE)
4 changes: 4 additions & 0 deletions apps/demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
FROM docker.io/nginx:stable-alpine

# Install wget
RUN apk --no-cache add wget

COPY dist/apps/demo/* /usr/share/nginx/html/
RUN echo "server {" > /etc/nginx/conf.d/default.conf && \
echo " listen 80;" >> /etc/nginx/conf.d/default.conf && \
Expand Down
22 changes: 17 additions & 5 deletions apps/holder-app-e2e/src/credentials.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { faker } from '@faker-js/faker';
import { test, expect, Page } from '@playwright/test';
import { getConfig, register } from './helpers';
import axios from 'axios';
import axios, { AxiosError } from 'axios';
import { GlobalConfig } from '../global-setup';

export const username = faker.internet.email();
Expand Down Expand Up @@ -48,17 +48,24 @@ async function getAxiosInstance(port: number) {

async function receiveCredential(pin = false) {
const axios = await getAxiosInstance(config.issuerPort);
const templates = await axios
.get('/templates')
.then((response) => response.data);
const credentialId = templates.find(
(template: any) => template.name === 'Identity'
).id;

const response = await axios
.post(`/sessions`, {
credentialSubject: {
prename: 'Max',
surname: 'Mustermann',
},
credentialId: 'Identity',
credentialId,
pin,
})
.catch((e) => {
console.log(e);
.catch((e: AxiosError) => {
console.log(JSON.stringify(e.response?.data, null, 2));
throw Error('Failed to create session');
});
const uri = response.data.uri;
Expand Down Expand Up @@ -98,8 +105,13 @@ test('issuance with pin', async () => {

test('verify credential', async () => {
await receiveCredential();
const credentialId = 'Identity';
const axios = await getAxiosInstance(config.verifierPort);
const templates = await axios
.get('/templates')
.then((response) => response.data);
const credentialId = templates.find(
(template: any) => template.value.name === 'Identity'
).id;
let uri = '';
try {
const response = await axios.post(`/siop/${credentialId}`);
Expand Down
3 changes: 3 additions & 0 deletions apps/holder-app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
FROM docker.io/nginx:stable-alpine

# Install wget
RUN apk --no-cache add wget

# Copy application files and the startup script with permissions
COPY dist/apps/holder-app/* /usr/share/nginx/html/
COPY --chmod=755 apps/holder-app/startup.sh /usr/local/bin/startup.sh
Expand Down
28 changes: 26 additions & 2 deletions apps/holder-app/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
isDevMode,
ErrorHandler,
} from '@angular/core';
import { Router, provideRouter } from '@angular/router';
import {
RouteReuseStrategy,
Router,
provideRouter,
withRouterConfig,
} from '@angular/router';

import { routes } from './app.routes';
import {
Expand All @@ -27,6 +32,24 @@ import { provideServiceWorker } from '@angular/service-worker';
import * as Sentry from '@sentry/angular';
import { environment } from '../environments/environment';

class MyStrategy implements RouteReuseStrategy {
shouldDetach() {
return false;
}
store() {
return null;
}
shouldAttach() {
return false;
}
retrieve() {
return null;
}
shouldReuseRoute() {
return false;
}
}

Sentry.init({
dsn: environment.sentryDsn,
enabled: !isDevMode(),
Expand All @@ -45,11 +68,12 @@ Sentry.init({

export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideRouter(routes, withRouterConfig({ onSameUrlNavigation: 'reload' })),
provideAnimations(),
provideOAuthClient(),
provideHttpClient(withInterceptorsFromDi()),
importProvidersFrom(ApiModule),
{ provide: RouteReuseStrategy, useClass: MyStrategy },
{ provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { hasBackdrop: true } },
{
provide: APP_INITIALIZER,
Expand Down
2 changes: 1 addition & 1 deletion apps/holder-backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ OIDC_AUTH_URL=http://host.docker.internal:8080
OIDC_REALM=wallet
OIDC_PUBLIC_CLIENT_ID=wallet
OIDC_ADMIN_CLIENT_ID=wallet-admin
OIDC_ADMIN_CLIENT_SECRET=secret
OIDC_ADMIN_CLIENT_SECRET=kwpCrguxUOn9gump77E0B3vAkiOhW8eL

# DB config
# DB_TYPE=postgres
Expand Down
23 changes: 2 additions & 21 deletions apps/holder-backend/src/app/credentials/credentials.controller.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import {
Body,
ConflictException,
Controller,
Delete,
Get,
Param,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import {
ApiBody,
ApiOAuth2,
ApiOperation,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import { ApiOAuth2, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { AuthGuard, AuthenticatedUser } from 'nest-keycloak-connect';
import { CredentialsService } from './credentials.service';
import { CreateCredentialDto } from './dto/create-credential.dto';
import { CredentialResponse } from './dto/credential-response.dto';
import { KeycloakUser } from '../auth/user';

Expand All @@ -29,16 +20,6 @@ import { KeycloakUser } from '../auth/user';
export class CredentialsController {
constructor(private readonly credentialsService: CredentialsService) {}

@ApiOperation({ summary: 'store a credential' })
@ApiBody({ type: CreateCredentialDto })
@Post()
create(
@Body() createCredentialDto: CreateCredentialDto,
@AuthenticatedUser() user: KeycloakUser
) {
return this.credentialsService.create(createCredentialDto, user.sub);
}

@ApiOperation({ summary: 'get all credentials' })
@ApiQuery({ name: 'archive', required: false, type: Boolean })
@Get()
Expand All @@ -52,7 +33,7 @@ export class CredentialsController {
);
return credentials.map((credential) => ({
id: credential.id,
display: credential.metaData.display[0],
display: credential.metaData.display?.[0],
issuer: credential.issuer,
}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { firstValueFrom } from 'rxjs';
import { Verifier } from '@sd-jwt/types';
import { JWK, JWTPayload } from '@sphereon/oid4vci-common';
import { CryptoService, ResolverService } from '@credhub/backend';
import { getListFromStatusListJWT } from '@sd-jwt/jwt-status-list';

type DateKey = 'exp' | 'nbf';
@Injectable()
Expand Down Expand Up @@ -119,7 +118,7 @@ export class CredentialsService {
return this.instance
.decode(credential)
.then((vc) =>
vc.jwt.payload[key] ? (vc.jwt.payload[key] as number) : undefined
vc.jwt.payload[key] ? (vc.jwt.payload[key] as number) * 1000 : undefined
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ export class Oid4vciService {
const sdjwtvc = await this.sdjwt.decode(
credentialResponse.credential as string
);
//TODO: also save the reference to the credential metadata. This will allow use to render the credential later. Either save the metadata or save a reference so it can be loaded on demand.
const credentialEntry = await this.credentialsService.create(
{
value: credentialResponse.credential as string,
Expand Down
17 changes: 11 additions & 6 deletions apps/issuer-backend/src/app/issuer/issuer-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class IssuerDataService {
this.metadata.credential_issuer = this.configSerivce.get('ISSUER_BASE_URL');

this.metadata.credential_configurations_supported =
this.templatesService.getSupported(await this.templatesService.listAll());
await this.templatesService.getSupported();
}

/**
Expand All @@ -51,18 +51,23 @@ export class IssuerDataService {

/**
* Returns the disclosure frame of the credential with the given id, throws an error if the credential is not supported.
* @param id
* @param vct
* @returns
*/
async getDisclosureFrame(id: string) {
async getDisclosureFrame(vct: string) {
if (this.configSerivce.get('CONFIG_RELOAD')) {
this.loadConfig();
}
const credential = await this.templatesService.getOne(id);
//becuase the vct is stored in a json field, we will need to fetch all elements and then filter
const credential = await this.templatesService
.listAll()
.then((templates) =>
templates.find((template) => template.value.schema.vct === vct)
);
if (!credential) {
throw new Error(`The credential with the id ${id} is not supported.`);
throw new Error(`The credential with the id ${vct} is not supported.`);
}
return credential.sd;
return credential.value.sd;
}

/**
Expand Down
25 changes: 21 additions & 4 deletions apps/issuer-backend/src/app/issuer/issuer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ import {
NotFoundException,
Param,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { IssuerService } from './issuer.service';
import { SessionRequestDto } from './dto/session-request.dto';
import { ApiOAuth2, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiOAuth2, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { AuthGuard } from 'nest-keycloak-connect';
import { SessionResponseDto } from './dto/session-response.dto';
import { CredentialOfferSession } from './dto/credential-offer-session.dto';
import { DBStates } from '@credhub/relying-party-shared';
import { CredentialsService } from '../credentials/credentials.service';
import { SessionEntryDto } from './dto/session-entry.dto';
import { CredentialOfferPayloadV1_0_13 } from '@sphereon/oid4vci-common';

@UseGuards(AuthGuard)
@ApiOAuth2([])
Expand All @@ -29,12 +31,27 @@ export class IssuerController {
) {}

@ApiOperation({ summary: 'Lists all sessions' })
@ApiQuery({ name: 'configId', required: false })
@Get()
async listAll(): Promise<CredentialOfferSession[]> {
async listAll(
@Query('configId') configId?: string
): Promise<CredentialOfferSession[]> {
return (
this.issuerService.vcIssuer
.credentialOfferSessions as DBStates<CredentialOfferSession>
).all();
)
.all()
.then((entries) => {
if (configId) {
return entries.filter((entry) =>
(
entry.credentialOffer
.credential_offer as CredentialOfferPayloadV1_0_13
).credential_configuration_ids.includes(configId)
);
}
return entries;
});
}

@ApiOperation({ summary: 'Returns the status for a session' })
Expand All @@ -50,7 +67,7 @@ export class IssuerController {
const credentials = await this.credentialsService.getBySessionId(id);
return {
session,
credentials: credentials,
credentials,
};
}

Expand Down
Loading

0 comments on commit 24c418b

Please sign in to comment.