-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Conversation
packages/main/src/controllers/credentials/CredentialTypeController.ts
Outdated
Show resolved
Hide resolved
await this.saveCredential(credential.id, supportRevocation) | ||
await this.saveCredential(credential.id, supportRevocation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as before: these methods are saving credential types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix: name credentialType
* @param {object} [options] - Additional options for credential issuance. | ||
* | ||
* ### Options | ||
* - `refId` (optional, `string`): A unique identifier for the credential. If provided: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great explanation! But it makes the reader think that the only requirement for credential revocation at issuance is to have the same refId
, while currently we also need to set autoRevocationEnabled
to do so.
If autoRevocationEnabled is false (default), multiple credentials with the same refId will be created. This is not necessarily wrong, but what happens if afterwards I want to issue a credential with the same refId and turn autoRevocationEnabled on? Will it revoke ALL previous credentials with the same refId?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs: update
* - Encrypted in the database for security. | ||
* - `credentialDefinitionId` (optional, `string`): Specifies the ID of the credential definition to use. | ||
* - If not provided, the first available credential definition is used. | ||
* - `autoRevocationEnabled` (optional, `boolean`): Whether automatic revocation is enabled (default false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be more self-explanatory. Maybe something like: revokeIfAlreadyIssued
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix: add revokeIfAlreadyIssued
threadId?: string | ||
|
||
@Column({ type: 'varchar', nullable: true }) | ||
hashIdentifier?: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this field should be renamed. refIdHash
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix: name refIdHash
Co-authored-by: Ariel Gentile <gentilester@gmail.com>
…ontroller.ts Co-authored-by: Ariel Gentile <gentilester@gmail.com>
Co-authored-by: Ariel Gentile <gentilester@gmail.com>
Co-authored-by: Ariel Gentile <gentilester@gmail.com>
} | ||
const { id: credentialDefinitionId, revocationSupported } = credentialType | ||
|
||
const cred = await this.credentialRepository.findOne({ |
There was a problem hiding this comment.
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.
} | ||
|
||
// private methods | ||
private async saveCredentialType( |
There was a problem hiding this comment.
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)?
return revocationRegistry | ||
} | ||
|
||
private refIdHash(refId: string): string { |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
* @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({ |
There was a problem hiding this comment.
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).
if (!cred) throw new Error(`Credencial with connectionId ${connectionId} not found.`) | ||
|
||
cred.threadId = threadId | ||
cred.revoked = true |
There was a problem hiding this comment.
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.
maximumCredentialNumber?: number | ||
} = {}, | ||
) { | ||
const { name = 'Chatbot', version = '1.0', supportRevocation, maximumCredentialNumber } = options |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, { |
There was a problem hiding this comment.
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.
Co-authored-by: Ariel Gentile <gentilester@gmail.com>
Summary
Flow:
Create a Credential
Rejection
Acceptance
Revocation
Additional Constraints:
0
to the maximum value minus 1 (-1
).1
to the maximum value fail.Changes
AppModule
of thenestjs-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 theAppModule
and the configuration approach avoided redundancy when the module is instantiated. Compatibility with individual configurations was maintained.nestjs-client
module. The methods are controlled through the library, allowing for handling rejections and revocations as well.demo-dts
. As observed, the configuration required for complete credential management is minimal.Related Issues
Testing
Checklist