Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/four-olives-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@openid4vc/openid4vci": minor
"@openid4vc/oauth2": patch
---

Add support for deferred credential issuance and Draft 16 of the OpenID for Verifiable Credential Issuance.
6 changes: 6 additions & 0 deletions .changeset/fruity-spiders-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@openid4vc/openid4vci": patch
"@openid4vc/oauth2": patch
---

Fix a myriad of typos across errors, comments, and variable names.
2 changes: 1 addition & 1 deletion packages/oauth2/src/Oauth2AuthorizationServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
verifyPushedAuthorizationRequest,
} from './authorization-request/verify-pushed-authorization-request'
import type { CallbackContext } from './callbacks'
import { type VerifyClientAttestationOptions, verifyClientAttestation } from './client-attestation/clent-attestation'
import { type VerifyClientAttestationOptions, verifyClientAttestation } from './client-attestation/client-attestation'
import { Oauth2ErrorCodes } from './common/z-oauth2-error'
import { type VerifyDpopJwtOptions, verifyDpopJwt } from './dpop/dpop'
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Parse Access Token Request', () => {
url: 'https://request.com/token',
},
})
).toThrow('Error occured during validation of authorization request.')
).toThrow('Error occurred during validation of authorization request.')
})

test('handles unknown grant_type', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/oauth2/src/access-token/introspect-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface IntrospectTokenOptions {
authorizationServerMetadata: AuthorizationServerMetadata

/**
* The provided acccess token
* The provided access token
*/
token: string

Expand All @@ -29,7 +29,7 @@ export interface IntrospectTokenOptions {
tokenTypeHint?: string

/**
* Additional payload to include in the introspection equest. Items will be encoded and sent
* Additional payload to include in the introspection request. Items will be encoded and sent
* using x-www-form-urlencoded format. Nested items (JSON) will be stringified and url encoded.
*/
additionalPayload?: Record<string, unknown>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { formatZodError } from '@openid4vc/utils'
import { extractClientAttestationJwtsFromHeaders } from '../client-attestation/clent-attestation'
import { extractClientAttestationJwtsFromHeaders } from '../client-attestation/client-attestation'
import type { RequestLike } from '../common/z-common'
import { Oauth2ErrorCodes } from '../common/z-oauth2-error'
import { extractDpopJwtFromHeaders } from '../dpop/dpop'
Expand Down Expand Up @@ -57,7 +57,7 @@ export interface ParseAccessTokenRequestOptions {

/**
* The access token request as a JSON object. Your server should decode the
* `x-www-url-form-urlencoded` body into an object (e.g. using `bodyParser.urlEncoed()` in express)
* `x-www-url-form-urlencoded` body into an object (e.g. using `bodyParser.urlEncoded()` in express)
*/
accessTokenRequest: Record<string, unknown>
}
Expand All @@ -74,7 +74,7 @@ export function parseAccessTokenRequest(options: ParseAccessTokenRequestOptions)
if (!parsedAccessTokenRequest.success) {
throw new Oauth2ServerErrorResponseError({
error: Oauth2ErrorCodes.InvalidRequest,
error_description: `Error occured during validation of authorization request.\n${formatZodError(parsedAccessTokenRequest.error)}`,
error_description: `Error occurred during validation of authorization request.\n${formatZodError(parsedAccessTokenRequest.error)}`,
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type CallbackContext, HashAlgorithm } from '../callbacks'
import { type VerifiedClientAttestationJwt, verifyClientAttestation } from '../client-attestation/clent-attestation'
import { type VerifiedClientAttestationJwt, verifyClientAttestation } from '../client-attestation/client-attestation'
import type { VerifiedClientAttestationPopJwt } from '../client-attestation/client-attestation-pop'
import {
oauthClientAttestationHeader,
Expand Down Expand Up @@ -306,7 +306,7 @@ async function verifyAccessTokenRequestClientAttestation(
{
error: Oauth2ErrorCodes.InvalidRequest,
error_description:
'Expected the DPoP JWK thumbprint value to match the JWK thumbprint of the client attestation confirmation JWK. Ensrue both DPoP and client attestation use the same key.',
'Expected the DPoP JWK thumbprint value to match the JWK thumbprint of the client attestation confirmation JWK. Ensure both DPoP and client attestation use the same key.',
},
{
status: 401,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function parseAuthorizationChallengeRequest(
if (!parsedAuthorizationChallengeRequest.success) {
throw new Oauth2ServerErrorResponseError({
error: Oauth2ErrorCodes.InvalidRequest,
error_description: `Error occured during validation of authorization challenge request.\n${formatZodError(parsedAuthorizationChallengeRequest.error)}`,
error_description: `Error occurred during validation of authorization challenge request.\n${formatZodError(parsedAuthorizationChallengeRequest.error)}`,
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extractClientAttestationJwtsFromHeaders } from '../client-attestation/clent-attestation'
import { extractClientAttestationJwtsFromHeaders } from '../client-attestation/client-attestation'
import type { RequestLike } from '../common/z-common'
import { Oauth2ErrorCodes } from '../common/z-oauth2-error'
import { extractDpopJwtFromHeaders } from '../dpop/dpop'
Expand Down Expand Up @@ -31,7 +31,7 @@ export interface ParseAuthorizationRequestResult {
jwt: string
}

// TOOD: we should revampt this to generic client authentication so we can suppor other
// TODO: we should revampt this to generic client authentication so we can suppor other
// method as well. We should also create a generic verify client authentication method.
/**
* The client attestation jwts from the authorization request headers.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type CallbackContext, HashAlgorithm } from '../callbacks'
import { type VerifiedClientAttestationJwt, verifyClientAttestation } from '../client-attestation/clent-attestation'
import { type VerifiedClientAttestationJwt, verifyClientAttestation } from '../client-attestation/client-attestation'
import type { VerifiedClientAttestationPopJwt } from '../client-attestation/client-attestation-pop'
import {
oauthClientAttestationHeader,
Expand All @@ -23,15 +23,15 @@ export interface VerifyAuthorizationRequestDpop {
* The dpop jwt from the pushed authorization request.
*
* If dpop is required, at least one of `jwt` or `jwkThumbprint` MUST
* be provided. If both are provided, the jwk thubmprints are matched
* be provided. If both are provided, the jwk thumbprints are matched
*/
jwt?: string

/**
* The jwk thumbprint as provided in the `dpop_jkt` parameter.
*
* If dpop is required, at least one of `jwt` or `jwkThumbprint` MUST
* be provided. If both are provided, the jwk thubmprints are matched
* be provided. If both are provided, the jwk thumbprints are matched
*/
jwkThumbprint?: string

Expand Down Expand Up @@ -73,7 +73,7 @@ export interface VerifyAuthorizationRequestReturn {
jwkThumbprint: string

/**
* The JWK will be returend if a DPoP proof was provided in the header.
* The JWK will be returned if a DPoP proof was provided in the header.
*/
jwk?: Jwk
}
Expand Down Expand Up @@ -188,7 +188,7 @@ async function verifyAuthorizationRequestClientAttestation(
{
error: Oauth2ErrorCodes.InvalidRequest,
error_description:
'Expected the DPoP JWK thumbprint value to match the JWK thumbprint of the client attestation confirmation JWK. Ensrue both DPoP and client attestation use the same key.',
'Expected the DPoP JWK thumbprint value to match the JWK thumbprint of the client attestation confirmation JWK. Ensure both DPoP and client attestation use the same key.',
},
{
status: 401,
Expand Down
8 changes: 6 additions & 2 deletions packages/oauth2/src/common/z-oauth2-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ export enum Oauth2ErrorCodes {
// OpenID4VCI
InvalidCredentialRequest = 'invalid_credential_request',
CredentialRequestDenied = 'credential_request_denied',
UnsupportedCredentialType = 'unsupported_credential_type',
UnsupportedCredentialFormat = 'unsupported_credential_format',
InvalidProof = 'invalid_proof',
InvalidNonce = 'invalid_nonce',
InvalidEncryptionParameters = 'invalid_encryption_parameters',
UnknownCredentialConfiguration = 'unknown_credential_configuration',
UnknownCredentialIdentifier = 'unknown_credential_identifier',
InvalidTransactionId = 'invalid_transaction_id',
// Removed from Draft 16+
UnsupportedCredentialType = 'unsupported_credential_type',
UnsupportedCredentialFormat = 'unsupported_credential_format',

// Jar
InvalidRequestUri = 'invalid_request_uri',
Expand Down
2 changes: 1 addition & 1 deletion packages/oauth2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type { RequestClientAttestationOptions } from './client-attestation/clien
export {
createClientAttestationJwt,
type CreateClientAttestationJwtOptions,
} from './client-attestation/clent-attestation'
} from './client-attestation/client-attestation'
export type {
ClientAttestationJwtHeader,
ClientAttestationJwtPayload,
Expand Down
6 changes: 3 additions & 3 deletions packages/oauth2/src/metadata/fetch-jwks-uri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { type JwkSet, zJwkSet } from '../common/jwk/z-jwk'
/**
* Fetch JWKs from a provided JWKs URI.
*
* Returns validated metadata if successfull response
* Returns validated metadata if successful response
* Throws error otherwise
*
* @throws {ValidationError} if successfull response but validation of response failed
* @throws {ValidationError} if successful response but validation of response failed
* @throws {InvalidFetchResponseError} if unsuccesful response
*/
export async function fetchJwks(jwksUrl: string, fetch?: Fetch): Promise<JwkSet> {
Expand All @@ -18,7 +18,7 @@ export async function fetchJwks(jwksUrl: string, fetch?: Fetch): Promise<JwkSet>
const { result, response } = await fetcher(zJwkSet, [ContentType.JwkSet, ContentType.Json], jwksUrl)
if (!response.ok) {
throw new InvalidFetchResponseError(
`Fetching JWKs from jwks_uri '${jwksUrl}' resulted in an unsuccessfull response with status code '${response.status}'.`,
`Fetching JWKs from jwks_uri '${jwksUrl}' resulted in an unsuccessful response with status code '${response.status}'.`,
await response.clone().text(),
response
)
Expand Down
8 changes: 4 additions & 4 deletions packages/oauth2/src/metadata/fetch-well-known-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { ValidationError } from '../../../utils/src/error/ValidationError'
* Fetch well known metadata and validate the response.
*
* Returns null if 404 is returned
* Returns validated metadata if successfull response
* Returns validated metadata if successful response
* Throws error otherwise
*
* @throws {ValidationError} if successfull response but validation of response failed
* @throws {InvalidFetchResponseError} if no successfull or 404 response
* @throws {ValidationError} if successful response but validation of response failed
* @throws {InvalidFetchResponseError} if no successful or 404 response
* @throws {Error} if parsing json from response fails
*/
export async function fetchWellKnownMetadata<Schema extends BaseSchema>(
Expand All @@ -28,7 +28,7 @@ export async function fetchWellKnownMetadata<Schema extends BaseSchema>(

if (!response.ok) {
throw new InvalidFetchResponseError(
`Fetching well known metadata from '${wellKnownMetadataUrl}' resulted in an unsuccessfull response with status '${response.status}'.`,
`Fetching well known metadata from '${wellKnownMetadataUrl}' resulted in an unsuccessful response with status '${response.status}'.`,
await response.clone().text(),
response
)
Expand Down
52 changes: 42 additions & 10 deletions packages/openid4vci/src/Openid4vciClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ import {
type RetrieveCredentialsResponseNotOk,
type RetrieveCredentialsResponseOk,
type RetrieveCredentialsWithFormatOptions,
type RetrieveDeferredCredentialsOptions,
type RetrieveDeferredCredentialsResponseOk,
retrieveCredentialsWithCredentialConfigurationId,
retrieveCredentialsWithFormat,
retrieveDeferredCredentials,
} from './credential-request/retrieve-credentials'
import { Openid4vciError } from './error/Openid4vciError'
import { Openid4vciRetrieveCredentialsError } from './error/Openid4vciRetrieveCredentialsError'
Expand All @@ -38,7 +41,7 @@ import { extractKnownCredentialConfigurationSupportedFormats } from './metadata/
import type { CredentialIssuerMetadata } from './metadata/credential-issuer/z-credential-issuer-metadata'
import { type IssuerMetadataResult, resolveIssuerMetadata } from './metadata/fetch-issuer-metadata'
import { type RequestNonceOptions, requestNonce } from './nonce/nonce-request'
import { type SendNotifcationOptions, sendNotifcation } from './notification/notification'
import { type SendNotificationOptions, sendNotification } from './notification/notification'
import { Openid4vciDraftVersion } from './version'

export enum AuthorizationFlow {
Expand Down Expand Up @@ -88,7 +91,7 @@ export class Openid4vciClient {
* Retrieve an authorization code for a presentation during issuance session
*
* This can only be called if an authorization challenge was performed before and returned a
* `presentation` paramater along with an `auth_session`. If the presentation response included
* `presentation` parameter along with an `auth_session`. If the presentation response included
* an `presentation_during_issuance_session` parameter it MUST be included in this request as well.
*/
public async retrieveAuthorizationCodeUsingPresentation(options: {
Expand Down Expand Up @@ -142,9 +145,9 @@ export class Openid4vciClient {
*
* In case the authorization challenge request returns an error with `insufficient_authorization`
* with a `presentation` field it means the authorization server expects presentation of credentials
* before issuance of crednetials. If this is the case, the value in `presentation` should be treated
* before issuance of credentials. If this is the case, the value in `presentation` should be treated
* as an openid4vp authorization request and submitted to the verifier. Once the presentation response
* has been submitted, the RP will respnosd with a `presentation_during_issuance_session` parameter.
* has been submitted, the RP will respond with a `presentation_during_issuance_session` parameter.
* Together with the `auth_session` parameter returned in this call you can retrieve an `authorization_code`
* using
*/
Expand Down Expand Up @@ -381,7 +384,7 @@ export class Openid4vciClient {
* Request a nonce to be used in credential request proofs from the `nonce_endpoint`
*
* @throws Openid4vciError - if no `nonce_endpoint` is configured in the issuer metadata
* @thrwos InvalidFetchResponseError - if the nonce endpoint did not return a succesfull response
* @throws InvalidFetchResponseError - if the nonce endpoint did not return a successful response
* @throws ValidationError - if validating the nonce response failed
*/
public async requestNonce(options: Pick<RequestNonceOptions, 'issuerMetadata'>) {
Expand Down Expand Up @@ -452,7 +455,7 @@ export class Openid4vciClient {
}

/**
* @throws Openid4vciRetrieveCredentialsError - if an unsuccesfull response or the respnose couldn't be parsed as credential response
* @throws Openid4vciRetrieveCredentialsError - if an unsuccessful response or the response couldn't be parsed as credential response
* @throws ValidationError - if validation of the credential request failed
* @throws Openid4vciError - if the `credentialConfigurationId` couldn't be found, or if the the format specific request couldn't be constructed
*/
Expand All @@ -470,7 +473,10 @@ export class Openid4vciClient {
> & { credentialConfigurationId: string }) {
let credentialResponse: RetrieveCredentialsResponseNotOk | RetrieveCredentialsResponseOk

if (issuerMetadata.originalDraftVersion === Openid4vciDraftVersion.Draft15) {
if (
issuerMetadata.originalDraftVersion === Openid4vciDraftVersion.Draft15 ||
issuerMetadata.originalDraftVersion === Openid4vciDraftVersion.Draft16
) {
credentialResponse = await retrieveCredentialsWithCredentialConfigurationId({
accessToken,
credentialConfigurationId,
Expand Down Expand Up @@ -511,7 +517,33 @@ export class Openid4vciClient {
}

/**
* @throws Openid4vciSendNotificationError - if an unsuccesfull response
* @throws Openid4vciRetrieveCredentialsError - if an unsuccessful response or the response couldn't be parsed as credential response
* @throws ValidationError - if validation of the credential request failed
*/
public async retrieveDeferredCredentials(
Comment thread
hacdias marked this conversation as resolved.
options: Pick<
RetrieveDeferredCredentialsOptions,
'issuerMetadata' | 'accessToken' | 'transactionId' | 'dpop' | 'additionalRequestPayload'
>
): Promise<RetrieveDeferredCredentialsResponseOk> {
const credentialResponse = await retrieveDeferredCredentials({
...options,
callbacks: this.options.callbacks,
})

if (!credentialResponse.ok) {
throw new Openid4vciRetrieveCredentialsError(
`Error retrieving deferred credentials from '${options.issuerMetadata.credentialIssuer.credential_issuer}'`,
credentialResponse,
await credentialResponse.response.clone().text()
)
}

return credentialResponse
}

/**
* @throws Openid4vciSendNotificationError - if an unsuccessful response
* @throws ValidationError - if validation of the notification request failed
*/
public async sendNotification({
Expand All @@ -521,10 +553,10 @@ export class Openid4vciClient {
accessToken,
dpop,
}: Pick<
SendNotifcationOptions,
SendNotificationOptions,
'accessToken' | 'additionalRequestPayload' | 'issuerMetadata' | 'dpop' | 'notification'
>) {
const notificationResponse = await sendNotifcation({
const notificationResponse = await sendNotification({
accessToken,
issuerMetadata,
additionalRequestPayload,
Expand Down
Loading