Skip to content

Commit c3e7a2e

Browse files
committed
fix(types): widen types used as input
W3C spec allows single elements instead of arrays for some properties of credentials.
1 parent d7f9578 commit c3e7a2e

File tree

5 files changed

+34
-24
lines changed

5 files changed

+34
-24
lines changed

src/__tests__/converters.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ describe('presentation', () => {
550550
const result = normalizePresentation({
551551
verifiableCredential: { foo: 'bar' },
552552
vp: { verifiableCredential: [{ foo: 'baz' }] }
553-
} as any)
553+
})
554554
expect(result).toMatchObject({
555555
verifiableCredential: [{ foo: 'bar' }, { foo: 'baz' }]
556556
})
@@ -646,7 +646,7 @@ describe('presentation', () => {
646646
})
647647

648648
it('merges type arrays for non-array types', () => {
649-
const result = normalizePresentation({ type: 'foo', vp: { type: 'bar' } } as any)
649+
const result = normalizePresentation({ type: 'foo', vp: { type: 'bar' } })
650650
expect(result).toMatchObject({ type: ['foo', 'bar'] })
651651
expect(result).not.toHaveProperty('vp')
652652
})
@@ -666,7 +666,7 @@ describe('presentation', () => {
666666
})
667667

668668
it('merges @context arrays for non-array contexts', () => {
669-
const result = normalizePresentation({ '@context': 'foo', context: 'bar', vp: { '@context': 'baz' } } as any)
669+
const result = normalizePresentation({ '@context': 'foo', context: 'bar', vp: { '@context': 'baz' } })
670670
expect(result).toMatchObject({ '@context': ['bar', 'foo', 'baz'] })
671671
expect(result).not.toHaveProperty('vp')
672672
expect(result).not.toHaveProperty('context')
@@ -773,7 +773,7 @@ describe('presentation', () => {
773773
const result = transformPresentationInput({
774774
verifiableCredential: [{ id: 'foo' }],
775775
vp: { verifiableCredential: [{ foo: 'bar' }, 'header.payload.signature'] }
776-
} as any)
776+
})
777777
expect(result).toMatchObject({
778778
vp: { verifiableCredential: [{ id: 'foo' }, { foo: 'bar' }, 'header.payload.signature'] }
779779
})
@@ -784,7 +784,7 @@ describe('presentation', () => {
784784
const result = transformPresentationInput({
785785
verifiableCredential: { id: 'foo' },
786786
vp: { verifiableCredential: { foo: 'bar' } }
787-
} as any)
787+
})
788788
expect(result).toMatchObject({ vp: { verifiableCredential: [{ id: 'foo' }, { foo: 'bar' }] } })
789789
expect(result).not.toHaveProperty('verifiableCredential')
790790
})
@@ -793,7 +793,7 @@ describe('presentation', () => {
793793
const result = transformPresentationInput({
794794
verifiableCredential: { id: 'foo', proof: { jwt: 'header.payload1.signature' } },
795795
vp: { verifiableCredential: [{ foo: 'bar' }, 'header.payload2.signature'] }
796-
} as any)
796+
})
797797
expect(result).toMatchObject({
798798
vp: { verifiableCredential: ['header.payload1.signature', { foo: 'bar' }, 'header.payload2.signature'] }
799799
})
@@ -804,7 +804,7 @@ describe('presentation', () => {
804804
const result = transformPresentationInput({
805805
verifiableCredential: undefined,
806806
vp: { verifiableCredential: [null, { foo: 'bar' }, 'header.payload2.signature'] }
807-
} as any)
807+
})
808808
expect(result).toMatchObject({ vp: { verifiableCredential: [{ foo: 'bar' }, 'header.payload2.signature'] } })
809809
expect(result).not.toHaveProperty('verifiableCredential')
810810
})
@@ -823,7 +823,7 @@ describe('presentation', () => {
823823
context: 'AA',
824824
'@context': 'BB',
825825
vp: { '@context': ['CC'] }
826-
} as any)
826+
})
827827
expect(result).toMatchObject({ vp: { '@context': ['AA', 'BB', 'CC'] } })
828828
expect(result).not.toHaveProperty('context')
829829
expect(result).not.toHaveProperty('@context')
@@ -854,7 +854,7 @@ describe('presentation', () => {
854854
})
855855

856856
it('merges type fields when not array types', () => {
857-
const result = transformPresentationInput({ type: 'AA', vp: { type: ['BB'] } } as any)
857+
const result = transformPresentationInput({ type: 'AA', vp: { type: ['BB'] } })
858858
expect(result).toMatchObject({ vp: { type: ['AA', 'BB'] } })
859859
expect(result).not.toHaveProperty('type')
860860
})

