Skip to content

Commit

Permalink
[Feature] Support for 3rd party auth (#400)
Browse files Browse the repository at this point in the history
* [Feature] Support for 3rd party auth

* [Feature] Updated tests

* [Feature] isGrafxToken > type (enum)
  • Loading branch information
Dvergar authored Dec 20, 2023
1 parent dfe63b3 commit b7c166e
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 14 deletions.
14 changes: 10 additions & 4 deletions packages/sdk/src/controllers/SubscriberController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ConfigType, Id } from '../types/CommonTypes';
import { AuthRefreshTypeEnum as AuthRefreshTypeEnum } from '../types/ConnectorTypes';
import { MeasurementUnit } from '../types/LayoutTypes';
import { ToolType } from '../utils/enums';

Expand Down Expand Up @@ -104,16 +105,21 @@ export class SubscriberController {
* Listener on authentication expiration.
* The callback should resolve to the refreshed authentication. If the
* listener is not defined, the http requests from the connector will return
* 403 with no refetch of assets.
* 401 with no refetch of assets.
*
* Only grafx tokens are supported for now.
* When this emits it means either:
* - the grafxToken needs to be renewed
* - the 3rd party auth (user impersonation) needs to be renewed.
*
* @param connectorId connector id
* @param type the type of auth renewal needed
*/
onAuthExpired = (connectorId: string) => {
onAuthExpired = (connectorId: string, type: AuthRefreshTypeEnum) => {
const callBack = this.config.onAuthExpired;

return callBack ? callBack(connectorId) : new Promise<string | null>((resolve) => resolve(null));
return callBack
? callBack(connectorId, type).then((auth) => JSON.stringify(auth))
: new Promise<string | null>((resolve) => resolve(null));
};

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/sdk/src/interactions/connector.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Connection, connectToChild } from 'penpal';
import { Id } from '../types/CommonTypes';
import { StudioStyling } from '../types/ConfigurationTypes';
import { AuthRefreshTypeEnum } from '../types/ConnectorTypes';

export const validateEditorLink = (editorLink: string) => {
const linkValidator = new RegExp(/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w]+\/$/);
Expand Down Expand Up @@ -52,7 +53,7 @@ export const setupFrame = (iframe: HTMLIFrameElement, editorLink: string, stylin
interface ConfigParameterTypes {
onActionsChanged: (state: string) => void;
onStateChanged: (state: string) => void;
onAuthExpired: (connectorId: string) => Promise<string | null>;
onAuthExpired: (connectorId: string, refreshType: AuthRefreshTypeEnum) => Promise<string | null>;
onDocumentLoaded: () => void;
onSelectedFramesContentChanged: (state: string) => void;
onSelectedFramesLayoutChanged: (state: string) => void;
Expand Down
48 changes: 42 additions & 6 deletions packages/sdk/src/tests/controllers/SubscriberContoller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import { FrameAnimationType } from '../../types/AnimationTypes';
import { VariableType } from '../../types/VariableTypes';

import { ToolType } from '../../utils/enums';
import { ConnectorStateType } from '../../types/ConnectorTypes';
import {
AuthCredentials,
ConnectorStateType,
RefreshedAuthCredendentials,
GrafxTokenAuthCredentials,
AuthCredentialsTypeEnum,
AuthRefreshTypeEnum,
} from '../../types/ConnectorTypes';
import type { PageSize } from '../../types/PageTypes';
import { CornerRadiusUpdateModel } from '../../types/ShapeTypes';
import { AsyncError, EditorAPI } from '../../types/CommonTypes';
Expand Down Expand Up @@ -274,24 +281,53 @@ describe('SubscriberController', () => {

const mockConfig = {
onAuthExpired() {
return new Promise<string | null>((resolve) => resolve(refreshedToken));
return new Promise<AuthCredentials | null>((resolve) =>
resolve(new GrafxTokenAuthCredentials(refreshedToken)),
);
},
};

jest.spyOn(mockConfig, 'onAuthExpired');
const mockedSubscriberController = new SubscriberController(mockConfig);

const result = await mockedSubscriberController.onAuthExpired(connectorId);
const resultJsonString = await mockedSubscriberController.onAuthExpired(
connectorId,
AuthRefreshTypeEnum.grafxToken,
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const resultAuth: GrafxTokenAuthCredentials = JSON.parse(resultJsonString!);

expect(result).toBe(refreshedToken);
expect(mockConfig.onAuthExpired).toHaveBeenCalledWith(connectorId);
expect(resultAuth.token).toBe(refreshedToken);
expect(mockConfig.onAuthExpired).toHaveBeenCalledWith(connectorId, AuthRefreshTypeEnum.grafxToken);
expect(mockConfig.onAuthExpired).toHaveBeenCalledTimes(1);
});

it('returns the notification defined by the callback', async () => {
const mockConfig = {
onAuthExpired() {
return new Promise<AuthCredentials | null>((resolve) => resolve(new RefreshedAuthCredendentials()));
},
};

jest.spyOn(mockConfig, 'onAuthExpired');
const mockedSubscriberController = new SubscriberController(mockConfig);

const resultJsonString = await mockedSubscriberController.onAuthExpired(
connectorId,
AuthRefreshTypeEnum.user,
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const resultAuth = JSON.parse(resultJsonString!);

expect(resultAuth.type).toBe(AuthCredentialsTypeEnum.refreshed);
expect(mockConfig.onAuthExpired).toHaveBeenCalledWith(connectorId, AuthRefreshTypeEnum.user);
expect(mockConfig.onAuthExpired).toHaveBeenCalledTimes(1);
});

it('returns a null token if the listener is not defined', async () => {
const mockedSubscriberController = new SubscriberController({});

const result = await mockedSubscriberController.onAuthExpired(connectorId);
const result = await mockedSubscriberController.onAuthExpired(connectorId, AuthRefreshTypeEnum.grafxToken);

expect(result).toBe(null);
});
Expand Down
6 changes: 3 additions & 3 deletions packages/sdk/src/types/CommonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DocumentType, UndoState } from './DocumentTypes';
import { DocumentColor } from './ColorStyleTypes';
import { ParagraphStyle } from './ParagraphStyleTypes';
import { CharacterStyle } from './CharacterStyleTypes';
import { ConnectorEvent } from './ConnectorTypes';
import { AuthCredentials, ConnectorEvent, AuthRefreshTypeEnum } from './ConnectorTypes';
import { PageSize } from './PageTypes';
import { SelectedTextStyle } from './TextStyleTypes';
import { CornerRadiusUpdateModel } from './ShapeTypes';
Expand All @@ -20,14 +20,14 @@ export type Id = string;
export type ConfigType = {
onActionsChanged?: (state: DocumentAction[]) => void;
onStateChanged?: () => void;
onAuthExpired?: (connectorId: string) => Promise<string | null>;
onAuthExpired?: (connectorId: string, type: AuthRefreshTypeEnum) => Promise<AuthCredentials | null>;
onDocumentLoaded?: () => void;
/**
* @deprecated use `onSelectedFramesLayoutChanged` instead
*
*/
onSelectedFrameLayoutChanged?: (state: FrameLayoutType) => void;
onSelectedFramesLayoutChanged?: (state: FrameLayoutType[]) => void;
onSelectedFramesLayoutChanged?: (states: FrameLayoutType[]) => void;
/**
* @deprecated use `onSelectedFramesContentChanged` instead
*/
Expand Down
32 changes: 32 additions & 0 deletions packages/sdk/src/types/ConnectorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,35 @@ export const grafxMediaConnectorRegistration: ConnectorLocalRegistration = {
url: 'grafx-media.json',
source: ConnectorRegistrationSource.local,
};

/**
* Grafx token to return to the engine when it expires.
*/
export class GrafxTokenAuthCredentials {
token: string;
type = AuthCredentialsTypeEnum.grafxToken;

constructor(token: string) {
this.token = token;
}
}

/**
* Notification to return to the engine whenever a 3rd party auth (user impersonation)
* has been renewed by the integration.
*/
export class RefreshedAuthCredendentials {
type = AuthCredentialsTypeEnum.refreshed;
}

export enum AuthCredentialsTypeEnum {
grafxToken,
refreshed,
}

export type AuthCredentials = GrafxTokenAuthCredentials | RefreshedAuthCredendentials;

export enum AuthRefreshTypeEnum {
grafxToken = 'grafxToken',
user = 'user',
}

0 comments on commit b7c166e

Please sign in to comment.