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

WIP: "feat: add issuance library" #89

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ The Affinidi Trust Development Kit (Affinidi TDK) is a modern interface that all
The Affinidi TDK provides three type of modules:

- [Clients](clients), which offer methods to access Affinidi services like IAM, Verifier, Wallets, and Login configuation, among others.

- [Libraries](libs), which provide high-level abstractions that combine logic and data to perform necessary business logic functionalities.

- [Packages](packages), which are commonly used utilities/helpers that are self-contained and composable.

Each module has its own README that you can check to better understand how to integrate it into your application.
Expand All @@ -17,6 +20,8 @@ The Affinidi TDK offers the following modules and programming languages:

| | Typescript | Python | Dart |
| ------------------------------------------------- | :------------------------------------------------: | :--------------------------------------------: | :------------------------------------------: |
| **Libraries** |
| [issuance](libs/issuance/README.md) | 🟢 | 🟢 | 🔴 |
| **Packages** |
| [auth-provider](packages/auth-provider/README.md) | 🟢 | 🟢 | 🔴 |
| **Clients** |
Expand Down
Empty file removed libs/.gitkeep
Empty file.
24 changes: 24 additions & 0 deletions libs/issuance/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"root": true,
"ignorePatterns": [
"*.js",
"*.d.ts",
"node_modules/"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier
],
"rules": {
"@typescript-eslint/no-explicit-any": "off"
}
}
34 changes: 34 additions & 0 deletions libs/issuance/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
tsconfig.json
.jsii

*.js
*.d.ts

### Node ###
# Logs
logs
*.log
npm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Dependency directories
node_modules/

# TypeScript cache
*.tsbuildinfo

# Optional eslint cache
.eslintcache

# default build output
dist/

# Temporary folders
tmp/
temp/

# Custom
.idea/
.vscode/
18 changes: 18 additions & 0 deletions libs/issuance/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm

node_modules
coverage
.vscode
.DS_Store
.nyc_output
.history
.idea
Thumbs.db


# Exclude jsii outdir
dist

# Include .jsii and .jsii.gz
!.jsii
!.jsii.gz
81 changes: 81 additions & 0 deletions libs/issuance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
## @affinidi/issuance

### Build JSII

```
npm i --prefix .
npm run build
npm run package
```

The code will be generated under /dist for each language.

### Usage

### JS

```bash
npm install <path_to_issuance.jsii.tgz>
```

```ts
import { Issuance } from '@affinidi/issuance'

const config = { authProvider, apiGatewayUrl }

const issuance = new Issuance(config)

const unsignedVC = await issuance.buildUnsignedVC({
credentialSubject: {
value1: '<value-1>',
value2: '<value-2>', // these will be based on the schema
},
holderDid: '<holder-did>',
expiresAt: 'XXXX-XX-XX',
typeName: '<schema-type>',
jsonLdContextUrl: '<schema-jsonLdContextUrl>',
jsonSchemaUrl: '<schema-jsonSchemaUrl>',
})
const issuedVc = await issuance.issueVC(unsignedVC, walletId)
```

You can refer [here](../../docs/examples/typescript/auth_provider.ts) to see how to generate authProvider

You can refer [here](../../docs/examples/typescript/client-cwe.ts) to see how to get walletId

### Python

```bash
pip install <path_to_affinidi-tdk-issuance.whl>
```

```python
import affinidi_tdk.issuance

config = {
'apiGatewayUrl': '<api_gateway_url>',
'authProvider' : '<auth_provider_package>'
}

issuance = affinidi_tdk.issuance.Issuance(config)

unsigned_vc_config = {
'credentialSubject': {
'value1': '<value-1>',
'value2': '<value-2>', # these will be based on the schema
},
'holderDid': '<holder-did>',
'expiresAt': 'XXXX-XX-XX',
'typeName': '<schema-type>',
'jsonLdContextUrl': '<schema-jsonLdContextUrl>',
'jsonSchemaUrl': '<schema-jsonSchemaUrl>',
}

unsigned_vc = issuance.build_unsigned_vc(unsigned_vc_config)

signed_vc = issuance.issue_vc(unsigned_vc, walletId)
```

You can refer [here](../../docs/examples/python/auth_provider.py) to see how to generate authProvider

You can refer [here](../../docs/examples/python/client-cwe.py) to see how to get walletId
35 changes: 35 additions & 0 deletions libs/issuance/helpers/cwe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
WalletApi,
Configuration,
SignCredentialResultDto,
} from '@affinidi-tdk/wallets-client'

export interface SignCredential extends SignCredentialResultDto {}

export interface ICWEParams {
authProvider: any
apiGatewayUrl: string
}

