diff --git a/guides/authentication.md b/guides/authentication.md index 896c9fe3945..4cd79349c9d 100644 --- a/guides/authentication.md +++ b/guides/authentication.md @@ -79,12 +79,12 @@ After signing in, the browser window will be redirected back to your application ```js import { CogniteClient, REDIRECT } from '@cognite/sdk'; const client = new CogniteClient({ ... }); -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', // default is redirect // but can be explicitly specified: onAuthenticate: REDIRECT, -}); +}}); // then use the SDK: const assets = await client.assets.retrieve({ id: 23232789217132 }); @@ -101,7 +101,7 @@ If you want a different redirect url back to your app after a successful / unsuc ```js import { CogniteClient, REDIRECT } from '@cognite/sdk'; const client = new CogniteClient({ ... }); -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', onAuthenticate: login => { login.redirect({ @@ -109,7 +109,7 @@ client.loginWithOAuth({ errorRedirectUrl: 'https://my-app.com/unsuccessful-login', // We encourage you to use this property as well. If not specified it will default to redirectUrl }); }, -}); +}}); ``` ### Authentication with pop-up @@ -130,10 +130,10 @@ if (isLoginPopupWindow()) { return; } const client = new CogniteClient({ ... }); -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', onAuthenticate: POPUP, -}); +}}); // then use the SDK: const assets = await client.assets.retrieve({ id: 23232789217132 }); @@ -154,7 +154,7 @@ If you want a different redirect url back to your application after a successful ```js import { CogniteClient, POPUP } from '@cognite/sdk'; const client = new CogniteClient({ ... }); -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', onAuthenticate: login => { login.popup({ @@ -162,7 +162,7 @@ client.loginWithOAuth({ errorRedirectUrl: 'https://my-app.com/unsuccessful-login', // We encourage you to use this property as well. If not specified it will default to redirectUrl }); }, -}); +}}); ``` This only affect the pop-up window. @@ -173,9 +173,9 @@ This only affect the pop-up window. To avoid waiting for the first `401`-response to occur you can trigger the authentication flow manually like this: ```js -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', -}); +}}); await client.authenticate(); // this will also return a boolean based on if the user successfully authenticated or not. ``` @@ -183,10 +183,10 @@ await client.authenticate(); // this will also return a boolean based on if the If you already have a access token you can use it to skip the authentication flow (see this [section](#tokens) on how to get hold of the token). If the token is invalid or timed out the SDK will trigger a standard auth-flow on the first 401-response from CDF. ```js -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', accessToken: 'ACCESS TOKEN FOR THE PROJECT HERE', -}); +}}); ``` > `client.authenticate()` will still override this and trigger a new authentication flow. @@ -194,12 +194,12 @@ client.loginWithOAuth({ It is possible to skip the authentication like this: ```js -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', onAuthenticate: login => { login.skip(); }, -}); +}}); ``` #### Combine different authentication methods @@ -208,7 +208,7 @@ If you want to use redirect method in the initialization of your app and use the you can implement something like this: ```js -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', onAuthenticate: login => { // some check: @@ -218,7 +218,7 @@ client.loginWithOAuth({ login.popup({ ... }); } }, -}); +}}); ``` #### Tokens @@ -226,12 +226,12 @@ client.loginWithOAuth({ If you need access to the tokens (access token, id token) from the login flow you can add a callback like this: ```js -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'CDF_OAUTH', options: { project: 'YOUR PROJECT NAME HERE', onTokens: ({accessToken, idToken}) => { // your logic here }, -}); +}}); ``` ### More @@ -269,7 +269,7 @@ After signing in, the browser window will be redirected back to your application > You might find useful example application using redirect Azure AD auth flow [here](../samples/react/authentication-aad/src/App.js). > Remember to provide the required environment variables in the `.env` file. - + #### Redirect sign in type example ```js @@ -278,11 +278,11 @@ const client = new CogniteClient({ ... }); // tenantId parameter can be skipped in order to use, // https://login.microsoftonline.com/common endpoint to authenticate user -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'AAD_OAUTH', options: { cluster: 'cdf-cluster-name', clientId: 'azure-application-client-id', tenantId: 'azure-tenant-id' -}); +}}); // authenticate to the provided cluster await client.authenticate(); @@ -297,7 +297,7 @@ const assets = await client.assets.retrieve({ id: 23232789217132 }); With the call `await client.authenticate()` you'll be redirected to the IdP to sign in. After you have signed in, you'll be redirected back and `await client.authenticate()` call will return you `true` as a result of the successful login. It is important -to set project for the `CogniteClient` instance via `client.setProject('project-name')` +to set project for the `CogniteClient` instance via `client.setProject('project-name')` ### Authentication via pop-up @@ -313,12 +313,12 @@ const client = new CogniteClient({ ... }); // tenantId parameter can be skipped in order to use, // https://login.microsoftonline.com/common endpoint to authenticate user -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'AAD_OAUTH', options: { cluster: 'cdf-cluster-name', clientId: 'azure-application-client-id', tenantId: 'azure-tenant-id', signInType: AZURE_AUTH_POPUP, -}); +}}); // authenticate to the provided cluster await client.authenticate(); @@ -343,11 +343,11 @@ This method works only for Azure AD authentication flow. You can also check whic import { CogniteClient, AZURE_AUTH_POPUP, AAD_OAUTH } from '@cognite/sdk'; const client = new CogniteClient({ ... }); -client.loginWithOAuth({ +client.loginWithOAuth({ type: 'AAD_OAUTH', options: { cluster: 'cdf-cluster-name', clientId: 'azure-application-client-id', signInType: AZURE_AUTH_POPUP, -}); +}}); // authenticate to the provided cluster await client.authenticate(); diff --git a/packages/core/package.json b/packages/core/package.json index 5ac2fc86736..18f53161ca5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -10,6 +10,7 @@ "scripts": { "clean": "rm -rf dist/ docs/", "test": "jest --config=../../jest.config.js --testPathPattern=/core/", + "test:debug": "node --inspect-brk ../../node_modules/.bin/jest --config=../../jest.config.js --testPathPattern=/core/ --runInBand" , "lint": "eslint 'src/**/*.{js,ts}'", "lint:fix": "yarn lint --fix", "prepublishOnly": "yarn build", diff --git a/packages/core/src/__tests__/cogniteClient.unit.spec.ts b/packages/core/src/__tests__/cogniteClient.unit.spec.ts index 5a7da114b74..f0e2cfc896f 100644 --- a/packages/core/src/__tests__/cogniteClient.unit.spec.ts +++ b/packages/core/src/__tests__/cogniteClient.unit.spec.ts @@ -1,7 +1,7 @@ // Copyright 2020 Cognite AS import nock from 'nock'; -import BaseCogniteClient, { AAD_OAUTH, CDF_OAUTH } from '../baseCogniteClient'; +import BaseCogniteClient from '../baseCogniteClient'; import { POPUP, REDIRECT } from '../authFlows/legacy'; import { API_KEY_HEADER, @@ -219,6 +219,12 @@ describe('CogniteClient', () => { await expect( // @ts-ignore async () => await client.loginWithOAuth() + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\`loginWithOAuth\` is missing parameter \`flow\`"` + ); + await expect( + // @ts-ignore + async () => await client.loginWithOAuth({ type: 'CDF_OAUTH' }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"\`loginWithOAuth\` is missing parameter \`options\`"` ); @@ -227,10 +233,11 @@ describe('CogniteClient', () => { test('missing project name', async () => { const client = setupClient(); await expect( - // @ts-ignore - async () => await client.loginWithOAuth({}) + async () => + // @ts-ignore + await client.loginWithOAuth({ type: 'INVALID_FLOW', options: {} }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"\`loginWithOAuth\` is missing correct \`options\` structure"` + '"`loginWithOAuth` was called with an unknown `flow`"' ); }); @@ -254,7 +261,10 @@ describe('CogniteClient', () => { test('default onAuthenticate function should be redirect', async done => { const client = setupClient(); - await client.loginWithOAuth({ project }); + await client.loginWithOAuth({ + type: 'CDF_OAUTH', + options: { project }, + }); mockRedirect.mockImplementationOnce(async () => { done(); }); @@ -264,22 +274,28 @@ describe('CogniteClient', () => { test('should return cdf oauth flow type in case cdf oauth usage', async () => { const client = setupClient(); await client.loginWithOAuth({ - project, - onAuthenticate: POPUP, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate: POPUP, + }, }); mockPopup.mockImplementationOnce(async () => { return {}; }); await client.authenticate(); - expect(client.getOAuthFlowType()).toEqual(CDF_OAUTH); + expect(client.getOAuthFlowType()).toEqual('CDF_OAUTH'); }); test('onAuthenticate: REDIRECT', async done => { const client = setupClient(); await client.loginWithOAuth({ - project, - onAuthenticate: REDIRECT, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate: REDIRECT, + }, }); mockRedirect.mockImplementationOnce(async () => { done(); @@ -290,8 +306,11 @@ describe('CogniteClient', () => { test('onAuthenticate: POPUP', async done => { const client = setupClient(); await client.loginWithOAuth({ - project, - onAuthenticate: POPUP, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate: POPUP, + }, }); mockPopup.mockImplementationOnce(async () => { done(); @@ -304,8 +323,11 @@ describe('CogniteClient', () => { const onAuthenticate = jest.fn(); const client = setupClient(mockBaseUrl); await client.loginWithOAuth({ - project, - onAuthenticate, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate, + }, }); onAuthenticate.mockImplementation(login => { login.skip(); @@ -345,8 +367,11 @@ describe('CogniteClient', () => { .reply(200, loggedInResponse); const isAuthenticated = await client.loginWithOAuth({ - project, - onAuthenticate, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate, + }, }); client.setOneTimeSdkHeader(disposableSdkHeader); @@ -375,8 +400,11 @@ describe('CogniteClient', () => { }); const client = setupClient(mockBaseUrl); await client.loginWithOAuth({ - project, - onAuthenticate, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate, + }, }); await expect(client.authenticate()).resolves.toBe(false); }); @@ -390,8 +418,11 @@ describe('CogniteClient', () => { ); const client = setupClient(mockBaseUrl); const result = await client.loginWithOAuth({ - project, - onHandleRedirectError, + type: 'CDF_OAUTH', + options: { + project, + onHandleRedirectError, + }, }); expect(result).toEqual(false); @@ -404,8 +435,11 @@ describe('CogniteClient', () => { .mockResolvedValueOnce(authTokens); const client = setupClient(mockBaseUrl); await client.loginWithOAuth({ - project, - onAuthenticate: POPUP, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate: POPUP, + }, }); nock(mockBaseUrl, { badheaders: [AUTHORIZATION_HEADER] }) .get('/') @@ -433,8 +467,11 @@ describe('CogniteClient', () => { login.skip(); }); await client.loginWithOAuth({ - project, - onAuthenticate, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate, + }, }); nock(mockBaseUrl) .get('/401') @@ -455,8 +492,11 @@ describe('CogniteClient', () => { test('should be able to provide an access token', async () => { const client = setupClient(mockBaseUrl); await client.loginWithOAuth({ - project, - accessToken: authTokens.accessToken, + type: 'CDF_OAUTH', + options: { + project, + accessToken: authTokens.accessToken, + }, }); nock(mockBaseUrl, { reqheaders: { @@ -472,9 +512,12 @@ describe('CogniteClient', () => { test('re-authenticate on 401', async done => { const client = setupClient(mockBaseUrl); await client.loginWithOAuth({ - project, - accessToken: authTokens.accessToken, - onAuthenticate: () => done(), + type: 'CDF_OAUTH', + options: { + project, + accessToken: authTokens.accessToken, + onAuthenticate: () => done(), + }, }); nock(mockBaseUrl) .get('/') @@ -486,10 +529,12 @@ describe('CogniteClient', () => { test('get cdf token with cognite auth flow', async () => { const client = setupClient(mockBaseUrl); await client.loginWithOAuth({ - project, - onAuthenticate: POPUP, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate: POPUP, + }, }); - mockPopup.mockImplementationOnce(async () => { return { ...authTokens }; }); @@ -508,10 +553,12 @@ describe('CogniteClient', () => { test("get cdf token as null if it's outdated with cognite auth flow", async () => { const client = setupClient(mockBaseUrl); await client.loginWithOAuth({ - project, - onAuthenticate: POPUP, + type: 'CDF_OAUTH', + options: { + project, + onAuthenticate: POPUP, + }, }); - mockPopup.mockImplementationOnce(async () => { return { ...authTokens }; }); @@ -555,9 +602,12 @@ describe('CogniteClient', () => { getCluster.mockReturnValueOnce(cluster); const result = await client.loginWithOAuth({ - clientId, - tenantId, - cluster, + type: 'AAD_OAUTH', + options: { + clientId, + tenantId, + cluster, + }, }); expect(login).toHaveBeenCalledTimes(0); @@ -573,10 +623,13 @@ describe('CogniteClient', () => { getCluster.mockReturnValueOnce(cluster); await client.loginWithOAuth({ - clientId, - tenantId, - cluster, - signInType: { type: 'loginPopup' }, + type: 'AAD_OAUTH', + options: { + clientId, + tenantId, + cluster, + signInType: { type: 'loginPopup' }, + }, }); const result = await client.authenticate(); @@ -593,9 +646,12 @@ describe('CogniteClient', () => { getCluster.mockReturnValueOnce(cluster); const oAuthResult = await client.loginWithOAuth({ - clientId, - tenantId, - cluster, + type: 'AAD_OAUTH', + options: { + clientId, + tenantId, + cluster, + }, }); const authResult = await client.authenticate(); @@ -611,9 +667,12 @@ describe('CogniteClient', () => { getCluster.mockReturnValueOnce(cluster); const oAuthResult = await client.loginWithOAuth({ - clientId, - tenantId, - cluster, + type: 'AAD_OAUTH', + options: { + clientId, + tenantId, + cluster, + }, }); const authResult = await client.authenticate(); @@ -632,7 +691,13 @@ describe('CogniteClient', () => { getCDFToken.mockResolvedValue(cdfToken); getCluster.mockReturnValueOnce(cluster); - const oAuthResult = await client.loginWithOAuth({ clientId, cluster }); + const oAuthResult = await client.loginWithOAuth({ + type: 'AAD_OAUTH', + options: { + clientId, + cluster, + }, + }); const authResult = await client.authenticate(); @@ -655,7 +720,13 @@ describe('CogniteClient', () => { getCDFToken.mockResolvedValueOnce(cdfToken); getCluster.mockReturnValueOnce(cluster); - const oAuthResult = await client.loginWithOAuth({ clientId, cluster }); + const oAuthResult = await client.loginWithOAuth({ + type: 'AAD_OAUTH', + options: { + clientId, + cluster, + }, + }); const authResult = await client.authenticate(); expect(oAuthResult).toEqual(false); @@ -672,14 +743,20 @@ describe('CogniteClient', () => { getCDFToken.mockResolvedValue(cdfToken); getCluster.mockReturnValueOnce(cluster); - await client.loginWithOAuth({ clientId, cluster }); + await client.loginWithOAuth({ + type: 'AAD_OAUTH', + options: { clientId, cluster }, + }); const result = await client.authenticate(); expect(result).toEqual(true); - expect(client.getOAuthFlowType()).toEqual(AAD_OAUTH); + expect(client.getOAuthFlowType()).toEqual('AAD_OAUTH'); }); test('should throw error on attempt to get Azure AD access token with cognite auth flow', async () => { - await client.loginWithOAuth({ project }); + await client.loginWithOAuth({ + type: 'CDF_OAUTH', + options: { project }, + }); await expect( async () => await client.getAzureADAccessToken() ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -694,7 +771,10 @@ describe('CogniteClient', () => { getCDFToken.mockResolvedValue('access_token'); getCluster.mockReturnValueOnce(cluster); - await client.loginWithOAuth({ clientId, tenantId, cluster }); + await client.loginWithOAuth({ + type: 'AAD_OAUTH', + options: { clientId, tenantId, cluster }, + }); const result = await client.authenticate(); @@ -716,10 +796,13 @@ describe('CogniteClient', () => { getCluster.mockReturnValueOnce(cluster); const result = await client.loginWithOAuth({ - clientId, - tenantId, - cluster, - onNoProjectAvailable, + type: 'AAD_OAUTH', + options: { + clientId, + tenantId, + cluster, + onNoProjectAvailable, + }, }); expect(result).toBe(false); @@ -737,11 +820,14 @@ describe('CogniteClient', () => { getCDFToken.mockResolvedValue('access_token'); const silentLogin = await client.loginWithOAuth({ - clientId, - tenantId, - cluster, - signInType: { type: 'loginPopup' }, - onNoProjectAvailable, + type: 'AAD_OAUTH', + options: { + clientId, + tenantId, + cluster, + signInType: { type: 'loginPopup' }, + onNoProjectAvailable, + }, }); const authenticated = await client.authenticate(); @@ -792,8 +878,11 @@ describe('CogniteClient', () => { .reply(200, { projects: ['project1', 'project2'] }); const result = await client.loginWithOAuth({ - authority, - requestParams, + type: 'ADFS_OAUTH', + options: { + authority, + requestParams, + }, }); const cdfToken = await client.getCDFToken(); @@ -816,8 +905,11 @@ describe('CogniteClient', () => { .reply(200, { projects: ['project1', 'project2'] }); const result = await client.loginWithOAuth({ - authority, - requestParams, + type: 'ADFS_OAUTH', + options: { + authority, + requestParams, + }, }); const authenticated = await client.authenticate(); @@ -829,6 +921,7 @@ describe('CogniteClient', () => { }); test('should login via redirect when silent login is not possible', async done => { const { location } = window; + // @ts-ignore delete window.location; window.location = { ...location, @@ -842,8 +935,11 @@ describe('CogniteClient', () => { const spyADFSLoginMethod = jest.spyOn(ADFS.prototype, 'login'); const result = await client.loginWithOAuth({ - authority, - requestParams, + type: 'ADFS_OAUTH', + options: { + authority, + requestParams, + }, }); client.authenticate(); @@ -874,9 +970,12 @@ describe('CogniteClient', () => { const onNoProjectAvailable = jest.fn(); const result = await client.loginWithOAuth({ - authority, - requestParams, - onNoProjectAvailable, + type: 'ADFS_OAUTH', + options: { + authority, + requestParams, + onNoProjectAvailable, + }, }); expect(result).toEqual(false); @@ -895,9 +994,12 @@ describe('CogniteClient', () => { const onNoProjectAvailable = jest.fn(); const result = await client.loginWithOAuth({ - authority, - requestParams, - onNoProjectAvailable, + type: 'ADFS_OAUTH', + options: { + authority, + requestParams, + onNoProjectAvailable, + }, }); const authenticated = await client.authenticate(); diff --git a/packages/core/src/baseCogniteClient.ts b/packages/core/src/baseCogniteClient.ts index 522dfe369fa..c18cbed57c1 100644 --- a/packages/core/src/baseCogniteClient.ts +++ b/packages/core/src/baseCogniteClient.ts @@ -21,13 +21,9 @@ import { MetadataMap } from './metadata'; import { bearerString, getBaseUrl, - isOAuthWithAADOptions, - isOAuthWithADFSOptions, - isOAuthWithCogniteOptions, isUsingSSL, projectUrl, isBrowser, - isOAuthWithOIDCAuthCodeOptions, } from './utils'; import { version } from '../package.json'; import { @@ -43,7 +39,7 @@ import { import { ADFS, ADFSRequestParams } from './authFlows/adfs'; import { OidcAuthCode, - OAuthLoginForOIDCAuthFlowOptions, + OIDCAuthFlowOptions, } from './authFlows/oidc_auth_code_flow'; import { AuthTokens } from './loginUtils'; import { User } from 'oidc-client'; @@ -68,15 +64,28 @@ export interface ApiKeyLoginOptions extends Project { apiKey: string; } -export const AAD_OAUTH = 'AAD_OAUTH'; -export const CDF_OAUTH = 'CDF_OAUTH'; -export const ADFS_OAUTH = 'ADFS_OAUTH'; -export const OIDC_AUTHORIZATION_CODE_FLOW = 'OIDC_AUTHORIZATION_CODE_FLOW'; +export type AAD_OAUTH = { + type: 'AAD_OAUTH'; + options: OAuthLoginForAADOptions; +}; +export type CDF_OAUTH = { + type: 'CDF_OAUTH'; + options: OAuthLoginForCogniteOptions; +}; +export type ADFS_OAUTH = { + type: 'ADFS_OAUTH'; + options: OAuthLoginForADFSOptions; +}; +export type OIDC_AUTHORIZATION_CODE_FLOW = { + type: 'OIDC_AUTHORIZATION_CODE_FLOW'; + options: OAuthLoginForOIDCAuthFlowOptions; +}; + export type AuthFlowType = - | typeof AAD_OAUTH - | typeof CDF_OAUTH - | typeof ADFS_OAUTH - | typeof OIDC_AUTHORIZATION_CODE_FLOW; + | AAD_OAUTH + | CDF_OAUTH + | ADFS_OAUTH + | OIDC_AUTHORIZATION_CODE_FLOW; /** * @deprecated @@ -107,11 +116,10 @@ export interface OAuthLoginForADFSOptions { onNoProjectAvailable?: () => void; } -export type OAuthLoginOptions = - | OAuthLoginForCogniteOptions - | OAuthLoginForAADOptions - | OAuthLoginForADFSOptions - | OAuthLoginForOIDCAuthFlowOptions; +export interface OAuthLoginForOIDCAuthFlowOptions extends OIDCAuthFlowOptions { + cluster: string; + onNoProjectAvailable?: () => void; +} export function accessApi(api: T | undefined): T { if (api === undefined) { @@ -150,6 +158,7 @@ export default class BaseCogniteClient { private adfsClient?: ADFS; private cogniteAuthClient?: CogniteAuthentication; private authCodeFlowManager?: OidcAuthCode; + /** * Create a new SDK client * @@ -254,24 +263,33 @@ export default class BaseCogniteClient { * * // using Cognite authentication flow * client.loginWithOAuth({ - * project: '[PROJECT]', - * onAuthenticate: REDIRECT // optional, REDIRECT is by default + * type: 'CDF_OAUTH', + * options: { + * project: '[PROJECT]', + * onAuthenticate: REDIRECT // optional, REDIRECT is by default + * } * }); * * // or you can sign in using AzureAD authentication flow (in case your projects supports it) * client.loginWithOAuth({ - * cluster: '[CLUSTER]', - * clientId: '[CLIENT_ID]', // client id of your AzureAD application - * tenantId: '[TENANT_ID]', // tenant id of your AzureAD tenant. Will be set to 'common' if not provided + * type: 'AAD_OAUTH', + * options: { + * cluster: '[CLUSTER]', + * clientId: '[CLIENT_ID]', // client id of your AzureAD application + * tenantId: '[TENANT_ID]', // tenant id of your AzureAD tenant. Will be set to 'common' if not provided + * } * }); * * // you also have ability to sign in using ADFS * client.loginWithOAuth({ - * authority: https://example.com/adfs/oauth2/authorize, - * requestParams: { - * cluster: 'cluster-name', - * clientId: 'adfs-client-id', - * }, + * type: 'ADFS_OAUTH', + * options: { + * authority: https://example.com/adfs/oauth2/authorize, + * requestParams: { + * cluster: 'cluster-name', + * clientId: 'adfs-client-id', + * }, + * } * }); * * // after sign in you can do calls with the client @@ -284,16 +302,17 @@ export default class BaseCogniteClient { * * @param options Login options */ - public loginWithOAuth = async ( - options: OAuthLoginOptions - ): Promise => { + public loginWithOAuth = async (flow: AuthFlowType): Promise => { let token = null; if (this.hasBeenLoggedIn) { throwReLogginError(); } - if (!options) { + if (!flow || !flow.type) { + throw Error('`loginWithOAuth` is missing parameter `flow`'); + } + if (!flow.options) { throw Error('`loginWithOAuth` is missing parameter `options`'); } @@ -303,18 +322,30 @@ export default class BaseCogniteClient { ); } + this.flow = flow; + let authenticate: () => Promise; - if (isOAuthWithCogniteOptions(options)) { - [authenticate, token] = await this.loginWithCognite(options); - } else if (isOAuthWithAADOptions(options)) { - [authenticate, token] = await this.loginWithAAD(options); - } else if (isOAuthWithADFSOptions(options)) { - [authenticate, token] = await this.loginWithADFS(options); - } else if (isOAuthWithOIDCAuthCodeOptions(options)) { - [authenticate, token] = await this.loginWithAuthCodeFlow(options); - } else { - throw Error('`loginWithOAuth` is missing correct `options` structure'); + switch (flow.type) { + case 'CDF_OAUTH': { + [authenticate, token] = await this.loginWithCognite(flow.options); + break; + } + case 'AAD_OAUTH': { + [authenticate, token] = await this.loginWithAAD(flow.options); + break; + } + case 'ADFS_OAUTH': { + [authenticate, token] = await this.loginWithADFS(flow.options); + break; + } + case 'OIDC_AUTHORIZATION_CODE_FLOW': { + [authenticate, token] = await this.loginWithAuthCodeFlow(flow.options); + break; + } + default: { + throw Error('`loginWithOAuth` was called with an unknown `flow`'); + } } this.httpClient.set401ResponseHandler(async (_, retry, reject) => { @@ -337,8 +368,9 @@ export default class BaseCogniteClient { */ public setBaseUrl = (baseUrl: string) => { if ( - this.flow === 'AAD_OAUTH' || - this.flow === 'OIDC_AUTHORIZATION_CODE_FLOW' + this.flow && + (this.flow.type === 'AAD_OAUTH' || + this.flow.type === 'OIDC_AUTHORIZATION_CODE_FLOW') ) { throw Error('`setBaseUrl` does not available with Azure AD auth flow'); } @@ -355,8 +387,8 @@ export default class BaseCogniteClient { /** * Provides information about which OAuth flow has been used */ - public getOAuthFlowType(): AuthFlowType | undefined { - return this.flow; + public getOAuthFlowType(): AuthFlowType['type'] | undefined { + return this.flow && this.flow.type; } /** @@ -370,7 +402,7 @@ export default class BaseCogniteClient { * ``` */ public async getCDFToken(): Promise { - switch (this.flow) { + switch (this.flow!.type) { case 'CDF_OAUTH': { const tokens = (await this.cogniteAuthClient!.getCDFToken(this.httpClient)) || null; @@ -589,7 +621,7 @@ export default class BaseCogniteClient { user = null; } - if (!user || !user.access_token) { + if (!user || user.access_token) { user = await authCodeFlowManager.login(); if (!user || !user.access_token) { @@ -611,7 +643,6 @@ export default class BaseCogniteClient { }; this.authCodeFlowManager = authCodeFlowManager; - this.flow = 'OIDC_AUTHORIZATION_CODE_FLOW'; const token = user ? user.access_token : null; return [authenticate, token]; @@ -677,7 +708,6 @@ export default class BaseCogniteClient { }; this.azureAdClient = azureAdClient; - this.flow = 'AAD_OAUTH'; return [authenticate, token]; }; @@ -722,7 +752,6 @@ export default class BaseCogniteClient { }; this.adfsClient = adfsClient; - this.flow = 'ADFS_OAUTH'; return [authenticate, token]; }; @@ -791,7 +820,6 @@ export default class BaseCogniteClient { }; this.cogniteAuthClient = cogniteAuthClient; - this.flow = 'CDF_OAUTH'; return [authenticate, token]; }; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 4168e582397..76f36e4c0ce 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -6,12 +6,6 @@ import isBuffer from 'is-buffer'; import { API_VERSION, BASE_URL } from './constants'; import { CogniteError } from './error'; import { CogniteMultiError } from './multiError'; -import { - OAuthLoginForAADOptions, - OAuthLoginForADFSOptions, - OAuthLoginForCogniteOptions, - OAuthLoginOptions, -} from './baseCogniteClient'; /** @hidden */ export function getBaseUrl(baseUrl?: string) { @@ -228,39 +222,3 @@ export function isUsingSSL() { export function isLocalhost(): boolean { return isBrowser() && location.hostname === 'localhost'; } - -/** @hidden */ -export function isOAuthWithCogniteOptions( - options: OAuthLoginOptions -): options is OAuthLoginForCogniteOptions { - return 'project' in options; -} - -/** @hidden */ -export function isOAuthWithAADOptions( - options: OAuthLoginOptions -): options is OAuthLoginForAADOptions { - return ['clientId', 'cluster'].every(key => key in options) && - // @ts-ignore - !options.flow; -} - -/** @hidden **/ -export function isOAuthWithADFSOptions( - options: OAuthLoginOptions -): options is OAuthLoginForADFSOptions { - return ['authority', 'requestParams'].every(key => key in options); -} - -/** @hidden **/ -export function isOAuthWithOIDCAuthCodeOptions( - options: OAuthLoginOptions -): options is OAuthLoginForADFSOptions { - return ( - ['openIdConfigurationUrl', 'clientId', 'cluster', 'flow', 'responseMode'].every( - key => key in options - ) && - // @ts-ignore - options.flow === 'authorization-flow' - ); -} diff --git a/samples/react/authentication-aad/src/App.js b/samples/react/authentication-aad/src/App.js index 3c277169b67..437b976d230 100644 --- a/samples/react/authentication-aad/src/App.js +++ b/samples/react/authentication-aad/src/App.js @@ -62,12 +62,14 @@ function App() { useEffect(() => { const login = async (client) => { const result = await client.loginWithOAuth({ + type: 'AAD_OAUTH', + options: { cluster, clientId, tenantId, signInType: { type: 'loginPopup' }, onNoProjectAvailable - }); + }}); client.setProject(project); diff --git a/samples/react/authentication-adfs/src/App.js b/samples/react/authentication-adfs/src/App.js index 93db4af0d59..6e0fc709758 100644 --- a/samples/react/authentication-adfs/src/App.js +++ b/samples/react/authentication-adfs/src/App.js @@ -58,13 +58,15 @@ function App() { useEffect(() => { const login = async (client) => { const result = await client.loginWithOAuth({ - authority, - requestParams: { - cluster, - clientId, - }, - onNoProjectAvailable - }); + type: 'ADFS_OAUTH', + options: { + authority, + requestParams: { + cluster, + clientId, + }, + onNoProjectAvailable + }}); client.setProject(project);