Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

credential-status

Package Overview
Dependencies
Maintainers
5
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

credential-status - npm Package Compare versions

Comparing version 1.2.4 to 2.0.0

24

package.json
{
"name": "credential-status",
"version": "1.2.4",
"version": "2.0.0",
"description": "credential status aggregator for did-jwt",

@@ -16,3 +16,2 @@ "main": "lib/index.js",

"lint": "eslint --ignore-pattern \"src/**/*.test.[jt]s\" \"src/**/*.[jt]s\"",
"prepare": "yarn build",
"prepublishOnly": "npm test && npm run lint",

@@ -37,19 +36,18 @@ "release": "semantic-release --debug"

"devDependencies": {
"@babel/preset-typescript": "^7.16.7",
"@babel/preset-typescript": "7.16.7",
"@semantic-release/changelog": "6.0.1",
"@semantic-release/git": "10.0.1",
"@types/jest": "27.4.0",
"@types/node": "14.18.8",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"@types/node": "16.11.21",
"@typescript-eslint/eslint-plugin": "5.10.0",
"@typescript-eslint/parser": "5.10.0",
"codecov": "3.8.3",
"eslint": "^8.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.7.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint": "8.7.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-jest": "26.1.0",
"eslint-plugin-prettier": "4.0.0",
"jest": "27.4.7",
"jest-extended": "0.11.5",
"microbundle": "^0.14.2",
"microbundle": "0.14.2",
"prettier": "2.5.1",
"semantic-release": "18.0.1",
"semantic-release": "19.0.2",
"typescript": "4.5.4"

@@ -56,0 +54,0 @@ },

@@ -1,7 +0,5 @@

import 'jest-extended'
import { CredentialJwtOrJSON, Status, StatusMethod, StatusResolver } from '../index'
import { Status, StatusMethod, StatusResolver } from '../index'
import { DIDDocument } from 'did-resolver'
import { SimpleSigner, createJWT } from 'did-jwt'
import { ES256KSigner, createJWT } from 'did-jwt'

@@ -11,3 +9,3 @@ const privateKey = 'a285ab66393c5fdda46d6fbad9e27fafd438254ab72ad5acb681a0e9f20f5d7b'

const issuer = `did:ethr:${signerAddress}`
const signer = SimpleSigner(privateKey)
const signer = ES256KSigner(privateKey)

@@ -26,61 +24,274 @@ const referenceDoc = {

test('should be able to instantiate Status', () => {
expect(new Status()).not.toBeNil()
})
describe('credential-status', () => {
describe('API', () => {
it('should be able to instantiate Status', () => {
expect(new Status()).not.toBeNull()
})
test('should be able to call checkStatus', () => {
const checker = new Status()
expect(checker.checkStatus).toBeFunction()
})
it('should be able to call checkStatus', () => {
const checker = new Status()
expect(typeof checker.checkStatus).toEqual('function')
})
test('should reject unknown status method', async () => {
const checker = new Status()
const token = await createJWT(
{ credentialStatus: { type: 'UnknownMethod', id: 'something something' } },
{ issuer, signer }
)
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({
error: 'Credential status method UnknownMethod unknown. Validity can not be determined.',
it('should pass through credential with no status requirement', async () => {
expect.assertions(1)
const token = await createJWT({}, { issuer, signer })
const checker = new Status()
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toEqual({
revoked: false,
message: 'credentialStatus property was not set on the original credential',
})
})
it('sample StatusResolver with easy registration', async () => {
class CustomStatusChecker implements StatusResolver {
checkStatus: StatusMethod = async (credential: CredentialJwtOrJSON, doc: DIDDocument) => {
return { revoked: false }
}
asStatusMethod = { CustomStatusChecker: this.checkStatus }
}
const checker = new Status({
...new CustomStatusChecker().asStatusMethod,
})
const token = await createJWT(
{ credentialStatus: { type: 'CustomStatusChecker', id: 'something something' } },
{ issuer, signer }
)
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({ revoked: false })
})
})
})
test('should pass through credential with no status requirement', async () => {
const token = await createJWT({}, { issuer, signer })
const checker = new Status()
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({})
})
describe('with unknown status method', () => {
it('in legacy JWT payload', async () => {
expect.assertions(1)
const checker = new Status()
const token = await createJWT(
{ credentialStatus: { type: 'UnknownMethod', id: 'something something' } },
{ issuer, signer }
)
await expect(checker.checkStatus(token, referenceDoc)).rejects.toThrow(
/unknown_method: credentialStatus method UnknownMethod unknown. Validity can not be determined./
)
})
test('should check status according to registered method', async () => {
const checkStatus: StatusMethod = async () => {
return { 'custom method works': true }
}
const checker = new Status({ CustomStatusChecker: checkStatus })
const token = await createJWT(
{ credentialStatus: { type: 'CustomStatusChecker', id: 'something something' } },
{ issuer, signer }
)
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({ 'custom method works': true })
})
it('in JWT.vc payload', async () => {
expect.assertions(1)
const checker = new Status()
const token = await createJWT(
{ vc: { credentialStatus: { type: 'UnknownMethod', id: 'something something' } } },
{ issuer, signer }
)
await expect(checker.checkStatus(token, referenceDoc)).rejects.toThrow(
/unknown_method: credentialStatus method UnknownMethod unknown. Validity can not be determined./
)
})
test('sample StatusResolver with easy registration', async () => {
class CustomStatusChecker implements StatusResolver {
checkStatus: StatusMethod = async (credential: string, doc: DIDDocument) => {
return { revoked: false }
}
asStatusMethod = { CustomStatusChecker: this.checkStatus }
}
it('in JWT.vp payload', async () => {
expect.assertions(1)
const checker = new Status()
const token = await createJWT(
{ vp: { credentialStatus: { type: 'UnknownMethod', id: 'something something' } } },
{ issuer, signer }
)
await expect(checker.checkStatus(token, referenceDoc)).rejects.toThrow(
/unknown_method: credentialStatus method UnknownMethod unknown. Validity can not be determined./
)
})
const checker = new Status({
...new CustomStatusChecker().asStatusMethod,
it('in plain JSON credential', async () => {
expect.assertions(1)
const checker = new Status()
await expect(
checker.checkStatus(
{
credentialStatus: {
type: 'UnknownMethod',
id: 'something something',
},
},
referenceDoc
)
).rejects.toThrow(
/unknown_method: credentialStatus method UnknownMethod unknown. Validity can not be determined./
)
})
it('in serialized JSON credential', async () => {
expect.assertions(1)
const checker = new Status()
await expect(
checker.checkStatus(
JSON.stringify({
credentialStatus: {
type: 'UnknownMethod',
id: 'something something',
},
}),
referenceDoc
)
).rejects.toThrow(
/unknown_method: credentialStatus method UnknownMethod unknown. Validity can not be determined./
)
})
})
const token = await createJWT(
{ credentialStatus: { type: 'CustomStatusChecker', id: 'something something' } },
{ issuer, signer }
)
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({ revoked: false })
describe('with malformed input', () => {
it('should reject credentialStatus entry with bad type', async () => {
expect.assertions(2)
const checker = new Status()
await expect(
checker.checkStatus(
{
credentialStatus: true,
} as any,
referenceDoc
)
).rejects.toThrow(
/bad_request: credentialStatus entry is not formatted correctly. Validity can not be determined./
)
await expect(
checker.checkStatus(
{
credentialStatus: 'this is not revoked, believe me',
} as any,
referenceDoc
)
).rejects.toThrow(
/bad_request: credentialStatus entry is not formatted correctly. Validity can not be determined./
)
})
it('should reject malformed credentialStatus entry', async () => {
expect.assertions(1)
const checker = new Status()
await expect(
checker.checkStatus(
{
credentialStatus: {
revoked: false,
message: 'believe me',
},
} as any,
referenceDoc
)
).rejects.toThrow(
/bad_request: credentialStatus entry is not formatted correctly. Validity can not be determined./
)
})
it('should reject credentialStatus entry with no type', async () => {
expect.assertions(1)
const checker = new Status()
await expect(
checker.checkStatus(
{
credentialStatus: {
notMyType: 'foo',
id: 'bar',
},
} as any,
referenceDoc
)
).rejects.toThrow(
/bad_request: credentialStatus entry is not formatted correctly. Validity can not be determined./
)
})
})
describe('with known status method', () => {
it('for legacy JWT.payload.credentialStatus', async () => {
const checkStatus: StatusMethod = async () => {
return { revoked: false, 'custom method works': true }
}
const checker = new Status({ CustomStatusChecker: checkStatus })
const token = await createJWT(
{ credentialStatus: { type: 'CustomStatusChecker', id: 'something something' } },
{ issuer, signer }
)
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({ revoked: false, 'custom method works': true })
})
it('for JWT.payload.vc.credentialStatus', async () => {
const checkStatus: StatusMethod = async () => {
return { revoked: false, 'custom method works': true }
}
const checker = new Status({ CustomStatusChecker: checkStatus })
const token = await createJWT(
{ vc: { credentialStatus: { type: 'CustomStatusChecker', id: 'something something' } } },
{ issuer, signer }
)
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({ revoked: false, 'custom method works': true })
})
it('for JWT.payload.vp.credentialStatus', async () => {
const checkStatus: StatusMethod = async () => {
return { revoked: false, 'custom method works': true }
}
const checker = new Status({ CustomStatusChecker: checkStatus })
const token = await createJWT(
{ vp: { credentialStatus: { type: 'CustomStatusChecker', id: 'something something' } } },
{ issuer, signer }
)
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({ revoked: false, 'custom method works': true })
})
it('for plain JSON with credentialStatus', async () => {
const checkStatus: StatusMethod = async () => {
return { revoked: false, 'custom method works': true }
}
const checker = new Status({ CustomStatusChecker: checkStatus })
const statusEntry = await checker.checkStatus(
{
credentialStatus: {
type: 'CustomStatusChecker',
id: 'something something',
},
},
referenceDoc
)
expect(statusEntry).toStrictEqual({ revoked: false, 'custom method works': true })
})
it('for serialized JSON with credentialStatus', async () => {
const checkStatus: StatusMethod = async () => {
return { revoked: false, 'custom method works': true }
}
const checker = new Status({ CustomStatusChecker: checkStatus })
const statusEntry = await checker.checkStatus(
JSON.stringify({
credentialStatus: {
type: 'CustomStatusChecker',
id: 'something something',
},
}),
referenceDoc
)
expect(statusEntry).toStrictEqual({ revoked: false, 'custom method works': true })
})
it('should prefer vc.credentialStatus for JWT credential', async () => {
const checkStatus: StatusMethod = async () => {
return { revoked: false, 'custom method works': true }
}
const checker = new Status({ CustomStatusChecker: checkStatus })
const token = await createJWT(
{
credentialStatus: { type: 'Unknown', id: 'nope' },
vc: { credentialStatus: { type: 'CustomStatusChecker', id: 'something something' } },
},
{ issuer, signer }
)
const statusEntry = await checker.checkStatus(token, referenceDoc)
expect(statusEntry).toStrictEqual({ revoked: false, 'custom method works': true })
})
})
})

@@ -5,3 +5,9 @@ import { decodeJWT } from 'did-jwt'

/**
* Represents the result of a status check
* Represents the result of a status check.
*
* Implementations should populate the `revoked` boolean property, but they can return additional metadata that is
* method specific.
*
* @alpha This API is still being developed and may be updated. Please follow progress or suggest improvements at
* [https://github.com/uport-project/credential-status]
*/

@@ -21,4 +27,11 @@ export interface CredentialStatus {

* ```json
* status : { type: "EthrStatusRegistry2019", id: "rinkeby:0xregistryAddress" }
* credentialStatus: {
* type: "EthrStatusRegistry2019",
* id: "rinkeby:0xregistryAddress"
* }
* ```
* See https://www.w3.org/TR/vc-data-model/#status
*
* @alpha This API is still being developed and may be updated. Please follow progress or suggest improvements at
* [https://github.com/uport-project/credential-status]
*/

@@ -34,3 +47,3 @@ export interface StatusEntry {

/**
* [draft] The interface expected for status resolvers.
* The interface expected for status resolvers.
* `checkStatus` should be called with a raw credential and it should Promise a [[CredentialStatus]] result.

@@ -49,2 +62,5 @@ * It is advisable that classes that implement this interface also provide a way to easily register the correct

* ```
*
* @alpha This API is still being developed and may be updated. Please follow progress or suggest improvements at
* [https://github.com/uport-project/credential-status]
*/

@@ -56,23 +72,31 @@ export interface StatusResolver {

/**
* The method signature expected to be implemented by credential status resolvers
* The Verifiable Credential or Presentation to be verified in either JSON/JSON-LD or JWT format.
*
* @alpha This API is still being developed and may be updated. Please follow progress or suggest improvements at
* [https://github.com/uport-project/credential-status]
*/
export type StatusMethod = (credential: string, didDoc: DIDDocument) => Promise<null | CredentialStatus>
export type CredentialJwtOrJSON = string | { credentialStatus?: StatusEntry }
interface JWTPayloadWithStatus {
credentialStatus?: StatusEntry
/**
* The method signature expected to be implemented by credential status resolvers.
*
* @param credential The credential whose status will be verified
* @param didDoc The DID document of the issuer.
*
* @return a Promise resolving to a `CredentialStatus` object or rejecting with a reason.
*
* @alpha This API is still being developed and may be updated. Please follow progress or suggest improvements at
* [https://github.com/uport-project/credential-status]
*/
export type StatusMethod = (credential: CredentialJwtOrJSON, didDoc: DIDDocument) => Promise<CredentialStatus>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[x: string]: any
}
interface StatusMethodRegistry {
[type: string]: StatusMethod
}
/**
* [draft] An implementation of a StatusMethod that can aggregate multiple other methods.
* It calls the appropriate method based on the `status.type` specified in the credential.
* It calls the appropriate method based on the `credentialStatus.type` specified in the credential.
*
* @alpha This API is still being developed and may be updated. Please follow progress or suggest improvements at
* [https://github.com/uport-project/credential-status]
*/
export class Status implements StatusResolver {
private registry: StatusMethodRegistry
private registry: Record<string, StatusMethod>

@@ -92,26 +116,48 @@ /**

*/
constructor(registry: StatusMethodRegistry = {}) {
constructor(registry: Record<string, StatusMethod> = {}) {
this.registry = registry
}
async checkStatus(credential: string, didDoc: DIDDocument): Promise<null | CredentialStatus> {
// TODO: validate the credential to be VerifiableCredential or VerifiablePresentation
const decoded = decodeJWT(credential)
const statusEntry = (decoded.payload as JWTPayloadWithStatus).credentialStatus
async checkStatus(credential: CredentialJwtOrJSON, didDoc: DIDDocument): Promise<CredentialStatus> {
let statusEntry: StatusEntry | undefined = undefined
if (typeof statusEntry === 'undefined') {
return {}
if (typeof credential === 'string') {
try {
const decoded = decodeJWT(credential)
statusEntry =
decoded?.payload?.vc?.credentialStatus || // JWT Verifiable Credential payload
decoded?.payload?.vp?.credentialStatus || // JWT Verifiable Presentation payload
decoded?.payload?.credentialStatus // legacy JWT payload
} catch (e1: unknown) {
// not a JWT credential or presentation
try {
const decoded = JSON.parse(credential)
statusEntry = decoded?.credentialStatus
} catch (e2: unknown) {
// not a JSON either.
}
}
} else {
statusEntry = credential.credentialStatus
}
if (!statusEntry) {
return {
revoked: false,
message: 'credentialStatus property was not set on the original credential',
}
} else if (typeof statusEntry !== 'object' || !statusEntry?.type) {
throw new Error('bad_request: credentialStatus entry is not formatted correctly. Validity can not be determined.')
}
const method = this.registry[statusEntry.type]
if (typeof method !== 'undefined' && method != null) {
if (!method) {
throw new Error(
`unknown_method: credentialStatus method ${statusEntry.type} unknown. Validity can not be determined.`
)
} else {
return method(credential, didDoc)
} else {
return {
// Once the credential status mechanisms in W3C get more stable, perhaps this can become a `reject`
error: `Credential status method ${statusEntry.type} unknown. Validity can not be determined.`,
}
}
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc