From acf2cdc4f149112a5d4ee664cbc74189ac2c1f9a Mon Sep 17 00:00:00 2001 From: "A.G.J. Cate" Date: Mon, 13 Jan 2025 08:16:20 +0100 Subject: [PATCH] chore: added acquiring authorization challenge authorization code to vci client using the rest api --- .../client/lib/AuthorizationCodeClient.ts | 63 ++++++++++++++++++- packages/client/lib/OpenID4VCIClient.ts | 32 ++++++++-- .../client/lib/OpenID4VCIClientV1_0_11.ts | 17 ++++- .../client/lib/OpenID4VCIClientV1_0_13.ts | 21 ++++++- .../lib/types/Authorization.types.ts | 11 ++++ 5 files changed, 132 insertions(+), 12 deletions(-) diff --git a/packages/client/lib/AuthorizationCodeClient.ts b/packages/client/lib/AuthorizationCodeClient.ts index 7aaa7b15..37acaffe 100644 --- a/packages/client/lib/AuthorizationCodeClient.ts +++ b/packages/client/lib/AuthorizationCodeClient.ts @@ -1,7 +1,9 @@ import { + AuthorizationChallengeCodeResponse, + AuthorizationChallengeErrorResponse, AuthorizationChallengeRequestOpts, AuthorizationDetails, AuthorizationRequestOpts, - CodeChallengeMethod, + CodeChallengeMethod, CommonAuthorizationChallengeRequest, convertJsonToURI, CreateRequestObjectMode, CredentialConfigurationSupportedV1_0_13, @@ -16,12 +18,13 @@ import { JsonURIMode, Jwt, OpenId4VCIVersion, + OpenIDResponse, PARMode, PKCEOpts, PushedAuthorizationResponse, RequestObjectOpts, - ResponseType, -} from '@sphereon/oid4vci-common'; + ResponseType +} from '@sphereon/oid4vci-common' import Debug from 'debug'; import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder'; @@ -272,3 +275,57 @@ const handleLocations = (endpointMetadata: EndpointMetadataResultV1_0_13, author } return authorizationDetails; }; + +export const acquireAuthorizationChallengeAuthCode = async (opts: AuthorizationChallengeRequestOpts): Promise> => { + return await acquireAuthorizationChallengeAuthCodeUsingRequest({ + authorizationChallengeRequest: await createAuthorizationChallengeRequest(opts) + }); +} + +export const acquireAuthorizationChallengeAuthCodeUsingRequest = async (opts: { authorizationChallengeRequest: CommonAuthorizationChallengeRequest }): Promise> => { + const { authorizationChallengeRequest } = opts + // TODO validate request + const authorizationChallengeCodeUrl = '' // TODO + const response = await sendAuthorizationChallengeRequest( + authorizationChallengeCodeUrl, + authorizationChallengeRequest + ); + + return response +} + +export const createAuthorizationChallengeRequest = async (opts: AuthorizationChallengeRequestOpts): Promise => { + const { + clientId, + issuerState, + authSession, + scope, + definitionId, + codeChallenge, + codeChallengeMethod, + presentationDuringIssuanceSession + } = opts; + + const request: CommonAuthorizationChallengeRequest = { + client_id: clientId, + issuer_state: issuerState, + auth_session: authSession, + scope, + code_challenge: codeChallenge, + code_challenge_method: codeChallengeMethod, + definition_id: definitionId, + presentation_during_issuance_session: presentationDuringIssuanceSession + } + + return request +} + +export const sendAuthorizationChallengeRequest = async ( + authorizationChallengeCodeUrl: string, + authorizationChallengeRequest: CommonAuthorizationChallengeRequest, + opts?: { headers?: Record } +): Promise> => { + return await formPost(authorizationChallengeCodeUrl, convertJsonToURI(authorizationChallengeRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }), { // TODO check encoding + customHeaders: opts?.headers ? opts.headers : undefined, + }); +} diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index 6769ebbd..8dfdaa0b 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -3,6 +3,9 @@ import { AccessTokenRequestOpts, AccessTokenResponse, Alg, + AuthorizationChallengeCodeResponse, + AuthorizationChallengeErrorResponse, + AuthorizationChallengeRequestOpts, AuthorizationRequestOpts, AuthorizationResponse, AuthorizationServerOpts, @@ -31,16 +34,20 @@ import { NotificationResponseResult, OID4VCICredentialFormat, OpenId4VCIVersion, + OpenIDResponse, PKCEOpts, ProofOfPossessionCallbacks, - toAuthorizationResponsePayload, -} from '@sphereon/oid4vci-common'; + toAuthorizationResponsePayload +} from '@sphereon/oid4vci-common' import { CredentialFormat } from '@sphereon/ssi-types'; import Debug from 'debug'; import { AccessTokenClient } from './AccessTokenClient'; import { AccessTokenClientV1_0_11 } from './AccessTokenClientV1_0_11'; -import { createAuthorizationRequestUrl } from './AuthorizationCodeClient'; +import { + acquireAuthorizationChallengeAuthCode, + createAuthorizationRequestUrl +} from './AuthorizationCodeClient' import { createAuthorizationRequestUrlV1_0_11 } from './AuthorizationCodeClientV1_0_11'; import { CredentialOfferClient } from './CredentialOfferClient'; import { CredentialRequestOpts } from './CredentialRequestClient'; @@ -270,10 +277,18 @@ export class OpenID4VCIClient { this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce }); } + public async acquireAuthorizationChallengeCode(opts: AuthorizationChallengeRequestOpts): Promise> { + const response = await acquireAuthorizationChallengeAuthCode({ + clientId: this._state.clientId ?? this._state.authorizationRequestOpts?.clientId, + ...opts + }) + return response + } + public async acquireAccessToken( opts?: Omit & { clientId?: string; - authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object + authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object // TODO we need to add support for the authorization code from the auth challenge additionalRequestParams?: Record; }, ): Promise { @@ -654,6 +669,15 @@ export class OpenID4VCIClient { return this.endpointMetadata ? this.endpointMetadata.credential_endpoint : `${this.getIssuer()}/credential`; } + public getAuthorizationChallengeEndpoint(): string | undefined { + this.assertIssuerData(); + return this.endpointMetadata?.authorization_challenge_endpoint; + } + + public hasAuthorizationChallengeEndpoint(): boolean { + return !!this.getAuthorizationChallengeEndpoint(); + } + public hasDeferredCredentialEndpoint(): boolean { return !!this.getAccessTokenEndpoint(); } diff --git a/packages/client/lib/OpenID4VCIClientV1_0_11.ts b/packages/client/lib/OpenID4VCIClientV1_0_11.ts index 4d06dece..6ea67234 100644 --- a/packages/client/lib/OpenID4VCIClientV1_0_11.ts +++ b/packages/client/lib/OpenID4VCIClientV1_0_11.ts @@ -3,6 +3,9 @@ import { AccessTokenRequestOpts, AccessTokenResponse, Alg, + AuthorizationChallengeCodeResponse, + AuthorizationChallengeErrorResponse, + AuthorizationChallengeRequestOpts, AuthorizationRequestOpts, AuthorizationResponse, AuthorizationServerOpts, @@ -25,10 +28,11 @@ import { KID_JWK_X5C_ERROR, OID4VCICredentialFormat, OpenId4VCIVersion, + OpenIDResponse, PKCEOpts, ProofOfPossessionCallbacks, - toAuthorizationResponsePayload, -} from '@sphereon/oid4vci-common'; + toAuthorizationResponsePayload +} from '@sphereon/oid4vci-common' import { CredentialFormat } from '@sphereon/ssi-types'; import Debug from 'debug'; @@ -39,6 +43,7 @@ import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClient import { MetadataClientV1_0_11 } from './MetadataClientV1_0_11'; import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder'; import { generateMissingPKCEOpts } from './functions'; +import { acquireAuthorizationChallengeAuthCode } from './AuthorizationCodeClient' const debug = Debug('sphereon:oid4vci'); @@ -256,6 +261,14 @@ export class OpenID4VCIClientV1_0_11 { this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce }); } + public async acquireAuthorizationChallengeCode(opts: AuthorizationChallengeRequestOpts): Promise> { + const response = await acquireAuthorizationChallengeAuthCode({ + clientId: this._state.clientId ?? this._state.authorizationRequestOpts?.clientId, + ...opts + }) + return response + } + public async acquireAccessToken( opts?: Omit & { clientId?: string; diff --git a/packages/client/lib/OpenID4VCIClientV1_0_13.ts b/packages/client/lib/OpenID4VCIClientV1_0_13.ts index 4aedcc9d..e7afaa64 100644 --- a/packages/client/lib/OpenID4VCIClientV1_0_13.ts +++ b/packages/client/lib/OpenID4VCIClientV1_0_13.ts @@ -3,6 +3,9 @@ import { AccessTokenRequestOpts, AccessTokenResponse, Alg, + AuthorizationChallengeCodeResponse, + AuthorizationChallengeErrorResponse, + AuthorizationChallengeRequestOpts, AuthorizationRequestOpts, AuthorizationResponse, AuthorizationServerOpts, @@ -25,15 +28,19 @@ import { NotificationResponseResult, OID4VCICredentialFormat, OpenId4VCIVersion, + OpenIDResponse, PKCEOpts, ProofOfPossessionCallbacks, - toAuthorizationResponsePayload, -} from '@sphereon/oid4vci-common'; + toAuthorizationResponsePayload +} from '@sphereon/oid4vci-common' import { CredentialFormat, DIDDocument } from '@sphereon/ssi-types'; import Debug from 'debug'; import { AccessTokenClient } from './AccessTokenClient'; -import { createAuthorizationRequestUrl } from './AuthorizationCodeClient'; +import { + acquireAuthorizationChallengeAuthCode, + createAuthorizationRequestUrl +} from './AuthorizationCodeClient' import { CredentialOfferClient } from './CredentialOfferClient'; import { CredentialRequestOpts } from './CredentialRequestClient'; import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13'; @@ -261,6 +268,14 @@ export class OpenID4VCIClientV1_0_13 { this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce }); } + public async acquireAuthorizationChallengeCode(opts: AuthorizationChallengeRequestOpts): Promise> { + const response = await acquireAuthorizationChallengeAuthCode({ + clientId: this._state.clientId ?? this._state.authorizationRequestOpts?.clientId, + ...opts + }) + return response + } + public async acquireAccessToken( opts?: Omit & { clientId?: string; diff --git a/packages/oid4vci-common/lib/types/Authorization.types.ts b/packages/oid4vci-common/lib/types/Authorization.types.ts index c13c1b9b..8803eb8e 100644 --- a/packages/oid4vci-common/lib/types/Authorization.types.ts +++ b/packages/oid4vci-common/lib/types/Authorization.types.ts @@ -122,6 +122,17 @@ export interface CommonAuthorizationChallengeRequest { presentation_during_issuance_session?: string; } +export interface AuthorizationChallengeRequestOpts { + clientId?: string; + issuerState?: string + authSession?: string + scope?: string + codeChallenge?: string + codeChallengeMethod?: CodeChallengeMethod + definitionId?: string + presentationDuringIssuanceSession?: string; +} + // https://www.ietf.org/archive/id/draft-parecki-oauth-first-party-apps-02.html#name-error-response export interface AuthorizationChallengeErrorResponse { /**