Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Refactor AppModule and Add Credential Management with Revocation Support #64

Merged
merged 141 commits into from
Jan 22, 2025

Conversation

lotharking
Copy link
Contributor

@lotharking lotharking commented Dec 16, 2024

Summary

Flow:

  1. Create a Credential

    • The user can request to create a credential.
    • The library validates whether the credential can be created and ensures there isn't already one associated with the same hash.
      • If a credential with the same hash exists, the credential will be revoked and associated with the new connection (from where the request is sent). A new credential request will then be initiated, ensuring the proper threadId is sent as part of the process
      • If valid, the credential creation request is sent.
  2. Rejection

    • Associates the thread and marks the credential as revoked (revoked only in terms of database nomenclature).
  3. Acceptance

    • Associates the thread with the credential.
  4. Revocation

    • When a credential is revoked, it is marked as revoked in the database.

Additional Constraints:

  • In all cases, a new record is created, ensuring indices do not overlap and atomicity is guaranteed during queries.
  • Records are expected to be assigned from 0 to the maximum value minus 1 (-1).
    • Validation has confirmed that assignments from 1 to the maximum value fail.
  • Two revocation records will always remain available.

Changes

  • A refactoring was implemented in the AppModule of the nestjs-client module. This was necessary because the original main module was not optimized for the recurrent use of variables, requiring multiple instantiations of the same variables. Modifying the AppModule and the configuration approach avoided redundancy when the module is instantiated. Compatibility with individual configurations was maintained.
  • Support for credential management and revocation was implemented when configuring the nestjs-client module. The methods are controlled through the library, allowing for handling rejections and revocations as well.
  • A demo was created to showcase how to use the demo-dts. As observed, the configuration required for complete credential management is minimal.

Related Issues

Testing

Checklist

  • I have commented on my code, especially in areas that are hard to understand.
  • I have added only changes relevant to the issue in question.
  • I have added tests to confirm that my fix is effective or that my feature works as intended.
  • I have modified existing tests as needed to accommodate my changes.
  • I have flagged specific areas for further review, if necessary.
  • I have updated the documentation where necessary.

@lotharking lotharking requested a review from genaris January 16, 2025 17:45
}
const { id: credentialDefinitionId, revocationSupported } = credentialType

const cred = await this.credentialRepository.findOne({
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that there is a chance that the user issued multiple credentials with the same refId (i.e. they didn't set revokeIfAlreadyIssued) and then they attempt to issue another credential with the same refId but using revokeIfAlreadyIssued = true, only the first credential found here will be revoked.

I think it would be safer to find all non-revoked issued credentials that match the refId and revoke/save them accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: implement revocation for all non-revoked credentials

}

// private methods
private async saveCredentialType(
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand the meaning of this method: why does it save a credential type in the credential repository?

Why don't you simply name it createRevocationRegistry and call it only when appropriate (i.e. supportRevocation is true when creating credential type or the current revocation registry is full)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: updated to createRevocationRegistry

return revocationRegistry
}

private refIdHash(refId: string): string {
Copy link
Contributor

Choose a reason for hiding this comment

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

The method itself seems to be a simple hash operation. What about making it general and simply call it hash(value: string)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: change name refIdHash by hash

* @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> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Now I understand why you called this service an events service in the beginning: accept and reject are user actions, so they are called by event handlers coming from the agent. Since they are not actions performed by us (such as credential issuance and revocation), they can be called `processAcceptance/processRejection' or 'handleAcceptance/handleRejection', showing that they are supposed to act as a result from an event coming from the DIDComm layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: change name to handleAcceptance/handleRejection

* @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({
Copy link
Contributor

Choose a reason for hiding this comment

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

A problem I see here is that you are using connectionId to identify the credential the event is related to. This is not correct, since you can issue multiple credentials to a single DIDComm connection.

The proper way you should identify a credential is by its threadId. Therefore, these accept and reject methods should only receive the threadId.

You can find the threadId in the response of the Message endpoint (the id correspond to the threadId).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: acept and reject only by thread

if (!cred) throw new Error(`Credencial with connectionId ${connectionId} not found.`)

cred.threadId = threadId
cred.revoked = true
Copy link
Contributor

Choose a reason for hiding this comment

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

A rejection by the user does not mean that a credential has been revoked. Maybe you can create a status field for the credential entity that covers the entire life cycle of a credential: probably offered, accepted, rejected and revoked may be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: implement to CredentialStatus

maximumCredentialNumber?: number
} = {},
) {
const { name = 'Chatbot', version = '1.0', supportRevocation, maximumCredentialNumber } = options
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it is correct to leave name and version as optional fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: remove name and version from optionals fields

revocationRegistryIndex: undefined,
}
}
const invalidRegistries = await transaction.find(CredentialEntity, {
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic is quite obscure to me. I don't think it will work.

I guess that somewhere (maybe another table/entity) you should keep track of the current revocation registry and index for each credential definition, and use that as the source of truth to assign the revocation registry/index of each issued credential, and tell if it is needed to create a new revocation registry or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

feat: Creation of the RevocationRegistryEntity for managing revocation records.

Please review this last change with special care. The RevocationRegistryEntity was created, and during its implementation, it was detected that the threadId returned by the CredentialIssuanceMessage was not being properly handled by the SA. As a solution, GenericRecords was used to retrieve it. Additionally, as you mentioned, the handling of credentials has been improved by dividing the entities.

@lotharking lotharking requested a review from genaris January 20, 2025 13:45
Signed-off-by: Ariel Gentile <gentilester@gmail.com>
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm'

@Entity('revocation_registries')
export class RevocationRegistryEntity {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add credentialDefinitionId

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

revocationDefinitionId!: string

@Column({ type: 'integer', nullable: false, default: 0 })
revocationRegistryIndex!: number
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
revocationRegistryIndex!: number
currentIndex!: number

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: implemented currentIndex

id!: string

@Column({ type: 'varchar', nullable: false })
credentialDefinitionId!: string
Copy link
Contributor

Choose a reason for hiding this comment

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

add revocationRegistryIndex

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix: add credentialDefinitionId to revocation registry entity

})
}
let lastCred = await transaction
.createQueryBuilder(RevocationRegistryEntity, 'registry')
Copy link
Contributor

Choose a reason for hiding this comment

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

add credentialDefinitionId to the filter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@lotharking lotharking requested a review from genaris January 22, 2025 13:22
@genaris genaris merged commit 9758805 into main Jan 22, 2025
2 checks passed
@genaris genaris deleted the feat/create-revocation-nestjs-client branch January 22, 2025 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support revocable credentials
2 participants