@microsoft/useragent-sdk
Version:
SDK for building decentralized identity wallets and enterprise agents.
218 lines (184 loc) • 7.41 kB
text/typescript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IHubError, IHubResponse, HubErrorCode } from '@decentralized-identity/hub-common-js';
import HubRequest from './requests/HubRequest';
import HubError from './HubError';
import HubCommitWriteRequest from './requests/HubCommitWriteRequest';
import HubWriteResponse from './responses/HubWriteResponse';
import HubObjectQueryRequest from './requests/HubObjectQueryRequest';
import HubObjectQueryResponse from './responses/HubObjectQueryResponse';
import HubCommitQueryRequest from './requests/HubCommitQueryRequest';
import HubCommitQueryResponse from './responses/HubCommitQueryResponse';
import nodeFetch, { Request, RequestInit } from 'node-fetch';
import Identifier from '../Identifier';
import DidProtocol, { DidProtocolOptions } from '../crypto/protocols/did/DidProtocol';
import HttpResolver from '../resolvers/HttpResolver';
import IResolver from '../resolvers/IResolver';
import UserAgentError from '../UserAgentError';
/**
* Options for instantiating a new Hub session.
*/
export class HubSessionOptions {
/**
* The DID of the client, i.e the identity of the user/app using this SDK.
*/
client?: Identifier;
/**
* The reference to the private key to use for signing when communicating with the Hub. Must be
* registered in the DID document of the clientDid.
*/
signingKeyReference: string | undefined;
/**
* The reference to the private key to use for decrypting the hub communication. Must be
* registered in the DID document of the clientDid.
*/
encryptionKeyReference: string | undefined;
/**
* The Identfier of the owner of the Hub with which we will be communicating.
*/
hubOwner?: Identifier;
/**
* The Identifier of the Hub, for addressing request envelopes.
*/
hubId: string = 'did:test:hub.id';
/**
* The HTTPS endpoint of the Hub.
*/
hubEndpoint: string = 'https://beta.hub.microsoft.com';
}
/**
* Represents a communication session with a particular Hub instance.
*/
export default class HubSession {
private client?: Identifier;
private hubId: string;
private hubEndpoint: string;
private hubOwner?: Identifier;
private signingKeyReference: string | undefined;
private encryptionKeyReference: string | undefined;
constructor(options: HubSessionOptions) {
this.client = options.client;
this.hubId = options.hubId;
this.hubEndpoint = options.hubEndpoint;
this.hubOwner = options.hubOwner;
this.signingKeyReference = options.signingKeyReference;
this.encryptionKeyReference = options.encryptionKeyReference;
}
/**
* Sends the given request to the Hub instance, and returns the associated response.
*
* @param request An instance or subclass of HubRequest to be sent.
*/
send(request: HubCommitWriteRequest): Promise<HubWriteResponse>;
send(request: HubObjectQueryRequest): Promise<HubObjectQueryResponse>;
send(request: HubCommitQueryRequest): Promise<HubCommitQueryResponse>;
async send(request: HubRequest): Promise<any> {
const rawRequestJson = await request.getRequestJson();
if (!this.client || !this.hubOwner) {
throw new HubError({
error_code: HubErrorCode.NotFound,
developer_message: `Identifiers not Specified`,
});
}
rawRequestJson.iss = this.client.id;
rawRequestJson.aud = this.hubId;
rawRequestJson.sub = this.hubOwner.id;
//rawRequestJson.commit = rawRequestJson.commit.serialize();
const rawRequestString = JSON.stringify(rawRequestJson);
const response = await this.makeRequest(rawRequestString);
let responseObject: IHubResponse<string>;
try {
responseObject = response;
} catch (e) {
throw new HubError({
error_code: HubErrorCode.ServerError,
developer_message: `Unexpected error decoding JSON response: ${e.message}`,
inner_error: e,
});
}
return HubSession.mapResponseToObject(responseObject);
}
/**
* Sends a raw (string) request body to the Hub and receives a response.
*
* @param message The raw request body to send.
* @param accessToken The access token to include in the request, if any.
*/
private async makeRequest(message: string): Promise<any> {
if (!this.client || !this.hubOwner) {
throw new HubError({
error_code: HubErrorCode.AuthenticationFailed,
developer_message: `Client Identifier not specified in options`,
});
}
let resolver: IResolver;
if (this.client.options && this.client.options.resolver) {
resolver = this.client.options.resolver;
} else {
resolver = new HttpResolver('https://beta.discover.did.microsoft.com/');
}
// await this.authentication.getAuthenticatedRequest(message, this.hubDid, accessToken);
const didProtocolOptions: DidProtocolOptions = {
sender: this.client,
resolver
};
const didProtocol = new DidProtocol(didProtocolOptions);
if (!this.signingKeyReference) {
throw new UserAgentError('Signing key reference is not defined');
}
const request = await didProtocol.signAndEncrypt(message, this.hubId);
const res = await this.callFetch(this.hubEndpoint, {
method: 'POST',
body: request,
headers: {
'Content-Type': 'application/jwt',
'Content-Length': request.length.toString()
},
});
if (res.status !== 200) {
const errorResponse = await res.json();
throw new HubError(errorResponse);
}
const response = await res.buffer();
return didProtocol.decryptAndVerify(<string>this.encryptionKeyReference, response);
}
/**
* Fetch API wrapper, to allow unit testing.
*
* @param url The URL to make a request to.
* @param init Request initialization details.
*/
private async callFetch(url: string | Request, init?: RequestInit) {
return nodeFetch(url, init);
}
/**
* Mapping of known response types.
*/
private static responseTypes = {
WriteResponse: HubWriteResponse,
ObjectQueryResponse: HubObjectQueryResponse,
CommitQueryResponse: HubCommitQueryResponse,
};
/**
* Transforms a JSON blob returned by the Hub into a subclass of HubResponse, based on the `@type`
* field of the response.
*
* @param response The Hub response to be transformed.
*/
private static mapResponseToObject(response: IHubResponse<string>) {
const responseTypeString = response['@type'];
const responseType = (<any> HubSession.responseTypes)[responseTypeString];
if (responseType) {
return new responseType(response);
}
if (response['@type'] === 'ErrorResponse') {
throw new HubError(<IHubError> <any> response);
}
throw new HubError({
error_code: HubErrorCode.NotImplemented,
developer_message: `Unexpected response type ${responseTypeString}`,
});
}
}