Skip to content

Commit

Permalink
feat: add Microsoft Request Service API support
Browse files Browse the repository at this point in the history
  • Loading branch information
nklomp committed Jul 12, 2022
1 parent b1f13bd commit 251ed60
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 102 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ test/*.js
/packages/vc-api-verifier/plugin.schema.json
/packages/connection-manager/plugin.schema.json
/packages/bls-key-manager/plugin.schema.json
/packages/ms-request-api/plugin.schema.json
3 changes: 1 addition & 2 deletions packages/connection-manager/__tests__/localAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,4 @@ const testContext = {

describe('Local integration tests', () => {
connectionManagerAgentLogic(testContext)
}
)
})
2 changes: 1 addition & 1 deletion packages/ms-request-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

# ms-request-api

Azure Active Directory (Azure AD) Verifiable Credentials allows you to issue a credential. This is a Veramo plugin that handles issuing a verifiable credential using issuer authentication info and issuer configuration.
Azure Active Directory (Azure AD) Verifiable Credentials allows you to issue a credential. This is a Veramo plugin that handles issuing a verifiable credential using issuer authentication info and issuer configuration.

### Installation

Expand Down
2 changes: 1 addition & 1 deletion packages/ms-request-api/__tests__/restAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const getAgent = (options?: IAgentOptions) =>

const setup = async (): Promise<boolean> => {
const config = getConfig('packages/ms-request-api/agent.yml')
const { agent } = createObjects(config, { agent: '/agent'})
const { agent } = createObjects(config, { agent: '/agent' })
serverAgent = agent

const agentRouter = AgentRouter({
Expand Down
38 changes: 15 additions & 23 deletions packages/ms-request-api/__tests__/shared/msRequestApiAgentLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,29 @@ var requestIssuanceResponse: IIssueRequestResponse = {
url: 'www.google.com',
expiry: new Date(1655935606),
id: 'fbef933e-f786-4b85-b1c8-6679346dc55d',
pin: '3683'

pin: '3683',
}

export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Promise<boolean>; tearDown: () => Promise<boolean> }) => {
describe('@sphereon/ms-request-api', () => {
let agent: ConfiguredAgent

beforeAll(async () => {

jest.mock('../../src/IssuerUtil', () => {
return {
fetchIssuanceRequestMs: jest.fn().mockResolvedValue(requestIssuanceResponse),
generatePin: jest.fn().mockResolvedValue(6363)
generatePin: jest.fn().mockResolvedValue(6363),
}
})

jest.mock('@sphereon/ms-authenticator', () => {
return {
ClientCredentialAuthenticator: jest.fn().mockResolvedValue('ey...'),
checkMsIdentityHostname: jest.fn().mockResolvedValue(MsAuthenticator.MS_IDENTITY_HOST_NAME_EU)
checkMsIdentityHostname: jest.fn().mockResolvedValue(MsAuthenticator.MS_IDENTITY_HOST_NAME_EU),
}
})
await testContext.setup()
agent = testContext.getAgent()

})

afterAll(async () => {
Expand All @@ -44,43 +41,38 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro
})

it('should request issuance from Issuer', async () => {
var requestConfigFile = '../../config/issuance_request_config.json';
var issuanceConfig: IClientIssuanceConfig = require(requestConfigFile);
var requestConfigFile = '../../config/issuance_request_config.json'
var issuanceConfig: IClientIssuanceConfig = require(requestConfigFile)
var clientIssueRequest: IClientIssueRequest = {
authenticationInfo: {
azClientId: 'AzClientID',
azClientSecret: 'AzClientSecret',
azTenantId: 'AzTenantId',
credentialManifestUrl: 'CredentialManifestUrl'
credentialManifestUrl: 'CredentialManifestUrl',
},
clientIssuanceConfig: issuanceConfig,
claims: {
"given_name": "FIRSTNAME",
"family_name": "LASTNAME"
}
given_name: 'FIRSTNAME',
family_name: 'LASTNAME',
},
}

// modify the callback method to make it easier to debug
// with tools like ngrok since the URI changes all the time
// this way you don't need to modify the callback URL in the payload every time
// ngrok changes the URI
clientIssueRequest.clientIssuanceConfig.callback.url = `https://6270-2a02-a458-e71a-1-68b4-31d2-b44f-12b.eu.ngrok.io/api/issuer/issuance-request-callback`;
clientIssueRequest.clientIssuanceConfig.callback.url = `https://6270-2a02-a458-e71a-1-68b4-31d2-b44f-12b.eu.ngrok.io/api/issuer/issuance-request-callback`

clientIssueRequest.clientIssuanceConfig.registration.clientName = "Sphereon Node.js SDK API Issuer";
clientIssueRequest.clientIssuanceConfig.registration.clientName = 'Sphereon Node.js SDK API Issuer'

// modify payload with new state, the state is used to be able to update the UI when callbacks are received from the VC Service
var id = uuidv4();
clientIssueRequest.clientIssuanceConfig.callback.state = id;
var id = uuidv4()
clientIssueRequest.clientIssuanceConfig.callback.state = id

const fetchIssuanceRequestMsMock = jest.fn().mockResolvedValue(requestIssuanceResponse);
const fetchIssuanceRequestMsMock = jest.fn().mockResolvedValue(requestIssuanceResponse)
fetchIssuanceRequestMs.prototype = fetchIssuanceRequestMsMock

return await expect(
agent.issuanceRequestMsVc(clientIssueRequest)
).resolves.not.toBeNull
return await expect(agent.issuanceRequestMsVc(clientIssueRequest)).resolves.not.toBeNull
})

})
}


8 changes: 4 additions & 4 deletions packages/ms-request-api/agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ agent:
plugins:
- $require: ./packages/ms-request-api/dist#MsRequestApi
$args:
- azClientId: {process.env.AZ_CLIENT_ID}
azClientSecret: {process.env.AZ_CLIENT_SECRET}
azTenantId: {process.env.AZ_TENANT_ID}
credentialManifestUrl: {process.env.CREDENTIAL_MANIFEST_URL}
- azClientId: { process.env.AZ_CLIENT_ID }
azClientSecret: { process.env.AZ_CLIENT_SECRET }
azTenantId: { process.env.AZ_TENANT_ID }
credentialManifestUrl: { process.env.CREDENTIAL_MANIFEST_URL }
6 changes: 3 additions & 3 deletions packages/ms-request-api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sphereon/ms-request-api",
"version": "0.5.2",
"version": "0.6.0",
"source": "src/index.ts",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand All @@ -17,10 +17,10 @@
"cross-fetch": "^3.1.5",
"global-fetch": "^0.2.2",
"express": "^4.17.1",
"express-session": "^1.17.2"
"express-session": "^1.17.2",
"@sphereon/ms-authenticator": "^0.6.0"
},
"devDependencies": {
"@sphereon/ms-authenticator": "^0.5.1",
"@types/express-session": "^1.17.4",
"@types/jest": "^27.0.2",
"@veramo/cli": "3.1.2-next.84",
Expand Down
49 changes: 25 additions & 24 deletions packages/ms-request-api/src/IssuerUtil.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
import { IIssueRequest, IIssueRequestResponse } from "./types/IMsRequestApi";
import { IIssueRequest, IIssueRequestResponse } from './types/IMsRequestApi'

export async function fetchIssuanceRequestMs(issuanceInfo: IIssueRequest, accessToken: string, msIdentityHostName: string): Promise<IIssueRequestResponse> {
var client_api_request_endpoint = `${msIdentityHostName}${issuanceInfo.authenticationInfo.azTenantId}/verifiablecredentials/request`;
export async function fetchIssuanceRequestMs(
issuanceInfo: IIssueRequest,
accessToken: string,
msIdentityHostName: string
): Promise<IIssueRequestResponse> {
var client_api_request_endpoint = `${msIdentityHostName}${issuanceInfo.authenticationInfo.azTenantId}/verifiablecredentials/request`

var payload = JSON.stringify(issuanceInfo.issuanceConfig);
const fetchOptions = {
method: 'POST',
body: payload,
headers: {
'Content-Type': 'application/json',
'Content-Length': payload.length.toString(),
'Authorization': `Bearer ${accessToken}`
}
};
const response = await fetch(client_api_request_endpoint, fetchOptions);
return await response.json();
var payload = JSON.stringify(issuanceInfo.issuanceConfig)
const fetchOptions = {
method: 'POST',
body: payload,
headers: {
'Content-Type': 'application/json',
'Content-Length': payload.length.toString(),
Authorization: `Bearer ${accessToken}`,
},
}
const response = await fetch(client_api_request_endpoint, fetchOptions)
return await response.json()
}

export function generatePin(digits: number) {
var add = 1, max = 12 - add;
max = Math.pow(10, digits + add);
var min = max / 10; // Math.pow(10, n) basically
var number = Math.floor(Math.random() * (max - min + 1)) + min;
return ("" + number).substring(add);
var add = 1,
max = 12 - add
max = Math.pow(10, digits + add)
var min = max / 10 // Math.pow(10, n) basically
var number = Math.floor(Math.random() * (max - min + 1)) + min
return ('' + number).substring(add)
}




34 changes: 20 additions & 14 deletions packages/ms-request-api/src/agent/MsRequestApi.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { IAgentPlugin } from '@veramo/core'
import { IClientIssueRequest, IIssueRequest, IIssueRequestResponse, IMsRequestApi, IRequiredContext, Issuance, IssuanceConfig } from '../types/IMsRequestApi'
import {
IClientIssueRequest,
IIssueRequest,
IIssueRequestResponse,
IMsRequestApi,
IRequiredContext,
Issuance,
IssuanceConfig,
} from '../types/IMsRequestApi'
import { ClientCredentialAuthenticator, checkMsIdentityHostname } from '@sphereon/ms-authenticator'
import { generatePin, fetchIssuanceRequestMs } from '../IssuerUtil';
import { generatePin, fetchIssuanceRequestMs } from '../IssuerUtil'
/**
* {@inheritDoc IMsRequestApi}
*/
export class MsRequestApi implements IAgentPlugin {
readonly methods: IMsRequestApi = {
issuanceRequestMsVc: this.issuanceRequestMsVc.bind(this)
issuanceRequestMsVc: this.issuanceRequestMsVc.bind(this),
}


/** {@inheritDoc IMsRequestApi.issuanceRequestMsVc} */
private async issuanceRequestMsVc(clientIssueRequest: IClientIssueRequest, context: IRequiredContext): Promise<IIssueRequestResponse> {
var accessToken = await ClientCredentialAuthenticator(clientIssueRequest.authenticationInfo);
var accessToken = await ClientCredentialAuthenticator(clientIssueRequest.authenticationInfo)

var msIdentityHostName = await checkMsIdentityHostname(clientIssueRequest.authenticationInfo);
var msIdentityHostName = await checkMsIdentityHostname(clientIssueRequest.authenticationInfo)

// Config Request and App Config File should be a parameter to this function
if (!clientIssueRequest.authenticationInfo.azTenantId) {
Expand All @@ -25,38 +32,37 @@ export class MsRequestApi implements IAgentPlugin {
// check if pin is required, if found make sure we set a new random pin
// pincode is only used when the payload contains claim value pairs which results in an IDTokenhint
if (clientIssueRequest.clientIssuanceConfig.issuance.pin) {
clientIssueRequest.clientIssuanceConfig.issuance.pin.value = generatePin(clientIssueRequest.clientIssuanceConfig.issuance.pin.length);
clientIssueRequest.clientIssuanceConfig.issuance.pin.value = generatePin(clientIssueRequest.clientIssuanceConfig.issuance.pin.length)
}

var issuance: Issuance = {
type: clientIssueRequest.clientIssuanceConfig.issuance.type,
manifest: clientIssueRequest.clientIssuanceConfig.issuance.manifest,
pin: clientIssueRequest.clientIssuanceConfig.issuance.pin,
claims: clientIssueRequest.claims
claims: clientIssueRequest.claims,
}

var issuanceConfig: IssuanceConfig = {
authority: clientIssueRequest.clientIssuanceConfig.authority,
includeQRCode: clientIssueRequest.clientIssuanceConfig.includeQRCode,
registration: clientIssueRequest.clientIssuanceConfig.registration,
callback: clientIssueRequest.clientIssuanceConfig.callback,
issuance: issuance
issuance: issuance,
}
var issueRequest: IIssueRequest = {
authenticationInfo: clientIssueRequest.authenticationInfo,
issuanceConfig: issuanceConfig
};
issuanceConfig: issuanceConfig,
}

var resp = await fetchIssuanceRequestMs(issueRequest, accessToken, msIdentityHostName)

// the response from the VC Request API call is returned to the caller (the UI). It contains the URI to the request which Authenticator can download after
// it has scanned the QR code. If the payload requested the VC Request service to create the QR code that is returned as well
// the javascript in the UI will use that QR code to display it on the screen to the user.
resp.id = issueRequest.issuanceConfig.callback.state; // add session id so browser can pull status
resp.id = issueRequest.issuanceConfig.callback.state // add session id so browser can pull status
if (issueRequest.issuanceConfig.issuance.pin) {
resp.pin = issueRequest.issuanceConfig.issuance.pin.value; // add pin code so browser can display it
resp.pin = issueRequest.issuanceConfig.issuance.pin.value // add pin code so browser can display it
}
return resp
}

}
50 changes: 25 additions & 25 deletions packages/ms-request-api/src/types/IMsRequestApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IAgentContext, IPluginMethodMap } from '@veramo/core'
import { IMsAuthenticationClientCredentialArgs } from '@sphereon/ms-authenticator'