src/converters.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ function normalizeJwtPresentation(input: JWT): Verifiable<W3CPresentation> {
318318
* @param input either a JWT or JWT payload, or a VerifiablePresentation
319319
*/
320320
export function normalizePresentation(
321-
input: Partial<PresentationPayload> | Partial<JwtPresentationPayload> | JWT
321+
input: Partial<PresentationPayload> | DeepPartial<JwtPresentationPayload> | JWT
322322
): Verifiable<W3CPresentation> {
323323
if (typeof input === 'string') {
324324
if (JWT_FORMAT.test(input)) {
@@ -349,7 +349,7 @@ export function normalizePresentation(
349349
* @param input either a JWT payload or a CredentialPayloadInput
350350
*/
351351
export function transformPresentationInput(
352-
input: Partial<PresentationPayload> | Partial<JwtPresentationPayload>
352+
input: Partial<PresentationPayload> | DeepPartial<JwtPresentationPayload>
353353
): JwtPresentationPayload {
354354
const result: Partial<JwtPresentationPayload> = { vp: { ...input.vp }, ...input }
355355

src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import {
2727

2828
export {
2929
Issuer,
30+
CredentialPayload,
31+
PresentationPayload,
3032
JwtCredentialPayload,
3133
JwtPresentationPayload,
3234
VerifiableCredential,
@@ -112,9 +114,8 @@ export function validateCredentialPayload(payload: CredentialPayload): void {
112114
validators.validateContext(payload['@context'])
113115
validators.validateVcType(payload.type)
114116
validators.validateCredentialSubject(payload.credentialSubject)
115-
if (payload.issuanceDate) validators.validateTimestamp(Math.floor(new Date(payload.issuanceDate).valueOf() / 1000))
116-
if (payload.expirationDate)
117-
validators.validateTimestamp(Math.floor(new Date(payload.expirationDate).valueOf() / 1000))
117+
if (payload.issuanceDate) validators.validateTimestamp(payload.issuanceDate)
118+
if (payload.expirationDate) validators.validateTimestamp(payload.expirationDate)
118119
}
119120

120121
export function validateJwtPresentationPayload(payload: JwtPresentationPayload): void {

src/types.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ export type DateType = string | Date
4646
* used as input when creating Verifiable Credentials
4747
*/
4848
interface FixedCredentialPayload {
49-
'@context': string[]
49+
'@context': string | string[]
5050
id?: string
51-
type: string[]
51+
type: string | string[]
5252
issuer: IssuerType
5353
issuanceDate: DateType
5454
expirationDate?: DateType
@@ -65,6 +65,8 @@ export type CredentialPayload = Extensible<FixedCredentialPayload>
6565
* This is meant to reflect unambiguous types for the properties in `CredentialPayload`
6666
*/
6767
interface NarrowCredentialDefinitions {
68+
'@context': string[]
69+
type: string[]
6870
issuer: Exclude<IssuerType, string>
6971
issuanceDate: string
7072
expirationDate?: string
@@ -92,19 +94,22 @@ export type W3CCredential = Extensible<Replace<FixedCredentialPayload, NarrowCre
9294
* used as input when creating Verifiable Presentations
9395
*/
9496
export interface FixedPresentationPayload {
95-
'@context': string[]
96-
type: string[]
97+
'@context': string | string[]
98+
type: string | string[]
9799
id?: string
98100
verifiableCredential: VerifiableCredential[]
99101
holder: string
100-
verifier?: string[]
102+
verifier?: string | string[]
101103
issuanceDate?: string
102104
expirationDate?: string
103105
}
104106

105107
export type PresentationPayload = Extensible<FixedPresentationPayload>
106108

107109
interface NarrowPresentationDefinitions {
110+
'@context': string[]
111+
type: string[]
112+
verifier: string[]
108113
verifiableCredential: Verifiable<W3CCredential>[]
109114
}
110115

src/validators.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import { DID_FORMAT, DEFAULT_CONTEXT, DEFAULT_VC_TYPE, DEFAULT_VP_TYPE, JWT_FORMAT } from './constants'
2-
import { JwtCredentialSubject } from './types'
1+
import { DEFAULT_CONTEXT, DEFAULT_VC_TYPE, DEFAULT_VP_TYPE, JWT_FORMAT } from './constants'
2+
import { JwtCredentialSubject, DateType } from './types'
33
import { VerifiableCredential } from 'src'
44
import { asArray } from './converters'
55

6+
function isDateObject(input: any) {
7+
return input && Object.prototype.toString.call(input) === '[object Date]' && !isNaN(input)
8+
}
9+
610
export function validateJwtFormat(value: VerifiableCredential): void {
711
if (typeof value === 'string' && !value.match(JWT_FORMAT)) {
812
throw new TypeError(`"${value}" is not a valid JWT format`)
@@ -16,14 +20,14 @@ export function validateJwtFormat(value: VerifiableCredential): void {
1620
// 10 digits max is 9999999999 -> 11/20/2286 @ 5:46pm (UTC)
1721
// 11 digits max is 99999999999 -> 11/16/5138 @ 9:46am (UTC)
1822
// 12 digits max is 999999999999 -> 09/27/33658 @ 1:46am (UTC)
19-
export function validateTimestamp(value: number | string): void {
23+
export function validateTimestamp(value: number | DateType): void {
2024
if (typeof value === 'number') {
2125
if (!(Number.isInteger(value) && value < 100000000000)) {
2226
throw new TypeError(`"${value}" is not a unix timestamp in seconds`)
2327
}
2428
} else if (typeof value === 'string') {
25-
validateTimestamp(new Date(value).valueOf() / 1000)
26-
} else {
29+
validateTimestamp(Math.floor(new Date(value).valueOf() / 1000))
30+
} else if (!isDateObject(value)) {
2731
throw new TypeError(`"${value}" is not a valid time`)
2832
}
2933
}

0 commit comments

Comments
 (0)