did-jwt-vc
Advanced tools
Comparing version 0.0.1-dev.0 to 0.1.0-dev.0
{ | ||
"name": "did-jwt-vc", | ||
"version": "0.0.1-dev.0", | ||
"version": "0.1.0-dev.0", | ||
"description": "Create and verify W3C Verifiable Credentials and Presentations in JWT format", | ||
@@ -23,3 +23,4 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"did-jwt": "^0.2.0" | ||
"did-jwt": "^0.2.0", | ||
"faker": "^4.1.0" | ||
}, | ||
@@ -47,2 +48,3 @@ "jest": { | ||
"devDependencies": { | ||
"@types/faker": "^4.1.5", | ||
"@types/jest": "^24.0.15", | ||
@@ -49,0 +51,0 @@ "ethr-did": "^1.1.0", |
import EthrDID from 'ethr-did' | ||
import { createVerifiableCredential, createPresentation } from '../index' | ||
import { decodeJWT } from 'did-jwt'; | ||
import { decodeJWT } from 'did-jwt' | ||
import { DEFAULT_TYPE, DEFAULT_CONTEXT } from '../constants' | ||
import { | ||
validateDidFormat, | ||
validateContext, | ||
validateJwtFormat, | ||
validateTimestamp, | ||
validateType, | ||
validateCredentialSubject | ||
} from '../validators' | ||
jest.mock('../validators') | ||
const issuerIdentity = { | ||
did: 'did:ethr:0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198', | ||
const mockValidateDidFormat = <jest.Mock<typeof validateDidFormat>>( | ||
validateDidFormat | ||
) | ||
const mockValidateJwtFormat = <jest.Mock<typeof validateJwtFormat>>( | ||
validateJwtFormat | ||
) | ||
const mockValidateTimestamp = <jest.Mock<typeof validateTimestamp>>( | ||
validateTimestamp | ||
) | ||
const mockValidateContext = <jest.Mock<typeof validateContext>>validateContext | ||
const mockValidateType = <jest.Mock<typeof validateType>>validateType | ||
const mockValidateCredentialSubject = <jest.Mock<typeof validateCredentialSubject>>validateCredentialSubject | ||
export const DID_A = 'did:ethr:0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198' | ||
export const DID_B = 'did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4' | ||
export const INVALID_DID = 'this is not a valid did' | ||
export const INVALID_TIMESTAMP = 1563905309015 | ||
export const EXTRA_CONTEXT_A = 'https://www.w3.org/2018/credentials/examples/v1' | ||
export const EXTRA_CONTEXT_B = 'custom vc context' | ||
export const EXTRA_TYPE_A = 'UniversityDegreeCredential' | ||
export const EXTRA_TYPE_B = 'custom vc type' | ||
export const VC_JWT = | ||
// tslint:disable-next-line: max-line-length | ||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjM4MjQ4MDksInN1YiI6ImRpZDpldGhyOjB4MTIzNDU2NzgiLCJuYmYiOjE1NjI5NTAyODI4MDEsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjY2FsYXVyw6lhdCBlbiBtdXNpcXVlcyBudW3DqXJpcXVlcyJ9fX0sImlzcyI6ImRpZDpldGhyOjB4ZjEyMzJmODQwZjNhZDdkMjNmY2RhYTg0ZDZjNjZkYWMyNGVmYjE5OCJ9.uYSRgDNmZnz0k5rORCBIIzEahVask5eQ2PFZI2_JAatvrpZ2t_3iTvPmBy6Kzt2W20fw5jUJ7GoZXJqoba4UVQA' | ||
const did = new EthrDID({ | ||
did: DID_A, | ||
address: '0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198', | ||
privateKey: 'd8b595680851765f38ea5405129244ba3cbad84467d190859f4c8b20c1ff6c75', | ||
privateKey: 'd8b595680851765f38ea5405129244ba3cbad84467d190859f4c8b20c1ff6c75' | ||
}) | ||
const verifiableCredentialPayload = { | ||
sub: DID_B, | ||
nbf: 1562950282, | ||
vc: { | ||
'@context': [DEFAULT_CONTEXT, EXTRA_CONTEXT_A], | ||
type: [DEFAULT_TYPE, EXTRA_TYPE_A], | ||
credentialSubject: { | ||
degree: { | ||
type: 'BachelorDegree', | ||
name: 'Baccalauréat en musiques numériques' | ||
} | ||
} | ||
} | ||
} | ||
const presentationPayload = { | ||
vp: { | ||
'@context': [DEFAULT_CONTEXT, EXTRA_CONTEXT_A], | ||
type: [DEFAULT_TYPE], | ||
verifiableCredential: [VC_JWT] | ||
} | ||
} | ||
const did = new EthrDID(issuerIdentity) | ||
beforeEach(() => { | ||
jest.resetAllMocks() | ||
}) | ||
const exampleVcJwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjM4MjQ4MDksInN1YiI6ImRpZDpldGhyOjB4MTIzNDU2NzgiLCJuYmYiOjE1NjI5NTAyODI4MDEsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjY2FsYXVyw6lhdCBlbiBtdXNpcXVlcyBudW3DqXJpcXVlcyJ9fX0sImlzcyI6ImRpZDpldGhyOjB4ZjEyMzJmODQwZjNhZDdkMjNmY2RhYTg0ZDZjNjZkYWMyNGVmYjE5OCJ9.uYSRgDNmZnz0k5rORCBIIzEahVask5eQ2PFZI2_JAatvrpZ2t_3iTvPmBy6Kzt2W20fw5jUJ7GoZXJqoba4UVQA' | ||
describe('createVerifiableCredential', () => { | ||
it('creates a valid Verifiable Credential JWT with required fields', async () => { | ||
const vcJwt = await createVerifiableCredential({ | ||
sub: 'did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4', | ||
nbf: 1562950282, | ||
vc: { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v1', | ||
'https://www.w3.org/2018/credentials/examples/v1' | ||
], | ||
type: [ | ||
'VerifiableCredential', | ||
'UniversityDegreeCredential' | ||
], | ||
credentialSubject: { | ||
'degree': { | ||
type: 'BachelorDegree', | ||
name: 'Baccalauréat en musiques numériques' | ||
} | ||
} | ||
} | ||
}, did) | ||
const vcJwt = await createVerifiableCredential( | ||
verifiableCredentialPayload, | ||
did | ||
) | ||
const decodedVc = await decodeJWT(vcJwt) | ||
@@ -41,2 +82,36 @@ const { iat, ...payload } = decodedVc.payload | ||
}) | ||
it('creates a valid Verifiable Credential JWT with extra optional fields', async () => { | ||
const vcJwt = await createVerifiableCredential( | ||
{ ...verifiableCredentialPayload, extra: 42 }, | ||
did | ||
) | ||
const decodedVc = await decodeJWT(vcJwt) | ||
const { iat, ...payload } = decodedVc.payload | ||
expect(payload).toMatchSnapshot() | ||
}) | ||
it('calls functions to validate required fields', async () => { | ||
await createVerifiableCredential(verifiableCredentialPayload, did) | ||
expect(mockValidateDidFormat).toHaveBeenCalledWith( | ||
verifiableCredentialPayload.sub | ||
) | ||
expect(mockValidateDidFormat).toHaveBeenCalledWith(did.did) | ||
expect(mockValidateTimestamp).toHaveBeenCalledWith( | ||
verifiableCredentialPayload.nbf | ||
) | ||
expect(mockValidateContext).toHaveBeenCalledWith( | ||
verifiableCredentialPayload.vc['@context'] | ||
) | ||
expect(mockValidateType).toHaveBeenCalledWith( | ||
verifiableCredentialPayload.vc.type | ||
) | ||
expect(mockValidateCredentialSubject).toHaveBeenCalledWith(verifiableCredentialPayload.vc.credentialSubject) | ||
}) | ||
it('calls functions to validate optional fields if they are present', async () => { | ||
const timestamp = Math.floor(new Date().getTime()) | ||
await createVerifiableCredential( | ||
{ ...verifiableCredentialPayload, exp: timestamp }, | ||
did | ||
) | ||
expect(mockValidateTimestamp).toHaveBeenCalledWith(timestamp) | ||
}) | ||
}) | ||
@@ -46,12 +121,3 @@ | ||
it('creates a valid Presentation JWT with required fields', async () => { | ||
const presentationJwt = await createPresentation({ | ||
vp: { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v1', | ||
'https://www.w3.org/2018/credentials/examples/v1' | ||
], | ||
type: ['VerifiableCredential'], | ||
verifiableCredential: [exampleVcJwt] | ||
} | ||
}, did) | ||
const presentationJwt = await createPresentation(presentationPayload, did) | ||
const decodedPresentation = await decodeJWT(presentationJwt) | ||
@@ -61,2 +127,51 @@ const { iat, ...payload } = decodedPresentation.payload | ||
}) | ||
}) | ||
it('creates a valid Presentation JWT with extra optional fields', async () => { | ||
const presentationJwt = await createPresentation( | ||
{ ...presentationPayload, extra: 42 }, | ||
did | ||
) | ||
const decodedPresentation = await decodeJWT(presentationJwt) | ||
const { iat, ...payload } = decodedPresentation.payload | ||
expect(payload).toMatchSnapshot() | ||
}) | ||
it('calls functions to validate required fields', async () => { | ||
await createPresentation(presentationPayload, did) | ||
expect(mockValidateDidFormat).toHaveBeenCalledWith(did.did) | ||
expect(mockValidateContext).toHaveBeenCalledWith( | ||
presentationPayload.vp['@context'] | ||
) | ||
expect(mockValidateType).toHaveBeenCalledWith(presentationPayload.vp.type) | ||
for (const vc of presentationPayload.vp.verifiableCredential) { | ||
expect(mockValidateJwtFormat).toHaveBeenCalledWith(vc) | ||
} | ||
}) | ||
it('throws a TypeError if vp.verifiableCredential is empty', async () => { | ||
await expect( | ||
createPresentation( | ||
{ | ||
...presentationPayload, | ||
vp: { | ||
'@context': presentationPayload.vp['@context'], | ||
type: presentationPayload.vp.type, | ||
verifiableCredential: [] | ||
} | ||
}, | ||
did | ||
) | ||
).rejects.toThrow(TypeError) | ||
}) | ||
it('calls functions to validate optional fields if they are present', async () => { | ||
const aud = INVALID_DID | ||
const timestamp = Math.floor(new Date().getTime()) | ||
await createPresentation( | ||
{ | ||
...presentationPayload, | ||
aud: INVALID_DID, | ||
exp: timestamp | ||
}, | ||
did | ||
) | ||
expect(mockValidateDidFormat).toHaveBeenCalledWith(aud) | ||
expect(mockValidateTimestamp).toHaveBeenCalledWith(timestamp) | ||
}) | ||
}) |
@@ -1,38 +0,12 @@ | ||
import { createJWT, Signer } from 'did-jwt' | ||
import { createJWT } from 'did-jwt' | ||
import { JWT_ALG } from './constants' | ||
import * as validators from './validators' | ||
import { | ||
VerifiableCredentialPayload, | ||
Issuer, | ||
PresentationPayload | ||
} from './types' | ||
const JWT_ALG = 'ES256K-R' | ||
export * from './VerifiableCredential' | ||
interface VC { | ||
'@context': string[] | ||
type: string[] | ||
credentialSubject: object | ||
} | ||
interface VerifiableCredentialPayload { | ||
sub: string | ||
nbf: number | ||
vc: VC | ||
aud?: string | ||
exp?: number | ||
jti?: string | ||
} | ||
interface VP { | ||
'@context': string[] | ||
type: string[] | ||
verifiableCredential: string[] | ||
} | ||
interface PresentationPayload { | ||
vp: VP | ||
aud?:string | ||
exp?: number | ||
jti?:string | ||
} | ||
interface Issuer { | ||
did: string | ||
signer: Signer | ||
} | ||
export async function createVerifiableCredential( | ||
@@ -42,2 +16,4 @@ payload: VerifiableCredentialPayload, | ||
): Promise<string> { | ||
validateVerifiableCredentialAttributes(payload) | ||
validators.validateDidFormat(issuer.did) | ||
return createJWT(payload, { | ||
@@ -54,2 +30,4 @@ issuer: issuer.did, | ||
): Promise<string> { | ||
validatePresentationAttributes(payload) | ||
validators.validateDidFormat(issuer.did) | ||
return createJWT(payload, { | ||
@@ -60,2 +38,26 @@ issuer: issuer.did, | ||
}) | ||
} | ||
} | ||
function validateVerifiableCredentialAttributes( | ||
payload: VerifiableCredentialPayload | ||
): void { | ||
validators.validateDidFormat(payload.sub) | ||
validators.validateContext(payload.vc['@context']) | ||
validators.validateType(payload.vc.type) | ||
validators.validateCredentialSubject(payload.vc.credentialSubject) | ||
if (payload.nbf) validators.validateTimestamp(payload.nbf) | ||
if (payload.exp) validators.validateTimestamp(payload.exp) | ||
} | ||
function validatePresentationAttributes(payload: PresentationPayload): void { | ||
validators.validateContext(payload.vp['@context']) | ||
validators.validateType(payload.vp.type) | ||
if (payload.vp.verifiableCredential.length < 1) { | ||
throw new TypeError('vp.verifiableCredential must not be empty') | ||
} | ||
for (const vc of payload.vp.verifiableCredential) { | ||
validators.validateJwtFormat(vc) | ||
} | ||
if (payload.aud) validators.validateDidFormat(payload.aud) | ||
if (payload.exp) validators.validateTimestamp(payload.exp) | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
42909
21
791
2
11
1
+ Addedfaker@^4.1.0
+ Addedfaker@4.1.0(transitive)