Skip to content

Commit

Permalink
CEA Auth changes (#2483)
Browse files Browse the repository at this point in the history
* initial cecAuth changes

* name fixes

* Adding support or Action Execute Auth

* taking off callback from CEC APIs

* fixing default value for external-app-auth input

* cec name updated to cea

* adding documentation for new CEA APIs

* changing .then logic to async/await

* changing incoming response from hubsdk

* moving interfaces to their original place

* telemetry fix + testing

* change file

* e2e test for isSupported

* matching string for e2e test

* addressig review feedback

* adding documenation

* using sendAndUnwrap call instead of sendMessageToParentAsync
  • Loading branch information
lakhveerk authored Oct 3, 2024
1 parent 078934e commit d7f13c7
Show file tree
Hide file tree
Showing 12 changed files with 1,171 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ExternalAppAuthenticationForCEA",
"version": ">2.29.0",
"hostSdkVersion": {
"web": ">=4.3.0"
},
"platform": "Web",
"testCases": [
{
"title": "checkExternalAppAuthenticationForCEACapability API Call - Success",
"type": "callResponse",
"boxSelector": "#box_checkExternalAppAuthenticationForCEACapability",
"expectedTestAppValue": "External App Authentication For CEA module is supported"
}
]
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "ExternalAppCardActionsForCEA",
"version": ">=2.29.0",
"hostSdkVersion": {
"web": ">=4.3.0"
},
"platform": "Web",
"testCases": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const AuthenticateAndResendRequest = (): React.ReactElement =>
defaultInput: JSON.stringify({
appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a',
authenticateParameters: {
url: 'https://www.example.com',
url: 'https://localhost:4000',
width: 100,
height: 100,
isExternal: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { AppId, externalAppAuthentication, externalAppAuthenticationForCEA } from '@microsoft/teams-js';
import React from 'react';

import { ApiWithoutInput } from '../utils/ApiWithoutInput';
import { ApiWithTextInput } from '../utils/ApiWithTextInput';
import { ModuleWrapper } from '../utils/ModuleWrapper';

const CheckExternalAppAuthenticationForCEACapability = (): React.ReactElement =>
ApiWithoutInput({
name: 'checkExternalAppAuthenticationForCEACapability',
title: 'Check External App Authentication CEA Capability',
onClick: async () =>
`External App Authentication For CEA module ${externalAppAuthenticationForCEA.isSupported() ? 'is' : 'is not'} supported`,
});

const AuthenticateWithOAuthForCEA = (): React.ReactElement =>
ApiWithTextInput<{
appId: string;
conversationId: string;
authenticateParameters: {
url: string;
width?: number;
height?: number;
isExternal?: boolean;
};
}>({
name: 'AuthenticateWithOAuthForCEA',
title: 'Authenticate With OAuth',
onClick: {
validateInput: (input) => {
if (!input.appId) {
throw new Error('appId is required');
}
if (!input.conversationId) {
throw new Error('conversationId is required');
}
if (!input.authenticateParameters) {
throw new Error('authenticateParameters is required');
}
},
submit: async (input) => {
await externalAppAuthenticationForCEA.authenticateWithOauth(new AppId(input.appId), input.conversationId, {
...input.authenticateParameters,
url: new URL(input.authenticateParameters.url),
});
return 'Completed';
},
},
defaultInput: JSON.stringify({
appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a',
conversationId: 'testConversationId',
authenticateParameters: {
url: 'https://localhost:4000',
width: 100,
height: 100,
isExternal: true,
},
}),
});

const AuthenticateWithSSOForCEA = (): React.ReactElement =>
ApiWithTextInput<{
appId: string;
conversationId: string;
authTokenRequest: externalAppAuthentication.AuthTokenRequestParameters;
}>({
name: 'authenticateWithSSOForCEA',
title: 'Authenticate With SSO',
onClick: {
validateInput: (input) => {
if (!input.appId) {
throw new Error('appId is required');
}
if (!input.conversationId) {
throw new Error('conversationId is required');
}
if (!input.authTokenRequest) {
throw new Error('authTokenRequest is required');
}
},
submit: async (input) => {
await externalAppAuthenticationForCEA.authenticateWithSSO(
new AppId(input.appId),
input.conversationId,
input.authTokenRequest,
);

return 'Completed';
},
},
defaultInput: JSON.stringify({
appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a',
conversationId: 'testConversationId',
authTokenRequest: {
claims: ['https://graph.microsoft.com'],
silent: true,
},
}),
});

const AuthenticateAndResendRequestForCEA = (): React.ReactElement =>
ApiWithTextInput<{
appId: string;
conversationId: string;
authenticateParameters: {
url: string;
width?: number;
height?: number;
isExternal?: boolean;
};
originalRequestInfo: externalAppAuthentication.IActionExecuteInvokeRequest;
}>({
name: 'authenticateAndResendRequestForCEA',
title: 'Authenticate And Resend Request',
onClick: {
validateInput: (input) => {
if (!input.appId) {
throw new Error('appId is required');
}
if (!input.conversationId) {
throw new Error('conversationId is required');
}
if (!input.authenticateParameters) {
throw new Error('authenticateParameters is required');
}
if (!input.originalRequestInfo) {
throw new Error('originalRequestInfo is required');
}
},
submit: async (input) => {
const result = await externalAppAuthenticationForCEA.authenticateAndResendRequest(
new AppId(input.appId),
input.conversationId,
{ ...input.authenticateParameters, url: new URL(input.authenticateParameters.url) },
input.originalRequestInfo,
);
return JSON.stringify(result);
},
},
defaultInput: JSON.stringify({
appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a',
conversationId: 'testConversationId',
authenticateParameters: {
url: 'https://localhost:4000',
width: 100,
height: 100,
isExternal: true,
},
originalRequestInfo: {
requestType: 'ActionExecuteInvokeRequest',
type: 'Action.Execute',
id: 'id1',
verb: 'verb1',
data: 'data1',
},
}),
});
const AuthenticateWithSSOAndResendRequestForCEA = (): React.ReactElement =>
ApiWithTextInput<{
appId: string;
conversationId: string;
authTokenRequest: externalAppAuthentication.AuthTokenRequestParameters;
originalRequestInfo: externalAppAuthentication.IActionExecuteInvokeRequest;
}>({
name: 'authenticateWithSSOAndResendRequestForCEA',
title: 'Authenticate With SSO And Resend Request',
onClick: {
validateInput: (input) => {
if (!input.appId) {
throw new Error('appId is required');
}
if (!input.conversationId) {
throw new Error('conversationId is required');
}
if (!input.authTokenRequest) {
throw new Error('authTokenRequest is required');
}
if (!input.originalRequestInfo) {
throw new Error('originalRequestInfo is required');
}
},
submit: async (input) => {
const result = await externalAppAuthenticationForCEA.authenticateWithSSOAndResendRequest(
new AppId(input.appId),
input.conversationId,
input.authTokenRequest,
input.originalRequestInfo,
);
return JSON.stringify(result);
},
},
defaultInput: JSON.stringify({
appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a',
conversationId: 'testConversationId',
authTokenRequest: {
claims: ['https://graph.microsoft.com'],
silent: true,
},
originalRequestInfo: {
requestType: 'ActionExecuteInvokeRequest',
type: 'Action.Execute',
id: 'id1',
verb: 'verb1',
data: 'data1',
},
}),
});

const ExternalAppAuthenticationForCEAAPIs = (): React.ReactElement => (
<ModuleWrapper title="External App Authentication for CEA">
<CheckExternalAppAuthenticationForCEACapability />
<AuthenticateWithOAuthForCEA />
<AuthenticateWithSSOForCEA />
<AuthenticateAndResendRequestForCEA />
<AuthenticateWithSSOAndResendRequestForCEA />
</ModuleWrapper>
);

export default ExternalAppAuthenticationForCEAAPIs;
2 changes: 2 additions & 0 deletions apps/teams-test-app/src/pages/TestApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import PeopleAPIs from '../components/PeopleAPIs';
import ChatAPIs from '../components/privateApis/ChatAPIs';
import CopilotAPIs from '../components/privateApis/CopilotAPIs';
import ExternalAppAuthenticationAPIs from '../components/privateApis/ExternalAppAuthenticationAPIs';
import ExternalAppAuthenticationForCEAAPIs from '../components/privateApis/ExternalAppAuthenticationForCEAAPIs';
import ExternalAppCardActionsAPIs from '../components/privateApis/ExternalAppCardActionsAPIs';
import ExternalAppCardActionsForCEAAPIs from '../components/privateApis/ExternalAppCardActionsForCEAAPIs';
import ExternalAppCommandsAPIs from '../components/privateApis/ExternalAppCommandsAPIs';
Expand Down Expand Up @@ -114,6 +115,7 @@ export const TestApp: React.FC = () => {
<DialogUrlBotAPIs />
<DialogUrlParentCommunicationAPIs childWindowRef={dialogWindowRef} />
<ExternalAppAuthenticationAPIs />
<ExternalAppAuthenticationForCEAAPIs />
<ExternalAppCardActionsAPIs />
<ExternalAppCardActionsForCEAAPIs />
<ExternalAppCommandsAPIs />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Added support for `ExternalAppAuthenticationForCEA` capability",
"packageName": "@microsoft/teams-js",
"email": "[email protected]",
"dependentChangeType": "patch"
}
4 changes: 4 additions & 0 deletions packages/teams-js/src/internal/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ export const enum ApiName {
ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest = 'externalAppAuthentication.authenticateWithSSOAndResendRequest',
ExternalAppAuthentication_AuthenticateWithOauth2 = 'externalAppAuthentication.authenticateWithOauth2',
ExternalAppAuthentication_AuthenticateWithPowerPlatformConnectorPlugins = 'externalAppAuthentication.authenticateWithPowerPlatformConnectorPlugins',
ExternalAppAuthenticationForCEA_AuthenticateWithOauth = 'externalAppAuthenticationForCEA.authenticateWithOauth',
ExternalAppAuthenticationForCEA_AuthenticateWithSSO = 'externalAppAuthenticationForCEA.authenticateWithSSO',
ExternalAppAuthenticationForCEA_AuthenticateAndResendRequest = 'externalAppAuthenticationForCEA.authenticateAndResendRequest',
ExternalAppAuthenticationForCEA_AuthenticateWithSSOAndResendRequest = 'externalAppAuthenticationForCEA.authenticateWithSSOAndResendRequest',
ExternalAppCardActions_ProcessActionOpenUrl = 'externalAppCardActions.processActionOpenUrl',
ExternalAppCardActions_ProcessActionSubmit = 'externalAppCardActions.processActionSubmit',
ExternalAppCardActionsForCEA_ProcessActionOpenUrl = 'externalAppCardActionsForCEA.processActionOpenUrl',
Expand Down
47 changes: 45 additions & 2 deletions packages/teams-js/src/private/externalAppAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,34 @@ export namespace externalAppAuthentication {
data: string | Record<string, unknown>;
}

/**
* @beta
* @hidden
* Determines if the provided response object is an instance of IActionExecuteResponse
* @internal
* Limited to Microsoft-internal use
* @param response The object to check whether it is of IActionExecuteResponse type
*/
export function isActionExecuteResponse(
response: unknown,
): response is externalAppAuthentication.IActionExecuteResponse {
const actionResponse = response as externalAppAuthentication.IActionExecuteResponse;

return (
actionResponse.responseType === externalAppAuthentication.InvokeResponseType.ActionExecuteInvokeResponse &&
actionResponse.value !== undefined &&
actionResponse.statusCode !== undefined &&
actionResponse.type !== undefined
);
}

/**
* @hidden
* This is the only allowed value for IActionExecuteInvokeRequest.type. Used for validation
* @internal
* Limited to Microsoft-internal use
*/
const ActionExecuteInvokeRequestType = 'Action.Execute';
export const ActionExecuteInvokeRequestType = 'Action.Execute';

/**
* @hidden
Expand Down Expand Up @@ -279,6 +300,27 @@ export namespace externalAppAuthentication {
message?: string;
}

/**
* @beta
* @hidden
* Determines if the provided error object is an instance of InvokeError
* @internal
* Limited to Microsoft-internal use
* @param err The error object to check whether it is of InvokeError type
*/
export function isInvokeError(err: unknown): err is externalAppAuthentication.InvokeError {
if (typeof err !== 'object' || err === null) {
return false;
}

const error = err as externalAppAuthentication.InvokeError;

return (
Object.values(externalAppAuthentication.InvokeErrorCode).includes(error.errorCode) &&
(error.message === undefined || typeof error.message === 'string')
);
}

/**
* @hidden
*
Expand All @@ -295,7 +337,8 @@ export namespace externalAppAuthentication {
* @internal
* Limited to Microsoft-internal use
*/
type InvokeErrorWrapper = InvokeError & { responseType: undefined };
export type InvokeErrorWrapper = InvokeError & { responseType: undefined };

/*********** END ERROR TYPE ***********/

/**
Expand Down
Loading

0 comments on commit d7f13c7

Please sign in to comment.