Skip to content
Merged
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
4 changes: 2 additions & 2 deletions dist/api/exchange-token.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { StoredOptions, ExchangeTokenOptions } from '../typings';
export default function exchangeToken(storedOptions: StoredOptions, options?: ExchangeTokenOptions): Promise<string>;
import { StoredOptions, ExchangeTokenOptions, Token } from '../typings';
export default function exchangeToken(storedOptions: StoredOptions, options?: ExchangeTokenOptions): Promise<Token>;
4 changes: 2 additions & 2 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StoredOptions, LogoutOptions, InitOptions, AuthorizeOptions, OnboardOptions, ExchangeTokenOptions, RequestLoginLinkOptions, TokenInfo, AuthorizeUrlOptions, LogoutUrlOptions, OnboardUrlOptions, LinkKitOpenServiceUrlOptions, MyAccountOpenServiceUrlOptions, LinkKitOpenServiceOptions, MyAccountOpenServiceOptions, ConfigsOptions, ConfigsOptionsWithoutIsNewTab, VaultOpenServiceUrlViewServiceList, VaultOpenServiceUrlViewServiceConnection, VaultOpenServiceUrlViewConnectionSetting, VaultOpenServiceUrlViewCustomerSupport, VaultOpenServiceViewServiceList, VaultOpenServiceViewServiceConnection, VaultOpenServiceViewConnectionSetting, VaultOpenServiceViewCustomerSupport } from './typings';
import { Token, StoredOptions, LogoutOptions, InitOptions, AuthorizeOptions, OnboardOptions, ExchangeTokenOptions, RequestLoginLinkOptions, TokenInfo, AuthorizeUrlOptions, LogoutUrlOptions, OnboardUrlOptions, LinkKitOpenServiceUrlOptions, MyAccountOpenServiceUrlOptions, LinkKitOpenServiceOptions, MyAccountOpenServiceOptions, ConfigsOptions, ConfigsOptionsWithoutIsNewTab, VaultOpenServiceUrlViewServiceList, VaultOpenServiceUrlViewServiceConnection, VaultOpenServiceUrlViewConnectionSetting, VaultOpenServiceUrlViewCustomerSupport, VaultOpenServiceViewServiceList, VaultOpenServiceViewServiceConnection, VaultOpenServiceViewConnectionSetting, VaultOpenServiceViewCustomerSupport } from './typings';
export * from './typings';
export declare class MtLinkSdk {
storedOptions: StoredOptions;
Expand All @@ -25,7 +25,7 @@ export declare class MtLinkSdk {
openServiceUrl(serviceId: 'vault', options?: VaultOpenServiceUrlViewConnectionSetting): string;
openServiceUrl(serviceId: 'vault', options?: VaultOpenServiceUrlViewCustomerSupport): string;
requestLoginLink(options?: RequestLoginLinkOptions): Promise<void>;
exchangeToken(options?: ExchangeTokenOptions): Promise<string>;
exchangeToken(options?: ExchangeTokenOptions): Promise<Token>;
tokenInfo(token: string): Promise<TokenInfo>;
}
declare const mtLinkSdk: MtLinkSdk;
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions dist/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,13 @@ export interface TokenInfo {
lang: string;
};
}
export interface Token {
access_token: string;
refresh_token: string;
token_type: string;
created_at: number;
expires_in: number;
scope: string;
resource_server: string;
}
export {};
16 changes: 11 additions & 5 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,20 +158,26 @@ This API has exactly the same parameters as `onboard`, the only difference being

### exchangeToken

Since we are using PKCE/Code grant, we will have to exchange the `code` for a token. You can optionally pass `code` via options parameter or it will fallback to automatically extract it from the browser URL.
Use this function to exchange an authorization `code` for a token. You can optionally pass `code` via options parameter, otherwise it will automatically extract the `code` URL parameter of the current URL.

`code` will be invalidated (can be used only once) after exchanged for a token, it is your responsibility to store the token yourself as the SDK does not store it internally.
The `code` can be used only once. The SDK does not store it internally, you have to store it in your application.

