Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sprind 26 session endpoint #139

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
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
18 changes: 18 additions & 0 deletions packages/client/lib/IssuerSessionClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { IssuerSessionIdRequestOpts, IssuerSessionResponse, OpenIDResponse, post } from '@sphereon/oid4vci-common';

import { LOG } from './index';

export const acquireIssuerSessionId = async (opts: IssuerSessionIdRequestOpts): Promise<IssuerSessionResponse> => {
LOG.debug(`acquiring issuer session endpoint from endpoint ${opts.sessionEndpoint}`);
const sessionResponse = (await post(opts.sessionEndpoint)) as OpenIDResponse<IssuerSessionResponse>;
if (sessionResponse.errorBody !== undefined) {
return Promise.reject(`an error occurred while requesting a issuer session token from endpoint ${opts.sessionEndpoint}:
${sessionResponse.errorBody.error} - ${sessionResponse.errorBody.error_description}`);
}
if (sessionResponse.successBody === undefined || !Object.keys(sessionResponse.successBody).includes('session_id')) {
return Promise.reject(
`an error occurred while requesting a issuer session token from endpoint ${opts.sessionEndpoint}, missing session_token response`,
);
}
return sessionResponse.successBody;
};
6 changes: 6 additions & 0 deletions packages/client/lib/MetadataClientV1_0_13.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class MetadataClientV1_0_13 {
let credential_endpoint: string | undefined;
let deferred_credential_endpoint: string | undefined;
let authorization_endpoint: string | undefined;
let session_endpoint: string | undefined;
let authorizationServerType: AuthorizationServerType = 'OID4VCI';
let authorization_servers: string[] = [issuer];
const oid4vciResponse = await MetadataClientV1_0_13.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
Expand Down Expand Up @@ -157,11 +158,16 @@ export class MetadataClientV1_0_13 {
credentialIssuerMetadata = authMetadata as CredentialIssuerMetadataV1_0_13;
}
debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);

