Skip to content

Commit

Permalink
Merge pull request #124 from protokol/feat/credentialOffer
Browse files Browse the repository at this point in the history
Feat/credential offer
  • Loading branch information
artuan authored Dec 11, 2024
2 parents 6496e8d + 2679585 commit 3cf9d25
Show file tree
Hide file tree
Showing 99 changed files with 6,697 additions and 2,274 deletions.
1 change: 1 addition & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.gitignor# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
3 changes: 3 additions & 0 deletions apps/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ ISSUER_DID=""
ISSUER_PRIVATE_KEY_ID=""
ISSUER_PRIVATE_KEY_JWK=""
ISSUER_PUBLIC_KEY_JWK=""

# EBSI Configuration
EBSI_NETWORK=conformance # or pilot, test, preprod, production
10 changes: 7 additions & 3 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,36 @@
"migration:generate": "npm run typeorm -- -d ./src/db/typeorm.config.ts migration:generate ./src/db/migrations/$npm_config_name",
"migration:create": "npm run typeorm -- migration:create ./src/db/migrations/$npm_config_name",
"migration:revert": "npm run typeorm -- -d ./src/db/typeorm.config.ts migration:revert",
"migration:show": "npm run typeorm -- migration:show -d ./src/db/typeorm.config.ts"
"migration:show": "npm run typeorm -- migration:show -d ./src/db/typeorm.config.ts",
"migration:dev": "pnpm migration:revert && pnpm migration:generate && mkdir -p ./src/db/migrations/ && mv ./src/db/[0-9]*-*.ts ./src/db/migrations/ && pnpm migration:run"
},
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.1.0",
"@cef-ebsi/key-did-resolver": "^2.0.1",
"@cef-ebsi/verifiable-credential": "^6.1.0",
"@faker-js/faker": "^8.4.1",
"@nestjs/common": "^10.3.7",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.2.0",
"@nestjs/typeorm": "^10.0.1",
"@protokol/kredential-core": "^0.0.1",
"@protokol/kredential-core": "^0.0.3",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"did-jwt": "^8.0.4",
"did-resolver": "^4.1.0",
"dotenv": "^16.3.1",
"jose": "^4.15.5",
"json-schema": "^0.4.0",
"json-schema-to-typescript": "^13.1.2",
"key-did-resolver": "^4.0.0",
"keycloak-connect": "^23.0.6",
"nest-keycloak-connect": "^1.10.0",
"nest-keycloak-connect": "^1.10.1",
"pg": "^8.11.3",
"qrcode": "^1.5.4",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"typeorm": "^0.3.20",
Expand Down
3,080 changes: 1,824 additions & 1,256 deletions apps/backend/pnpm-lock.yaml

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions apps/backend/src/api-key/api-key.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Controller, Post, Body, Get, Delete, Param, UseGuards } from '@nestjs/common';
import { ApiKeyService } from './api-key.service';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';

@Controller('api-keys')
@ApiTags('API Keys')
@ApiBearerAuth()
export class ApiKeyController {
constructor(private apiKeyService: ApiKeyService) { }

@Post()
async createApiKey(
@Body() body: { name: string; allowedCredentialTypes: string[] }
) {
return await this.apiKeyService.createApiKey(
body.name,
body.allowedCredentialTypes
);
}

@Get()
async listApiKeys() {
return await this.apiKeyService.listApiKeys();
}

@Delete(':id')
async revokeApiKey(@Param('id') id: string) {
return await this.apiKeyService.revokeApiKey(parseInt(id));
}
}
36 changes: 36 additions & 0 deletions apps/backend/src/api-key/api-key.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ApiKey } from '../entities/api-key.entity';
import { Request } from 'express';

@Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(
@InjectRepository(ApiKey)
private apiKeyRepository: Repository<ApiKey>,
) { }

async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const apiKey = request.headers['x-api-key'];

if (!apiKey) {
throw new UnauthorizedException('API key is missing');
}

const apiKeyEntity = await this.apiKeyRepository.findOne({
where: { key: apiKey as string, isActive: true }
});

if (!apiKeyEntity) {
throw new UnauthorizedException('Invalid API key');
}

if (apiKeyEntity.expires_at && apiKeyEntity.expires_at < new Date()) {
throw new UnauthorizedException('API key has expired');
}

