Skip to content

Commit

Permalink
Revocation Service Implementation
Browse files Browse the repository at this point in the history
Fixes openwallet-foundation-labs#24

Signed-off-by: Mirko Mollik <[email protected]>
  • Loading branch information
cre8 committed May 22, 2024
1 parent 1f01f91 commit 2766758
Show file tree
Hide file tree
Showing 120 changed files with 3,356 additions and 733 deletions.
18 changes: 11 additions & 7 deletions apps/holder-app/src/app/scanner/scanner.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,7 @@ export class ScannerComponent implements OnInit, OnDestroy {
* Stop the scanner when leaving the page
*/
async ngOnDestroy(): Promise<void> {
if (this.scanner) {
await this.scanner.stop();
}
await this.stopScanning();
}

/**
Expand Down Expand Up @@ -120,7 +118,7 @@ export class ScannerComponent implements OnInit, OnDestroy {
* @param cameraId
*/
async changeCamera(cameraId: string) {
await this.scanner?.stop();
await this.stopScanning();
this.selectedDevice = cameraId;
await this.startCamera();
}
Expand All @@ -134,15 +132,21 @@ export class ScannerComponent implements OnInit, OnDestroy {
if (decodedText.startsWith('openid-credential-offer://')) {
this.showRequest(decodedText, 'receive');
// use a constant for the verification schema
await this.scanner?.stop();
await this.stopScanning();
} else if (decodedText.startsWith('openid://')) {
this.showRequest(decodedText, 'send');
await this.scanner?.stop();
await this.stopScanning();
} else {
alert("Scanned text doesn't match the expected format");
}
}

private async stopScanning() {
if (this.scanner?.isScanning) {
await this.scanner.stop();
}
}

/**
* Send a credential request to the demo issuer
*/
Expand Down Expand Up @@ -178,7 +182,7 @@ export class ScannerComponent implements OnInit, OnDestroy {
* @param action
*/
async showRequest(url: string, action: 'send' | 'receive') {
await this.scanner?.stop();
await this.stopScanning();
this.url = url;
if (action === 'receive') {
this.status = 'showRequest';
Expand Down
2 changes: 2 additions & 0 deletions apps/holder-backend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Oid4vcModule } from './oid4vc/oid4vc.module';
import { HistoryModule } from './history/history.module';
import { AppController } from './app.controller';
import { SettingsModule } from './settings/settings.module';
import { ScheduleModule } from '@nestjs/schedule';

@Module({
imports: [
Expand All @@ -19,6 +20,7 @@ import { SettingsModule } from './settings/settings.module';
...DB_VALIDATION_SCHEMA,
}),
}),
ScheduleModule.forRoot(),
AuthModule,
DbModule,
KeysModule.forRootSync(),
Expand Down
32 changes: 29 additions & 3 deletions apps/holder-backend/src/app/credentials/credentials.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export class CredentialsService {
@InjectRepository(Credential)
private credentialRepository: Repository<Credential>
) {
this.instance = new SDJwtVcInstance({ hasher: digest });
this.instance = new SDJwtVcInstance({
hasher: digest,
verifier: () => Promise.resolve(true),
});
this.updateStatus();
}

async create(createCredentialDto: CreateCredentialDto, user: string) {
Expand All @@ -25,12 +29,23 @@ export class CredentialsService {
credential.value = createCredentialDto.value;
credential.metaData = createCredentialDto.metaData;
credential.issuer = createCredentialDto.issuer;
credential.exp = await this.getExp(createCredentialDto.value);
await this.credentialRepository.save(credential);
return {
id: credential.id,
};
}

private getExp(credential: string) {
return this.instance
.decode(credential)
.then((vc) =>
vc.jwt.payload.exp
? new Date((vc.jwt.payload.exp as number) * 1000)
: undefined
);
}

findAll(user: string) {
return this.credentialRepository.find({ where: { user } });
}
Expand All @@ -43,8 +58,6 @@ export class CredentialsService {
return this.findOne(id, user).then(async (entry) => {
const sdjwtvc = await this.instance.decode(entry.value);
const claims = await sdjwtvc.getClaims<Record<string, unknown>>(digest);
claims.status = undefined;
entry.value = undefined;
entry.user = undefined;
return {
...entry,
Expand All @@ -56,4 +69,17 @@ export class CredentialsService {
remove(id: string, user: string) {
return this.credentialRepository.delete({ id, user });
}

async updateStatus() {
const credentials = await this.credentialRepository.find();
for (const credential of credentials) {
await this.instance.verify(credential.value).catch(async (err: Error) => {
if (err.message.includes('Status is not valid')) {
//update the status in the db.
credential.status = 'revoked';
await this.credentialRepository.save(credential);
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,16 @@ export class Credential {
@Column({ nullable: true, type: 'json' })
@ApiProperty({ type: CredentialIssuer })
issuer: MetadataDisplay;

/**
* The expiration date of the credential
*/
@Column({ nullable: true })
exp?: Date;

/**
* The status of the credential
*/
@Column({ nullable: true })
status?: string;
}
15 changes: 14 additions & 1 deletion apps/issuer-backend/src/app/issuer/dto/session-request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { IsBoolean, IsObject, IsOptional, IsString } from 'class-validator';
import {
IsBoolean,
IsNumber,
IsObject,
IsOptional,
IsString,
} from 'class-validator';
export class SessionRequestDto {
/**
* The subject of the credential that should be issued.
Expand All @@ -19,4 +25,11 @@ export class SessionRequestDto {
@IsBoolean()
@IsOptional()
pin: boolean;

/**
* The expiration time of the credential; seconds since unix epoch.
*/
@IsNumber()
@IsOptional()
exp?: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class SessionResponseDto {
uri: string;
userPin?: string;
id: string;
status: string;
}
3 changes: 3 additions & 0 deletions apps/issuer-backend/src/app/issuer/dto/session-status.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class SessionStatus {
status: string;
}
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 @@ -69,7 +69,7 @@ export class IssuerDataService {
if (!credential) {
throw new Error(`The credential with the id ${id} is not supported.`);
}
return credential.schema;
return credential;
}

/**
Expand Down
20 changes: 16 additions & 4 deletions apps/issuer-backend/src/app/issuer/issuer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@ import {
} from '@nestjs/common';
import { IssuerService } from './issuer.service';
import { SessionRequestDto } from './dto/session-request.dto';
import { ApiOAuth2, ApiOperation, ApiTags } from '@nestjs/swagger';
import {
ApiOAuth2,
ApiOkResponse,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { AuthGuard } from 'nest-keycloak-connect';
import { SessionResponseDto } from './dto/session-response.dto';
import { SessionStatus } from './dto/session-status.dto';

@UseGuards(AuthGuard)
@ApiOAuth2([])
Expand All @@ -22,18 +30,22 @@ export class IssuerController {

@ApiOperation({ summary: 'Returns the status for a session' })
@Get(':id')
async getSession(@Param('id') id: string) {
async getSession(@Param('id') id: string): Promise<SessionStatus> {
const session =
await this.issuerService.vcIssuer.credentialOfferSessions.get(id);
if (!session) {
throw new NotFoundException(`Session with id ${id} not found`);
}
return session;
return {
status: session.status,
};
}

@ApiOperation({ summary: 'Creates a new session request' })
@Post()
async request(@Body() values: SessionRequestDto) {
async request(
@Body() values: SessionRequestDto
): Promise<SessionResponseDto> {
return this.issuerService.createRequest(values);
}

Expand Down
60 changes: 39 additions & 21 deletions apps/issuer-backend/src/app/issuer/issuer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ import { CredentialsService } from '../credentials/credentials.service';
import { KeyService } from '@my-wallet/relying-party-shared';
import { IssuerMetadata } from './types';
import { StatusService } from '../status/status.service';
import { SessionResponseDto } from './dto/session-response.dto';

interface CredentialDataSupplierInput {
credentialSubject: Record<string, unknown>;
exp: number;
}

@Injectable()
export class IssuerService implements OnModuleInit {
Expand Down Expand Up @@ -70,27 +76,42 @@ export class IssuerService implements OnModuleInit {
};
}

async createRequest(values: SessionRequestDto) {
async createRequest(values: SessionRequestDto): Promise<SessionResponseDto> {
const credentialId = values.credentialId;
const sessionId = v4();
try {
const credential = this.issuerDataService.getCredential(credentialId);
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) {
exp = values.exp;
} else if (credential.ttl) {
const expDate = new Date();
expDate.setSeconds(expDate.getSeconds() + credential.ttl);
exp = expDate.getTime();
}

const credentialDataSupplierInput: CredentialDataSupplierInput = {
credentialSubject: values.credentialSubject,
exp,
};
const response = await this.vcIssuer.createCredentialOfferURI({
credentials: [
this.issuerDataService.getCredential(credentialId).id as string,
],
credentials: [credential.schema.id as string],
grants: {
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
'pre-authorized_code': sessionId,
user_pin_required: values.pin,
},
},
credentialDataSupplierInput: {
credentialSubject: values.credentialSubject,
//TODO: allow to pass more values like should a status list be used. These values are defined be the issuer, not the holder that should receive the credential.
},
credentialDataSupplierInput,
});
//we are returning the response to the client
return response;
return {
uri: response.uri,
id: sessionId,
userPin: response.userPin,
status: 'CREATED',
};
} catch (error) {
throw new ConflictException(error.message);
}
Expand Down Expand Up @@ -134,17 +155,21 @@ export class IssuerService implements OnModuleInit {
* @returns
*/
const credentialDataSupplier: CredentialDataSupplier = async (args) => {
const status = await this.statusService.getEmptySlot();
const status_list = await this.statusService.getEmptySlot();
const status = {
status_list,
};

const credential: SdJwtDecodedVerifiableCredentialPayload = {
iat: new Date().getTime(),
iss: args.credentialOffer.credential_offer.credential_issuer,
vct: (args.credentialRequest as CredentialRequestSdJwtVc).vct,
jti: v4(),
...args.credentialDataSupplierInput.credentialSubject,
status: {
status_list: status,
},
...(args.credentialDataSupplierInput as CredentialDataSupplierInput)
.credentialSubject,
//TODO: can be removed when correct type is set in PEX
status: status as any,
exp: args.credentialDataSupplierInput.exp,
};
return Promise.resolve({
credential,
Expand Down Expand Up @@ -242,12 +267,5 @@ export class IssuerService implements OnModuleInit {
},
},
});

// start the webserver.
// expressSupport.start();
//print the routes, only for debugging purposes
// if (process.env.NODE_ENV === 'development') {
// expressListRoutes(expressSupport.express);
// }
}
}
2 changes: 2 additions & 0 deletions apps/issuer-backend/src/app/issuer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ export interface IssuerMetadata {
export interface CredentialSchema {
schema: CredentialSupported;
sd: DisclosureFrame<Record<string, unknown | boolean>>;
// time to live in seconds, it will be added on the current time to get the expiration time.
ttl?: number;
}
8 changes: 8 additions & 0 deletions apps/issuer-frontend/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@
"tags": ["latest"]
}
}
},
"api": {
"executor": "nx:run-commands",
"options": {
"commands": [
"npx @openapitools/openapi-generator-cli generate -g typescript-angular --skip-validate-spec -i http://localhost:3001/api-json -o apps/issuer-frontend/src/app/api --additional-properties=supportsES6=true,enumPropertyNaming=original,serviceSuffix=ApiService"
]
}
}
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
.gitignore
22 changes: 22 additions & 0 deletions apps/issuer-frontend/src/app/api/.openapi-generator/FILES
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
README.md
api.module.ts
api/api.ts
api/credentials.service.ts
api/default.service.ts
api/sessions.service.ts
api/status.service.ts
api/wellKnown.service.ts
configuration.ts
encoder.ts
git_push.sh
index.ts
model/changeStatusDto.ts
model/createListDto.ts
model/credential.ts
model/models.ts
model/sessionRequestDto.ts
model/sessionResponseDto.ts
model/sessionStatus.ts
model/statusList.ts
param.ts
variables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.6.0
File renamed without changes.
Loading

0 comments on commit 2766758

Please sign in to comment.