Skip to content

Commit

Permalink
Allow for headers or token to be passed into methods
Browse files Browse the repository at this point in the history
  • Loading branch information
gilberthl-mh authored and gillyhl committed Jun 12, 2023
1 parent ace4874 commit 7e9e9f3
Show file tree
Hide file tree
Showing 44 changed files with 401 additions and 220 deletions.
11 changes: 11 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ const moneyhub = await Moneyhub({
})
```

When making calls to our methods that require authentication, you can provide an extra argument at the end of these methods. This argument must be an object with these optional keys:

```js
{
token: "full.access.token" // if specified will be added to authorisation header of request
headers: {
Authorization: "Bearer full.access.token" // can be used to specify authorisation header or additional headers
}
}
```

Once the api client has been initialised it provides a simple promise based interface with the following methods:

### Auth API
Expand Down
64 changes: 64 additions & 0 deletions src/__tests__/accounts-with-extra-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint-disable max-nested-callbacks */
import {expect} from "chai"

import {Accounts, Moneyhub, MoneyhubInstance} from ".."
import {expectTypeOf} from "expect-type"

describe("Accounts with extra options", function() {
let moneyhub: MoneyhubInstance,
userId: string

before(async function() {
const {
testUserId,
} = this.config
userId = testUserId
moneyhub = await Moneyhub(this.config)
})

it("errors with malformed token", async function() {
let error: any
try {
await moneyhub.getAccounts({userId}, {
token: "malformed.token",
})
} catch (err) {
error = err
}
expect(error.response.statusCode).to.equal(401)
})

it("errors with malformed headers", async function() {
let error: any
try {
await moneyhub.getAccounts({userId}, {
headers: {
Authorization: "malformed.header",
},
})
} catch (err) {
error = err
}
expect(error.response.statusCode).to.equal(401)
})

it("succeeds with given token", async function() {
const {access_token: token} = await moneyhub.getClientCredentialTokens({scope: "accounts:read", sub: userId})

const accounts = await moneyhub.getAccounts({userId}, {
token,
})
expectTypeOf<Accounts.Account[]>(accounts.data)
})

it("succeeds with given headers", async function() {
const {access_token: token} = await moneyhub.getClientCredentialTokens({scope: "accounts:read", sub: userId})

const accounts = await moneyhub.getAccounts({userId}, {
headers: {
Authorization: `Bearer ${token}`,
},
})
expectTypeOf<Accounts.Account[]>(accounts.data)
})
})
23 changes: 19 additions & 4 deletions src/request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import got, {Options} from "got"
import got, {Options, Headers, OptionsOfJSONResponseBody} from "got"
import {Client} from "openid-client"
import qs from "query-string"
import * as R from "ramda"
Expand All @@ -14,6 +14,7 @@ interface RequestOptions extends Pick<Options, "method" | "headers" | "searchPar
scope: string
sub?: string
}
options?: ExtraOptions
}

interface Links {
Expand Down Expand Up @@ -43,6 +44,11 @@ export interface ApiResponse<T> {
meta?: object
}

export interface ExtraOptions {
token?: string
headers?: Headers
}

const getResponseBody = (err: unknown) => {
let body: {
code?: string
Expand Down Expand Up @@ -75,24 +81,33 @@ export default ({
options: {
timeout?: number
}
// eslint-disable-next-line max-statements, complexity
}) => async <T>(
url: string,
opts: RequestOptions = {},
): Promise<T> => {
const gotOpts = {
const gotOpts: OptionsOfJSONResponseBody = {
method: opts.method || "GET",
headers: opts.headers || {},
searchParams: qs.stringify(opts.searchParams),
timeout,
}

if (opts.cc) {
if (opts.options?.token) {
gotOpts.headers = R.assoc("Authorization", `Bearer ${opts.options.token}`, gotOpts.headers)
}

if (opts.options?.headers) {
gotOpts.headers = R.mergeDeepRight(gotOpts.headers || {}, opts.options.headers) as Headers
}

if (!gotOpts.headers?.Authorization && opts.cc) {
const {access_token} = await client.grant({
grant_type: "client_credentials",
scope: opts.cc.scope,
sub: opts.cc.sub,
})
gotOpts.headers.Authorization = `Bearer ${access_token}`
gotOpts.headers = R.assoc("Authorization", `Bearer ${access_token}`, gotOpts.headers)
}

if (opts.body) {
Expand Down
54 changes: 36 additions & 18 deletions src/requests/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,106 +8,117 @@ export default ({
const {resourceServerUrl} = config

return {
getAccounts: async ({userId, params = {}}) =>
getAccounts: async ({userId, params = {}}, options) =>
request(`${resourceServerUrl}/accounts`, {
searchParams: params,
cc: {
scope: "accounts:read",
sub: userId,
},
options,
}),

getAccountsWithDetails: async ({userId, params = {}}) =>
getAccountsWithDetails: async ({userId, params = {}}, options) =>
request(`${resourceServerUrl}/accounts`, {
searchParams: params,
cc: {
scope: "accounts:read accounts_details:read",
sub: userId,
},
options,
}),

getAccountsList: async ({userId, params = {}}) =>
getAccountsList: async ({userId, params = {}}, options) =>
request(`${resourceServerUrl}/accounts-list`, {
searchParams: params,
cc: {
scope: "accounts:read",
sub: userId,
},
options,
}),

getAccountsListWithDetails: async ({userId, params = {}}) =>
getAccountsListWithDetails: async ({userId, params = {}}, options) =>
request(`${resourceServerUrl}/accounts-list`, {
searchParams: params,
cc: {
scope: "accounts:read accounts_details:read",
sub: userId,
},
options,
}),

getAccount: async ({userId, accountId}) =>
getAccount: async ({userId, accountId}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}`, {
cc: {
scope: "accounts:read",
sub: userId,
},
options,
}),