return true;
}
}
19 changes: 19 additions & 0 deletions apps/backend/src/api-key/api-key.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Student } from "../entities/student.entity";
import { VerifiableCredential } from "@entities/verifiableCredential.entity";
import { Did } from "../entities/did.entity";
import { ApiKeyService } from "./api-key.service";
import { ApiKeyController } from "./api-key.controller";
import { ApiKey } from "@entities/api-key.entity";
import { ApiKeyGuard } from "./api-key.guard";

@Module({
imports: [
TypeOrmModule.forFeature([ApiKey])
],
providers: [ApiKeyService, ApiKeyGuard],
controllers: [ApiKeyController],
exports: [ApiKeyService, ApiKeyGuard, TypeOrmModule]
})
export class ApiKeyModule { }
40 changes: 40 additions & 0 deletions apps/backend/src/api-key/api-key.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ApiKey } from '../entities/api-key.entity';
import { randomBytes } from 'crypto';
@Injectable()
export class ApiKeyService {
constructor(
@InjectRepository(ApiKey)
private apiKeyRepository: Repository<ApiKey>,
) { }

async createApiKey(name: string, allowedCredentialTypes: string[]): Promise<ApiKey> {
const key = randomBytes(32).toString('hex');

const apiKey = this.apiKeyRepository.create({
key,
name,
allowedCredentialTypes
});

return await this.apiKeyRepository.save(apiKey);
}

async listApiKeys(): Promise<ApiKey[]> {
return await this.apiKeyRepository.find({
select: ['id', 'name', 'created_at', 'expires_at', 'isActive', 'allowedCredentialTypes', 'key']
});
}

async revokeApiKey(id: number): Promise<void> {
const apiKey = await this.apiKeyRepository.findOne({ where: { id } });
if (!apiKey) {
throw new NotFoundException('API key not found');
}

apiKey.isActive = false;
await this.apiKeyRepository.save(apiKey);
}
}
57 changes: 49 additions & 8 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ import { LoggerMiddleware } from "./logger/LoggerMiddleware";
import { StateService } from "./state/state.service";
import { State } from "@entities/state.entity";
import { ProxyService } from "./proxy/proxy.service";
import { ApiKeyController } from "./api-key/api-key.controller";
import { ApiKeyService } from "./api-key/api-key.service";
import { ApiKeyModule } from "./api-key/api-key.module";
import { CredentialOfferModule } from "./credential-offer/credential-offer.module";
import { CredentialOfferController } from "./credential-offer/credential-offer.controller";
import { CredentialOfferService } from "./credential-offer/credential-offer.service";
import { DidModule } from "./student/did.module";
import { EbsiConfigService } from "./network/ebsi-config.service";
import { OfferController } from "./credential-offer/offer.controller";
import { SchemaTemplateService } from "./schemas/schema-template.service";
import { SchemaTemplateController } from "./schemas/schema-template.controller";
import { SchemaTemplateModule } from "./schemas/schema-template.module";
import { VpService } from "./vp/vp.service";
import { PresentationDefinitionService } from "./presentation/presentation-definition.service";
import { PresentationDefinitionController } from "./presentation/presentation-definition.controller";
import { PresentationDefinitionModule } from "./presentation/presentation-definition.module";
import { RequestUriController } from "./request-uri/requestUri.controller";
import { RequestUriService } from "./request-uri/requestUri.service";
import { VerificationService } from "./verification/verification.service";
import { ScopeCredentialMappingService } from "./scope-mapping/scope-mapping.service";
import { ScopeCredentialMappingController } from "./scope-mapping/scope-mapping.controller";
import { ScopeMappingModule } from "./scope-mapping/scope-mapping.module";
@Module({
imports: [
ConfigModule.forRoot({
Expand Down Expand Up @@ -69,13 +91,26 @@ import { ProxyService } from "./proxy/proxy.service";
ProgramModule,
CourseModule,
DiplomaModule,
EnrollmentModule
EnrollmentModule,
CredentialOfferModule,
ApiKeyModule,
DidModule,
SchemaTemplateModule,
PresentationDefinitionModule,
ScopeMappingModule
],
controllers: [
AppController,
VcController,
AuthController,
ProxyController
ProxyController,
ApiKeyController,
CredentialOfferController,
OfferController,
SchemaTemplateController,
PresentationDefinitionController,
RequestUriController,
ScopeCredentialMappingController
],
providers: [
AppService,
Expand All @@ -100,19 +135,25 @@ import { ProxyService } from "./proxy/proxy.service";
OpenIDProviderService,
IssuerService,
AuthService,
ProxyService
ProxyService,
ApiKeyService,
CredentialOfferService,
EbsiConfigService,
VpService,
PresentationDefinitionService,
RequestUriService,
SchemaTemplateService,
VerificationService,
ScopeCredentialMappingService
],
exports: [],
})

export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
console.log("!!!!!!!!!!AppModule");
console.log(process.env.KC_CLIENT_ID);
consumer.apply((req, res, next) => {
console.log('Auth Debug:');
console.log('Public Key:', process.env.KC_REALM_PUBLIC_KEY);
console.log('Token:', req.headers.authorization);
// console.log('req', req);
// console.log('req', req.url)
next();
}).forRoutes('*');
consumer.apply(LoggerMiddleware).forRoutes('*');
Expand Down
42 changes: 31 additions & 11 deletions apps/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Body, Controller, Get, Post, Query, Res, Headers, Inject } from '@nestjs/common';
import { Body, Controller, Get, Post, Query, Res, Headers, Inject, HttpException, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
import {
Public,
Expand All @@ -8,6 +8,7 @@ import { IssuerService } from './../issuer/issuer.service';
import { AuthorizeRequest, JWK, TokenRequestBody } from '@protokol/kredential-core';
import { ApiTags } from '@nestjs/swagger';
import { AuthService } from './auth.service';
import { handleError } from 'src/error/ebsi-error.util';


interface JWKS {
Expand All @@ -22,14 +23,17 @@ export class AuthController {

@Get('.well-known/openid-credential-issuer')
@Public(true)
getIssuerMetadata() {
return this.provider.getInstance().getIssuerMetadata();
async getIssuerMetadata() {
const provider = await this.provider.getInstance();
return provider.getIssuerMetadata();
}

@Get('.well-known/openid-configuration')
@Get('authorize/.well-known/openid-configuration')
// @Get('.well-known/openid-configuration')
@Public(true)
getConfigMetadata() {
return this.provider.getInstance().getConfigMetadata();
async getConfigMetadata() {
const provider = await this.provider.getInstance();
return provider.getConfigMetadata();
}

@Get('jwks')
Expand All @@ -51,10 +55,25 @@ export class AuthController {
const { code, url } = await this.auth.authorize(req);
return res.redirect(code, url);
} catch (error) {
return res.status(400).json({ message: error.message });
throw handleError(error);
}
}

@Get('verifier')
@Public(true)
async verifier(
@Query() req: AuthorizeRequest,
@Res() res: Response,
) {
try {
const { code, url } = await this.auth.authorize(req);
return res.redirect(code, url);
} catch (error) {
throw handleError(error);
}
}


@Post('direct_post')
@Public(true)
async directPost(
Expand All @@ -64,9 +83,10 @@ export class AuthController {
) {
try {
const { code, url } = await this.auth.directPost(req, headers);

return res.redirect(code, url);
} catch (error) {
return res.status(400).json({ message: error.message });
throw handleError(error);
}
}

Expand All @@ -81,7 +101,7 @@ export class AuthController {
const { header, code, response } = await this.auth.token(req);
return res.status(code).json(response);
} catch (error) {
return res.status(400).json({ message: error.message });
throw handleError(error);
}
}

Expand All @@ -96,7 +116,7 @@ export class AuthController {
const { code, response } = await this.auth.credentail(req);
return res.status(code).json(response);
} catch (error) {
return res.status(400).json({ message: error.message });
throw handleError(error);
}
}

Expand All @@ -111,7 +131,7 @@ export class AuthController {
const { code, response } = await this.auth.credentilDeferred(req, headers);
return res.status(code).json(response);
} catch (error) {
return res.status(400).json({ message: error.message });
throw handleError(error);
}
}
}
Loading

1 comment on commit 3cf9d25

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for ebsi-vector-frontend ready!

✅ Preview
https://ebsi-vector-frontend-5b6vywhlc-protokol.vercel.app

Built with commit 3cf9d25.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.