Skip to content

Commit

Permalink
Authorization check for issuer and verifier (#60)
Browse files Browse the repository at this point in the history
* Authorization check for issuer and verifier
Fixes #53

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

* load new logo for app

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

* add removed spec file

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

---------

Signed-off-by: Mirko Mollik <[email protected]>
  • Loading branch information
cre8 authored Jun 12, 2024
1 parent 5f2e66d commit aa0da58
Show file tree
Hide file tree
Showing 40 changed files with 215 additions and 121 deletions.
4 changes: 1 addition & 3 deletions apps/demo/src/app/eid/eid.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@
<p>Verification completed!</p>
<div fxLayout="row" fxLayoutGap="16px">
<button (click)="reset()" mat-button color="primary">Reset</button>
<button routerLink="/" mat-flat-button color="primary">
Back to list
</button>
<a routerLink="/" mat-flat-button color="primary">Back to list</a>
</div>
</div>
</mat-step>
Expand Down
Binary file removed apps/holder-app/src/assets/icons/favicon.ico
Binary file not shown.
Binary file removed apps/holder-app/src/assets/icons/icon-128x128.png
Binary file not shown.
Binary file removed apps/holder-app/src/assets/icons/icon-144x144.png
Binary file not shown.
Binary file removed apps/holder-app/src/assets/icons/icon-152x152.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/holder-app/src/assets/icons/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/holder-app/src/assets/icons/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed apps/holder-app/src/assets/icons/icon-512x512.png
Binary file not shown.
Binary file removed apps/holder-app/src/assets/icons/icon-72x72.png
Binary file not shown.
Binary file removed apps/holder-app/src/assets/icons/icon-96x96.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed apps/holder-app/src/assets/images/icon.png
Binary file not shown.
Binary file removed apps/holder-app/src/assets/screenshot/main-wide.png
Binary file not shown.
Binary file removed apps/holder-app/src/assets/screenshot/main.png
Binary file not shown.
Binary file modified apps/holder-app/src/favicon.ico
Binary file not shown.
60 changes: 6 additions & 54 deletions apps/holder-app/src/manifest.webmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -8,73 +8,25 @@
"id": "/",
"scope": "./",
"start_url": "./",
"screenshots": [
{
"src": "assets/screenshot/main.png",
"sizes": "1082x2402",
"type": "image/png",
"form_factor": "narrow",
"label": "Home screen of the credentials"
},
{
"src": "assets/screenshot/main-wide.png",
"sizes": "2879x2152",
"type": "image/png",
"form_factor": "wide",
"label": "Home screen of the credentials"
}
],
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"src": "assets/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "assets/icons/icon-512x512.png",
"src": "assets/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "assets/icons/maskable_icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "assets/icons/maskable_icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "assets/icons/maskable_icon-128x128.png",
"sizes": "128x128",
"src": "assets/icons/maskable_icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "assets/icons/maskable_icon-512x512.png",
"src": "assets/icons/maskable_icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
Expand Down
33 changes: 33 additions & 0 deletions apps/holder-backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Holder backend

The holder backend is a service managing the credentials and keys of multiple users. Beside storing the information, it is also handling the communication with relying parties, so performs also the business logic. The authentication is done via openid connect with keycloak.

## Tech stack
The backend is based on nestjs, where all endpoints are published as rest api. The OpenAPI interface can be found at `/api`.

## Configuration
Configuration is done via environment variables. By default the service will use the `.env` file in the root folder, but for production you can pass the variables via the environment into the docker image.
The required variables will be checked on startup. If a variable is missing, the service will exit with an error message.

## Database
The backend is using `typeorm`, allowing to use different database. Right now it supports `sqlite` and `postgres`. The type is define via the `DB_TYPE` environment variable. The default value is `postgres`.

## Key management
Two different key management systems are supported by the backend.
- `db`: the keys are stored unencrypted in the database
- `vault`: the keys are stored in a hashicorp vault instance

The type is set via `KM_TYPE`, the default value is `db`. The type storage is set for all users. We could implement to use different systems for different users in the future if we see a demand.

The implementation of other vault systems or other approaches are possible by implementing the `KeysService` interface.

## Authentication

Authentication is realized via open id connect. There is no event the service is listening on the authentication service like keycloak. If a valid token is sent, the service will use the `sub` as the unique user id.
When the user deletes his account, the backend will delete all records related to the user. It will also send a request to keycloak to delete the user.
The service needs a valid client that is able to validate, it also needs to be configured as a service account with the role to delete users.

In case another authentication system is needed, the `OIDCClient` interface has to be implemented.

## Health check
The service is exposing a health check endpoint at `/health`. It will return a `200` status code if the service is healthy.
1 change: 1 addition & 0 deletions apps/holder-backend/src/app/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
export const OIDC_VALIDATION_SCHEMA = {
OIDC_AUTH_URL: Joi.string().required(),
OIDC_REALM: Joi.string().required(),
OIDC_PUBLIC_CLIENT_ID: Joi.string().required(),
...OIDC_CLIENT_SCHEMA,
};

Expand Down
2 changes: 0 additions & 2 deletions apps/holder-backend/src/app/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';

export const USER_DELETED_EVENT = 'user.deleted';
Expand All @@ -7,7 +6,6 @@ export class UserDeletedEvent {
id: string;
}

@Injectable()
export class AuthService {
constructor(private eventEmitter: EventEmitter2) {}

Expand Down
28 changes: 21 additions & 7 deletions apps/holder-backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app/app.module';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useLogger(['error', 'warn']);
app.useGlobalPipes(new ValidationPipe());
app.enableCors();
const configService = app.get<ConfigService>(ConfigService);

const config = new DocumentBuilder()
.setTitle('API')
.setExternalDoc('json format', '/api-json')
Expand All @@ -17,9 +20,21 @@ async function bootstrap() {
type: 'oauth2',
flows: {
password: {
tokenUrl: `${process.env.OIDC_AUTH_URL}/realms/${process.env.OIDC_REALM}/protocol/openid-connect/token`,
authorizationUrl: `${process.env.OIDC_AUTH_URL}/realms/${process.env.OIDC_REALM}/protocol/openid-connect/auth`,
refreshUrl: `${process.env.OIDC_AUTH_URL}/realms/${process.env.OIDC_REALM}/protocol/openid-connect/token`,
tokenUrl: `${configService.get(
'OIDC_AUTH_URL'
)}/realms/${configService.get(
'OIDC_REALM'
)}/protocol/openid-connect/token`,
authorizationUrl: `${configService.get(
'OIDC_AUTH_URL'
)}/realms/${configService.get(
'OIDC_REALM'
)}/protocol/openid-connect/auth`,
refreshUrl: `${configService.get(
'OIDC_AUTH_URL'
)}/realms/${configService.get(
'OIDC_REALM'
)}/protocol/openid-connect/token`,
scopes: {},
},
},
Expand All @@ -30,11 +45,10 @@ async function bootstrap() {
swaggerOptions: {
persistAuthorization: true,
initOAuth: {
clientId: process.env.OIDC_PUBLIC_CLIENT_ID,
// clientSecret: process.env.OIDC_CLIENT_SECRET,
realm: process.env.OIDC_REALM,
scopes: [],
clientId: configService.get('OIDC_PUBLIC_CLIENT_ID'),
realm: configService.get('OIDC_REALM'),
},
scopes: [],
},
});
await app.listen(3000);
Expand Down
3 changes: 2 additions & 1 deletion apps/issuer-backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ ISSUER_BASE_URL=http://localhost:3001
# Keycloak config
OIDC_AUTH_URL=http://host.docker.internal:8080
OIDC_REALM=wallet
OIDC_CLIENT_ID=issuer
OIDC_CLIENT_ID=relying-party
OIDC_CLIENT_SECRET=hA0mbfpKl8wdMrUxr2EjKtL5SGsKFW5D

# DB config
DB_TYPE=sqlite
Expand Down
29 changes: 29 additions & 0 deletions apps/issuer-backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Issuer backend

The issuer backend is a service for issuing credentials to users. It will also store issued credential so they can be revoked. The different schemas of credentials are mounted into the container are fetched on demand.

## Tech stack
The backend is based on nestjs, where all endpoints are published as rest api. The OpenAPI interface can be found at `/api`.

## Configuration
Configuration is done via environment variables. By default the service will use the `.env` file in the root folder, but for production you can pass the variables via the environment into the docker image.
The required variables will be checked on startup. If a variable is missing, the service will exit with an error message.

## Database
The backend is using `typeorm`, allowing to use different database. Right now it supports `sqlite` and `postgres`. The type is define via the `DB_TYPE` environment variable. The default value is `postgres`.

## Key management
Two different key management systems are supported by the backend.
- `file`: the keys are stored unencrypted in the file system
- `vault`: the keys are stored in a hashicorp vault instance

The type is set via `KM_TYPE`, the default value is `file`.

The implementation of other vault systems or other approaches are possible by implementing the `KeysService` interface.

## Authentication

Authentication is realized via open id connect. The JWT needs to have the role `issuer` to be able to access the endpoints.

## Health check
The service is exposing a health check endpoint at `/health`. It will return a `200` status code if the service is healthy.
2 changes: 1 addition & 1 deletion apps/issuer-backend/src/app/issuer/issuer-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class IssuerDataService {
this.metadata = JSON.parse(
readFileSync(join(folder, 'metadata.json'), 'utf-8')
) as CredentialIssuerMetadataOpts;
this.metadata.credential_issuer = process.env.ISSUER_BASE_URL as string;
this.metadata.credential_issuer = this.configSerivce.get('ISSUER_BASE_URL');

if (!this.metadata.credentials_supported) {
this.metadata.credentials_supported = [];
Expand Down
13 changes: 7 additions & 6 deletions apps/issuer-backend/src/app/issuer/issuer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { KeyService } from '@credhub/relying-party-shared';
import { IssuerMetadata } from './types';
import { StatusService } from '../status/status.service';
import { SessionResponseDto } from './dto/session-response.dto';
import { ConfigService } from '@nestjs/config';

interface CredentialDataSupplierInput {
credentialSubject: Record<string, unknown>;
Expand All @@ -55,7 +56,8 @@ export class IssuerService implements OnModuleInit {
@Inject('KeyService') private keyService: KeyService,
private issuerDataService: IssuerDataService,
private credentialsService: CredentialsService,
private statusService: StatusService
private statusService: StatusService,
private configService: ConfigService
) {
this.express = this.getExpressInstance();
}
Expand All @@ -69,7 +71,7 @@ export class IssuerService implements OnModuleInit {
*/
async getIssuerMetadata(): Promise<IssuerMetadata> {
return {
issuer: process.env.ISSUER_BASE_URL as string,
issuer: this.configService.get<string>('ISSUER_BASE_URL'),
jwks: {
keys: [await this.keyService.getPublicKey()],
},
Expand All @@ -81,7 +83,6 @@ export class IssuerService implements OnModuleInit {
const sessionId = v4();
try {
const credential = this.issuerDataService.getCredential(credentialId);
console.log(credential);
let exp: number | undefined;
// we either use the passed exp value or the ttl of the credential. If none is set, the credential will not expire.
if (values.exp) {
Expand Down Expand Up @@ -127,7 +128,7 @@ export class IssuerService implements OnModuleInit {
const cors = new ExpressCorsConfigurer().allowOrigin('*');
return ExpressBuilder.fromServerOpts({
existingExpress: this.adapterHost.httpAdapter.getInstance(),
port: process.env.PORT ? Number.parseInt(process.env.PORT) : 3000,
port: this.configService.get<number>('PORT', 3000),
hostname: '0.0.0.0',
})
.withCorsConfigurer(cors)
Expand Down Expand Up @@ -258,11 +259,11 @@ export class IssuerService implements OnModuleInit {
*/
new OID4VCIServer(this.express, {
issuer: this.vcIssuer,
baseUrl: process.env.ISSUER_BASE_URL,
baseUrl: this.configService.get<string>('ISSUER_BASE_URL'),
endpointOpts: {
tokenEndpointOpts: {
accessTokenSignerCallback: signerCallback,
accessTokenIssuer: process.env.ISSUER_BASE_URL,
accessTokenIssuer: this.configService.get<string>('ISSUER_BASE_URL'),
preAuthorizedCodeExpirationDuration: 1000 * 60 * 10,
tokenExpiresIn: 300,
},
Expand Down
30 changes: 21 additions & 9 deletions apps/issuer-backend/src/app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ async function bootstrap() {
app.useLogger(['error', 'warn']);
app.useGlobalPipes(new ValidationPipe());
app.enableCors();
const configService = app.get<ConfigService>(ConfigService);

const config = new DocumentBuilder()
.setTitle('API')
.setExternalDoc('json format', '/api-json')
Expand All @@ -17,9 +19,21 @@ async function bootstrap() {
type: 'oauth2',
flows: {
clientCredentials: {
tokenUrl: `${process.env.OIDC_AUTH_URL}/realms/${process.env.OIDC_REALM}/protocol/openid-connect/token`,
authorizationUrl: `${process.env.OIDC_AUTH_URL}/realms/${process.env.OIDC_REALM}/protocol/openid-connect/auth`,
refreshUrl: `${process.env.OIDC_AUTH_URL}/realms/${process.env.OIDC_REALM}/protocol/openid-connect/token`,
tokenUrl: `${configService.get(
'OIDC_AUTH_URL'
)}/realms/${configService.get(
'OIDC_REALM'
)}/protocol/openid-connect/token`,
authorizationUrl: `${configService.get(
'OIDC_AUTH_URL'
)}/realms/${configService.get(
'OIDC_REALM'
)}/protocol/openid-connect/auth`,
refreshUrl: `${configService.get(
'OIDC_AUTH_URL'
)}/realms/${configService.get(
'OIDC_REALM'
)}/protocol/openid-connect/token`,
scopes: {},
},
},
Expand All @@ -30,15 +44,13 @@ async function bootstrap() {
swaggerOptions: {
persistAuthorization: true,
initOAuth: {
clientId: process.env.OIDC_CLIENT_ID,
clientSecret: process.env.OIDC_CLIENT_SECRET,
realm: process.env.OIDC_REALM,
scopes: [],
clientId: configService.get('OIDC_CLIENT_ID'),
clientSecret: configService.get('OIDC_CLIENT_SECRET'),
realm: configService.get('OIDC_REALM'),
},
scopes: [],
},
});

const configService = app.get(ConfigService);
await app.listen(configService.get('PORT'));
}
bootstrap();
Loading

0 comments on commit aa0da58

Please sign in to comment.