Refer [here](https://www.oauth.com/oauth2-servers/pkce/authorization-code-exchange/) for more details.

<h6>Usage:</h6>

One way to use this API is by calling it in the script on your redirection page. For example, if `authorize` redirects to `https://yourapp.com/callback?code=somecode`, you can call this function in the script loaded on that redirection page and the client library will automatically extract the code to exchange for a token.
One way to use this API is by calling it on your redirection page. For example, if `authorize` redirects to `https://yourapp.com/callback?code=somecode`, you can call this function in the script loaded on that redirection page and the client library will automatically extract the code to exchange for a token.

Alternatively, you can extract the `code` manually from the redirect URL and pass it to this function via the options object yourself.

```javascript
const token = await mtLinkSdk.exchangeToken(options);
token.access_token; // access token
token.refresh_token; // refresh token
token.token_type; // token type
token.created_at: // created at in seconds
token.expires_in; // expiry in seconds
token.scope; // scope of the token
```

| Parameter | Type | Required | Default Value | Description |
Expand Down Expand Up @@ -253,7 +259,7 @@ This method generates a URL to log out the guest. See the `logout` API for detai
mtLinkSdk.logoutUrl(options);
```

This API has exactly the same parameters as `logout`, the only difference being that it returns an URL instead of opening immediately with `window.open`.
This API has exactly the same parameters as `logout`, the only difference being that it returns an URL instead of opening immediately with `window.open`.

#### Open Vault Services Page

Expand Down Expand Up @@ -355,7 +361,7 @@ This method can generate URLs for various services provided by Moneytree, such a
mtLinkSdk.openServiceUrl(serviceId, options);
```

This API has exactly the same parameters as `openService`, the only difference being that it returns an URL instead of opening immediately with `window.open`.
This API has exactly the same parameters as `openService`, the only difference being that it returns an URL instead of opening immediately with `window.open`.

### requestLoginLink

Expand Down
8 changes: 4 additions & 4 deletions sample/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,17 +263,17 @@ const disabledFunctions = () => {

const validateToken = async () => {
try {
const accessToken = await mtLinkSdk.exchangeToken();
elements.accessTokenLabel.innerText = `Your access token is ${accessToken}.`;
const token = await mtLinkSdk.exchangeToken();
elements.accessTokenLabel.innerText = `Your access token is ${token.access_token}.`;
const authHeaders = new Headers({
method: 'GET',
Authorization: `Bearer ${accessToken}`
Authorization: `Bearer ${token.access_token}`
});
const response = await fetch('https://myaccount-staging.getmoneytree.com/oauth/token/info.json', {
headers: authHeaders
});
const data: ITokenInfo = await response.json();
elements.accessTokenLabel.innerText = `Your access token is ${accessToken}
elements.accessTokenLabel.innerText = `Your access token is ${token.access_token}
It was generated for the app: ${data.aud.name}.
It will expire on ${new Date(data.exp * 1000)}.
It allows you to: ${data.scopes.join(', ')}
Expand Down
13 changes: 11 additions & 2 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,18 @@ describe('index', () => {
expect(result5).toBeUndefined();
expect(requestLoginLink).toBeCalledWith(storedOptions, { loginLinkTo: 'settings' });

mocked(exchangeToken).mockResolvedValueOnce('test');
const token = {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600,
token_type: 'bearer',
scope: 'guest_read',
created_at: Date.now(),
resource_server: 'jp-api'
};
mocked(exchangeToken).mockResolvedValueOnce(token);
const result6 = await instance.exchangeToken({ code: 'code' });
expect(result6).toBe('test');
expect(result6).toEqual(token);
expect(exchangeToken).toBeCalledWith(storedOptions, { code: 'code' });

// @ts-ignore: set tokenInfo with invalid type value
Expand Down
34 changes: 19 additions & 15 deletions src/api/__tests__/exchange-token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ describe('api', () => {
const clientId = 'clientId';
const code = 'code';
const redirectUri = 'redirectUri';
const token = 'token';
const token = {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600,
token_type: 'bearer',
scope: 'guest_read',
created_at: Date.now(),
resource_server: 'jp-api'
};
const state = 'state';

const mtLinkSdk = new MtLinkSdk();
Expand Down Expand Up @@ -48,8 +56,7 @@ describe('api', () => {

test('make request', async () => {
fetch.mockClear();

fetch.mockResponseOnce(JSON.stringify({ access_token: token }));
fetch.mockResponseOnce(JSON.stringify(token));

await exchangeToken(mtLinkSdk.storedOptions, { code, codeVerifier: '' });

Expand Down Expand Up @@ -94,36 +101,33 @@ describe('api', () => {

test('auto extract code from url query if no code was passed', async () => {
fetch.mockClear();
fetch.mockResponseOnce(JSON.stringify(token));

const code1 = 'code1';
const code2 = 'code2';

fetch.mockResponseOnce(JSON.stringify({ access_token: token }));
const code = 'realCode';

jest.spyOn(window, 'location', 'get').mockReturnValueOnce({
search: `?code=${code1}&code=${code2}`
search: `?code=otherCode&code=${code}`
} as typeof window.location);

await exchangeToken(mtLinkSdk.storedOptions, { state });

const result = fetch.mock.calls[0][1] || {};
const data = JSON.parse(result.body as string);

expect(data.code).toBe(code2);
expect(data.code).toBe(code);
});

test('auto extract state from url query if no state was passed or set during init', async () => {
fetch.mockClear();

const state1 = 'state1';

fetch.mockResponseOnce(JSON.stringify({ access_token: token }));
fetch.mockResponseOnce(JSON.stringify(token));

jest.spyOn(window, 'location', 'get').mockReturnValueOnce({
search: `?state=${state1}&state=${state}`
search: `?state=otherState&state=${state}`
} as typeof window.location);

await expect(exchangeToken(mtLinkSdk.storedOptions, { code, redirectUri })).resolves.toBe(token);
const actual = await exchangeToken(mtLinkSdk.storedOptions, { code, redirectUri });

expect(actual).toEqual(token);
});

test('non browser environment will not auto extract code from url', async () => {
Expand Down
6 changes: 3 additions & 3 deletions src/api/exchange-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import qs from 'qs';

import { generateSdkHeaderInfo } from '../helper';
import { MY_ACCOUNT_DOMAINS } from '../server-paths';
import { StoredOptions, ExchangeTokenOptions } from '../typings';
import { StoredOptions, ExchangeTokenOptions, Token } from '../typings';
import storage from '../storage';

function getCode(): string | undefined {
Expand All @@ -21,7 +21,7 @@ function getCode(): string | undefined {
export default async function exchangeToken(
storedOptions: StoredOptions,
options: ExchangeTokenOptions = {}
): Promise<string> {
): Promise<Token> {
const { clientId, redirectUri: defaultRedirectUri, mode } = storedOptions;

if (!clientId) {
Expand Down Expand Up @@ -64,7 +64,7 @@ export default async function exchangeToken(
throw new Error(result.error_description);
}

return result.access_token;
return result as Token;
} catch (error) {
throw new Error(`[mt-link-sdk] \`exchangeToken\` execution failed. ${error}`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import requestLoginLink from './api/request-login-link';
import exchangeToken from './api/exchange-token';
import tokenInfo from './api/token-info';
import {
Token,
StoredOptions,
ServiceId,
LogoutOptions,
Expand Down Expand Up @@ -141,7 +142,7 @@ export class MtLinkSdk {
return requestLoginLink(this.storedOptions, options);
}

public exchangeToken(options?: ExchangeTokenOptions): Promise<string> {
public exchangeToken(options?: ExchangeTokenOptions): Promise<Token> {
return exchangeToken(this.storedOptions, options);
}

Expand Down
10 changes: 10 additions & 0 deletions src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,13 @@ export interface TokenInfo {
lang: string;
};
}

export interface Token {
access_token: string;
refresh_token: string;
token_type: string;
created_at: number;
expires_in: number;
scope: string;
resource_server: string;
}