Skip to content

Commit

Permalink
chore: PR feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
sanderPostma committed Jan 25, 2025
1 parent 6d68df1 commit 6ac08a7
Show file tree
Hide file tree
Showing 19 changed files with 192 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,6 @@ export class StatusList2021Entity extends StatusListEntity {
export class OAuthStatusListEntity extends StatusListEntity {
@Column({ type: 'integer', name: 'bitsPerStatus', nullable: false })
bitsPerStatus!: number
@Column({ type: 'varchar', name: 'expiresAt', nullable: true })
expiresAt?: string
@Column({ type: 'timestamptz', name: 'expiresAt', nullable: true })
expiresAt?: Date
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class UpdateStatusList1737110469001 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "StatusList" ALTER COLUMN "indexingDirection" DROP NOT NULL`)
await queryRunner.query(`ALTER TABLE "StatusList" ALTER COLUMN "statusPurpose" DROP NOT NULL`)
await queryRunner.query(`ALTER TABLE "StatusList" ADD "bitsPerStatus" integer`)
await queryRunner.query(`ALTER TABLE "StatusList" ADD "expiresAt" varchar`)
await queryRunner.query(`ALTER TABLE "StatusList" ADD "expiresAt" timestamp with time zone`)
}

public async down(queryRunner: QueryRunner): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class UpdateStatusList1737110469000 implements MigrationInterface {
"statusPurpose" varchar,
"statusListCredential" text,
"bitsPerStatus" integer,
"expiresAt" varchar,
"expiresAt" datetime,
CONSTRAINT "UQ_correlationId" UNIQUE ("correlationId")
)`,
)
Expand Down
2 changes: 1 addition & 1 deletion packages/data-store/src/types/statusList/statusList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface IStatusList2021Entity extends IStatusListEntity {

export interface IOAuthStatusListEntity extends IStatusListEntity {
bitsPerStatus: number
expiresAt?: string
expiresAt?: Date
}

export interface IStatusListEntryEntity {
Expand Down
24 changes: 11 additions & 13 deletions packages/data-store/src/utils/statusList/MappingUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const statusListFrom = (entity: StatusListEntity): IStatusListEntity => {
throw new Error(`Invalid status list type ${typeof entity}`)
}

function setBaseFields(entity: StatusListEntity, args: IStatusListEntity) {
const setBaseFields = (entity: StatusListEntity, args: IStatusListEntity) => {
entity.id = args.id
entity.correlationId = args.correlationId
entity.length = args.length
Expand All @@ -70,15 +70,13 @@ function setBaseFields(entity: StatusListEntity, args: IStatusListEntity) {
entity.statusListCredential = args.statusListCredential
}

function getBaseFields(entity: StatusListEntity): Omit<IStatusListEntity, 'type'> {
return {
id: entity.id,
correlationId: entity.correlationId,
length: entity.length,
issuer: entity.issuer,
driverType: entity.driverType,
credentialIdMode: entity.credentialIdMode,
proofFormat: entity.proofFormat,
statusListCredential: entity.statusListCredential,
}
}
const getBaseFields = (entity: StatusListEntity): Omit<IStatusListEntity, 'type'> => ({
id: entity.id,
correlationId: entity.correlationId,
length: entity.length,
issuer: entity.issuer,
driverType: entity.driverType,
credentialIdMode: entity.credentialIdMode,
proofFormat: entity.proofFormat,
statusListCredential: entity.statusListCredential,
})
4 changes: 2 additions & 2 deletions packages/ssi-types/src/types/status-list.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { W3CVerifiableCredential } from './w3c-vc'
import { ProofFormat as VmoProofFormat } from '@veramo/core'
import { ProofFormat as VeramoProofFormat } from '@veramo/core'

export enum StatusListType {
StatusList2021 = 'StatusList2021',
Expand All @@ -9,4 +9,4 @@ export type CWT = string

export type StatusListCredential = W3CVerifiableCredential | CWT

export type ProofFormat = VmoProofFormat | 'cbor'
export type ProofFormat = VeramoProofFormat | 'cbor'
4 changes: 1 addition & 3 deletions packages/vc-status-list-issuer-drivers/src/drivers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,7 @@ export class AgentDataSourceStatusListDriver implements IStatusListDriver {
},
statusListEntry,
}
}

if (this.isOAuthStatusListEntity(statusList)) {
} else if (this.isOAuthStatusListEntity(statusList)) {
return {
credentialStatus: {
id: `${statusList.id}#${statusListEntry.statusListIndex}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function statusListResultToEntity(result: StatusListResult): StatusList20
return Object.assign(new OAuthStatusListEntity(), {
...baseFields,
bitsPerStatus: result.oauthStatusList.bitsPerStatus,
expiresAt: undefined, // Optional field
expiresAt: result.oauthStatusList.expiresAt,
})
}
throw new Error(`Unsupported status list type: ${result.type}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export function getStatusListCredentialIndexStatusEndpoint(router: Router, conte
const type = details.type === StatusListType.StatusList2021 ? 'StatusList2021Entry' : details.type
const status = await checkStatusIndexFromStatusListCredential({
statusListCredential: details.statusListCredential,
statusPurpose: details.statusList2021?.statusPurpose,
...(details.type === StatusListType.StatusList2021 ? { statusPurpose: details.statusList2021?.statusPurpose } : {}),
type,
id: details.id,
statusListIndex,
Expand Down
25 changes: 14 additions & 11 deletions packages/vc-status-list-issuer/src/agent/StatusListPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@sphereon/ssi-sdk.vc-status-list'
import { getDriver } from '@sphereon/ssi-sdk.vc-status-list-issuer-drivers'
import { Loggers } from '@sphereon/ssi-types'
import { IAgentContext, IAgentPlugin } from '@veramo/core'
import { IAgentContext, IAgentPlugin, IKeyManager } from '@veramo/core'
import { createStatusListFromInstance, handleCredentialStatus } from '../functions'
import { StatusListInstance } from '../types'

Expand Down Expand Up @@ -47,7 +47,10 @@ export class StatusListPlugin implements IAgentPlugin {
this.autoCreateInstances = opts.autoCreateInstances ?? true
}

private async slGetStatusList(args: GetStatusListArgs, context: IAgentContext<IRequiredPlugins & IStatusListPlugin>): Promise<StatusListResult> {
private async slGetStatusList(
args: GetStatusListArgs,
context: IAgentContext<IRequiredPlugins & IStatusListPlugin & IKeyManager>,
): Promise<StatusListResult> {
const sl = this.instances.find((instance) => instance.id === args.id || instance.correlationId === args.correlationId)
const dataSource =
(sl?.dataSource ?? args?.dataSource)
Expand All @@ -73,7 +76,7 @@ export class StatusListPlugin implements IAgentPlugin {

private async slCreateStatusList(
args: CreateNewStatusListArgs,
context: IAgentContext<IRequiredPlugins & IStatusListPlugin>,
context: IAgentContext<IRequiredPlugins & IStatusListPlugin & IKeyManager>,
): Promise<StatusListResult> {
const sl = await createNewStatusList(args, context)
const dataSource = args?.dataSource
Expand All @@ -86,29 +89,29 @@ export class StatusListPlugin implements IAgentPlugin {
correlationId: sl.correlationId,
dataSource,
})
let statusList: StatusListResult | undefined = undefined
let statusListDetails: StatusListResult | undefined = undefined
try {
statusList = await this.slGetStatusList(args, context)
statusListDetails = await this.slGetStatusList(args, context)
} catch (e) {
// That is fine if there is no status list yet
}
if (statusList && this.instances.find((sl) => sl.id === args.id || sl.correlationId === args.correlationId)) {
if (statusListDetails && this.instances.find((sl) => sl.id === args.id || sl.correlationId === args.correlationId)) {
return Promise.reject(Error(`Status list with id ${args.id} or correlation id ${args.correlationId} already exists`))
} else {
statusList = await driver.createStatusList({
statusListDetails = await driver.createStatusList({
statusListCredential: sl.statusListCredential,
correlationId: sl.correlationId,
})
this.instances.push({
correlationId: statusList.correlationId,
id: statusList.id,
correlationId: statusListDetails.correlationId,
id: statusListDetails.id,
dataSource,
driverType: statusList.driverType!,
driverType: statusListDetails.driverType!,
driverOptions: driver.getOptions(),
})
}

return statusList
return statusListDetails
}

private async slAddStatusToCredential(args: IAddStatusToCredentialArgs, context: IRequiredContext): Promise<CredentialWithStatusSupport> {
Expand Down
29 changes: 13 additions & 16 deletions packages/vc-status-list/src/functions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
import { CredentialMapper, ProofFormat, StatusListDriverType, StatusListType, StatusListCredential, StatusPurpose2021 } from '@sphereon/ssi-types'
import { CredentialStatus, DIDDocument, IAgentContext, ICredentialPlugin, ProofFormat as VmoProofFormat } from '@veramo/core'
import { CredentialMapper, ProofFormat, StatusListCredential, StatusListDriverType, StatusListType, StatusPurpose2021 } from '@sphereon/ssi-types'
import { CredentialStatus, DIDDocument, IAgentContext, ICredentialPlugin, ProofFormat as VeramoProofFormat } from '@veramo/core'

import { checkStatus } from '@sphereon/vc-status-list'
import { CredentialJwtOrJSON, StatusMethod } from 'credential-status'
Expand All @@ -11,7 +11,7 @@ import {
StatusListResult,
StatusOAuth,
UpdateStatusListFromEncodedListArgs,
UpdateStatusListFromStatusListCredentialArgs,
UpdateStatusListIndexArgs,
} from './types'
import { assertValidProofType, determineStatusListType, getAssertedValue, getAssertedValues } from './utils'
import { getStatusListImplementation } from './impl/StatusListFactory'
Expand Down Expand Up @@ -155,7 +155,7 @@ export async function createNewStatusList(
}

export async function updateStatusIndexFromStatusListCredential(
args: UpdateStatusListFromStatusListCredentialArgs,
args: UpdateStatusListIndexArgs,
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
): Promise<StatusListResult> {
const credential = getAssertedValue('statusListCredential', args.statusListCredential)
Expand All @@ -176,16 +176,14 @@ export async function statusListCredentialToDetails(args: {
if (!type) {
throw new Error('Invalid status list credential type')
}
const statusListType = type.replace('Credential', '') as StatusListType
const statusListType = type.replace('Credential', '') as StatusListType // "StatusList2021Credential" is a VC schema type and does not map 1:1 to our internal StatusListType enum

const implementation = getStatusListImplementation(statusListType)
return implementation.updateStatusListIndex(
{
statusListCredential: args.statusListCredential,
statusListIndex: 0,
value: 0,
},
{} as IAgentContext<ICredentialPlugin & IIdentifierResolution>,
)
return await implementation.toStatusListDetails({
statusListPayload: credential,
correlationId: args.correlationId,
driverType: args.driverType,
})
}

export async function updateStatusListIndexFromEncodedList(
Expand All @@ -197,7 +195,6 @@ export async function updateStatusListIndexFromEncodedList(
return implementation.updateStatusListFromEncodedList(args, context)
}

// TODO Is this still in use? Or do we need to redesign this after having multiple status list types?
export async function statusList2021ToVerifiableCredential(
args: StatusList2021ToVerifiableCredentialArgs,
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
Expand All @@ -210,7 +207,7 @@ export async function statusList2021ToVerifiableCredential(
})
const proofFormat: ProofFormat = args?.proofFormat ?? 'lds'
assertValidProofType(StatusListType.StatusList2021, proofFormat)
const vmoProofFormat: VmoProofFormat = proofFormat as VmoProofFormat
const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat

const encodedList = getAssertedValue('encodedList', args.encodedList)
const statusPurpose = getAssertedValue('statusPurpose', args.statusPurpose)
Expand All @@ -231,7 +228,7 @@ export async function statusList2021ToVerifiableCredential(
const verifiableCredential = await context.agent.createVerifiableCredential({
credential,
keyRef: identifier.kmsKeyRef,
proofFormat: vmoProofFormat,
proofFormat: veramoProofFormat,
fetchRemoteContexts: true,
})

Expand Down
6 changes: 6 additions & 0 deletions packages/vc-status-list/src/impl/IStatusList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Status2021,
StatusListResult,
StatusOAuth,
ToStatusListDetailsArgs,
UpdateStatusListFromEncodedListArgs,
UpdateStatusListIndexArgs,
} from '../types'
Expand Down Expand Up @@ -33,4 +34,9 @@ export interface IStatusList {
* Checks the status at a given index in the status list
*/
checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth>

/**
* Collects the status list details
*/
toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult>
}
43 changes: 31 additions & 12 deletions packages/vc-status-list/src/impl/OAuthStatusList.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
import { IAgentContext, ICredentialPlugin } from '@veramo/core'
import { ProofFormat, StatusListType } from '@sphereon/ssi-types'
import { IAgentContext, ICredentialPlugin, IKeyManager } from '@veramo/core'
import { CompactJWT, CWT, ProofFormat, StatusListType } from '@sphereon/ssi-types'
import {
CheckStatusIndexArgs,
CreateStatusListArgs,
SignedStatusListData,
StatusListResult,
StatusOAuth,
ToStatusListDetailsArgs,
UpdateStatusListFromEncodedListArgs,
UpdateStatusListIndexArgs,
} from '../types'
import { determineProofFormat, getAssertedValue, getAssertedValues } from '../utils'
import { IStatusList } from './IStatusList'
import { StatusList, StatusListJWTHeaderParameters } from '@sd-jwt/jwt-status-list'
import { StatusList } from '@sd-jwt/jwt-status-list'
import { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
import { createSignedJwt, decodeStatusListJWT } from './encoding/jwt'
import { createSignedCbor, decodeStatusListCWT } from './encoding/cbor'

type IRequiredContext = IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution>
type IRequiredContext = IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>

export const BITS_PER_STATUS_DEFAULT = 2 // 2 bits are sufficient for 0x00 - "VALID" 0x01 - "INVALID" & 0x02 - "SUSPENDED"
export const DEFAULT_BITS_PER_STATUS = 2 // 2 bits are sufficient for 0x00 - "VALID" 0x01 - "INVALID" & 0x02 - "SUSPENDED"
export const DEFAULT_LIST_LENGTH = 250000
export const DEFAULT_PROOF_FORMAT = 'jwt' as ProofFormat
export const STATUS_LIST_JWT_HEADER: StatusListJWTHeaderParameters = {
alg: 'EdDSA',
typ: 'statuslist+jwt',
}

export class OAuthStatusListImplementation implements IStatusList {
async createNewStatusList(args: CreateStatusListArgs, context: IRequiredContext): Promise<StatusListResult> {
Expand All @@ -36,7 +33,7 @@ export class OAuthStatusListImplementation implements IStatusList {
const proofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
const { issuer, id, keyRef } = args
const length = args.length ?? DEFAULT_LIST_LENGTH
const bitsPerStatus = args.oauthStatusList.bitsPerStatus ?? BITS_PER_STATUS_DEFAULT
const bitsPerStatus = args.oauthStatusList.bitsPerStatus ?? DEFAULT_BITS_PER_STATUS
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
const correlationId = getAssertedValue('correlationId', args.correlationId)

Expand Down Expand Up @@ -103,7 +100,7 @@ export class OAuthStatusListImplementation implements IStatusList {

const proofFormat = args.proofFormat ?? DEFAULT_PROOF_FORMAT
const { issuer, id } = getAssertedValues(args)
const bitsPerStatus = args.oauthStatusList.bitsPerStatus ?? BITS_PER_STATUS_DEFAULT
const bitsPerStatus = args.oauthStatusList.bitsPerStatus ?? DEFAULT_BITS_PER_STATUS
const issuerString = typeof issuer === 'string' ? issuer : issuer.id

const listToUpdate = StatusList.decompressStatusList(args.encodedList, bitsPerStatus)
Expand Down Expand Up @@ -143,9 +140,31 @@ export class OAuthStatusListImplementation implements IStatusList {
return statusList.getStatus(index)
}

async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
const { statusListPayload } = args as { statusListPayload: CompactJWT | CWT }
const proofFormat = determineProofFormat(statusListPayload)
const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListPayload) : decodeStatusListCWT(statusListPayload)
const { statusList, issuer, id } = decoded

return {
id,
encodedList: statusList.compressStatusList(),
issuer,
type: StatusListType.OAuthStatusList,
proofFormat,
length: statusList.statusList.length,
statusListCredential: statusListPayload,
oauthStatusList: {
bitsPerStatus: statusList.getBitsPerStatus(),
},
...(args.correlationId && { correlationId: args.correlationId }),
...(args.driverType && { driverType: args.driverType }),
}
}

private async createSignedStatusList(
proofFormat: 'jwt' | 'lds' | 'EthereumEip712Signature2021' | 'cbor',
context: IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution>,
context: IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>,
statusList: StatusList,
issuerString: string,
id: string,
Expand Down
Loading

0 comments on commit 6ac08a7

Please sign in to comment.