Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:Sphereon-Opensource/OID4VCI into…
Browse files Browse the repository at this point in the history
… develop
  • Loading branch information
auer-martin committed Jul 31, 2024
2 parents c7c6af4 + 858a8ea commit 86d0423
Show file tree
Hide file tree
Showing 7 changed files with 23 additions and 61 deletions.
1 change: 1 addition & 0 deletions packages/client/lib/AccessTokenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export class AccessTokenClient {
if (credentialOfferRequest?.supportedFlows.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
this.assertAlphanumericPin(opts.pinMetadata, pin);
request.user_pin = pin;
request.tx_code = pin;

request.grant_type = GrantTypes.PRE_AUTHORIZED_CODE;
// we actually know it is there because of the isPreAuthCode call
Expand Down
21 changes: 10 additions & 11 deletions packages/client/lib/AuthorizationCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,16 @@ export const createAuthorizationRequestUrl = async ({
const { redirectUri, requestObjectOpts = { requestObjectMode: CreateRequestObjectMode.NONE } } = authorizationRequest;
const client_id = clientId ?? authorizationRequest.clientId;

let { scope, authorizationDetails } = authorizationRequest;
const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
// Authorization server metadata takes precedence
const authorizationMetadata = endpointMetadata.authorizationServerMetadata ?? endpointMetadata.credentialIssuerMetadata

let { authorizationDetails } = authorizationRequest;
const parMode = authorizationMetadata?.require_pushed_authorization_requests
? PARMode.REQUIRE
: (authorizationRequest.parMode ?? (client_id ? PARMode.AUTO : PARMode.NEVER));
// Scope and authorization_details can be used in the same authorization request
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
if (!scope && !authorizationDetails) {
if (!authorizationRequest.scope && !authorizationDetails) {
if (!credentialOffer) {
throw Error('Please provide a scope or authorization_details if no credential offer is present');
}
Expand Down Expand Up @@ -177,12 +180,8 @@ export const createAuthorizationRequestUrl = async ({
if (!endpointMetadata?.authorization_endpoint) {
throw Error('Server metadata does not contain authorization endpoint');
}
const parEndpoint = endpointMetadata.credentialIssuerMetadata?.pushed_authorization_request_endpoint;
const parEndpoint = authorizationMetadata?.pushed_authorization_request_endpoint;

// add 'openid' scope if not present
if (!scope?.includes('openid')) {
scope = ['openid', scope].filter((s) => !!s).join(' ');
}

let queryObj: Record<string, any> | PushedAuthorizationResponse = {
response_type: ResponseType.AUTH_CODE,
Expand All @@ -194,7 +193,7 @@ export const createAuthorizationRequestUrl = async ({
...(redirectUri && { redirect_uri: redirectUri }),
...(client_id && { client_id }),
...(credentialOffer?.issuerState && { issuer_state: credentialOffer.issuerState }),
scope,
scope: authorizationRequest.scope,
};

if (!parEndpoint && parMode === PARMode.REQUIRE) {
Expand All @@ -210,11 +209,11 @@ export const createAuthorizationRequestUrl = async ({
{ contentType: 'application/x-www-form-urlencoded', accept: 'application/json' },
);
if (parResponse.errorBody || !parResponse.successBody) {
console.log(JSON.stringify(parResponse.errorBody));
console.log('Falling back to regular request URI, since PAR failed');
if (parMode === PARMode.REQUIRE) {
throw Error(`PAR error: ${parResponse.origResponse.statusText}`);
}

debug('Falling back to regular request URI, since PAR failed', JSON.stringify(parResponse.errorBody));
} else {
debug(`PAR response: ${JSON.stringify(parResponse.successBody, null, 2)}`);
queryObj = { /*response_type: ResponseType.AUTH_CODE,*/ client_id, request_uri: parResponse.successBody.request_uri };
Expand Down
25 changes: 2 additions & 23 deletions packages/client/lib/__tests__/OpenID4VCIClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,6 @@ describe('OpenID4VCIClient should', () => {
}),
).rejects.toThrow(Error('Server metadata does not contain authorization endpoint'));
});
it("injects 'openid' as the first scope if not provided", async () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
client._state.endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;

const url = await client.createAuthorizationRequestUrl({
pkce: {
codeChallengeMethod: CodeChallengeMethod.S256,
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
},
authorizationRequest: {
scope: 'TestCredential',
redirectUri: 'http://localhost:8881/cb',
},
});

const urlSearchParams = new URLSearchParams(url.split('?')[1]);
const scope = urlSearchParams.get('scope')?.split(' ');

expect(scope?.[0]).toBe('openid');
});
it('throw an error if no scope and no authorization_details is provided', async () => {
nock(MOCK_URL).get(/.*/).reply(200, {});
nock(MOCK_URL).get(WellKnownEndpoints.OAUTH_AS).reply(200, {});
Expand Down Expand Up @@ -149,7 +128,7 @@ describe('OpenID4VCIClient should', () => {
},
}),
).resolves.toEqual(
'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client',
);
});
it('create an authorization request url with authorization_details object property', async () => {
Expand All @@ -176,7 +155,7 @@ describe('OpenID4VCIClient should', () => {
},
}),
).resolves.toEqual(
'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client',
);
});

Expand Down
25 changes: 2 additions & 23 deletions packages/client/lib/__tests__/OpenID4VCIClientV1_0_13.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,6 @@ describe('OpenID4VCIClientV1_0_13 should', () => {
}),
).rejects.toThrow(Error('Server metadata does not contain authorization endpoint'));
});
it("injects 'openid' as the first scope if not provided", async () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
client._state.endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;

const url = await client.createAuthorizationRequestUrl({
pkce: {
codeChallengeMethod: CodeChallengeMethod.S256,
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
},
authorizationRequest: {
scope: 'TestCredential',
redirectUri: 'http://localhost:8881/cb',
},
});

const urlSearchParams = new URLSearchParams(url.split('?')[1]);
const scope = urlSearchParams.get('scope')?.split(' ');

expect(scope?.[0]).toBe('openid');
});
it('throw an error if no scope and no authorization_details is provided', async () => {
nock(MOCK_URL).get(/.*/).reply(200, {});
nock(MOCK_URL).get(WellKnownEndpoints.OAUTH_AS).reply(200, {});
Expand Down Expand Up @@ -141,7 +120,7 @@ describe('OpenID4VCIClientV1_0_13 should', () => {
},
}),
).resolves.toEqual(
'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client',
);
});
it('create an authorization request url with authorization_details object property', async () => {
Expand All @@ -168,7 +147,7 @@ describe('OpenID4VCIClientV1_0_13 should', () => {
},
}),
).resolves.toEqual(
'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
'https://server.example.com/v1/auth/authorize?response_type=code&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client',
);
});

