Skip to content

Commit

Permalink
feat: add credential service
Browse files Browse the repository at this point in the history
  • Loading branch information
lotharking committed Dec 20, 2024
1 parent 9129370 commit f44010e
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 20 deletions.
1 change: 1 addition & 0 deletions packages/nestjs-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"dependencies": {
"@2060.io/service-agent-model": "*",
"@2060.io/service-agent-client": "*",
"@credo-ts/core": "^0.5.11",
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
Expand Down
8 changes: 4 additions & 4 deletions packages/nestjs-client/src/credentials/credential.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export class CredentialEntity {
@Column({ type: 'varchar', nullable: false })
credentialDefinitionId!: string

@Column({ type: 'varchar', nullable: true })
revocationDefinitionId?: string
@Column({ type: 'varchar', nullable: false })
revocationDefinitionId!: string

@Column({ type: 'integer', nullable: true })
revocationRegistryIndex?: number
@Column({ type: 'integer', nullable: false })
revocationRegistryIndex!: number

@Column({ type: 'integer', nullable: false })
maximumCredentialNumber!: number
Expand Down
104 changes: 88 additions & 16 deletions packages/nestjs-client/src/credentials/credential.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiClient, ApiVersion } from '@2060.io/service-agent-client'
import { Claim, CredentialIssuanceMessage, CredentialRevocationMessage } from '@2060.io/service-agent-model'
import { Sha256, utils } from '@credo-ts/core'
import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { EntityManager, Repository } from 'typeorm'
Expand All @@ -16,7 +17,7 @@ export class CredentialEventService implements OnModuleInit {
private readonly url: string
private readonly apiVersion: ApiVersion
private readonly apiClient: ApiClient

//Credential type definitions
private readonly name: string
private readonly version: string
Expand Down Expand Up @@ -52,7 +53,7 @@ export class CredentialEventService implements OnModuleInit {

if (!credential) {
const credential = await this.apiClient.credentialTypes.create({
id: '', // TODO: implement uuid
id: utils.uuid(),
name: this.name,
version: this.version,
attributes: this.attributes,
Expand All @@ -63,20 +64,6 @@ export class CredentialEventService implements OnModuleInit {
}
}

async createRevocationRegistry(credentialDefinitionId: string) {
const revocationRegistry = await this.apiClient.revocationRegistry.create({
credentialDefinitionId,
maximumCredentialNumber: this.maximumCredentialNumber,
})
const credentialRev = this.credentialRepository.create({
credentialDefinitionId,
revocationDefinitionId: revocationRegistry,
revocationRegistryIndex: 0,
maximumCredentialNumber: this.maximumCredentialNumber,
})
await this.credentialRepository.save(credentialRev)
}

/**
* Sends a credential issuance to the specified connection using the provided claims.
* This method initiates the issuance process by sending claims as part of a credential to
Expand All @@ -97,7 +84,67 @@ export class CredentialEventService implements OnModuleInit {
* @returns {Promise<void>} A promise that resolves when the credential issuance is successfully
* sent. If an error occurs during the process, the promise will be rejected.
*/
async issuance(connectionId: string, records: Record<string, any>, hash: string): Promise<void> {
const [{ id: credentialDefinitionId }] = await this.apiClient.credentialTypes.getAll()
const claims: Claim[] = []
if (records) {
Object.entries(records).forEach(([key, value]) => {
claims.push(
new Claim({
name: key,
value: value ?? null,
}),
)
})
}

const { revocationDefinitionId, revocationRegistryIndex } = await this.entityManager.transaction(
async transaction => {
const lastCred = await transaction.findOne(CredentialEntity, {
where: {
credentialDefinitionId,
},
order: { revocationRegistryIndex: 'DESC' },
lock: { mode: 'pessimistic_write' },
})
if (!lastCred)
throw new Error(
'No valid registry definition found. Please restart the service and ensure the module is imported correctly',
)

const newCredential = await transaction.save(CredentialEntity, {
connectionId,
credentialDefinitionId,
revocationDefinitionId: lastCred.revocationDefinitionId,
revocationRegistryIndex: lastCred.revocationRegistryIndex + 1,
hash: Buffer.from(new Sha256().hash(hash)),
maximumCredentialNumber: lastCred.maximumCredentialNumber,
})
return {
revocationDefinitionId: newCredential.revocationDefinitionId,
revocationRegistryIndex: newCredential.revocationRegistryIndex,
}
},
)

await this.apiClient.messages.send(
new CredentialIssuanceMessage({
connectionId,
credentialDefinitionId,
revocationRegistryDefinitionId: revocationDefinitionId,
revocationRegistryIndex: revocationRegistryIndex,
claims: claims,
}),
)
this.logger.debug('sendCredential with claims: ' + JSON.stringify(claims))
}

/**
* Accepts a credential by associating it with the provided thread ID.
* @param connectionId - The connection ID associated with the credential.
* @param threadId - The thread ID to link with the credential.
* @throws Error if no credential is found with the specified connection ID.
*/
async accept(connectionId: string, threadId: string): Promise<void> {
const cred = await this.credentialRepository.findOne({
where: { connectionId: connectionId },
Expand All @@ -107,6 +154,11 @@ export class CredentialEventService implements OnModuleInit {
await this.credentialRepository.update(cred.id, { threadId })
}

/**
* Revokes a credential associated with the provided thread ID.
* @param threadId - The thread ID linked to the credential to revoke.
* @throws Error if no credential is found with the specified thread ID or if the credential has no connection ID.
*/
async revoke(threadId: string): Promise<void> {
const cred = await this.credentialRepository.findOne({ where: { threadId } })
if (!cred || !cred.connectionId) {
Expand All @@ -122,4 +174,24 @@ export class CredentialEventService implements OnModuleInit {
)
this.logger.log(`Revoke Credential: ${cred.id}`)
}

// private methods
private async createRevocationRegistry(credentialDefinitionId: string) {
const revocationRegistry = await this.apiClient.revocationRegistry.create({
credentialDefinitionId,
maximumCredentialNumber: this.maximumCredentialNumber,
})
const credentialRev = this.credentialRepository.create({
credentialDefinitionId,
revocationDefinitionId: revocationRegistry,
revocationRegistryIndex: 0,
maximumCredentialNumber: this.maximumCredentialNumber,
})
await this.credentialRepository.save(credentialRev)
}

private async createRegistry(record: Partial<CredentialEntity>) {
const credentialRev = this.credentialRepository.create(record)
await this.credentialRepository.save(credentialRev)
}
}

0 comments on commit f44010e

Please sign in to comment.