Skip to content

Commit

Permalink
feat: statuslist2021 support
Browse files Browse the repository at this point in the history
  • Loading branch information
nklomp committed Sep 6, 2023
1 parent 61729f3 commit 46986dd
Show file tree
Hide file tree
Showing 65 changed files with 6,423 additions and 1,231 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"license": "Apache-2.0",
"scripts": {
"preinstall": "npx only-allow pnpm",
"build": "pnpm build:js && pnpm build:copyfiles && pnpm build:api && pnpm build:schema",
"build": "rimraf --glob ./packages/*/tsconfig.tsbuildinfo && pnpm build:js && pnpm build:copyfiles && pnpm build:api && pnpm build:schema",
"build:clean": "lerna clean -y && pnpm install && lerna run build:clean --concurrency 1 && pnpm build:copyfiles && pnpm build:schema",
"build:js": "pnpm -r --stream build",
"build:api": "pnpm -r --stream extract-api",
Expand Down
5 changes: 4 additions & 1 deletion packages/agent-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
"@veramo/core": "4.2.0",
"jsonpointer": "^5.0.1",
"typeorm": "^0.3.12",
"debug": "^4.3.4",
"url-parse": "^1.5.10",
"yaml": "^2.2.2"
},
"devDependencies": {
"@types/url-parse": "^1.4.8"
"@types/url-parse": "^1.4.8",
"@types/debug": "^4.1.8",
"typescript": "4.9.5"
},
"files": [
"dist/**/*",
Expand Down
79 changes: 79 additions & 0 deletions packages/agent-config/src/dataSources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Debug from 'debug'
import { DataSource } from 'typeorm'
import { BaseDataSourceOptions } from 'typeorm/data-source/BaseDataSourceOptions'
import { DataSourceOptions } from 'typeorm/data-source/DataSourceOptions'

const debug = Debug(`demo:databaseService`)

export class DataSources {
private dataSources = new Map<string, DataSource>()
private configs

private static singleton: DataSources

public static singleInstance() {
if (!DataSources.singleton) {
DataSources.singleton = new DataSources()
}
return DataSources.singleton
}

public static newInstance(configs?: Map<string, DataSourceOptions>) {
return new DataSources(configs)
}

private constructor(configs?: Map<string, DataSourceOptions>) {
this.configs = configs ?? new Map<string, BaseDataSourceOptions>()
}

addConfig(dbName: string, config: DataSourceOptions): this {
this.configs.set(dbName, config)
return this
}

deleteConfig(dbName: string): this {
this.configs.delete(dbName)
return this
}

getConfig(dbName: string): BaseDataSourceOptions {
const config = this.configs.get(dbName)
if (!config) {
throw Error(`No DB config found for ${dbName}`)
}
return config
}

public getDbNames(): string[] {
return [...this.configs.keys()]
}

async getDbConnection(dbName: string): Promise<DataSource> {
const config = this.getConfig(dbName)
/*if (config.synchronize) {
return Promise.reject(
`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`
)
}*/

let dataSource = this.dataSources.get(dbName)
if (dataSource) {
return dataSource
}

dataSource = await new DataSource({ ...(config as DataSourceOptions), name: dbName }).initialize()
this.dataSources.set(dbName, dataSource)
if (config.synchronize) {
debug(`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`)
} else if (config.migrationsRun) {
debug(
`Migrations are currently managed from config. Please set migrationsRun and synchronize to false to get consistent behaviour. We run migrations from code explicitly`
)
} else {
debug(`Running ${dataSource.migrations.length} migration(s) from code if needed...`)
await dataSource.runMigrations()
debug(`${dataSource.migrations.length} migration(s) from code were inspected and applied`)
}
return dataSource
}
}
1 change: 1 addition & 0 deletions packages/agent-config/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* @public
*/
export * from './dataSources'
export * from './agentCreator'
export * from './objectCreator'
export * from './generic'
4 changes: 3 additions & 1 deletion packages/data-store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc --build",
"typeorm": "ts-node --project ./tsconfig.json -r tsconfig-paths/register ../../node_modules/typeorm/cli.js --config src/migrations/internal-migrations-ormconfig.ts",
"typeorm": "ts-node --project ./tsconfig.json -r tsconfig-paths/register ../../node_modules/.pnpm/typeorm@0.3.12_sqlite3@5.1.6_ts-node@10.9.1/node_modules/typeorm/cli.js --config src/migrations/internal-migrations-ormconfig.ts",
"typeorm-sqlite:migration:generate": "pnpm run typeorm -- migration:generate -c migration-sqlite -d src/migrations/sqlite -n",
"typeorm-sqlite:migration:run": "pnpm run typeorm -- migration:run -c migration-sqlite",
"typeorm-postgres:migration:generate": "pnpm run typeorm -- migration:generate -c migration-postgres -d src/migrations/postgres -n",
Expand All @@ -15,7 +15,9 @@
"dependencies": {
"@sphereon/ssi-types": "workspace:^",
"@veramo/core": "4.2.0",
"reflect-metadata": "^0.1.13",
"class-validator": "^0.14.0",
"tsconfig-paths": "^4.2.0",
"debug": "^4.3.4",
"typeorm": "^0.3.12"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
IIssuer,
OriginalVerifiableCredential,
StatusListCredentialIdMode,
StatusListDriverType,
StatusListIndexingDirection,
StatusListType,
StatusPurpose2021,
} from '@sphereon/ssi-types'
import { ProofFormat } from '@veramo/core'
import { BaseEntity, Column, Entity, OneToMany, PrimaryColumn, Unique } from 'typeorm'
import { StatusListEntryEntity } from './StatusList2021EntryEntity'

@Entity('StatusList')
@Unique('UQ_correlationId', ['correlationId'])
export class StatusListEntity extends BaseEntity {
@PrimaryColumn({ name: 'id', type: 'varchar' })
id!: string

@Column({ name: 'correlationId', type: 'varchar', nullable: false })
correlationId!: string

@Column({ name: 'length', nullable: false, unique: false })
length!: number

@Column({
name: 'issuer',
type: 'text',
nullable: false,
unique: false,
transformer: {
from(value: string): string | IIssuer {
if (value?.trim()?.startsWith('{')) {
return JSON.parse(value)
}
return value
},
to(value: string | IIssuer): string {
if (typeof value === 'string') {
return value
}
return JSON.stringify(value)
},
},
})
issuer!: string | IIssuer

@Column('simple-enum', { name: 'type', enum: StatusListType, nullable: false, default: StatusListType.StatusList2021 })
type!: StatusListType

@Column('simple-enum', { name: 'driverType', enum: StatusListDriverType, nullable: false, default: StatusListDriverType.AGENT_TYPEORM })
driverType!: StatusListDriverType

@Column('simple-enum', {
name: 'credentialIdMode',
enum: StatusListCredentialIdMode,
nullable: false,
default: StatusListCredentialIdMode.ISSUANCE,
})
credentialIdMode!: StatusListCredentialIdMode

@Column({ type: 'varchar', name: 'proofFormat', enum: ['lds', 'jwt'], nullable: false, default: 'lds' })
proofFormat!: ProofFormat

@Column({ type: 'varchar', name: 'indexingDirection', enum: ['rightToLeft'], nullable: false, default: 'rightToLeft' })
indexingDirection!: StatusListIndexingDirection

@Column({ type: 'varchar', name: 'statusPurpose', nullable: false, default: 'revocation' })
statusPurpose!: StatusPurpose2021

@Column({
name: 'statusListCredential',
type: 'text',
nullable: true,
unique: false,
transformer: {
from(value: string): OriginalVerifiableCredential {
if (value?.startsWith('ey')) {
return value
}
return JSON.parse(value)
},
to(value: OriginalVerifiableCredential): string {
if (typeof value === 'string') {
return value
}
return JSON.stringify(value)
},
},
})
statusListCredential?: OriginalVerifiableCredential

@OneToMany((type) => StatusListEntryEntity, (entry) => entry.statusList)
statusListEntries!: StatusListEntryEntity[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Validate } from 'class-validator'
import { BaseEntity, Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'
import { IsNonEmptyStringConstraint } from '../validators'
import { StatusListEntity } from './StatusList2021Entity'

@Entity('StatusListEntry')
// @Unique('uq_credential_statuslist', ['statusList', 'credentialId']) // disabled because one prop can be null
// @Unique('uq_credentialHash_statuslistId', ['statusList', 'credentialHash']) // disabled because one prop can be null
export class StatusListEntryEntity extends BaseEntity {
@PrimaryColumn({ name: 'statusListId', type: 'varchar' })
@ManyToOne(() => StatusListEntity, (statusList) => statusList.statusListEntries)
statusList!: StatusListEntity

@PrimaryColumn({ name: 'statusListIndex', nullable: false, unique: false })
@Validate(IsNonEmptyStringConstraint, { message: 'Status list index is required' })
statusListIndex!: number

@Column({ name: 'credentialId', nullable: true })
credentialId?: string

@Column({ name: 'credentialHash', length: 128, nullable: true, unique: false })
credentialHash?: string

@Column({ name: 'correlationId', length: 255, nullable: true, unique: false })
correlationId?: string

@Column({ name: 'value', length: 50, nullable: true, unique: false })
value?: string
}
13 changes: 13 additions & 0 deletions packages/data-store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ import { ImageDimensionsEntity, imageDimensionsEntityFrom } from './entities/iss
import { IssuerLocaleBrandingEntity, issuerLocaleBrandingEntityFrom } from './entities/issuanceBranding/IssuerLocaleBrandingEntity'
import { IssuerBrandingEntity, issuerBrandingEntityFrom } from './entities/issuanceBranding/IssuerBrandingEntity'
import { TextAttributesEntity, textAttributesEntityFrom } from './entities/issuanceBranding/TextAttributesEntity'
import { StatusListEntity } from './entities/statusList2021/StatusList2021Entity'
import { StatusListEntryEntity } from './entities/statusList2021/StatusList2021EntryEntity'
import { IStatusListEntity, IStatusListEntryEntity } from './types'

export { ContactStore } from './contact/ContactStore'
export { AbstractContactStore } from './contact/AbstractContactStore'
export { AbstractIssuanceBrandingStore } from './issuanceBranding/AbstractIssuanceBrandingStore'
export { IssuanceBrandingStore } from './issuanceBranding/IssuanceBrandingStore'
export { StatusListStore } from './statusList/StatusListStore'
export { DataStoreMigrations } from './migrations'
export * from './types'

Expand All @@ -46,6 +50,11 @@ export const DataStoreIssuanceBrandingEntities = [
IssuerLocaleBrandingEntity,
]

export const DataStoreStatusListEntities = [StatusListEntity, StatusListEntryEntity]

// All entities combined if a party wants to enable them all at once
export const DataStoreEntities = [...DataStoreContactEntities, ...DataStoreIssuanceBrandingEntities, ...DataStoreStatusListEntities]

export {
BaseConfigEntity,
ConnectionEntity,
Expand Down Expand Up @@ -79,4 +88,8 @@ export {
textAttributesEntityFrom,
issuerLocaleBrandingEntityFrom,
credentialLocaleBrandingEntityFrom,
IStatusListEntity,
IStatusListEntryEntity,
StatusListEntity,
StatusListEntryEntity,
}
54 changes: 54 additions & 0 deletions packages/data-store/src/migrations/generic/3-CreateStatusList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Debug from 'debug'
import { MigrationInterface, QueryRunner } from 'typeorm'
import { CreateStatusList1693866470001 } from '../postgres/CreateStatusList1693866470001-CreateStatusList'
import { CreateStatusList1693866470002 } from '../sqlite/1693866470000-CreateStatusList'

const debug = Debug('sphereon:ssi-sdk:migrations')

export class CreateStatusList1693866470000 implements MigrationInterface {
name = 'CreateStatusList1693866470000'

public async up(queryRunner: QueryRunner): Promise<void> {
debug('migration: creating issuance branding tables')
const dbType = queryRunner.connection.driver.options.type
if (dbType === 'postgres') {
debug('using postgres migration file')
const mig = new CreateStatusList1693866470001()
const up = await mig.up(queryRunner)
debug('Migration statements executed')
return up
} else if (dbType === 'sqlite' || 'react-native') {
debug('using sqlite/react-native migration file')
const mig = new CreateStatusList1693866470002()
const up = await mig.up(queryRunner)
debug('Migration statements executed')
return up
} else {
return Promise.reject(
"Migrations are currently only supported for sqlite, react-native and postgres. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now"
)
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
debug('migration: reverting issuance branding tables')
const dbType = queryRunner.connection.driver.options.type
if (dbType === 'postgres') {
debug('using postgres migration file')
const mig = new CreateStatusList1693866470002()
const down = await mig.down(queryRunner)
debug('Migration statements executed')
return down
} else if (dbType === 'sqlite' || 'react-native') {
debug('using sqlite/react-native migration file')
const mig = new CreateStatusList1693866470002()
const down = await mig.down(queryRunner)
debug('Migration statements executed')
return down
} else {
return Promise.reject(
"Migrations are currently only supported for sqlite, react-native and postgres. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now"
)
}
}
}
12 changes: 10 additions & 2 deletions packages/data-store/src/migrations/generic/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CreateContacts1659463079429 } from './1-CreateContacts'
import { CreateIssuanceBranding1659463079429 } from './1-CreateIssuanceBranding'
import { CreateIssuanceBranding1659463079429 } from './2-CreateIssuanceBranding'
import { CreateStatusList1693866470000 } from './3-CreateStatusList'

/**
* The migrations array that SHOULD be used when initializing a TypeORM database connection.
Expand All @@ -8,4 +9,11 @@ import { CreateIssuanceBranding1659463079429 } from './1-CreateIssuanceBranding'
*
* @public
*/
export const DataStoreMigrations = [CreateContacts1659463079429, CreateIssuanceBranding1659463079429]

// Individual migrations per purpose. Allows parties to not run migrations and thus create/update tables if they are not using a particular feature (yet)
export const DataStoreContactMigrations = [CreateContacts1659463079429]
export const DataStoreIssuanceBrandingMigrations = [CreateIssuanceBranding1659463079429]
export const DataStoreStatusListMigrations = [CreateStatusList1693866470000]

// All migrations together
export const DataStoreMigrations = [CreateContacts1659463079429, CreateIssuanceBranding1659463079429, CreateStatusList1693866470000]
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { enableUuidv4 } from './uuid'

export class CreateContacts1659463079428 implements MigrationInterface {
name = 'CreateContacts1659463079428'

public async up(queryRunner: QueryRunner): Promise<void> {
await enableUuidv4(queryRunner)
await queryRunner.query(
`CREATE TABLE "BaseConfigEntity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "client_id" character varying(255), "client_secret" character varying(255), "scopes" text, "issuer" character varying(255), "redirect_url" text, "dangerously_allow_insecure_http_requests" boolean, "client_auth_method" text, "identifier" character varying(255), "session_id" character varying(255), "type" character varying NOT NULL, "connectionId" uuid, CONSTRAINT "REL_BaseConfig_connectionId" UNIQUE ("connectionId"), CONSTRAINT "PK_BaseConfigEntity_id" PRIMARY KEY ("id"))`
)
Expand All @@ -13,13 +15,13 @@ export class CreateContacts1659463079428 implements MigrationInterface {
`CREATE TABLE "CorrelationIdentifier" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "type" "public"."CorrelationIdentifier_type_enum" NOT NULL, "correlation_id" text NOT NULL, "identityId" uuid, CONSTRAINT "UQ_Correlation_id" UNIQUE ("correlation_id"), CONSTRAINT "REL_CorrelationIdentifier_identityId" UNIQUE ("identityId"), CONSTRAINT "PK_CorrelationIdentifier_id" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE TABLE "Contact" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "alias" character varying(255) NOT NULL, "uri" character varying(255) NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_Name" UNIQUE ("name"), CONSTRAINT "UQ_Alias" UNIQUE ("alias"), CONSTRAINT "PK_Contact_id" PRIMARY KEY ("id"))`
`CREATE TABLE "Contact" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "alias" character varying(255) NOT NULL, "uri" character varying(255) NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_Name" UNIQUE ("name"), CONSTRAINT "UQ_Contact_Alias" UNIQUE ("alias"), CONSTRAINT "PK_Contact_id" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE TABLE "IdentityMetadata" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "label" character varying(255) NOT NULL, "value" character varying(255) NOT NULL, "identityId" uuid, CONSTRAINT "PK_IdentityMetadata_id" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE TABLE "Identity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "alias" character varying(255) NOT NULL, "roles" text, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), "contactId" uuid, CONSTRAINT "UQ_Alias" UNIQUE ("alias"), CONSTRAINT "PK_Identity_id" PRIMARY KEY ("id"))`
`CREATE TABLE "Identity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "alias" character varying(255) NOT NULL, "roles" text, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), "contactId" uuid, CONSTRAINT "UQ_Identity_Alias" UNIQUE ("alias"), CONSTRAINT "PK_Identity_id" PRIMARY KEY ("id"))`
)
await queryRunner.query(`CREATE TYPE "public"."Connection_type_enum" AS ENUM('OIDC', 'SIOPv2', 'SIOPv2+OpenID4VP')`)
await queryRunner.query(
Expand Down
Loading

0 comments on commit 46986dd

Please sign in to comment.