Expand Down
1 change: 0 additions & 1 deletion packages/issuer/lib/VcIssuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ export class VcIssuer<DIDDoc extends object> {
status,
notification_id: uuidv4(),
...(userPin && { txCode: userPin }), // We used to use userPin according to older specs. We map these onto txCode now. If both are used, txCode in the end wins, even if they are different
...(txCode && { txCode }),
...(opts.credentialDataSupplierInput && { credentialDataSupplierInput: opts.credentialDataSupplierInput }),
credentialOffer,
}
Expand Down
9 changes: 7 additions & 2 deletions packages/issuer/lib/tokens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,17 @@ export const assertValidAccessTokenRequest = async (
invalid_request:
the Authorization Server does not expect a PIN in the pre-authorized flow but the client provides a PIN
*/
if (!credentialOfferSession.credentialOffer.credential_offer?.grants?.[GrantTypes.PRE_AUTHORIZED_CODE]?.tx_code && request.tx_code) {
if (
!credentialOfferSession.credentialOffer.credential_offer?.grants?.[GrantTypes.PRE_AUTHORIZED_CODE]?.tx_code &&
request.tx_code &&
!request.user_pin
) {
// >= v13
throw new TokenError(400, TokenErrorResponse.invalid_request, USER_PIN_NOT_REQUIRED_ERROR)
} else if (
!credentialOfferSession.credentialOffer.credential_offer?.grants?.[GrantTypes.PRE_AUTHORIZED_CODE]?.user_pin_required &&
request.user_pin
request.user_pin &&
!request.tx_code
) {
// <= v12
throw new TokenError(400, TokenErrorResponse.invalid_request, USER_PIN_NOT_REQUIRED_ERROR)
Expand Down
2 changes: 1 addition & 1 deletion packages/oid4vci-common/lib/types/Authorization.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ export interface AccessTokenRequest {
'pre-authorized_code': string;
redirect_uri?: string;
scope?: string;
user_pin?: string; //pre draft 13
user_pin?: string; //this is for v11, not required in v13 anymore
tx_code?: string; //draft 13
[s: string]: unknown;
}
Expand Down

0 comments on commit 86d0423

Please sign in to comment.