diff --git a/samples/da-repairs-oauth-validated/.funcignore b/samples/da-repairs-oauth-validated/.funcignore
new file mode 100644
index 0000000..8af9cc6
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/.funcignore
@@ -0,0 +1,21 @@
+.funcignore
+*.js.map
+*.ts
+.git*
+.localConfigs
+.vscode
+local.settings.json
+test
+tsconfig.json
+.DS_Store
+.deployment
+node_modules/.bin
+node_modules/azure-functions-core-tools
+README.md
+tsconfig.json
+teamsapp.yml
+teamsapp.*.yml
+/env/
+/appPackage/
+/infra/
+/devTools/
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/.gitignore b/samples/da-repairs-oauth-validated/.gitignore
new file mode 100644
index 0000000..7ed4abe
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/.gitignore
@@ -0,0 +1,31 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+# TeamsFx files
+env/.env.*.user
+env/.env.dev
+env/.env.local
+.DS_Store
+build
+appPackage/build
+.deployment
+
+# dependencies
+/node_modules
+
+# testing
+/coverage
+
+# Dev tool directories
+/devTools/
+
+# TypeScript output
+dist
+out
+
+# Azure Functions artifacts
+bin
+obj
+appsettings.json
+local.settings.json
+
+# Local data
+.localConfigs
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/.vscode/extensions.json b/samples/da-repairs-oauth-validated/.vscode/extensions.json
new file mode 100644
index 0000000..aac0a6e
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "TeamsDevApp.ms-teams-vscode-extension"
+ ]
+}
diff --git a/samples/da-repairs-oauth-validated/.vscode/launch.json b/samples/da-repairs-oauth-validated/.vscode/launch.json
new file mode 100644
index 0000000..910cce1
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/.vscode/launch.json
@@ -0,0 +1,97 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch App in Teams (Edge)",
+ "type": "msedge",
+ "request": "launch",
+ "url": "https://www.office.com/chat?auth=2",
+ "cascadeTerminateToConfigurations": [
+ "Attach to Backend"
+ ],
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen",
+ "perScriptSourcemaps": "yes"
+ },
+ {
+ "name": "Launch App in Teams (Chrome)",
+ "type": "chrome",
+ "request": "launch",
+ "url": "https://www.office.com/chat?auth=2",
+ "cascadeTerminateToConfigurations": [
+ "Attach to Backend"
+ ],
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen",
+ "perScriptSourcemaps": "yes"
+ },
+ {
+ "name": "Preview in Copilot (Edge)",
+ "type": "msedge",
+ "request": "launch",
+ "url": "https://www.office.com/chat?auth=2",
+ "presentation": {
+ "group": "remote",
+ "order": 1
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Preview in Copilot (Chrome)",
+ "type": "chrome",
+ "request": "launch",
+ "url": "https://www.office.com/chat?auth=2",
+ "presentation": {
+ "group": "remote",
+ "order": 2
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Attach to Backend",
+ "type": "node",
+ "request": "attach",
+ "port": 9229,
+ "restart": true,
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen"
+ }
+ ],
+ "compounds": [
+ {
+ "name": "Debug in Copilot (Edge)",
+ "configurations": [
+ "Launch App in Teams (Edge)",
+ "Attach to Backend"
+ ],
+ "preLaunchTask": "Start Teams App Locally",
+ "presentation": {
+ "group": "all",
+ "order": 1
+ },
+ "stopAll": true
+ },
+ {
+ "name": "Debug in Copilot (Chrome)",
+ "configurations": [
+ "Launch App in Teams (Chrome)",
+ "Attach to Backend"
+ ],
+ "preLaunchTask": "Start Teams App Locally",
+ "presentation": {
+ "group": "all",
+ "order": 2
+ },
+ "stopAll": true
+ }
+ ]
+}
diff --git a/samples/da-repairs-oauth-validated/.vscode/settings.json b/samples/da-repairs-oauth-validated/.vscode/settings.json
new file mode 100644
index 0000000..0ed7b2e
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/.vscode/settings.json
@@ -0,0 +1,13 @@
+{
+ "debug.onTaskErrors": "abort",
+ "json.schemas": [
+ {
+ "fileMatch": [
+ "/aad.*.json"
+ ],
+ "schema": {}
+ }
+ ],
+ "azureFunctions.stopFuncTaskPostDebug": false,
+ "azureFunctions.showProjectWarning": false,
+}
diff --git a/samples/da-repairs-oauth-validated/.vscode/tasks.json b/samples/da-repairs-oauth-validated/.vscode/tasks.json
new file mode 100644
index 0000000..dbc7dc2
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/.vscode/tasks.json
@@ -0,0 +1,129 @@
+// This file is automatically generated by Teams Toolkit.
+// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0.
+// See https://aka.ms/teamsfx-tasks for details on how to customize each task.
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Start Teams App Locally",
+ "dependsOn": [
+ "Validate prerequisites",
+ "Start local tunnel",
+ "Create resources",
+ "Build project",
+ "Start application"
+ ],
+ "dependsOrder": "sequence"
+ },
+ {
+ "label": "Validate prerequisites",
+ "type": "teamsfx",
+ "command": "debug-check-prerequisites",
+ "args": {
+ "prerequisites": [
+ "nodejs",
+ "m365Account",
+ "portOccupancy"
+ ],
+ "portOccupancy": [
+ 7071,
+ 9229
+ ]
+ }
+ },
+ {
+ // Start the local tunnel service to forward public URL to local port and inspect traffic.
+ // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions.
+ "label": "Start local tunnel",
+ "type": "teamsfx",
+ "command": "debug-start-local-tunnel",
+ "args": {
+ "type": "dev-tunnel",
+ "ports": [
+ {
+ "portNumber": 7071,
+ "protocol": "http",
+ "access": "public",
+ "writeToEnvironmentFile": {
+ "endpoint": "OPENAPI_SERVER_URL", // output tunnel endpoint as OPENAPI_SERVER_URL
+ }
+ }
+ ],
+ "env": "local"
+ },
+ "isBackground": true,
+ "problemMatcher": "$teamsfx-local-tunnel-watch"
+ },
+ {
+ "label": "Create resources",
+ "type": "teamsfx",
+ "command": "provision",
+ "args": {
+ "env": "local"
+ }
+ },
+ {
+ "label": "Build project",
+ "type": "teamsfx",
+ "command": "deploy",
+ "args": {
+ "env": "local"
+ }
+ },
+ {
+ "label": "Start application",
+ "dependsOn": [
+ "Start backend"
+ ]
+ },
+ {
+ "label": "Start backend",
+ "type": "shell",
+ "command": "npm run dev:teamsfx",
+ "isBackground": true,
+ "options": {
+ "cwd": "${workspaceFolder}",
+ "env": {
+ "PATH": "${workspaceFolder}/devTools/func:${env:PATH}"
+ }
+ },
+ "windows": {
+ "options": {
+ "env": {
+ "PATH": "${workspaceFolder}/devTools/func;${env:PATH}"
+ }
+ }
+ },
+ "problemMatcher": {
+ "pattern": {
+ "regexp": "^.*$",
+ "file": 0,
+ "location": 1,
+ "message": 2
+ },
+ "background": {
+ "activeOnStart": true,
+ "beginsPattern": "^.*(Job host stopped|signaling restart).*$",
+ "endsPattern": "^.*(Worker process started and initialized|Host lock lease acquired by instance ID).*$"
+ }
+ },
+ "presentation": {
+ "reveal": "silent"
+ },
+ "dependsOn": "Watch backend"
+ },
+ {
+ "label": "Watch backend",
+ "type": "shell",
+ "command": "npm run watch:teamsfx",
+ "isBackground": true,
+ "options": {
+ "cwd": "${workspaceFolder}"
+ },
+ "problemMatcher": "$tsc-watch",
+ "presentation": {
+ "reveal": "silent"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/README.md b/samples/da-repairs-oauth-validated/README.md
new file mode 100644
index 0000000..5ec2f17
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/README.md
@@ -0,0 +1,66 @@
+# Declarative Agent with an API plugin connected to an API secured with OAuth with validation
+
+## Summary
+
+This sample demonstrates how to build a declarative agent for Microsoft 365 Copilot that answers questions about repairs. The agent uses an API plugin to connect to an API secured with Entra ID.
+
+![picture of the app in action](./assets/screenshot.gif)
+
+The project contains an Azure Function, but unlike the [da-repairs-oauth sample](../da-repairs-oauth/) relies on Azure App Services authentication ("Easy Auth") for authentication, this sample validates access tokens in code. Teams Toolkit currently uses Easy Auth as shown in this sample. Here are some advantages of validating the token in your code instead:
+
+ - Since Easy Auth doesn't work locally, local requests are not authenticated. In addition to a small security opening, this causes the app to have 2 plugin files, including an anonymous one for local debugging. In this sample, local requests are authenticated and the packaging source files are the same for all environments.
+
+ - If the code is deployed outside of Azure app services, and if the included Bicep files aren't used, the code will appear to work but will do no token validation at all, thus wide open to anonymous requests.
+
+ - With the Easy Auth scenario, Copilot is sending the access token directly to Azure App Services authentication. If something goes wrong there is no way to inspect the access token and debugging options are limited. In this sample you can set a breakpoint to inspect the token and walk through the validation to see what went wrong.
+
+ - Easy Auth does not check the scope, or if the token is an app token
+
+ For these reasons, developers may choose to follow this approach, which is made possible by an open source library ([jwt-validate](https://www.npmjs.com/package/jwt-validate)) by [Waldek Mastykarz](https://github.com/waldekmastykarz). This library is not a Microsoft product, and is subject to an MIT license (i.e. use at your own risk). Many thanks to Waldek for creating this library since Microsoft does not currently provide a token validation library for NodeJS.
+
+
+## Prerequisites
+![drop](https://img.shields.io/badge/Teams Toolkit for VS Code-5.10-green.svg)
+
+ * Microsoft 365 tenant with Microsoft 365 Copilot
+ * [Visual Studio Code](https://code.visualstudio.com/) with [Teams Toolkit](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension) v5.10 or greater
+ * [NodeJS v18](https://nodejs.org/en/download/package-manager)
+ * [Azure Functions core tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools)
+
+_Please list any portions of the toolchain required to build and use the sample, along with download links_
+
+## Version history
+
+Version|Date|Author|Comments
+-------|----|----|--------
+1.0|October 9, 2024|Bob German|Initial release
+
+## Disclaimer
+
+**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
+
+---
+
+## Minimal Path to Awesome
+
+* Clone this repository
+* Open the cloned copy of this folder with Visual Studio Code
+* Install required npm packages
+
+```shell
+ npm install
+```
+
+* Press F5 to run the application. A browser window should open offering to add your application to Microsoft Teams.
+
+
+## Features
+
+This sample illustrates the following concepts:
+
+Building a declarative agent for Microsoft 365 Copilot with an API plugin
+Connecting an API plugin to an API secured with OAuth
+Using Azure Functions to build an API secured with Azure App Service authentication and authorization (Easy Auth)
+Using dev tunnels to test the API plugin locally
+
+
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/aad.manifest.json b/samples/da-repairs-oauth-validated/aad.manifest.json
new file mode 100644
index 0000000..33c7104
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/aad.manifest.json
@@ -0,0 +1,40 @@
+{
+ "id": "${{AAD_APP_OBJECT_ID}}",
+ "appId": "${{AAD_APP_CLIENT_ID}}",
+ "name": "Repairs-OAuth-aad",
+ "accessTokenAcceptedVersion": 2,
+ "signInAudience": "AzureADMyOrg",
+ "optionalClaims": {
+ "idToken": [],
+ "accessToken": [
+ {
+ "name": "idtyp",
+ "source": null,
+ "essential": false,
+ "additionalProperties": []
+ }
+ ],
+ "saml2Token": []
+ },
+ "oauth2Permissions": [
+ {
+ "adminConsentDescription": "Allows Copilot to read repair records on your behalf.",
+ "adminConsentDisplayName": "Read repairs",
+ "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}",
+ "isEnabled": true,
+ "type": "User",
+ "userConsentDescription": "Allows Copilot to read repair records.",
+ "userConsentDisplayName": "Read repairs",
+ "value": "repairs_read"
+ }
+ ],
+ "replyUrlsWithType": [
+ {
+ "url": "https://teams.microsoft.com/api/platform/v1.0/oAuthRedirect",
+ "type": "Web"
+ }
+ ],
+ "identifierUris": [
+ "api://${{AAD_APP_CLIENT_ID}}"
+ ]
+}
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/appPackage/ai-plugin.json b/samples/da-repairs-oauth-validated/appPackage/ai-plugin.json
new file mode 100644
index 0000000..23c9270
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/appPackage/ai-plugin.json
@@ -0,0 +1,89 @@
+{
+ "$schema": "https://aka.ms/json-schemas/copilot/plugin/v2.1/schema.json",
+ "schema_version": "v2.1",
+ "namespace": "repairs",
+ "name_for_human": "Repairs-OAuth${{APP_NAME_SUFFIX}}",
+ "description_for_human": "Track your repair records",
+ "description_for_model": "Plugin for searching a repair list, you can search by who's assigned to the repair.",
+ "functions": [
+ {
+ "name": "listRepairs",
+ "description": "Returns a list of repairs with their details and images",
+ "capabilities": {
+ "response_semantics": {
+ "data_path": "$.results",
+ "properties": {
+ "title": "$.title",
+ "subtitle": "$.description",
+ "url": "$.image"
+ },
+ "static_template": {
+ "type": "AdaptiveCard",
+ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+ "version": "1.5",
+ "body": [
+ {
+ "type": "Container",
+ "$data": "${$root}",
+ "items": [
+ {
+ "type": "TextBlock",
+ "text": "id: ${if(id, id, 'N/A')}",
+ "wrap": true
+ },
+ {
+ "type": "TextBlock",
+ "text": "title: ${if(title, title, 'N/A')}",
+ "wrap": true
+ },
+ {
+ "type": "TextBlock",
+ "text": "description: ${if(description, description, 'N/A')}",
+ "wrap": true
+ },
+ {
+ "type": "TextBlock",
+ "text": "assignedTo: ${if(assignedTo, assignedTo, 'N/A')}",
+ "wrap": true
+ },
+ {
+ "type": "TextBlock",
+ "text": "date: ${if(date, date, 'N/A')}",
+ "wrap": true
+ },
+ {
+ "type": "Image",
+ "url": "${image}",
+ "$when": "${image != null}"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "runtimes": [
+ {
+ "type": "OpenApi",
+ "auth": {
+ "type": "OAuthPluginVault",
+ "reference_id": "${{OAUTH2AUTHCODE_CONFIGURATION_ID}}"
+ },
+ "spec": {
+ "url": "apiSpecificationFile/repair.yml",
+ "progress_style": "ShowUsageWithInputAndOutput"
+ },
+ "run_for_functions": ["listRepairs"]
+ }
+ ],
+ "capabilities": {
+ "localization": {},
+ "conversation_starters": [
+ {
+ "text": "List all repairs"
+ }
+ ]
+ }
+}
diff --git a/samples/da-repairs-oauth-validated/appPackage/apiSpecificationFile/repair.yml b/samples/da-repairs-oauth-validated/appPackage/apiSpecificationFile/repair.yml
new file mode 100644
index 0000000..27afb73
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/appPackage/apiSpecificationFile/repair.yml
@@ -0,0 +1,67 @@
+openapi: 3.0.0
+info:
+ title: Repair Service
+ description: A simple service to manage repairs
+ version: 1.0.0
+servers:
+ - url: ${{OPENAPI_SERVER_URL}}/api
+ description: The repair api server
+components:
+ securitySchemes:
+ oAuth2AuthCode:
+ type: oauth2
+ description: OAuth configuration for the repair service
+ flows:
+ authorizationCode:
+ authorizationUrl: https://login.microsoftonline.com/${{AAD_APP_TENANT_ID}}/oauth2/v2.0/authorize
+ tokenUrl: https://login.microsoftonline.com/${{AAD_APP_TENANT_ID}}/oauth2/v2.0/token
+ scopes:
+ api://${{AAD_APP_CLIENT_ID}}/repairs_read: Read repair records
+paths:
+ /repairs:
+ get:
+ operationId: listRepairs
+ summary: List all repairs
+ description: Returns a list of repairs with their details and images
+ security:
+ - oAuth2AuthCode: []
+ parameters:
+ - name: assignedTo
+ in: query
+ description: Filter repairs by who they're assigned to
+ schema:
+ type: string
+ required: false
+ responses:
+ '200':
+ description: A list of repairs
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ results:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The unique identifier of the repair
+ title:
+ type: string
+ description: The short summary of the repair
+ description:
+ type: string
+ description: The detailed description of the repair
+ assignedTo:
+ type: string
+ description: The user who is responsible for the repair
+ date:
+ type: string
+ format: date-time
+ description: The date and time when the repair is scheduled or completed
+ image:
+ type: string
+ format: uri
+ description: The URL of the image of the item to be repaired or the repair process
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/appPackage/color.png b/samples/da-repairs-oauth-validated/appPackage/color.png
new file mode 100644
index 0000000..11e255f
Binary files /dev/null and b/samples/da-repairs-oauth-validated/appPackage/color.png differ
diff --git a/samples/da-repairs-oauth-validated/appPackage/instruction.txt b/samples/da-repairs-oauth-validated/appPackage/instruction.txt
new file mode 100644
index 0000000..b6aaf8c
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/appPackage/instruction.txt
@@ -0,0 +1 @@
+You will help the user find car repair records assigned to a specific person, the name of the person should be provided by the user. The user will provide the name of the person and you will need to understand the user's intent and provide the car repair records assigned to that person. You can only access and leverage the data from the 'repairPlugin' action.
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/appPackage/manifest.json b/samples/da-repairs-oauth-validated/appPackage/manifest.json
new file mode 100644
index 0000000..a67bdf3
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/appPackage/manifest.json
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.schema.json",
+ "manifestVersion": "devPreview",
+ "id": "${{TEAMS_APP_ID}}",
+ "version": "1.0.0",
+ "developer": {
+ "name": "Teams App, Inc.",
+ "websiteUrl": "https://www.example.com",
+ "privacyUrl": "https://www.example.com/privacy",
+ "termsOfUseUrl": "https://www.example.com/termsofuse"
+ },
+ "icons": {
+ "color": "color.png",
+ "outline": "outline.png"
+ },
+ "name": {
+ "short": "Repairs-OAuth${{APP_NAME_SUFFIX}}",
+ "full": "Full name for Repairs-OAuth"
+ },
+ "description": {
+ "short": "Track and monitor car repair records for stress-free maintenance management.",
+ "full": "The ultimate solution for hassle-free car maintenance management makes tracking and monitoring your car repair records a breeze."
+ },
+ "accentColor": "#FFFFFF",
+ "copilotExtensions": {
+ "declarativeCopilots": [
+ {
+ "id": "repairDeclarativeAgent",
+ "file": "repairDeclarativeAgent.json"
+ }
+ ]
+ },
+ "permissions": [
+ "identity",
+ "messageTeamMembers"
+ ]
+}
diff --git a/samples/da-repairs-oauth-validated/appPackage/outline.png b/samples/da-repairs-oauth-validated/appPackage/outline.png
new file mode 100644
index 0000000..f7a4c86
Binary files /dev/null and b/samples/da-repairs-oauth-validated/appPackage/outline.png differ
diff --git a/samples/da-repairs-oauth-validated/appPackage/repairDeclarativeAgent.json b/samples/da-repairs-oauth-validated/appPackage/repairDeclarativeAgent.json
new file mode 100644
index 0000000..7523285
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/appPackage/repairDeclarativeAgent.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://aka.ms/json-schemas/copilot/declarative-agent/v1.0/schema.json",
+ "version": "v1.0",
+ "name": "Repairs-OAuth${{APP_NAME_SUFFIX}}",
+ "description": "This declarative agent helps you with finding car repair records.",
+ "instructions": "$[file('instruction.txt')]",
+ "conversation_starters": [
+ {
+ "text": "Show repair records assigned to Karin Blair"
+ }
+ ],
+ "actions": [
+ {
+ "id": "repairPlugin",
+ "file": "ai-plugin.json"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/assets/sample.json b/samples/da-repairs-oauth-validated/assets/sample.json
new file mode 100644
index 0000000..a16348d
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/assets/sample.json
@@ -0,0 +1,68 @@
+[
+ {
+ "name": "pnp-copilot-pro-dev-da-repairs-oauth-validated",
+ "source": "pnp",
+ "title": "Declarative Agent with an API plugin that secured by Entra ID that validates its own tokens",
+ "shortDescription": "This sample demonstrates how to build a declarative agent for Microsoft 365 Copilot that answers questions about repairs. The agent uses an API plugin to connect to an API secured with OAuth. The project contains an Azure Function that validates tokens using an open source library.",
+ "url": "https://github.com/pnp/copilot-pro-dev-samples/tree/main/samples/da-repairs-oauth",
+ "downloadUrl": "https://pnp.github.io/download-partial/?url=https://github.com/pnp/copilot-pro-dev-samples/tree/main/samples/da-repairs-oauth",
+ "longDescription": [
+ "This sample demonstrates how to build a declarative agent for Microsoft 365 Copilot that answers questions about repairs. The agent uses an API plugin to connect to an API secured with OAuth. The project contains an Azure Function that serves as the API and uses an open source library to validate Entra ID tokens to secure access to APIs."
+ ],
+ "creationDateTime": "2024-10-09",
+ "updateDateTime": "2024-10-09",
+ "products": [
+ "Microsoft 365 Copilot"
+ ],
+ "metadata": [
+ {
+ "key": "PLATFORM",
+ "value": "Node.js"
+ },
+ {
+ "key": "LANGUAGE",
+ "value": "TypeScript"
+ },
+ {
+ "key": "API-PLUGIN",
+ "value": "Yes"
+ },
+ {
+ "key": "GRAPH-CONNECTOR",
+ "value": "No"
+ }
+ ],
+ "thumbnails": [
+ {
+ "type": "image",
+ "order": 100,
+ "url": "https://github.com/pnp/copilot-pro-dev-samples/raw/main/samples/da-repairs-oauth-validated/assets/screenshot.png",
+ "alt": "Declarative agent answering questions about repairs using the information from an API secured with Entra ID"
+ }
+ ],
+ "authors": [
+ {
+ "gitHubAccount": "BobGerman",
+ "pictureUrl": "https://github.com/BobGerman.png",
+ "name": "Bob German"
+ }
+ ],
+ "references": [
+ {
+ "name": "Microsoft 365 Copilot extensibility",
+ "description": "Learn more about what Microsoft 365 Copilot and how you can extend it.",
+ "url": "https://learn.microsoft.com/microsoft-365-copilot/extensibility/"
+ },
+ {
+ "name": "Declarative agents for Microsoft 365 Copilot overview",
+ "description": "Learn more about what declarative agents for Microsoft 365 Copilot are.",
+ "url": "https://learn.microsoft.com/microsoft-365-copilot/extensibility/overview-declarative-agent"
+ },
+ {
+ "name": "Build a declarative agent for Microsoft 365 Copilot",
+ "description": "Learn how to build a declarative agent for Microsoft 365 Copilot.",
+ "url": "https://learn.microsoft.com/microsoft-365-copilot/extensibility/build-declarative-agents"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/assets/screenshot.gif b/samples/da-repairs-oauth-validated/assets/screenshot.gif
new file mode 100644
index 0000000..452ea54
Binary files /dev/null and b/samples/da-repairs-oauth-validated/assets/screenshot.gif differ
diff --git a/samples/da-repairs-oauth-validated/env/.env.local.sample b/samples/da-repairs-oauth-validated/env/.env.local.sample
new file mode 100644
index 0000000..bcf5c5f
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/env/.env.local.sample
@@ -0,0 +1,20 @@
+# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment.
+
+# Built-in environment variables
+TEAMSFX_ENV=local
+APP_NAME_SUFFIX=local
+
+# Generated during provision, you can also add your own variables.
+TEAMS_APP_ID=
+TEAMS_APP_PACKAGE_PATH=
+FUNC_ENDPOINT=
+TEAMS_APP_TENANT_ID=
+TEAMS_APP_UPDATE_TIME=
+OPENAPI_SERVER_URL=
+
+# Generated during deploy, you can also add your own variables.
+FUNC_PATH=
+FUNC_NAME=
+M365_TITLE_ID=
+M365_APP_ID=
+
diff --git a/samples/da-repairs-oauth-validated/env/.env.local.user.sample b/samples/da-repairs-oauth-validated/env/.env.local.user.sample
new file mode 100644
index 0000000..2548e49
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/env/.env.local.user.sample
@@ -0,0 +1,3 @@
+# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project.
+
+# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs.
diff --git a/samples/da-repairs-oauth-validated/host.json b/samples/da-repairs-oauth-validated/host.json
new file mode 100644
index 0000000..06d01bd
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/host.json
@@ -0,0 +1,15 @@
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ }
+ }
+ },
+ "extensionBundle": {
+ "id": "Microsoft.Azure.Functions.ExtensionBundle",
+ "version": "[4.*, 5.0.0)"
+ }
+}
diff --git a/samples/da-repairs-oauth-validated/infra/azure.bicep b/samples/da-repairs-oauth-validated/infra/azure.bicep
new file mode 100644
index 0000000..8aba050
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/infra/azure.bicep
@@ -0,0 +1,108 @@
+@maxLength(20)
+@minLength(4)
+param resourceBaseName string
+param functionAppSKU string
+param aadAppClientId string
+@secure()
+param aadAppClientSecret string
+param aadAppTenantId string
+param aadAppOauthAuthorityHost string
+param location string = resourceGroup().location
+param serverfarmsName string = resourceBaseName
+param functionAppName string = resourceBaseName
+
+
+// Compute resources for Azure Functions
+resource serverfarms 'Microsoft.Web/serverfarms@2021-02-01' = {
+ name: serverfarmsName
+ location: location
+ sku: {
+ name: functionAppSKU // You can follow https://aka.ms/teamsfx-bicep-add-param-tutorial to add functionServerfarmsSku property to provisionParameters to override the default value "Y1".
+ }
+ properties: {}
+}
+
+// Azure Functions that hosts your function code
+resource functionApp 'Microsoft.Web/sites@2021-02-01' = {
+ name: functionAppName
+ kind: 'functionapp'
+ location: location
+ properties: {
+ serverFarmId: serverfarms.id
+ httpsOnly: true
+ siteConfig: {
+ appSettings: [
+ {
+ name: 'FUNCTIONS_EXTENSION_VERSION'
+ value: '~4' // Use Azure Functions runtime v4
+ }
+ {
+ name: 'FUNCTIONS_WORKER_RUNTIME'
+ value: 'node' // Set runtime to NodeJS
+ }
+ {
+ name: 'WEBSITE_RUN_FROM_PACKAGE'
+ value: '1' // Run Azure Functions from a package file
+ }
+ {
+ name: 'WEBSITE_NODE_DEFAULT_VERSION'
+ value: '~18' // Set NodeJS version to 18.x
+ }
+ {
+ name: 'M365_CLIENT_ID'
+ value: aadAppClientId
+ }
+ {
+ name: 'M365_CLIENT_SECRET'
+ value: aadAppClientSecret
+ }
+ {
+ name: 'M365_TENANT_ID'
+ value: aadAppTenantId
+ }
+ {
+ name: 'M365_AUTHORITY_HOST'
+ value: aadAppOauthAuthorityHost
+ }
+ ]
+ ftpsState: 'FtpsOnly'
+ }
+ }
+}
+var apiEndpoint = 'https://${functionApp.properties.defaultHostName}'
+var oauthAuthority = uri(aadAppOauthAuthorityHost, aadAppTenantId)
+var aadApplicationIdUri = 'api://${aadAppClientId}'
+
+// Configure Azure Functions to use Azure AD for authentication.
+resource authSettings 'Microsoft.Web/sites/config@2021-02-01' = {
+ parent: functionApp
+ name: 'authsettingsV2'
+ properties: {
+ globalValidation: {
+ requireAuthentication: true
+ unauthenticatedClientAction: 'Return401'
+ }
+ identityProviders: {
+ azureActiveDirectory: {
+ enabled: true
+ registration: {
+ openIdIssuer: oauthAuthority
+ clientId: aadAppClientId
+ }
+ validation: {
+ allowedAudiences: [
+ aadAppClientId
+ aadApplicationIdUri
+ ]
+ }
+ }
+ }
+ }
+}
+
+
+// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details.
+output API_FUNCTION_ENDPOINT string = apiEndpoint
+output API_FUNCTION_RESOURCE_ID string = functionApp.id
+output OPENAPI_SERVER_URL string = apiEndpoint
+output OPENAPI_SERVER_DOMAIN string = functionApp.properties.defaultHostName
diff --git a/samples/da-repairs-oauth-validated/infra/azure.parameters.json b/samples/da-repairs-oauth-validated/infra/azure.parameters.json
new file mode 100644
index 0000000..54ae429
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/infra/azure.parameters.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "resourceBaseName": {
+ "value": "plugin${{RESOURCE_SUFFIX}}"
+ },
+ "functionAppSKU": {
+ "value": "Y1"
+ },
+ "aadAppClientId": {
+ "value": "${{AAD_APP_CLIENT_ID}}"
+ },
+ "aadAppClientSecret": {
+ "value": "${{SECRET_AAD_APP_CLIENT_SECRET}}"
+ },
+ "aadAppTenantId": {
+ "value": "${{AAD_APP_TENANT_ID}}"
+ },
+ "aadAppOauthAuthorityHost": {
+ "value": "${{AAD_APP_OAUTH_AUTHORITY_HOST}}"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/da-repairs-oauth-validated/package-lock.json b/samples/da-repairs-oauth-validated/package-lock.json
new file mode 100644
index 0000000..25e3ba1
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/package-lock.json
@@ -0,0 +1,476 @@
+{
+ "name": "apipluginoauth",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "apipluginoauth",
+ "version": "1.0.0",
+ "dependencies": {
+ "@azure/functions": "^4.3.0",
+ "jwt-validate": "^0.5.0"
+ },
+ "devDependencies": {
+ "@types/node": "^18.11.9",
+ "env-cmd": "^10.1.0",
+ "typescript": "^4.1.6"
+ }
+ },
+ "node_modules/@azure/functions": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-4.5.1.tgz",
+ "integrity": "sha512-ikiw1IrM2W9NlQM3XazcX+4Sq3XAjZi4eeG22B5InKC2x5i7MatGF2S/Gn1ACZ+fEInwu+Ru9J8DlnBv1/hIvg==",
+ "dependencies": {
+ "cookie": "^0.6.0",
+ "long": "^4.0.0",
+ "undici": "^5.13.0"
+ },
+ "engines": {
+ "node": ">=18.0"
+ }
+ },
+ "node_modules/@fastify/busboy": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
+ "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.5",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+ "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
+ "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.6",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
+ "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="
+ },
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
+ "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.19.54",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.54.tgz",
+ "integrity": "sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.9.16",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
+ "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A=="
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
+ },
+ "node_modules/@types/send": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+ "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.7",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+ "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/env-cmd": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz",
+ "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==",
+ "dev": true,
+ "dependencies": {
+ "commander": "^4.0.0",
+ "cross-spawn": "^7.0.0"
+ },
+ "bin": {
+ "env-cmd": "bin/env-cmd.js"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/jose": {
+ "version": "4.15.9",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
+ "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jwks-rsa": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz",
+ "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==",
+ "dependencies": {
+ "@types/express": "^4.17.17",
+ "@types/jsonwebtoken": "^9.0.2",
+ "debug": "^4.3.4",
+ "jose": "^4.14.6",
+ "limiter": "^1.1.5",
+ "lru-memoizer": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jwt-validate": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jwt-validate/-/jwt-validate-0.5.0.tgz",
+ "integrity": "sha512-9y9UK+zAExW0jkUkkgRpywG4ibimQAgYVnLu0p/rz669wZXJ+1Wdo82+EGio+WNHsb8De8eCscKry2pgI5tJoQ==",
+ "dependencies": {
+ "jsonwebtoken": "^9.0.2",
+ "jwks-rsa": "^3.1.0",
+ "lru-memoizer": "^2.3.0"
+ }
+ },
+ "node_modules/limiter": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
+ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
+ },
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+ },
+ "node_modules/long": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lru-memoizer": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz",
+ "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==",
+ "dependencies": {
+ "lodash.clonedeep": "^4.5.0",
+ "lru-cache": "6.0.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/undici": {
+ "version": "5.28.4",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
+ "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
+ "dependencies": {
+ "@fastify/busboy": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ }
+ }
+}
diff --git a/samples/da-repairs-oauth-validated/package.json b/samples/da-repairs-oauth-validated/package.json
new file mode 100644
index 0000000..1951058
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "apipluginoauth",
+ "version": "1.0.0",
+ "scripts": {
+ "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev",
+ "dev": "func start --typescript --language-worker=\"--inspect=9229\" --port \"7071\" --cors \"*\"",
+ "build": "tsc",
+ "watch:teamsfx": "tsc --watch",
+ "watch": "tsc -w",
+ "prestart": "npm run build",
+ "start": "npx func start",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@azure/functions": "^4.3.0",
+ "jwt-validate": "^0.5.0"
+ },
+ "devDependencies": {
+ "@types/node": "^18.11.9",
+ "env-cmd": "^10.1.0",
+ "typescript": "^4.1.6"
+ },
+ "main": "dist/src/functions/*.js"
+}
diff --git a/samples/da-repairs-oauth-validated/src/functions/repairs.ts b/samples/da-repairs-oauth-validated/src/functions/repairs.ts
new file mode 100644
index 0000000..ce90dd9
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/src/functions/repairs.ts
@@ -0,0 +1,102 @@
+/* This code sample provides a starter kit to implement server side logic for your Teams App in TypeScript,
+ * refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference for complete Azure Functions
+ * developer guide.
+ */
+
+import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
+
+// ADDED FOR TOKEN VALIDATION
+import { TokenValidator, ValidateTokenOptions, getEntraJwksUri } from 'jwt-validate';
+let validator: TokenValidator;
+// END ADDED FOR TOKEN VALIDATION
+
+import repairRecords from "../repairsData.json";
+
+/**
+ * This function handles the HTTP request and returns the repair information.
+ *
+ * @param {HttpRequest} req - The HTTP request.
+ * @param {InvocationContext} context - The Azure Functions context object.
+ * @returns {Promise} - A promise that resolves with the HTTP response containing the repair information.
+ */
+export async function repairs(
+ req: HttpRequest,
+ context: InvocationContext
+): Promise {
+ context.log("HTTP trigger function processed a request.");
+
+ // Initialize response.
+ const res: HttpResponseInit = {
+ status: 200,
+ jsonBody: {
+ results: repairRecords,
+ },
+ };
+
+ // Get the assignedTo query parameter.
+ const assignedTo = req.query.get("assignedTo");
+
+ // ADDED FOR TOKEN VALIDATION
+ // Try to validate the token and get user's basic information
+ try {
+ const { AAD_APP_CLIENT_ID, AAD_APP_TENANT_ID, AAD_APP_OAUTH_AUTHORITY } = process.env;
+ const token = req.headers.get("Authorization")?.split(" ")[1];
+ if (token) {
+
+ if (!validator) {
+ const entraJwksUri = await getEntraJwksUri(AAD_APP_TENANT_ID);
+ validator = new TokenValidator({
+ jwksUri: entraJwksUri
+ });
+ console.log("Token validator created");
+ }
+
+ const options: ValidateTokenOptions = {
+ audience: `${AAD_APP_CLIENT_ID}`,
+ // NOTE: Issuer will be different for non-public clouds
+ issuer: `${AAD_APP_OAUTH_AUTHORITY}/v2.0`,
+ scp: ["repairs_read"]
+ };
+
+ const validToken = await validator.validateToken(token, options);
+
+ const userId = validToken.oid;
+ const userName = validToken.name;
+ console.log(`Token is valid for user ${userName} (${userId})`);
+ } else {
+ console.error("No token found in request");
+ throw (new Error("No token found in request"));
+ }
+ }
+ catch (ex) {
+ // Token is missing or invalid - return a 401 error
+ console.error(ex);
+ return {
+ status: 401
+ };
+ }
+ // END ADDED FOR TOKEN VALIDATION
+
+ // If the assignedTo query parameter is not provided, return the response.
+ if (!assignedTo) {
+ return res;
+ }
+
+ // Filter the repair information by the assignedTo query parameter.
+ const repairs = repairRecords.filter((item) => {
+ const fullName = item.assignedTo.toLowerCase();
+ const query = assignedTo.trim().toLowerCase();
+ const [firstName, lastName] = fullName.split(" ");
+ return fullName === query || firstName === query || lastName === query;
+ });
+
+ // Return filtered repair records, or an empty array if no records were found.
+ res.jsonBody.results = repairs ?? [];
+ return res;
+}
+
+app.http("repairs", {
+ methods: ["GET"],
+ authLevel: "anonymous",
+ handler: repairs,
+});
diff --git a/samples/da-repairs-oauth-validated/src/repairsData.json b/samples/da-repairs-oauth-validated/src/repairsData.json
new file mode 100644
index 0000000..fd4227e
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/src/repairsData.json
@@ -0,0 +1,50 @@
+[
+ {
+ "id": "1",
+ "title": "Oil change",
+ "description": "Need to drain the old engine oil and replace it with fresh oil to keep the engine lubricated and running smoothly.",
+ "assignedTo": "Karin Blair",
+ "date": "2023-05-23",
+ "image": "https://www.howmuchisit.org/wp-content/uploads/2011/01/oil-change.jpg"
+ },
+ {
+ "id": "2",
+ "title": "Brake repairs",
+ "description": "Conduct brake repairs, including replacing worn brake pads, resurfacing or replacing brake rotors, and repairing or replacing other components of the brake system.",
+ "assignedTo": "Issac Fielder",
+ "date": "2023-05-24",
+ "image": "https://upload.wikimedia.org/wikipedia/commons/7/71/Disk_brake_dsc03680.jpg"
+ },
+ {
+ "id": "3",
+ "title": "Tire service",
+ "description": "Rotate and replace tires, moving them from one position to another on the vehicle to ensure even wear and removing worn tires and installing new ones.",
+ "assignedTo": "Karin Blair",
+ "date": "2023-05-24",
+ "image": "https://th.bing.com/th/id/OIP.N64J4jmqmnbQc5dHvTm-QAHaE8?pid=ImgDet&rs=1"
+ },
+ {
+ "id": "4",
+ "title": "Battery replacement",
+ "description": "Remove the old battery and install a new one to ensure that the vehicle start reliably and the electrical systems function properly.",
+ "assignedTo": "Ashley McCarthy",
+ "date": "2023-05-25",
+ "image": "https://i.stack.imgur.com/4ftuj.jpg"
+ },
+ {
+ "id": "5",
+ "title": "Engine tune-up",
+ "description": "This can include a variety of services such as replacing spark plugs, air filters, and fuel filters to keep the engine running smoothly and efficiently.",
+ "assignedTo": "Karin Blair",
+ "date": "2023-05-28",
+ "image": "https://th.bing.com/th/id/R.e4c01dd9f232947e6a92beb0a36294a5?rik=P076LRx7J6Xnrg&riu=http%3a%2f%2fupload.wikimedia.org%2fwikipedia%2fcommons%2ff%2ff3%2f1990_300zx_engine.jpg&ehk=f8KyT78eO3b%2fBiXzh6BZr7ze7f56TWgPST%2bY%2f%2bHqhXQ%3d&risl=&pid=ImgRaw&r=0"
+ },
+ {
+ "id": "6",
+ "title": "Suspension and steering repairs",
+ "description": "This can include repairing or replacing components of the suspension and steering systems to ensure that the vehicle handles and rides smoothly.",
+ "assignedTo": "Daisy Phillips",
+ "date": "2023-05-29",
+ "image": "https://i.stack.imgur.com/4v5OI.jpg"
+ }
+]
diff --git a/samples/da-repairs-oauth-validated/teamsapp.local.yml b/samples/da-repairs-oauth-validated/teamsapp.local.yml
new file mode 100644
index 0000000..cf5c98f
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/teamsapp.local.yml
@@ -0,0 +1,129 @@
+# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.7/yaml.schema.json
+# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
+# Visit https://aka.ms/teamsfx-actions for details on actions
+version: v1.7
+
+provision:
+ # Creates a new Microsoft Entra app to authenticate users if
+ # the environment variable that stores clientId is empty
+ - uses: aadApp/create
+ with:
+ # Note: when you run aadApp/update, the Microsoft Entra app name will be updated
+ # based on the definition in manifest. If you don't want to change the
+ # name, make sure the name in Microsoft Entra manifest is the same with the name
+ # defined here.
+ name: repairs-oauth-aad
+ # If the value is false, the action will not generate client secret for you
+ generateClientSecret: true
+ # Authenticate users with a Microsoft work or school account in your
+ # organization's Microsoft Entra tenant (for example, single tenant).
+ signInAudience: AzureADMyOrg
+ # Write the information of created resources into environment file for the
+ # specified environment variable(s).
+ writeToEnvironmentFile:
+ clientId: AAD_APP_CLIENT_ID
+ # Environment variable that starts with `SECRET_` will be stored to the
+ # .env.{envName}.user environment file
+ clientSecret: SECRET_AAD_APP_CLIENT_SECRET
+ objectId: AAD_APP_OBJECT_ID
+ tenantId: AAD_APP_TENANT_ID
+ authority: AAD_APP_OAUTH_AUTHORITY
+ authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST
+
+ # Creates a Teams app
+ - uses: teamsApp/create
+ with:
+ # Teams app name
+ name: Repairs-OAuth${{APP_NAME_SUFFIX}}
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ teamsAppId: TEAMS_APP_ID
+
+ # Apply the Microsoft Entra manifest to an existing Microsoft Entra app. Will use the object id in
+ # manifest file to determine which Microsoft Entra app to update.
+ - uses: aadApp/update
+ with:
+ # Relative path to this file. Environment variables in manifest will
+ # be replaced before apply to Microsoft Entra app
+ manifestPath: ./aad.manifest.json
+ outputFilePath: ./build/aad.manifest.json
+
+ - uses: oauth/register
+ with:
+ name: oAuth2AuthCode
+ flow: authorizationCode
+ appId: ${{TEAMS_APP_ID}}
+ clientId: ${{AAD_APP_CLIENT_ID}}
+ clientSecret: ${{SECRET_AAD_APP_CLIENT_SECRET}}
+ # Path to OpenAPI description document
+ apiSpecPath: ./appPackage/apiSpecificationFile/repair.yml
+ writeToEnvironmentFile:
+ configurationId: OAUTH2AUTHCODE_CONFIGURATION_ID
+
+ # Set required variables for local launch
+ - uses: script
+ with:
+ run:
+ echo "::set-teamsfx-env FUNC_NAME=repair";
+ echo "::set-teamsfx-env FUNC_ENDPOINT=http://localhost:7071";
+
+ # Add to copy auth variables to local settings
+ - uses: file/createOrUpdateEnvironmentFile
+ with:
+ target: ./.localConfigs
+ envs:
+ AAD_APP_CLIENT_ID: ${{AAD_APP_CLIENT_ID}}
+ AAD_APP_TENANT_ID: ${{AAD_APP_TENANT_ID}}
+ AAD_APP_OAUTH_AUTHORITY: ${{AAD_APP_OAUTH_AUTHORITY}}
+
+ # Build Teams app package with latest env value
+ - uses: teamsApp/zipAppPackage
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+ outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ outputFolder: ./appPackage/build
+
+ # Validate app package using validation rules
+ - uses: teamsApp/validateAppPackage
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Apply the Teams app manifest to an existing Teams app in
+ # Teams Developer Portal.
+ # Will use the app id in manifest file to determine which Teams app to update.
+ - uses: teamsApp/update
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Extend your Teams app to Outlook and the Microsoft 365 app
+ - uses: teamsApp/extendToM365
+ with:
+ # Relative path to the build app package.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ titleId: M365_TITLE_ID
+ appId: M365_APP_ID
+
+deploy:
+ # Install development tool(s)
+ - uses: devTool/install
+ with:
+ func:
+ version: ~4.0.5530
+ symlinkDir: ./devTools/func
+ # Write the information of installed development tool(s) into environment
+ # file for the specified environment variable(s).
+ writeToEnvironmentFile:
+ funcPath: FUNC_PATH
+
+ # Run npm command
+ - uses: cli/runNpmCommand
+ name: install dependencies
+ with:
+ args: install --no-audit
diff --git a/samples/da-repairs-oauth-validated/teamsapp.yml b/samples/da-repairs-oauth-validated/teamsapp.yml
new file mode 100644
index 0000000..5296e4b
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/teamsapp.yml
@@ -0,0 +1,183 @@
+# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.7/yaml.schema.json
+# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
+# Visit https://aka.ms/teamsfx-actions for details on actions
+version: v1.7
+
+environmentFolderPath: ./env
+
+# Triggered when 'teamsapp provision' is executed
+provision:
+ # Creates a new Microsoft Entra app to authenticate users if
+ # the environment variable that stores clientId is empty
+ - uses: aadApp/create
+ with:
+ # Note: when you run aadApp/update, the Microsoft Entra app name will be updated
+ # based on the definition in manifest. If you don't want to change the
+ # name, make sure the name in Microsoft Entra manifest is the same with the name
+ # defined here.
+ name: Repairs-OAuth-aad
+ # If the value is false, the action will not generate client secret for you
+ generateClientSecret: true
+ # Authenticate users with a Microsoft work or school account in your
+ # organization's Microsoft Entra tenant (for example, single tenant).
+ signInAudience: AzureADMyOrg
+ # Write the information of created resources into environment file for the
+ # specified environment variable(s).
+ writeToEnvironmentFile:
+ clientId: AAD_APP_CLIENT_ID
+ # Environment variable that starts with `SECRET_` will be stored to the
+ # .env.{envName}.user environment file
+ clientSecret: SECRET_AAD_APP_CLIENT_SECRET
+ objectId: AAD_APP_OBJECT_ID
+ tenantId: AAD_APP_TENANT_ID
+ authority: AAD_APP_OAUTH_AUTHORITY
+ authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST
+
+ # Creates a Teams app
+ - uses: teamsApp/create
+ with:
+ # Teams app name
+ name: Repairs-OAuth${{APP_NAME_SUFFIX}}
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ teamsAppId: TEAMS_APP_ID
+
+ - uses: arm/deploy # Deploy given ARM templates parallelly.
+ with:
+ # AZURE_SUBSCRIPTION_ID is a built-in environment variable,
+ # if its value is empty, TeamsFx will prompt you to select a subscription.
+ # Referencing other environment variables with empty values
+ # will skip the subscription selection prompt.
+ subscriptionId: ${{AZURE_SUBSCRIPTION_ID}}
+ # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable,
+ # if its value is empty, TeamsFx will prompt you to select or create one
+ # resource group.
+ # Referencing other environment variables with empty values
+ # will skip the resource group selection prompt.
+ resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}}
+ templates:
+ - path: ./infra/azure.bicep # Relative path to this file
+ # Relative path to this yaml file.
+ # Placeholders will be replaced with corresponding environment
+ # variable before ARM deployment.
+ parameters: ./infra/azure.parameters.json
+ # Required when deploying ARM template
+ deploymentName: Create-resources-for-api-plugin
+ # Teams Toolkit will download this bicep CLI version from github for you,
+ # will use bicep CLI in PATH if you remove this config.
+ bicepCliVersion: v0.9.1
+
+ # Apply the Microsoft Entra manifest to an existing Microsoft Entra app. Will use the object id in
+ # manifest file to determine which Microsoft Entra app to update.
+ - uses: aadApp/update
+ with:
+ # Relative path to this file. Environment variables in manifest will
+ # be replaced before apply to Microsoft Entra app
+ manifestPath: ./aad.manifest.json
+ outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json
+
+ - uses: oauth/register
+ with:
+ name: oAuth2AuthCode
+ flow: authorizationCode
+ appId: ${{TEAMS_APP_ID}}
+ clientId: ${{AAD_APP_CLIENT_ID}}
+ clientSecret: ${{SECRET_AAD_APP_CLIENT_SECRET}}
+ # Path to OpenAPI description document
+ apiSpecPath: ./appPackage/apiSpecificationFile/repair.yml
+ writeToEnvironmentFile:
+ configurationId: OAUTH2AUTHCODE_CONFIGURATION_ID
+
+ # Build Teams app package with latest env value
+ - uses: teamsApp/zipAppPackage
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+ outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ outputFolder: ./appPackage/build
+
+ # Validate app package using validation rules
+ - uses: teamsApp/validateAppPackage
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Apply the Teams app manifest to an existing Teams app in
+ # Teams Developer Portal.
+ # Will use the app id in manifest file to determine which Teams app to update.
+ - uses: teamsApp/update
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Extend your Teams app to Outlook and the Microsoft 365 app
+ - uses: teamsApp/extendToM365
+ with:
+ # Relative path to the build app package.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ titleId: M365_TITLE_ID
+ appId: M365_APP_ID
+
+# Triggered when 'teamsapp deploy' is executed
+deploy:
+ # Run npm command
+ - uses: cli/runNpmCommand
+ name: install dependencies
+ with:
+ args: install
+
+ - uses: cli/runNpmCommand
+ name: build app
+ with:
+ args: run build --if-present
+
+ # Deploy your application to Azure Functions using the zip deploy feature.
+ # For additional details, see at https://aka.ms/zip-deploy-to-azure-functions
+ - uses: azureFunctions/zipDeploy
+ with:
+ # deploy base folder
+ artifactFolder: .
+ # Ignore file location, leave blank will ignore nothing
+ ignoreFile: .funcignore
+ # The resource id of the cloud resource to be deployed to.
+ # This key will be generated by arm/deploy action automatically.
+ # You can replace it with your existing Azure Resource id
+ # or add it to your environment variable file.
+ resourceId: ${{API_FUNCTION_RESOURCE_ID}}
+
+# Triggered when 'teamsapp publish' is executed
+publish:
+ # Build Teams app package with latest env value
+ - uses: teamsApp/zipAppPackage
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+ outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ outputFolder: ./appPackage/build
+ # Validate app package using validation rules
+ - uses: teamsApp/validateAppPackage
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Apply the Teams app manifest to an existing Teams app in
+ # Teams Developer Portal.
+ # Will use the app id in manifest file to determine which Teams app to update.
+ - uses: teamsApp/update
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Publish the app to
+ # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps)
+ # for review and approval
+ - uses: teamsApp/publishAppPackage
+ with:
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ publishedAppId: TEAMS_APP_PUBLISHED_APP_ID
+projectId: 3bbfb625-ccb6-4542-9925-63b86a2e6927
diff --git a/samples/da-repairs-oauth-validated/tsconfig.json b/samples/da-repairs-oauth-validated/tsconfig.json
new file mode 100644
index 0000000..a8d6956
--- /dev/null
+++ b/samples/da-repairs-oauth-validated/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "outDir": "dist",
+ "rootDir": ".",
+ "sourceMap": true,
+ "strict": false,
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "typeRoots": ["./node_modules/@types"]
+ }
+}
\ No newline at end of file
diff --git a/samples/da-repairs-oauth/env/.env.local b/samples/da-repairs-oauth/env/.env.local
index c33ac4f..ed98233 100644
--- a/samples/da-repairs-oauth/env/.env.local
+++ b/samples/da-repairs-oauth/env/.env.local
@@ -5,3 +5,19 @@ TEAMSFX_ENV=local
APP_NAME_SUFFIX=local
# Generated during provision, you can also add your own variables.
+
+OPENAPI_SERVER_URL=https://phxzt7jw-7071.use.devtunnels.ms
+AAD_APP_CLIENT_ID=e6dc2eb0-684f-434e-b0d3-fbac7a47fb14
+AAD_APP_OBJECT_ID=7790fac8-3614-4172-be4b-d3c5aa25aad1
+AAD_APP_TENANT_ID=883a30d4-ca91-4cbb-b025-9c0f6d7820a0
+AAD_APP_OAUTH_AUTHORITY=https://login.microsoftonline.com/883a30d4-ca91-4cbb-b025-9c0f6d7820a0
+AAD_APP_OAUTH_AUTHORITY_HOST=https://login.microsoftonline.com
+TEAMS_APP_ID=3bda7b14-01bf-47ba-a9b7-faa79cf44b1c
+TEAMS_APP_TENANT_ID=883a30d4-ca91-4cbb-b025-9c0f6d7820a0
+AAD_APP_ACCESS_AS_USER_PERMISSION_ID=24337f31-bd0f-4523-8675-58b62127e9af
+OAUTH2AUTHCODE_CONFIGURATION_ID=ODgzYTMwZDQtY2E5MS00Y2JiLWIwMjUtOWMwZjZkNzgyMGEwIyNkMTI5ZThiMS00NzJjLTRiM2YtOGM1YS00NTJjZmYyZWIzYTE=
+FUNC_NAME=repair
+FUNC_ENDPOINT=http://localhost:7071
+M365_TITLE_ID=U_3c188b85-2ca0-e920-a8f2-64e82109051d
+M365_APP_ID=65b097b3-f5fc-48bc-afec-077bd44d45bc
+FUNC_PATH=c:\Source\Forks\copilot-pro-dev-samples\samples\da-repairs-oauth\devTools\func
\ No newline at end of file
diff --git a/samples/da-repairs-oauth/teamsapp.yml b/samples/da-repairs-oauth/teamsapp.yml
index 2d0238d..06c8791 100644
--- a/samples/da-repairs-oauth/teamsapp.yml
+++ b/samples/da-repairs-oauth/teamsapp.yml
@@ -180,3 +180,4 @@ publish:
# the specified environment variable(s).
writeToEnvironmentFile:
publishedAppId: TEAMS_APP_PUBLISHED_APP_ID
+projectId: 0837938e-eb6a-4e86-8e6d-8ab549dcbffe