if (credentialIssuerMetadata?.session_endpoint !== undefined) {
session_endpoint = credentialIssuerMetadata.session_endpoint;
}
return {
issuer,
token_endpoint,
credential_endpoint,
deferred_credential_endpoint,
session_endpoint,
authorization_server: authorization_servers[0],
authorization_endpoint,
authorizationServerType,
Expand Down
12 changes: 12 additions & 0 deletions packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
getSupportedCredentials,
getTypesFromCredentialSupported,
getTypesFromObject,
IssuerSessionResponse,
KID_JWK_X5C_ERROR,
NotificationRequest,
NotificationResult,
Expand All @@ -44,6 +45,7 @@ import { CredentialOfferClient } from './CredentialOfferClient';
import { CredentialRequestOpts } from './CredentialRequestClient';
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
import { acquireIssuerSessionId } from './IssuerSessionClient';
import { MetadataClient } from './MetadataClient';
import { OpenID4VCIClientStateV1_0_11 } from './OpenID4VCIClientV1_0_11';
import { OpenID4VCIClientStateV1_0_13 } from './OpenID4VCIClientV1_0_13';
Expand Down Expand Up @@ -361,6 +363,16 @@ export class OpenID4VCIClient {
return this.accessTokenResponse;
}

public async acquireIssuerSessionId(): Promise<IssuerSessionResponse | undefined> {
if (!this._state.endpointMetadata) {
return Promise.reject('endpointMetadata no loaded, retrieveServerMetadata()');
}
if (!('session_endpoint' in this._state.endpointMetadata) || !this._state.endpointMetadata.session_endpoint) {
return undefined;
}
return acquireIssuerSessionId({ sessionEndpoint: this._state.endpointMetadata.session_endpoint });
}

public async acquireCredentials({
credentialTypes,
context,
Expand Down
60 changes: 60 additions & 0 deletions packages/client/lib/__tests__/IssuerSessionClient.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { IssuerSessionIdRequestOpts } from '@sphereon/oid4vci-common';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import nock from 'nock';

import { acquireIssuerSessionId } from '../IssuerSessionClient';

describe('IssuerSessionClient', () => {
describe('acquireIssuerSessionId', () => {
const mockSessionEndpoint = 'https://server.example.com/session_endpoint';
const mockSessionId = 'iOiJSUzI1NiIsInR';

beforeEach(() => {
nock.cleanAll();
});

it('should successfully acquire an issuer session ID', async () => {
const mockResponse = {
session_id: mockSessionId,
};

nock('https://server.example.com').post('/session_endpoint').reply(200, mockResponse, { 'Content-Type': 'application/json' });

const opts: IssuerSessionIdRequestOpts = {
sessionEndpoint: mockSessionEndpoint,
};

const result = await acquireIssuerSessionId(opts);

expect(result).toEqual(mockResponse);
});

it('should reject with an error if the response contains an error body', async () => {
const mockErrorResponse = {
error: 'invalid_request',
error_description: 'The request is missing a required parameter',
};

nock('https://server.example.com').post('/session_endpoint').reply(400, mockErrorResponse, { 'Content-Type': 'application/json' });

const opts: IssuerSessionIdRequestOpts = {
sessionEndpoint: mockSessionEndpoint,
};

await expect(acquireIssuerSessionId(opts)).rejects.toMatch(/an error occurred while requesting a issuer session token/);
});

it('should reject with an error if the response is missing the session_token', async () => {
nock('https://server.example.com').post('/session_endpoint').reply(200, undefined, { 'Content-Type': 'application/json' });

const opts: IssuerSessionIdRequestOpts = {
sessionEndpoint: mockSessionEndpoint,
};

await expect(acquireIssuerSessionId(opts)).rejects.toMatch(
/an error occurred while requesting a issuer session token.*missing session_token response/,
);
});
});
});
1 change: 1 addition & 0 deletions packages/client/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export * from './MetadataClientV1_0_11';
export * from './OpenID4VCIClient';
export * from './OpenID4VCIClientV1_0_13';
export * from './OpenID4VCIClientV1_0_11';
export * from './IssuerSessionClient';
export * from './ProofOfPossessionBuilder';
4 changes: 4 additions & 0 deletions packages/oid4vci-common/lib/types/CredentialIssuance.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export interface CredentialResponse extends ExperimentalSubjectIssuance {
notification_id?: string;
}

export interface IssuerSessionResponse {
session_id: string;
}

export interface CredentialOfferRequestWithBaseUrl extends UniformCredentialOfferRequest {
scheme: string;
clientId?: string;
Expand Down
4 changes: 4 additions & 0 deletions packages/oid4vci-common/lib/types/Generic.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,7 @@ export type NotificationResult = {
export interface NotificationErrorResponse {
error: NotificationError | string;
}

export interface IssuerSessionIdRequestOpts {
sessionEndpoint: string;
}
2 changes: 2 additions & 0 deletions packages/oid4vci-common/lib/types/v1_0_13.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface IssuerMetadataV1_0_13 {
notification_endpoint?: string;
credential_response_encryption?: ResponseEncryption;
token_endpoint?: string;
session_endpoint?: string;
display?: MetadataDisplay[];

[x: string]: unknown;
Expand Down Expand Up @@ -191,6 +192,7 @@ export interface EndpointMetadataResultV1_0_13 extends EndpointMetadata {
authorizationServerType: AuthorizationServerType;
authorizationServerMetadata?: AuthorizationServerMetadata;
credentialIssuerMetadata?: Partial<AuthorizationServerMetadata> & IssuerMetadataV1_0_13;
session_endpoint?: string;
}

// For now we extend the opts above. Only difference is that the credential endpoint is optional in the Opts, as it can come from other sources. The value is however required in the eventual Issuer Metadata
Expand Down