export class Cwe {
private readonly walletApi: WalletApi
constructor({ authProvider, apiGatewayUrl }: ICWEParams) {
this.walletApi = new WalletApi(
new Configuration({
apiKey: authProvider.fetchProjectScopedToken.bind(authProvider),
basePath: `${apiGatewayUrl}/cwe`,
})
)
}

public async signCredential(
walletId: string,
signCredentialInput: any
): Promise<{ [key: string]: any }> {
const signedCredential = (
await this.walletApi.signCredential(walletId, signCredentialInput)
).data

return signedCredential
}
}
1 change: 1 addition & 0 deletions libs/issuance/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ICWEParams, Cwe } from './cwe'
2 changes: 2 additions & 0 deletions libs/issuance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './helpers'
export * from './issuance'
154 changes: 154 additions & 0 deletions libs/issuance/issuance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Cwe } from './helpers/cwe'
import { parse } from 'did-resolver'
import { randomBytes } from 'crypto'

export interface IIssuanceParams {
projectScopedToken: string
apiGatewayUrl: string
}

export interface IBuildUnsignedCredentialInput {
jsonLdContextUrl?: string
jsonSchemaUrl?: string
id: string
credentialType: string
holderId: string
credentialSubject: Record<string, any>
issuanceDate: string
expirationDate?: string
}

export interface IUnsignedW3cCredential {
context: { [key: string]: any }
id: string
credentialType: string[]
holder: { [key: string]: any }
credentialSubject: { [key: string]: any }
issuanceDate: string
expirationDate?: string | undefined
credentialSchema?: { [key: string]: any }
}

export interface IBuildUnsignedVCInput {
/**
* @deprecated credentialType is deprecated.
Use "typeName", "jsonLdContextUrl" and "jsonSchemaUrl" instead.
*/
credentialType?: string
/** Optional only if type defined */
jsonLdContextUrl?: string
/** Optional only if type defined */
jsonSchemaUrl?: string
/** Required when type field is not given, will be used as a type of cred when passed */
typeName?: string

credentialSubject?: { [key: string]: any }

/**
* @deprecated data is deprecated.
* Use "credentialSubject" instead.
*/
data?: { [key: string]: any }

/**
* @pattern ^did:.*$
*/
holderDid: string

expiresAt?: string | undefined
}

export class Issuance {
private readonly authProvider
private readonly apiGatewayUrl: string = ''
private readonly cwe: Cwe

constructor({ authProvider, apiGatewayUrl }: { [key: string]: any }) {
this.authProvider = authProvider
this.apiGatewayUrl = apiGatewayUrl
this.cwe = new Cwe({
authProvider: this.authProvider,
apiGatewayUrl: this.apiGatewayUrl,
})
}

private buildUnsignedCredential = ({
credentialSubject,
jsonLdContextUrl,
jsonSchemaUrl,
expirationDate,
issuanceDate,
holderId,
credentialType: credType,
id,
}: IBuildUnsignedCredentialInput): IUnsignedW3cCredential => ({
context: ['https://www.w3.org/2018/credentials/v1', jsonLdContextUrl],
id,
credentialType: ['VerifiableCredential', credType],
holder: {
id: holderId,
},
credentialSubject,
...(jsonSchemaUrl && {
credentialSchema: {
id: jsonSchemaUrl,
type: 'JsonSchemaValidator2018',
},
}),
issuanceDate,
expirationDate,
})

public async buildUnsignedVC(input: any): Promise<{ [key: string]: any }> {
const {
credentialSubject,
holderDid,
expiresAt,
typeName,
credentialType,
data,
jsonLdContextUrl,
jsonSchemaUrl,
}: IBuildUnsignedVCInput = input

if ((data && credentialSubject) || (!data && !credentialSubject)) {
throw new Error('"data" or "credentialSubject" must be provided')
}

if (!holderDid) {
throw new Error('"holderDid" must be provided')
}

const {
context,
credentialType: credType,
...unsignedCredentialRaw
} = this.buildUnsignedCredential({
credentialSubject: credentialSubject ?? { data },
jsonLdContextUrl,
jsonSchemaUrl,
expirationDate: expiresAt ? new Date(expiresAt).toISOString() : undefined,
issuanceDate: new Date().toISOString(),
holderId: parse(holderDid)!.did,
credentialType: typeName || (credentialType as string),
id: `claimId:${randomBytes(8).toString('hex')}`, // This import was originally from @affinidi/common
})

const unsignedCredential = {
...unsignedCredentialRaw,
type: credType,
'@context': context,
}

return { unsignedCredential }
}
// use buildUnsignedVC before issuing a vc
public async issueVC(
vc: any,
walletId: string,
): Promise<{ [key: string]: any }> {
const signedCredential = await this.cwe.signCredential(walletId, vc)

return signedCredential
}
}
Loading
Loading