getAccountBalances: async ({userId, accountId}) =>
getAccountBalances: async ({userId, accountId}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}/balances`, {
cc: {
scope: "accounts:read",
sub: userId,
},
options,
}),

getAccountWithDetails: async ({userId, accountId}) =>
getAccountWithDetails: async ({userId, accountId}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}`, {
cc: {
scope: "accounts:read accounts_details:read",
sub: userId,
},
options,
}),

getAccountHoldings: async ({userId, accountId}) =>
getAccountHoldings: async ({userId, accountId}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}/holdings`, {
cc: {
scope: "accounts:read",
sub: userId,
},
options,
}),

getAccountHoldingsWithMatches: async ({userId, accountId}) =>
getAccountHoldingsWithMatches: async ({userId, accountId}, options) =>
request(
`${resourceServerUrl}/accounts/${accountId}/holdings-with-matches`,
{
cc: {
scope: "accounts:read",
sub: userId,
},
options,
},
),

getAccountHolding: async ({userId, accountId, holdingId}) =>
getAccountHolding: async ({userId, accountId, holdingId}, options) =>
request(
`${resourceServerUrl}/accounts/${accountId}/holdings/${holdingId}`,
{
cc: {
scope: "accounts:read",
sub: userId,
},
options,
},
),

getAccountCounterparties: async ({userId, accountId, params = {}}) =>
getAccountCounterparties: async ({userId, accountId, params = {}}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}/counterparties`, {
searchParams: params,
cc: {
scope: "accounts:read transactions:read:all",
sub: userId,
},
options,
}),

getAccountRecurringTransactions: async ({userId, accountId}) =>
getAccountRecurringTransactions: async ({userId, accountId}, options) =>
request(
`${resourceServerUrl}/accounts/${accountId}/recurring-transactions`,
{
Expand All @@ -116,63 +127,70 @@ export default ({
scope: "accounts:read transactions:read:all",
sub: userId,
},
options,
},
),

getAccountStandingOrders: async ({userId, accountId}) =>
getAccountStandingOrders: async ({userId, accountId}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}/standing-orders`, {
cc: {
scope: "accounts:read standing_orders:read",
sub: userId,
},
options,
}),

getAccountStandingOrdersWithDetail: async ({userId, accountId}) =>
getAccountStandingOrdersWithDetail: async ({userId, accountId}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}/standing-orders`, {
cc: {
scope: "accounts:read standing_orders_detail:read",
sub: userId,
},
options,
}),

createAccount: async ({userId, account}) =>
createAccount: async ({userId, account}, options) =>
request(`${resourceServerUrl}/accounts`, {
method: "POST",
cc: {
scope: "accounts:read accounts:write:all",
sub: userId,
},
body: account,
options,
}),

deleteAccount: async ({userId, accountId}) =>
deleteAccount: async ({userId, accountId}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}`, {
method: "DELETE",
cc: {
scope: "accounts:write:all",
sub: userId,
},
returnStatus: true,
options,
}),

addAccountBalance: async ({userId, accountId, balance}) =>
addAccountBalance: async ({userId, accountId, balance}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}/balances`, {
method: "POST",
cc: {
scope: "accounts:read accounts:write:all",
sub: userId,
},
body: balance,
options,
}),

updateAccount: async ({userId, accountId, account}) =>
updateAccount: async ({userId, accountId, account}, options) =>
request(`${resourceServerUrl}/accounts/${accountId}`, {
method: "PATCH",
cc: {
scope: "accounts:read accounts:write:all",
sub: userId,
},
body: account,
options,
}),
}
}
Loading

0 comments on commit 7e9e9f3

Please sign in to comment.