export interface IMsRequestApi extends IPluginMethodMap {
issuanceRequestMsVc(clientIssueRequest: IClientIssueRequest, context: IRequiredContext) : Promise<IIssueRequestResponse>
issuanceRequestMsVc(clientIssueRequest: IClientIssueRequest, context: IRequiredContext): Promise<IIssueRequestResponse>
}

export interface IClientIssueRequest {
Expand All @@ -12,17 +12,17 @@ export interface IClientIssueRequest {
}

export interface IClientIssuanceConfig {
authority: string;
includeQRCode: boolean;
registration: Registration;
callback: Callback;
issuance: IClientIssuance;
authority: string
includeQRCode: boolean
registration: Registration
callback: Callback
issuance: IClientIssuance
}

export interface IClientIssuance {
type: string;
manifest: string;
pin: Pin;
type: string
manifest: string
pin: Pin
}

export interface IIssueRequest {
Expand All @@ -39,41 +39,41 @@ export interface IIssueRequestResponse {
}

export interface Registration {
clientName: string;
clientName: string
}

export interface Headers {
apiKey: string;
apiKey: string
}

export interface Callback {
url: string;
state: string;
headers: Headers;
url: string
state: string
headers: Headers
}

export interface Pin {
value: string;
length: number;
value: string
length: number
}

export type CredentialSubject = {
[x: string]: any
}

export interface Issuance {
type: string;
manifest: string;
pin: Pin;
claims: CredentialSubject;
type: string
manifest: string
pin: Pin
claims: CredentialSubject
}

export interface IssuanceConfig {
authority: string;
includeQRCode: boolean;
registration: Registration;
callback: Callback;
issuance: Issuance;
authority: string
includeQRCode: boolean
registration: Registration
callback: Callback
issuance: Issuance
}

export type IRequiredContext = IAgentContext<Record<string, never>>
2 changes: 1 addition & 1 deletion packages/ms-request-api/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"outDir": "dist",
"declarationDir": "dist"
},
"references": [{ "path": "../ssi-sdk-core" }, { "path": "../ms-authenticator" }]
"references": [{ "path": "../ms-authenticator" }]
}
Loading

0 comments on commit 251ed60

Please sign in to comment.