├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── ask-a-question.md │ └── bug_report.md ├── dependabot.yml └── workflows │ ├── auto-merge-dependabot.yml │ ├── codeql-analysis.yml │ └── nodejs.yml ├── .gitignore ├── .prettierrc ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICES.md ├── README-localized ├── README-es-es.md ├── README-fr-fr.md ├── README-ja-jp.md ├── README-pt-br.md ├── README-ru-ru.md └── README-zh-cn.md ├── README.md ├── SECURITY.md ├── TROUBLESHOOTING.md ├── app.js ├── bin └── www ├── helpers ├── certHelper.js ├── dbHelper.js ├── graphHelper.js ├── socketHelper.js └── tokenHelper.js ├── images ├── aad-portal-app-registrations.png ├── copy-secret-value.png ├── ngrok-https-url.png ├── register-an-app.png ├── remove-configured-permission.png ├── teams-channel-notifications.png └── user-inbox-notifications.png ├── package-lock.json ├── package.json ├── public ├── images │ └── g-raph.png ├── javascript │ └── watch-client.js └── stylesheets │ └── style.css ├── routes ├── apponly.js ├── delegated.js ├── index.js ├── lifecycle.js ├── listen.js └── watch.js ├── sample.env ├── tests └── configTests.js └── views ├── error.pug ├── index.pug ├── layout.pug └── watch.pug /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @microsoftgraph/msgraph-devx-samples-write -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask-a-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: Ask a question about Graph, adding features to this sample, etc. 4 | title: '' 5 | labels: question, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you for taking an interest in Microsoft Graph development! Please feel free to ask a question here, but keep in mind the following: 11 | 12 | - This is not an official Microsoft support channel, and our ability to respond to questions here is limited. Questions about Graph, or questions about adding a new feature to the sample, will be answered on a best-effort basis. 13 | - Questions about Microsoft Graph should be asked on [Microsoft Q&A](https://learn.microsoft.com/answers/products/graph). 14 | - Issues with Microsoft Graph itself should be handled through [support](https://developer.microsoft.com/graph/support). 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug report, needs triage 6 | assignees: '' 7 | --- 8 | 9 | ### Describe the bug 10 | 11 | A clear and concise description of the bug. 12 | 13 | ### To Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. Go to '...' 18 | 1. Click on '....' 19 | 1. Scroll down to '....' 20 | 1. See error 21 | 22 | ### Expected behavior 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | ### Screenshots 27 | 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | ### Desktop 31 | 32 | - OS: [e.g. iOS] 33 | - Browser [e.g. chrome, safari] 34 | - Version [e.g. 22] 35 | 36 | ### Dependency versions 37 | 38 | - Authentication library (MSAL, etc.) version: 39 | - Graph library (Graph SDK, REST library, etc.) version: 40 | 41 | ### Additional context 42 | 43 | Add any other context about the problem here. 44 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: / 10 | schedule: 11 | interval: weekly 12 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Auto-merge dependabot updates 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | permissions: 8 | pull-requests: write 9 | contents: write 10 | 11 | jobs: 12 | 13 | dependabot-merge: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | if: ${{ github.actor == 'dependabot[bot]' }} 18 | 19 | steps: 20 | - name: Dependabot metadata 21 | id: metadata 22 | uses: dependabot/fetch-metadata@v2.4.0 23 | with: 24 | github-token: "${{ secrets.GITHUB_TOKEN }}" 25 | 26 | - name: Enable auto-merge for Dependabot PRs 27 | # Only if version bump is not a major version change 28 | if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}} 29 | run: gh pr merge --auto --merge "$PR_URL" 30 | env: 31 | PR_URL: ${{github.event.pull_request.html_url}} 32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 33 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | CodeQL-Build: 13 | 14 | runs-on: ubuntu-latest 15 | permissions: 16 | security-events: write 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | 22 | # Initializes the CodeQL tools for scanning. 23 | - name: Initialize CodeQL 24 | uses: github/codeql-action/init@v3 25 | # Override language selection by uncommenting this and choosing your languages 26 | # with: 27 | # languages: go, javascript, csharp, python, cpp, java 28 | 29 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 30 | # If this step fails, then you should remove it and run the build manually (see below) 31 | - name: Autobuild 32 | uses: github/codeql-action/autobuild@v3 33 | 34 | # ℹ️ Command-line programs to run using the OS shell. 35 | # 📚 https://git.io/JvXDl 36 | 37 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 38 | # and modify them (or add more) to build your code if your project 39 | # uses a compiled language 40 | 41 | #- run: | 42 | # make bootstrap 43 | # make release 44 | 45 | - name: Perform CodeQL Analysis 46 | uses: github/codeql-action/analyze@v3 47 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [18.x, 20.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run build --if-present 29 | - run: npm run lint 30 | # - run: npm test 31 | # env: 32 | # CI: true 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # database files 2 | *.sqlite3 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 31 | node_modules 32 | # Windows image file caches 33 | Thumbs.db 34 | ehthumbs.db 35 | 36 | # Folder config file 37 | Desktop.ini 38 | 39 | # Recycle Bin used on file shares 40 | $RECYCLE.BIN/ 41 | 42 | # Windows Installer files 43 | *.cab 44 | *.msi 45 | *.msm 46 | *.msp 47 | 48 | # Windows shortcuts 49 | *.lnk 50 | .DS_Store 51 | .AppleDouble 52 | .LSOverride 53 | 54 | # Icon must end with two \r 55 | Icon 56 | 57 | 58 | # Thumbnails 59 | ._* 60 | 61 | # Files that might appear in the root of a volume 62 | .DocumentRevisions-V100 63 | .fseventsd 64 | .Spotlight-V100 65 | .TemporaryItems 66 | .Trashes 67 | .VolumeIcon.icns 68 | 69 | # Directories potentially created on remote AFP share 70 | .AppleDB 71 | .AppleDesktop 72 | Network Trash Folder 73 | Temporary Items 74 | .apdisk 75 | *~ 76 | 77 | # KDE directory preferences 78 | .directory 79 | 80 | # Linux trash folder which might appear on any partition or disk 81 | .Trash-* 82 | # -*- mode: gitignore; -*- 83 | *~ 84 | \#*\# 85 | /.emacs.desktop 86 | /.emacs.desktop.lock 87 | *.elc 88 | auto-save-list 89 | tramp 90 | .\#* 91 | 92 | # Org-mode 93 | .org-id-locations 94 | *_archive 95 | 96 | # flymake-mode 97 | *_flymake.* 98 | 99 | # eshell files 100 | /eshell/history 101 | /eshell/lastdir 102 | 103 | # elpa packages 104 | /elpa/ 105 | 106 | # reftex files 107 | *.rel 108 | 109 | # AUCTeX auto folder 110 | /auto/ 111 | 112 | # cask packages 113 | .cask/ 114 | [._]*.s[a-w][a-z] 115 | [._]s[a-w][a-z] 116 | *.un~ 117 | Session.vim 118 | .netrwhist 119 | *~ 120 | .settings 121 | .vs 122 | typings 123 | 124 | # tmp files generated by openssl 125 | .tmp/ 126 | *.pfx 127 | *.pem 128 | 129 | # Environment variables 130 | .env 131 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "endOfLine": "auto" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "preLaunchTask": "npm: test", 15 | "program": "${workspaceFolder}\\bin\\www" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "apponly", 4 | "choco", 5 | "g", 6 | "holowaychuk", 7 | "jwks", 8 | "Multitenant", 9 | "raph", 10 | "resave", 11 | "signin", 12 | "signout" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "test", 7 | "group": "test", 8 | "problemMatcher": [], 9 | "label": "npm: test", 10 | "detail": "mocha ./tests/configTests.js" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | [https://cla.microsoft.com](https://cla.microsoft.com). 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 14 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NOTICES.md: -------------------------------------------------------------------------------- 1 | # Notices 2 | 3 | This project uses the following third-party components: 4 | 5 | NodeJS, Copyright (c) by Joyent, Inc. and other Node contributors, is available under the [The MIT License](https://raw.githubusercontent.com/nodejs/node/master/LICENSE). 6 | 7 | body-parser, Copyright (c) by Douglas Christopher Wilson, is available under [The MIT License](https://raw.githubusercontent.com/expressjs/body-parser/master/LICENSE). 8 | 9 | debug, Copyright (c) by TJ Holowaychuk, is available under [The MIT License](http://opensource.org/licenses/MIT). 10 | 11 | express, Copyright (c) by Douglas Christopher Wilson, is available under [The MIT License](https://raw.githubusercontent.com/strongloop/express/master/LICENSE). 12 | 13 | morgan, Copyright (c) by Douglas Christopher Wilson, is available under [The MIT License](https://raw.githubusercontent.com/expressjs/morgan/master/LICENSE). 14 | 15 | pug, Copyright (c) by TJ Holowaychuk, is available under [The MIT License](https://raw.githubusercontent.com/pugjs/pug/master/packages/pug/LICENSE). 16 | 17 | socket.io, Copyright (c) by Automattic, is available under [The MIT License](https://raw.githubusercontent.com/socketio/socket.io/master/LICENSE). 18 | -------------------------------------------------------------------------------- /README-localized/README-es-es.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | products: 4 | - ms-graph 5 | languages: 6 | - nodejs 7 | - javascript 8 | description: "Cree suscripciones de webhook de Microsoft Graph para una aplicación Node.js, para que pueda recibir notificaciones de cambios en los datos de la cuenta de Microsoft de un usuario." 9 | extensions: 10 | contentType: samples 11 | technologies: 12 | - Microsoft Graph 13 | createdDate: 3/9/2016 4:12:18 PM 14 | --- 15 | # Ejemplo de webhooks de Microsoft Graph para Node.js 16 | 17 | [![Estado de la compilación](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample.svg)](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample) 18 | 19 | ## Tabla de contenido. ## 20 | * [Introducción](#introduction) 21 | 22 | * [Capturas de pantalla](#screenshots) 23 | 24 | * [Requisitos previos](#prerequisites) 25 | 26 | * [Registrar la aplicación](#Register-the-app) 27 | 28 | * [Configurar un túnel para el localhost](#Configure-a-tunnel-for-your-localhost) 29 | 30 | * [Configurar y ejecutar la aplicación web](#Configure-and-run-the-web-app) 31 | 32 | * [Colaboradores](#contributing) 33 | 34 | * [Preguntas y comentarios](#Questions-and-Comments) 35 | 36 | * [Recursos adicionales](#Additional-resources) 37 | 38 | 39 | ## Introducción 40 | 41 | 42 | En este ejemplo de Node.js, se muestra cómo empezar a recibir notificaciones de Microsoft Graph. Las tareas comunes que una aplicación web realiza con los webhooks Microsoft Graph. 43 | 44 | - Inicie sesión con la cuenta profesional o educativa de los usuarios para obtener un token de acceso. 45 | - Usar el token de acceso para crear una suscripción de webhook. 46 | - Devuelva un token de validación para confirmar la dirección URL de notificación. 47 | - Preste atención a las notificaciones de Microsoft Graph. 48 | - Solicite más información en Microsoft Office 365 usando datos en la notificación. 49 | 50 | ## Capturas de pantalla 51 | 52 | 53 | 1. En primer lugar, necesita iniciar sesión. 54 | 55 | ![iniciar sesión](https://user-images.githubusercontent.com/3375461/31968683-c373ad30-b8c6-11e7-9d01-413fab9fd6d5.png) 56 | 57 | 1. Una vez que haya iniciado sesión, la aplicación atenderá los correos electrónicos entrantes. 58 | 59 | ![atender](https://user-images.githubusercontent.com/3375461/31968718-e19696c4-b8c6-11e7-91f2-f1806be0b134.png) 60 | 61 | 1. Después de enviar el correo electrónico a la dirección, verá el correo electrónico en la aplicación. 62 | 63 | ![correo electrónico](https://user-images.githubusercontent.com/3375461/31968754-0ce4dafc-b8c7-11e7-8458-8152d598228e.png) 64 | 65 | ## Requisitos previos 66 | 67 | 68 | Para usar el ejemplo de webhook, necesita lo siguiente: 69 | 70 | - [Node.js](https://nodejs.org/) (versión 4 o 5) 71 | - Una [cuenta profesional o educativa](http://dev.office.com/devprogram). 72 | 73 | ## Registrar la aplicación 74 | 75 | 76 | Esta aplicación usa el extremo de Azure AD, por lo que puede registrarlo en [Azure Portal](https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/ApplicationsListBlade). 77 | 78 | 1. Inicie sesión en Microsoft Azure Portal con una cuenta personal, profesional o educativa de Microsoft. 79 | 1. Si su cuenta le da acceso a más de un inquilino, seleccione su cuenta en la esquina superior derecha y establezca su sesión de portal con el inquilino Azure AD deseado (usando cambiar directorio). 80 | 1. En el panel de navegación de la izquierda, seleccione el **servicio de Azure Active Directory**, y luego seleccione **registros de aplicaciones.** ![](readme-images/registrations.png) 81 | 82 | 1. Seleccione **Nuevo registro de aplicaciones**. 83 | 84 | 1. Escriba un nombre descriptivo para la aplicación. 85 | 1. Seleccione “Aplicación web o API” como **Tipo de aplicación**. 86 | 1. Escriba `http://localhost:3000/callback` para la **URL de inicio de sesión**. 87 | 1. En la sección tipos de cuentas admitidas, seleccione cuentas en cualquier directorio organizacional y cuentas personales de Microsoft (por ejemplo, Skype, Xbox, Outlook.com). 88 | 1. Haga clic en **Crear**. 89 | 90 | 1. Escoja la nueva aplicación de la lista de aplicaciones registradas. 91 | En la página **Información general** de la aplicación, busque el valor **Id. de la aplicación (cliente)** y guárdelo para más tarde. 92 | Lo necesitará para configurar el archivo de configuración de Visual Studio para este proyecto. 93 | ![](readme-images/client.png) 94 | 1. Configurar los permisos de la aplicación: 95 | 96 | 1. Seleccione **Configuración** > **Permisos necesarios** > **Agregar**. 97 | 1. Escoja **Seleccionar una API** > **Microsoft Graph** y después haga clic en **Seleccionar**. 98 | 1. Elija **Seleccionar permisos**, desplácese hasta **Permisos delegados**, elija **Mail.Read** y después haga clic en **Seleccionar**. 99 | 1. Haga clic en **Listo**. 100 | ![](readme-images/permissions.png) 101 | 102 | 1. Seleccione **Certificados y secretos** en **Administrar**. Seleccione el botón **Nuevo secreto de cliente**. Escriba un valor en Descripción y seleccione una de las opciones de Expirar y luego seleccione **Agregar**. 103 | 104 | ![](readme-images/secrets.png) 105 | 106 | 1. **Importante**: Copie el valor clave: este es el secreto de la aplicación. No podrá volver a obtener acceso a este valor una vez que abandone esta hoja. 107 | 108 | Deberá usar el **ID** y el **secreto de aplicación** para configurarla. 109 | 110 | ## Configurar un túnel para el localhost 111 | 112 | El ejemplo usa localhost como servidor de desarrollo. Por este motivo, es necesario un túnel que pueda reenviar las solicitudes de una dirección URL de Internet a su localhost. Si, por algún motivo, no desea usar un túnel, vea [Hosting without a tunnel](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Hosting-the-sample-without-a-tunnel) (Hospedar sin un túnel). Si desea obtener una explicación detallada sobre los motivos por los que debe usar un túnel, vea [Why do I have to use a tunnel?](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Why-do-I-have-to-use-a-tunnel) (¿Por qué tengo que usar un túnel?) 113 | 114 | Para este ejemplo, usamos [ngrok](https://ngrok.com/) para crear el túnel. Para configurar ngrok: 115 | 116 | 1. [Descargue](https://ngrok.com/download) y descomprima los archivos binarios de ngrok para su plataforma. 117 | 1. Escriba el siguiente comando: 118 | 119 | ```Shell 120 | ngrok http 3000 121 | ``` 122 | 123 | 1. Tome nota de la *dirección URL pública https* que ngrok le proporciona. Este es un ejemplo: 124 | 125 | ```http 126 | https://{NGROK_ID}.ngrok.io 127 | ``` 128 | 129 | Necesitará el valor `NGROK_ID` en la siguiente sección. 130 | 131 | ## Configurar y ejecutar la aplicación web 132 | 133 | 1. Use un editor de texto para abrir `constants.js`. 134 | 1. Reemplace `ENTER_YOUR_CLIENT_ID` por el identificador de cliente de la aplicación registrada en Azure. 135 | 1. Reemplace `ENTER_YOUR_SECRET` con el secreto de cliente de la aplicación registrada en Azure. 136 | 1. Reemplace `NGROK_ID` con el valor de *dirección URL https pública* de la sección anterior. 137 | ![](const) 138 | 1. Instale las dependencias ejecutando el siguiente comando: 139 | 140 | ```Shell 141 | npm install 142 | ``` 143 | 144 | 1. Inicie la aplicación de con el siguiente comando: 145 | 146 | ```Shell 147 | npm start 148 | ``` 149 | > **Nota:** También puede hacer que la aplicación espere a un depurador. Para esperar a un depurador, utilice el siguiente comando en su lugar: 150 | > 151 | > ```Shell 152 | > npm run debug 153 | > ``` 154 | También puede adjuntar el depurador incluido en Microsoft Visual Studio Code. Para más información, vea [Debugging in Visual Studio Code](https://code.visualstudio.com/Docs/editor/debugging) (Depurar con Visual Studio Code). 155 | 156 | 1. Abra el explorador y vaya a [http://localhost:3000](http://localhost:3000). 157 | 158 | ## Colaboradores 159 | 160 | Si quiere hacer su aportación a este ejemplo, vea [CONTRIBUTING.MD](/CONTRIBUTING.md). 161 | 162 | Este proyecto ha adoptado el [Código de conducta de código abierto de Microsoft](https://opensource.microsoft.com/codeofconduct/). Para obtener más información, vea [Preguntas frecuentes sobre el código de conducta](https://opensource.microsoft.com/codeofconduct/faq/) o póngase en contacto con [opencode@microsoft.com](mailto:opencode@microsoft.com) si tiene otras preguntas o comentarios. 163 | 164 | ## Preguntas y comentarios 165 | 166 | Nos encantaría recibir sus comentarios acerca del ejemplo de Webhook de Microsoft Graph. Puede enviarnos sus preguntas y sugerencias a través de la sección [Problemas](https://github.com/OfficeDev/Microsoft-Graph-NodeJs-Webhooks/issues) de este repositorio. 167 | 168 | ¿Tiene preguntas sobre el desarrollo de Office 365? Publíquelas en [Stack Overflow](http://stackoverflow.com/questions/tagged/Office365+API). Asegúrese de etiquetar sus preguntas o comentarios con \[Office365] y \[API]. 169 | 170 | ## Recursos adicionales 171 | 172 | - [Información general de Microsoft Graph](http://graph.microsoft.io/) 173 | - [Documentación de referencia de la suscripción](https://graph.microsoft.io/en-us/docs/api-reference/beta/resources/subscription) 174 | 175 | ## Derechos de autor 176 | 177 | Copyright (c) 2019 Microsoft. Todos los derechos reservados. 178 | -------------------------------------------------------------------------------- /README-localized/README-fr-fr.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | products: 4 | - ms-graph 5 | languages: 6 | - nodejs 7 | - javascript 8 | description: "Créez des abonnements webhook Microsoft Graph pour une application Node.js afin qu’elle puisse recevoir des notifications lorsque des modifications sont apportées aux données de compte Microsoft d’un utilisateur." 9 | extensions: 10 | contentType: samples 11 | technologies: 12 | - Microsoft Graph 13 | createdDate: 3/9/2016 4:12:18 PM 14 | --- 15 | # Exemple de webhooks Microsoft Graph pour Node.js 16 | 17 | [![État de création](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample.svg)](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample) 18 | 19 | ## Table des matières. ## 20 | * [Introduction](#introduction) 21 | 22 | * [Captures d’écran](#screenshots) 23 | 24 | * [Conditions préalables](#prerequisites) 25 | 26 | * [Inscription de l’application](#Register-the-app) 27 | 28 | * [Configurer un tunnel pour votre localhost](#Configure-a-tunnel-for-your-localhost) 29 | 30 | * [Configurer et exécuter l’application web](#Configure-and-run-the-web-app) 31 | 32 | * [Contribution](#contributing) 33 | 34 | * [Questions et commentaires](#Questions-and-Comments) 35 | 36 | * [Ressources supplémentaires](#Additional-resources) 37 | 38 | 39 | ## Introduction 40 | 41 | 42 | Cet exemple Node.js présente comment commencer à recevoir des notifications de Microsoft Graph. Voici les tâches habituelles qu’une application web effectue avec des webhooks Microsoft Graph. 43 | 44 | - Connectez vos utilisateurs à l’aide de leur compte professionnel ou scolaire pour l'obtention d'un jeton d’accès. 45 | - Utiliser le jeton d'accès pour créer un abonnement webhook. 46 | - Renvoyez un jeton de validation pour confirmer l'URL de notification. 47 | - Surveillez les notifications provenant de Microsoft Graph. 48 | - Demandez davantage d’informations dans Microsoft Office 365 en utilisant les données de la notification. 49 | 50 | ## Captures d’écran 51 | 52 | 53 | 1. Vous devez tout d'abord vous connecter. 54 | 55 | ![Se connecter](https://user-images.githubusercontent.com/3375461/31968683-c373ad30-b8c6-11e7-9d01-413fab9fd6d5.png) 56 | 57 | 1. Une fois connecté, l’application observe les messages électroniques entrants. 58 | 59 | ![observation](https://user-images.githubusercontent.com/3375461/31968718-e19696c4-b8c6-11e7-91f2-f1806be0b134.png) 60 | 61 | 1. Après envoi de l'e-mail vers l’adresse, celui-ci apparaît dans l’application. 62 | 63 | ![e-mail](https://user-images.githubusercontent.com/3375461/31968754-0ce4dafc-b8c7-11e7-8458-8152d598228e.png) 64 | 65 | ## Conditions préalables 66 | 67 | 68 | Pour utiliser l'exemple webhook, l'élément suivant est nécessaire : 69 | 70 | - [Node.js](https://nodejs.org/) version 4 ou 5. 71 | - Un [compte professionnel ou scolaire](http://dev.office.com/devprogram). 72 | 73 | ## Inscription de l’application 74 | 75 | 76 | Cette application utilise le point de terminaison Azure Active Directory. Vous devez donc l’enregistrer sur le [Portail Azure](https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/ApplicationsListBlade). 77 | 78 | 1. Connectez-vous au portail Microsoft Azure à l’aide d’un compte professionnel ou scolaire, ou d’un compte Microsoft personnel. 79 | 1. Si votre compte vous propose un accès à plusieurs clients, sélectionnez votre compte en haut à droite et définissez votre session de portail sur le client Azure AD souhaité (à l’aide de Changer de répertoire). 80 | 1. Dans le volet de navigation gauche, sélectionnez le service **Azure Active Directory**, puis sélectionnez **Inscriptions d’applications** 81 | ![](readme-images/registrations.png). 82 | 83 | 1. Choisissez **Nouvelle inscription d’application**. 84 | 85 | 1. Entrez un nom convivial pour l’application. 86 | 1. Choisissez « Web app/API » en tant que **Type d'application**. 87 | 1. Entrez `http://localhost:3000/callback` comme **URL de connexion**. 88 | 1. Dans la section types de comptes pris en charge, sélectionnez Comptes dans un annuaire organisationnel et les comptes personnels Microsoft (par ex. Skype, Xbox, Outlook.com). 89 | 1. Cliquez sur **Créer**. 90 | 91 | 1. Choisissez votre nouvelle application dans la liste des applications enregistrées. 92 | Sur la page **Vue d’ensemble** de l’application, notez la valeur d'**ID d’application (client)** et conservez-la pour plus tard. Vous devez paramétrer le fichier de configuration de Visual Studio pour ce projet. 93 | ![](readme-images/client.png) 94 | 1. Configurez les autorisations pour votre application : 95 | 96 | 1. Choisissez **Paramètres** > **Autorisations nécessaires** > **Ajouter**. 97 | 1. Choisissez **Sélectionner une API** > **Microsoft Graph**, puis cliquez sur **Sélectionner**. 98 | 1. Choisissez **Sélectionner des autorisations**, faites défiler vers le bas jusqu’à **Autorisations déléguées**, sélectionnez **Mail.Read**, puis cliquez sur **Sélectionner**. 99 | 1. Cliquez sur **Terminé**. 100 | ![](readme-images/permissions.png) 101 | 102 | 1. Sélectionnez **Certificats & secrets** sous **Gérer**. Sélectionnez le bouton **Nouvelle clé secrète client**. Entrez une valeur dans la Description, puis sélectionnez l'une des options pour Expire le, et choisissez **Ajouter**. 103 | 104 | ![](readme-images/secrets.png) 105 | 106 | 1. **Important** : Copier la valeur de clé : elle constitue le secret de votre application. Vous ne pourrez pas accéder de nouveau à cette valeur après avoir quitté ce panneau. 107 | 108 | Vous utiliserez l’**ID de l’application** et le **secret** pour configurer l’application. 109 | 110 | ## Configurer un tunnel pour votre localhost 111 | 112 | L’exemple utilise localhost en tant que serveur de développement. Un tunnel est par conséquent nécessaire pour transférer les demandes d’une URL sur Internet vers votre localhost. Si, pour une raison quelconque, vous ne souhaitez pas utiliser de tunnel, consultez l'[Hébergement sans tunnel](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Hosting-the-sample-without-a-tunnel). Pour obtenir des explications détaillées sur l’utilisation d’un tunnel, consultez [Pourquoi dois-je utiliser un tunnel ?](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Why-do-I-have-to-use-a-tunnel) 113 | 114 | Dans cet exemple, [ngrok](https://ngrok.com/) est utilisé pour créer le tunnel. Configuration de ngrok : 115 | 116 | 1. [Télécharger](https://ngrok.com/download) et décompresser des fichiers binaires ngrok pour votre plateforme. 117 | 1. Tapez la commande suivante : 118 | 119 | ```Shell 120 | ngrok http 3000 121 | ``` 122 | 123 | 1. Notez l’*URL publique https* fourni par ngrok. Voici un exemple : 124 | 125 | ```http 126 | https://{NGROK_ID}.ngrok.io 127 | ``` 128 | 129 | La valeur `NGROK_ID` est nécessaire dans la section suivante. 130 | 131 | ## Configurer et exécuter l’application web 132 | 133 | 1. Utilisez un éditeur de texte pour ouvrir `constants.js`. 134 | 1. Remplacez `ENTER_YOUR_CLIENT_ID` par l’ID client de votre application Azure enregistrée. 135 | 1. Remplacez `ENTER_YOUR_SECRET` par la clé secrète client de votre application Azure enregistrée. 136 | 1. Remplacez `NGROK_ID` par la valeur dans l'*URL public https* de la section précédente. 137 | ![](const) 138 | 1. Installez les dépendances exécutant la commande suivante : 139 | 140 | ```Shell 141 | npm install 142 | ``` 143 | 144 | 1. Démarrez l’application avec la commande suivante : 145 | 146 | ```Shell 147 | npm start 148 | ``` 149 | > **Remarque :** Vous pouvez également faire en sorte que l'application attende un débogueur. Pour l'attente d'un débogueur, utilisez la commande suivante à la place : 150 | > 151 | > ```Shell 152 | > npm run debug 153 | > ``` 154 | > Vous pouvez également joindre le débogueur inclus dans Microsoft Visual Studio Code. Pour plus d’informations, consultez le [Débogage dans Visual Studio Code](https://code.visualstudio.com/Docs/editor/debugging). 155 | 156 | 1. Ouvrez votre navigateur et accédez à [http://localhost:3000](http://localhost:3000). 157 | 158 | ## Contribution 159 | 160 | Si vous souhaitez contribuer à cet exemple, voir [CONTRIBUTING.MD](/CONTRIBUTING.md). 161 | 162 | Ce projet a adopté le [code de conduite Open Source de Microsoft](https://opensource.microsoft.com/codeofconduct/). Pour en savoir plus, reportez-vous à la [FAQ relative au code de conduite](https://opensource.microsoft.com/codeofconduct/faq/) ou contactez [opencode@microsoft.com](mailto:opencode@microsoft.com) pour toute question ou tout commentaire. 163 | 164 | ## Questions et commentaires 165 | 166 | Nous serions ravis de connaître votre opinion sur l'application de l'exemple de webhook Microsoft Graph. Vous pouvez nous faire part de vos questions et suggestions dans la rubrique [Problèmes](https://github.com/OfficeDev/Microsoft-Graph-NodeJs-Webhooks/issues) de ce référentiel. 167 | 168 | Vous avez des questions sur le développement dans Office 365 ? Publiez-les sur [Stack Overflow](http://stackoverflow.com/questions/tagged/Office365+API). N'oubliez pas de poser vos questions ou commentaires en incluant les balises \[API] et \[Office365]. 169 | 170 | ## Ressources supplémentaires 171 | 172 | - [Présentation de Microsoft Graph](http://graph.microsoft.io/) 173 | - [Documentation de référence sur l'inscription](https://graph.microsoft.io/en-us/docs/api-reference/beta/resources/subscription) 174 | 175 | ## Copyright 176 | 177 | Copyright (c) 2019 Microsoft. Tous droits réservés. 178 | -------------------------------------------------------------------------------- /README-localized/README-ja-jp.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | products: 4 | - ms-graph 5 | languages: 6 | - nodejs 7 | - javascript 8 | description: "Node.js アプリ用の Microsoft Graph Webhook サブスクリプションを作成し、ユーザーの Microsoft アカウント データに関する変更通知を受け取れるようにできます。" 9 | extensions: 10 | contentType: samples 11 | technologies: 12 | - Microsoft Graph 13 | createdDate: 3/9/2016 4:12:18 PM 14 | --- 15 | # Node.js 用 Microsoft Graph Webhooks サンプル 16 | 17 | [![ビルドの状態](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample.svg)](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample) 18 | 19 | ## 目次 ## 20 | * [概要](#introduction) 21 | 22 | * [スクリーンショット](#screenshots) 23 | 24 | * [前提条件](#prerequisites) 25 | 26 | * [アプリを登録する](#Register-the-app) 27 | 28 | * [localhost のトンネルを構成する](#Configure-a-tunnel-for-your-localhost) 29 | 30 | * [Web アプリの構成して実行する](#Configure-and-run-the-web-app) 31 | 32 | * [投稿](#contributing) 33 | 34 | * [質問とコメント](#Questions-and-Comments) 35 | 36 | * [その他の技術情報](#Additional-resources) 37 | 38 | 39 | ## 概要 40 | 41 | 42 | この Node.js サンプルでは、Microsoft Graph からの通知の取得を開始する方法を示します。Web アプリケーションが Microsoft Graph Webhooks を使用して実行する一般的なタスクを次に示します。 43 | 44 | - ユーザーを職場または学校のアカウントでサインインさせて、アクセス トークンを取得させます。 45 | - アクセス トークンを使用して、Webhook サブスクリプションを作成します。 46 | - 検証トークンを送り返して通知 URL を確認します。 47 | - Microsoft Graph からの通知をリッスンします。 48 | - 通知のデータを使用して、Microsoft Office 365 で詳細情報を要求します。 49 | 50 | ## スクリーンショット 51 | 52 | 53 | 1. まずサインインが必要です。 54 | 55 | ![署名](https://user-images.githubusercontent.com/3375461/31968683-c373ad30-b8c6-11e7-9d01-413fab9fd6d5.png) 56 | 57 | 1. サインインすると、アプリは受信メールをリッスンします。 58 | 59 | ![リスニング](https://user-images.githubusercontent.com/3375461/31968718-e19696c4-b8c6-11e7-91f2-f1806be0b134.png) 60 | 61 | 1. アドレスにメールを送信すると、アプリにメールが表示されます。 62 | 63 | ![メール](https://user-images.githubusercontent.com/3375461/31968754-0ce4dafc-b8c7-11e7-8458-8152d598228e.png) 64 | 65 | ## 前提条件 66 | 67 | 68 | Webhook サンプルを使うには、次が必要です。 69 | 70 | - [Node.js](https://nodejs.org/) (バージョン 4 または 5)。 71 | - [職場または学校のアカウント](http://dev.office.com/devprogram)。 72 | 73 | ## アプリを登録する 74 | 75 | 76 | このアプリは Azure AD エンドポイントを使用するため、[Azure ポータル](https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/ApplicationsListBlade)に登録します。 77 | 78 | 1. 職場または学校のアカウントか、個人の Microsoft アカウントを使用して、Azure ポータルにサインインします。 79 | 1. ご利用のアカウントで複数のテナントにアクセスできる場合は、右上隅でアカウントを選択し、ポータルのセッションを目的の Azure AD テナントに設定します (Switch Directory を使用)。 80 | 1. 左側のナビゲーション ウィンドウで、[**Azure Active Directory] サービス**を選択し、[**アプリの登録**] を選択します。 81 | ![](readme-images/registrations.png) 82 | 83 | 1. [**新しいアプリケーションの登録**] を選択します。 84 | 85 | 1. このアプリケーションのフレンドリ名を入力します。 86 | 1. **アプリケーション タイプ**として [Web App/API] (Web アプリ/API) を選択します。 87 | 1. **サインオン URL** に `http://localhost:3000/callback` と入力します。 88 | 1. [サポートされているアカウントの種類] セクションで、[組織ディレクトリ内のアカウントと個人の Microsoft アカウント (例: Skype、Xbox、Outlook.com)] を選択します。 89 | 1. [**作成**] をクリックします。 90 | 91 | 1. 登録済みアプリケーションの一覧から新しいアプリケーションを選択します。 92 | アプリの [**概要**] ページで、[**Application (client) ID**] (アプリケーション (クライアント) ID) の値を確認し、後で使用するために記録します。この情報は、このプロジェクトで Visual Studio 構成ファイルを設定するのに必要になります。 93 | ![](readme-images/client.png) 94 | 1. アプリケーションのアクセス許可を構成します。 95 | 96 | 1. [**設定**]、[**必要なアクセス許可**]、[**追加**] の順に選択します。 97 | 1. [**API を選択します**]、[**Microsoft Graph**] を選択して、[**選択**] をクリックします。 98 | 1. [**アクセス許可を選択**] を選択し、[**委任されたアクセス許可**] までスクロールし、[**Mail.Read**] を選択し、[**選択**] をクリックします。 99 | 1. [**完了**] をクリックします。 100 | ![](readme-images/permissions.png) 101 | 102 | 1. [**管理**] で [**証明書とシークレット**] を選択します。[**新しいクライアント シークレット**] ボタンを選択します。[説明] に値を入力し、[有効期限] のオプションのいずれかを選び、[**追加**] を選択します。 103 | 104 | ![](readme-images/secrets.png) 105 | 106 | 1. **重要**:キー値をコピーします--これがアプリのシークレットです。このブレードを離れた後は、この値に再度アクセスできなくなります。 107 | 108 | アプリを構成するには、**アプリケーション ID** と**シークレット**を使用します。 109 | 110 | ## localhost のトンネルを構成する 111 | 112 | このサンプルでは、開発サーバーとして localhost を使用しています。このため、インターネット上の URL から localhost に要求を転送できるトンネルが必要です。何らかの理由でトンネルを使用したくない場合は、「[Hosting without a tunnel (トンネルを使用しないでホストする)](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Hosting-the-sample-without-a-tunnel)」をご覧ください。トンネルを使用する理由について詳細な説明が必要な場合は、「[Why do I have to use a tunnel? (トンネルを使用する理由)](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Why-do-I-have-to-use-a-tunnel)」を参照してください。 113 | 114 | このサンプルでは、[ngrok](https://ngrok.com/) を使用してトンネルを作成します。ngrok を構成するには: 115 | 116 | 1. プラットフォームに合った ngrok バイナリを[ダウンロード](https://ngrok.com/download)して解凍します。 117 | 1. 次のコマンドを入力します。 118 | 119 | ```Shell 120 | ngrok http 3000 121 | ``` 122 | 123 | 1. ngrok が提供する *https パブリック URL* のメモを取ります。以下は例です。 124 | 125 | ```http 126 | https://{NGROK_ID}.ngrok.io 127 | ``` 128 | 129 | `NGROK_ID` 値は次のセクションで必要になります。 130 | 131 | ## Web アプリの構成して実行する 132 | 133 | 1. テキスト エディターを使用して `constants.js` を開きます。 134 | 1. `ENTER_YOUR_CLIENT_ID` を登録済みの Azure アプリケーションのクライアント ID と置き換えます。 135 | 1. `ENTER_YOUR_SECRET` を登録済みの Azure アプリケーションのクライアント シークレットと置き換えます。 136 | 1. `NGROK_ID` を前のセクションの *https パブリック URL* の値と置き換えます。 137 | ![](const) 138 | 1. 次のコマンドを実行して、依存関係をインストールします。 139 | 140 | ```Shell 141 | npm install 142 | ``` 143 | 144 | 1. 次のコマンドを使用して、アプリケーションを起動します。 145 | 146 | ```Shell 147 | npm start 148 | ``` 149 | > **注:**アプリケーションにデバッガーを待たせることもできます。デバッガーを待つには、代わりに次のコマンドを使用します。 150 | > 151 | > ```Shell 152 | > npm run debug 153 | > ``` 154 | > Microsoft Visual Studio Code に含まれるデバッガーをアタッチすることもできます。詳細については、「[Debugging in Visual Studio Code (Visual Studio Code でのデバッグ)](https://code.visualstudio.com/Docs/editor/debugging)」を参照してください。 155 | 156 | 1. ブラウザーを開き、[http://localhost:3000](http://localhost:3000) に移動します。 157 | 158 | ## 投稿 159 | 160 | このサンプルに投稿する場合は、[CONTRIBUTING.MD](/CONTRIBUTING.md) を参照してください。 161 | 162 | このプロジェクトでは、[Microsoft Open Source Code of Conduct (Microsoft オープン ソース倫理規定)](https://opensource.microsoft.com/codeofconduct/) が採用されています。詳細については、「[Code of Conduct の FAQ (倫理規定の FAQ)](https://opensource.microsoft.com/codeofconduct/faq/)」を参照してください。また、その他の質問やコメントがあれば、[opencode@microsoft.com](mailto:opencode@microsoft.com) までお問い合わせください。 163 | 164 | ## 質問とコメント 165 | 166 | Microsoft Graph Webhook サンプルに関するフィードバックをぜひお寄せください。質問や提案は、このリポジトリの「[問題](https://github.com/OfficeDev/Microsoft-Graph-NodeJs-Webhooks/issues)」セクションで送信できます。 167 | 168 | Office 365 の開発に関する質問をお持ちですか ?[Stack Overflow](http://stackoverflow.com/questions/tagged/Office365+API) に投稿しましょう。質問やコメントには、必ず [Office365] と [API] のタグを付けてください。 169 | 170 | ## その他の技術情報 171 | 172 | - [Microsoft Graph の概要](http://graph.microsoft.io/) 173 | - [サブスクリプション リファレンス ドキュメント](https://graph.microsoft.io/en-us/docs/api-reference/beta/resources/subscription) 174 | 175 | ## 著作権 176 | 177 | Copyright (c) 2019 Microsoft.All rights reserved. 178 | -------------------------------------------------------------------------------- /README-localized/README-pt-br.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | products: 4 | - ms-graph 5 | languages: 6 | - nodejs 7 | - javascript 8 | description: "Criar assinaturas de webhook do Microsoft Graph para o aplicativo Node.js., para que ele possa receber notificações de alterações nos dados da conta da Microsoft de um usuário." 9 | extensions: 10 | contentType: samples 11 | technologies: 12 | - Microsoft Graph 13 | createdDate: 3/9/2016 4:12:18 PM 14 | --- 15 | # Exemplo de webhooks do Microsoft Graph para Node.js 16 | 17 | [![Status da compilação](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample.svg)](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample) 18 | 19 | ## Sumário ## 20 | * [Introdução](#introduction) 21 | 22 | * [Capturas de tela](#screenshots) 23 | 24 | * [Pré-requisitos](#prerequisites) 25 | 26 | * [Registrar o aplicativo](#Register-the-app) 27 | 28 | * [Configurar um túnel para seu localhost](#Configure-a-tunnel-for-your-localhost) 29 | 30 | * [Configurar e executar o aplicativo](#Configure-and-run-the-web-app) 31 | 32 | * [Colaboração](#contributing) 33 | 34 | * [Perguntas e comentários](#Questions-and-Comments) 35 | 36 | * [Recursos adicionais](#Additional-resources) 37 | 38 | 39 | ## Introdução 40 | 41 | 42 | Esse exemplo do Node.js mostra como começar a receber notificações do Microsoft Graph. A seguir, são apresentadas tarefas comuns que um aplicativo da Web executa com assinaturas dos webhooks do Microsoft Graph. 43 | 44 | - Faça login nos usuários com a conta corporativa ou escolar para obter um token de acesso. 45 | - Use o token de acesso para criar uma assinatura para um webhook. 46 | - Devolva um token de validação para confirmar a URL de notificação. 47 | - Ouça as notificações do Microsoft Graph. 48 | - Solicitação para obter mais informações sobre o Microsoft Office 365 usando dados na notificação. 49 | 50 | ## Capturas de tela 51 | 52 | 53 | 1. Primeiro, você precisa entrar. 54 | 55 | ![entrar](https://user-images.githubusercontent.com/3375461/31968683-c373ad30-b8c6-11e7-9d01-413fab9fd6d5.png) 56 | 57 | 1. Depois de entrar, o aplicativo ouvirá os e-mails de entrada. 58 | 59 | ![ouvir](https://user-images.githubusercontent.com/3375461/31968718-e19696c4-b8c6-11e7-91f2-f1806be0b134.png) 60 | 61 | 1. Depois de enviar o e-mail ao endereço, você visualizará o e-mail no aplicativo. 62 | 63 | ![e-mail](https://user-images.githubusercontent.com/3375461/31968754-0ce4dafc-b8c7-11e7-8458-8152d598228e.png) 64 | 65 | ## Pré-requisitos 66 | 67 | 68 | Para usar este webhook, você precisa do seguinte: 69 | 70 | - [Node.js](https://nodejs.org/) versão 4 ou 5. 71 | - Uma [conta corporativa ou de estudante](http://dev.office.com/devprogram). 72 | 73 | ## Registrar o aplicativo 74 | 75 | 76 | Esse aplicativo utiliza o terminal do Azure AD para registrá-lo no [portal do Azure](https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/ApplicationsListBlade). 77 | 78 | 1. Entrar no portal do Azure utilizando uma conta corporativa, de estudante ou pessoal da Microsoft. 79 | 1. Se sua conta permitir o acesso a mais de um locatário, clique na sua conta no canto superior direito e configure sua sessão do portal ao locatário do Azure AD desejado (utilizando o Diretório de alternadores). 80 | 1. No painel de navegação à esquerda, clique no serviço **serviços do Diretório ativo do Azure**, e depois clique em **Registros do aplicativo** 81 | ![](readme-images/registrations.png). 82 | 83 | 1. Clique em **Novo registro do aplicativo**. 84 | 85 | 1. Digite um nome amigável ao aplicativo. 86 | 1. Clique em Aplicativo Web/API como o **Tipo de Aplicativo**. 87 | 1. Digite `http://localhost:3000/callback` para a **URL de entrada**. 88 | 1. Na seção Tipos de conta com suporte, clique em Contas em qualquer diretório organizacional e contas pessoais do Microsoft (por exemplo: Skype, Xbox, Outlook.com). 89 | 1. Clique em **Criar**. 90 | 91 | 1. Escolha seu novo aplicativo na lista de aplicativos registrados.Na página **Visão geral** do aplicativo, encontre o valor da **ID do aplicativo (cliente)** e registre-o para usar mais tarde. 92 | Será necessário que você configure o arquivo de configuração do Visual Studio para este projeto. 93 | ![](readme-images/client.png) 94 | 1. Configure permissões para o seu aplicativo: 95 | 96 | 1. Clique em **Configurações** > **Permissões necessárias** > **Add**. 97 | 1. Clique em **Selecione uma API** > **Microsoft Graph**e clique em **Selecionar**. 98 | 1. Clique em **Selecionar permissões**, role para baixo até **Permissões delegadas**, clique em **Leitura do correio** e, em seguida, clique em **Selecionar**. 99 | 1. Clique em ****concluído. 100 | ![](readme-images/permissions.png) 101 | 102 | 1. Clique em **Certificados e segredos** em **Gerenciar**. Clique com o botão em **Novo segredo do cliente**. Insira um valor em Descrição e clique em uma das opções de Expira e cliquem em **Adicionar**. 103 | 104 | ![](readme-images/secrets.png) 105 | 106 | 1. **Importante**: Copie o valor chave--esse é o segredo do seu aplicativo. Você não conseguirá acessar esse valor novamente depois de sair desse painel. 107 | 108 | Você usará a **ID do aplicativo** e **de segredo** para configurar o aplicativo. 109 | 110 | ## Configurar um túnel para seu localhost 111 | 112 | O exemplo utiliza localhosts como o servidor de desenvolvimento. Por essa razão, precisamos de um encapsulamento que poderá encaminhar solicitações de uma URL na Internet ao seu localhost. Se, por alguma razão, você não quiser usar um encapsulamento, consulte [Hospedagem sem um encapsulamento](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Hosting-the-sample-without-a-tunnel). Se quiser obter uma explicação detalhada de como usar um encapsulamento, consulte [Por que preciso usar um encapsulamento?](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Why-do-I-have-to-use-a-tunnel) 113 | 114 | Neste exemplo, usamos [ngrok](https://ngrok.com/) para criar o encapsulamento. Para configurar o ngrok: 115 | 116 | 1. [Baixar](https://ngrok.com/download) e descompactar os binários ngrok da sua plataforma. 117 | 1. Digite o seguinte comando: 118 | 119 | ```Shell 120 | ngrok http 3000 121 | ``` 122 | 123 | 1. Anote a * URL pública https*que o ngrok fornece a você. Isto é um exemplo: 124 | 125 | ```http 126 | https://{NGROK_ID}.ngrok.io 127 | ``` 128 | 129 | Você precisará do valor `NGROK_ID` nas próximas seções. 130 | 131 | ## Configure e execute o aplicativo da Web 132 | 133 | 1. Use um editor de texto para abrir `constants.js`. 134 | 1. Substitua `ENTER_YOUR_CLIENT_ID` pela ID do cliente do aplicativo Azure registrado. 135 | 1. Substitua `ENTER_YOUR_SECRET` pelo segredo do cliente do aplicativo Azure registrado. 136 | 1. Substitua `NGROK_ID` pelo valor na *URL pública https* da seção anterior. 137 | ![](const) 138 | 1. Instale as dependências executando o seguinte comando: 139 | 140 | ```Shell 141 | npm install 142 | ``` 143 | 144 | 1. Inicie o aplicativo com o seguinte comando: 145 | 146 | ```Shell 147 | npm start 148 | ``` 149 | > **Observação:** Você também pode fazer com que o aplicativo aguarde por um depurador. Para esperar por um depurador, use o seguinte comando: 150 | > 151 | > ```Shell 152 | > npm run debug 153 | > ``` 154 | > Você também pode anexar o depurador incluído no código do Microsoft Visual Studio. Para obter mais informações, consulte Depurador no Visual Studio Code[](https://code.visualstudio.com/Docs/editor/debugging). 155 | 156 | 1. Abra um navegador e vá para [http://localhost:3000](http://localhost:3000). 157 | 158 | ## Colaboração 159 | 160 | Se quiser contribuir para esse exemplo, confira [CONTRIBUTING.MD](/CONTRIBUTING.md). 161 | 162 | Este projeto adotou o [Código de Conduta de Código Aberto da Microsoft](https://opensource.microsoft.com/codeofconduct/). Para saber mais, confira as [Perguntas frequentes sobre o Código de Conduta](https://opensource.microsoft.com/codeofconduct/faq/) ou entre em contato pelo [opencode@microsoft.com](mailto:opencode@microsoft.com) se tiver outras dúvidas ou comentários. 163 | 164 | ## Perguntas e comentários 165 | 166 | Gostaríamos de receber seus comentários sobre o exemplo Microsoft Graph Webhook. Você pode enviar perguntas e sugestões na seção [Problemas](https://github.com/OfficeDev/Microsoft-Graph-NodeJs-Webhooks/issues) deste repositório. 167 | 168 | Perguntas de desenvolvimento do Office 365? Poste-as em [Excedente](http://stackoverflow.com/questions/tagged/Office365+API). Certifique-se de marcar suas perguntas ou comentários com \[Office365] e \[API]. 169 | 170 | ## Recursos adicionais 171 | 172 | - [Visão geral do Microsoft Graph](http://graph.microsoft.io/) 173 | - [Documentação de referência da assinatura](https://graph.microsoft.io/en-us/docs/api-reference/beta/resources/subscription) 174 | 175 | ## Direitos autorais 176 | 177 | Copyright (c) 2019 Microsoft. Todos os direitos reservados. 178 | -------------------------------------------------------------------------------- /README-localized/README-ru-ru.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | products: 4 | - ms-graph 5 | languages: 6 | - nodejs 7 | - javascript 8 | description: "Создайте подписки Microsoft Graph webhook для приложения Node.js, чтобы оно могло получать уведомления об изменениях в данных учетной записи пользователя Microsoft" 9 | extensions: 10 | contentType: samples 11 | technologies: 12 | - Microsoft Graph 13 | createdDate: 3/9/2016 4:12:18 PM 14 | --- 15 | # Пример Microsoft Graph Webhooks для Node.js 16 | 17 | [![Состояние сборки](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample.svg)](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample) 18 | 19 | ## Содержание ## 20 | * [Введение](#introduction) 21 | 22 | * [Снимки экрана](#screenshots) 23 | 24 | * [Предварительные требования](#prerequisites) 25 | 26 | * [Регистрация приложения](#Register-the-app) 27 | 28 | * [Настройка туннеля для вашего localhost](#Configure-a-tunnel-for-your-localhost) 29 | 30 | * [Настройка и запуск веб-приложения](#Configure-and-run-the-web-app) 31 | 32 | * [Участие](#contributing) 33 | 34 | * [Вопросы и примечания](#Questions-and-Comments) 35 | 36 | * [Дополнительные ресурсы](#Additional-resources) 37 | 38 | 39 | ## Введение 40 | 41 | 42 | В этом примере Node.js показано, как начать получать уведомления от Microsoft Graph. Ниже приведены типичные задачи, которые веб-приложение выполняет с веб-заездами Microsoft Graph. 43 | 44 | - Войдите в систему, используя учетную запись своей работы или школы, чтобы получить токен доступа. 45 | - Используйте токен доступа для создания подписки на webhook. 46 | - Возврат маркера проверки для подтверждения URL-адреса уведомления. 47 | - Слушайте уведомления от Microsoft Graph. 48 | - Запросите дополнительную информацию в Microsoft Office 365, используя данные в уведомлении. 49 | 50 | ## Снимки экрана 51 | 52 | 53 | 1. Сначала вам нужно войти. 54 | 55 | ![войти](https://user-images.githubusercontent.com/3375461/31968683-c373ad30-b8c6-11e7-9d01-413fab9fd6d5.png) 56 | 57 | 1. После входа приложение будет прослушивать входящие электронные письма. 58 | 59 | ![listening](https://user-images.githubusercontent.com/3375461/31968718-e19696c4-b8c6-11e7-91f2-f1806be0b134.png) 60 | 61 | 1. После отправки электронного письма на адрес, вы увидите его в приложении. 62 | 63 | ![email](https://user-images.githubusercontent.com/3375461/31968754-0ce4dafc-b8c7-11e7-8458-8152d598228e.png) 64 | 65 | ## Предварительные требования 66 | 67 | 68 | Чтобы использовать пример Webhook, вам необходимо следующее: 69 | 70 | - [Node.js](https://nodejs.org/) (версия 4\. или 5) 71 | - [Рабочая или учебная учетная запись](http://dev.office.com/devprogram) 72 | 73 | ## Регистрация приложения 74 | 75 | 76 | Это приложение использует конечную точку Azure AD, поэтому вы зарегистрируете ее на [портале Azure](https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/ApplicationsListBlade). 77 | 78 | 1. Войдите на портал Azure, используя рабочую или учебную учетную запись или личную учетную запись Microsoft. 79 | 1. Если ваша учетная запись предоставляет вам доступ более чем к одному арендатору, выберите свою учетную запись в верхнем правом углу и установите для сеанса портала нужный клиент Azure AD (с помощью Switch Directory). 80 | 1. В левой области навигации выберите службу **Azure Active Directory**, а затем выберите **Регистрация приложений** 81 | ![](readme-images/registrations.png) 82 | 83 | 1. Выберите **Регистрация нового приложения**. 84 | 85 | 1. Введите понятное имя для приложения. 86 | 1. Выберите «Веб-приложение / API» в качестве **Типа приложения**. 87 | 1. Введите`http://localhost:3000/callback` для **URL входа**. 88 | 1. В разделе Поддерживаемые типы учетных записей выберите Учетные записи в любом организационном каталоге и личные учетные записи Microsoft (например, Skype, Xbox, Outlook.com). 89 | 1. Нажмите **Создать**. 90 | 91 | 1. Выберите новое приложение из списка зарегистрированных приложений.На странице **Обзор** приложения найдите значение **Идентификатор приложения (клиент)** и запишите его, чтобы использовать его позже. 92 | Это необходимо для того, чтобы настроить файл конфигурации Visual Studio для этого проекта. 93 | ![](readme-images/client.png) 94 | 1. Настройте разрешения для приложения: 95 | 96 | 1. Выберите **параметры ** > **необходимые разрешения** > **добавить**. 97 | 1. Выберите **выбрать API** > **Microsoft Graph**и щелкните **выберите**. 98 | 1. Выберите **выбрать разрешения**, прокрутите страницу вниз до раздела **делегированные разрешения**, выберите **почта. Прочтите**и щелкните **выберите**. 99 | 1. Нажмите кнопку **Готово**. 100 | ![](readme-images/permissions.png) 101 | 102 | 1. Выберите **Сертификаты и секреты** в разделе **Управление**. Нажмите кнопку **Новый секрет клиента**. Введите значение в поле «Описание», выберите один из параметров «Срок действия» и нажмите **Добавить**. 103 | 104 | ![](readme-images/secrets.png) 105 | 106 | 1. **Важно!** Скопируйте значение ключа - это секрет вашего приложения. Вы не сможете получить доступ к этому значению снова после того, как покинете этот блейд. 107 | 108 | Вы будете использовать **идентификатор приложения** и **секрет** для настройки приложения. 109 | 110 | ## Настройте туннель для вашего localhost 111 | 112 | В примере на сервере разработки используется localhost. По этой причине нам нужен туннель, который может пересылать запросы с URL-адреса в Интернете на ваш локальный хост. Если по какой-либо причине вы не хотите использовать туннель, см. [Хостинг без туннеля](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Hosting-the-sample-without-a-tunnel). Если вам нужно подробное объяснение того, зачем использовать туннель, см. [Почему я должен использовать туннель?](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Why-do-I-have-to-use-a-tunnel) 113 | 114 | Для этого примера мы используем [ngrok](https://ngrok.com/) для создания туннеля. Чтобы настроить ngrok: 115 | 116 | 1. [скачать](https://ngrok.com/download) и распаковать ngrok двоичные файлы. 117 | 1. Введите следующую команду: 118 | 119 | ```Shell 120 | ngrok http 3000 121 | ``` 122 | 123 | 1. Обратите внимание на *общедоступный URL-адрес* https, который вам предоставляет ngrok. Это пример: 124 | 125 | ```http 126 | https://{NGROK_ID}.ngrok.io 127 | ``` 128 | 129 | Вам понадобится значение `NGROK_ID` в следующем разделе. 130 | 131 | ## Настройка и запуск веб-приложения 132 | 133 | 1. Откройте `constants.js`, используя текстовый редактор. 134 | 1. Замените `ENTER_YOUR_CLIENT_ID` на идентификатор клиента для зарегистрированного в Azure приложения. 135 | 1. Замените `ENTER_YOUR_SECRET` на секрет клиента для зарегистрированного в Azure приложения. 136 | 1. Замените `NGROK_ID` значение в *общедоступный URL-адрес HTTPS, который* из предыдущего раздела. 137 | ![](const) 138 | 1. Установите зависимости, выполнив следующую команду: 139 | 140 | ```Shell 141 | npm install 142 | ``` 143 | 144 | 1. Запустите приложение, выполнив следующую команду: 145 | 146 | ```Shell 147 | npm start 148 | ``` 149 | > **Примечание.** Вы также можете заставить приложение ждать отладчик. Чтобы дождаться отладчика, используйте следующую команду: 150 | > 151 | > ```Shell 152 | > npm run debug 153 | > ``` 154 | > можно также вложить отладчик, который входит в код Microsoft Visual Studio. Дополнительные сведения см. в статье [Отладка в Visual Studio Code](https://code.visualstudio.com/Docs/editor/debugging). 155 | 156 | 1. Откройте браузер и перейдите в [http://localhost:3000](http://localhost:3000). 157 | 158 | ## Участие 159 | 160 | Если вы хотите добавить код в этот пример, просмотрите статью [CONTRIBUTING.MD](/CONTRIBUTING.md). 161 | 162 | Этот проект соответствует [Правилам поведения разработчиков открытого кода Майкрософт](https://opensource.microsoft.com/codeofconduct/). Дополнительные сведения см. в разделе [часто задаваемых вопросов о правилах поведения](https://opensource.microsoft.com/codeofconduct/faq/). Если у вас возникли вопросы или замечания, напишите нам по адресу [opencode@microsoft.com](mailto:opencode@microsoft.com). 163 | 164 | ## Вопросы и комментарии 165 | 166 | Мы хотели бы получить ваши отзывы о образце Microsoft Graph Webhook. Вы можете отправлять нам вопросы и предложения в разделе [Проблемы](https://github.com/OfficeDev/Microsoft-Graph-NodeJs-Webhooks/issues) этого репозитория. 167 | 168 | Вопросы, связанные с разработкой Office 365 Разместите их в [Stack Overflow](http://stackoverflow.com/questions/tagged/Office365+API). Обязательно пометьте свои вопросы или комментарии с помощью \[Office365] и \[API]. 169 | 170 | ## Дополнительные ресурсы 171 | 172 | - [Обзор Microsoft Graph](http://graph.microsoft.io/) 173 | - [Подписная справочная документация](https://graph.microsoft.io/en-us/docs/api-reference/beta/resources/subscription) 174 | 175 | ## Авторские права 176 | 177 | (c) Корпорация Майкрософт (Microsoft Corporation), 2019. Все права защищены. 178 | -------------------------------------------------------------------------------- /README-localized/README-zh-cn.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | products: 4 | - ms-graph 5 | languages: 6 | - nodejs 7 | - javascript 8 | description: "为 Node.js 应用创建 Microsoft Graph webhook 订阅,以便它可以接收用户的 Microsoft 帐户数据更改的通知。" 9 | extensions: 10 | contentType: samples 11 | technologies: 12 | - Microsoft Graph 13 | createdDate: 3/9/2016 4:12:18 PM 14 | --- 15 | # 面向 Node.js 的 Microsoft Graph Webhook 示例 16 | 17 | [![生成状态](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample.svg)](https://travis-ci.org/microsoftgraph/nodejs-webhooks-rest-sample) 18 | 19 | ## 目录。 ## 20 | * [简介](#introduction) 21 | 22 | * [屏幕截图](#screenshots) 23 | 24 | * [先决条件](#prerequisites) 25 | 26 | * [注册应用](#Register-the-app) 27 | 28 | * [配置 localhost 隧道](#Configure-a-tunnel-for-your-localhost) 29 | 30 | * [配置并运行 Web 应用程序](#Configure-and-run-the-web-app) 31 | 32 | * [参与](#contributing) 33 | 34 | * [问题和意见](#Questions-and-Comments) 35 | 36 | * [其他资源](#Additional-resources) 37 | 38 | 39 | ## 简介 40 | 41 | 42 | 此 Node.js 示例演示如何开始从 Microsoft Graph 获取通知。下面是 Web 应用程序可通过 Microsoft Graph webhook 执行的常见任务。 43 | 44 | - 使用工作或学校账户登录用户,以获取访问令牌。 45 | - 使用访问令牌创建 webhook 订阅。 46 | - 回发验证令牌以确认通知 URL。 47 | - 侦听来自 Microsoft Graph 的通知。 48 | - 使用通知中的数据请求 Microsoft Office 365 中的更多资源。 49 | 50 | ## 屏幕截图 51 | 52 | 53 | 1. 首先需要登录。 54 | 55 | ![登录](https://user-images.githubusercontent.com/3375461/31968683-c373ad30-b8c6-11e7-9d01-413fab9fd6d5.png) 56 | 57 | 1. 登录后,应用程序将侦听传入电子邮件。 58 | 59 | ![侦听](https://user-images.githubusercontent.com/3375461/31968718-e19696c4-b8c6-11e7-91f2-f1806be0b134.png) 60 | 61 | 1. 发送电子邮件至地址后,将在应用程序中看到电子邮件。 62 | 63 | ![电子邮件](https://user-images.githubusercontent.com/3375461/31968754-0ce4dafc-b8c7-11e7-8458-8152d598228e.png) 64 | 65 | ## 先决条件 66 | 67 | 68 | 使用 Webhook 示例,需要以下内容: 69 | 70 | - [Node.js](https://nodejs.org/) 版本 4 或 5。 71 | - 一个[工作或学校帐户](http://dev.office.com/devprogram)。 72 | 73 | ## 注册应用 74 | 75 | 76 | 此应用使用 Azure AD 终结点,因此将在 [Azure 门户](https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/ApplicationsListBlade)中注册。 77 | 78 | 1. 使用工作/学校帐户或 Microsoft 个人帐户登录到 Azure 门户。 79 | 1. 如果你的帐户有权访问多个租户,请在右上角选择该帐户,并将门户会话设置为所需的 Azure AD 租户(使用“切换目录”)。 80 | 1. 在左侧导航窗格中选择“**Azure Active Directory 服务**”,然后选择“**应用注册**”。 81 | ![](readme-images/registrations.png) 82 | 83 | 1. 选择“**新建应用程序注册**”。 84 | 85 | 1. 为应用程序输入一个友好的名称。 86 | 1. 选择“Web 应用/API”作为**应用程序类型**。 87 | 1. 在“**登录 URL**”上输入 `http://localhost:3000/callback`。 88 | 1. 在“支持的帐户类型”部分,选择“任何组织目录中的帐户和个人 Microsoft 帐户(例如 Skype、Xbox、Outlook.com)”。 89 | 1. 单击“**创建**”。 90 | 91 | 1. 从已注册应用的列表中选择新应用。在应用的“**概述**”页上,查找“**应用程序(客户端) ID**”值,并稍后记录下来。 92 | 你将需要它来为此项目配置 Visual Studio 配置文件。 93 | ![](readme-images/client.png) 94 | 1. 配置应用程序的权限: 95 | 96 | 1. 选择“**设置**” > “**所需权限**” > “**添加**”。 97 | 1. 选择“**选择 API**”页 > **Microsoft Graph**,然后单击“**选择**”。 98 | 1. 选择 “**选择权限**”,向下滚动到“**委派权限**”,然后选择**Mail.Read**,然后单击“**选择**”。 99 | 1. 单击“**完成**”。 100 | ![](readme-images/permissions.png) 101 | 102 | 1. 选择**管理**下的**证书和密码**。选择**新客户端密码**按钮。在“说明”中输入数值,并选择一个“过期”选项并选择“**添加**”。 103 | 104 | ![](readme-images/secrets.png) 105 | 106 | 1. **重要说明**:复制密钥值 -- 这是应用程序的密码。离开此边栏选项卡后将无法再访问该值。 107 | 108 | 将使用此“**应用程序 ID**” 和应用“**密码**”配置应用。 109 | 110 | ## 配置 localhost 隧道 111 | 112 | 示例使用 localhost 作为开发服务器。因此,我们需要一个隧道将请求从网上 URL 转发到 localhost。如果因为任何原因不想使用隧道,参见“[无隧道托管](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Hosting-the-sample-without-a-tunnel)”。如果需要有关为什么使用隧道的详细解释,参见“[为何我需要使用隧道?](https://github.com/OfficeDev/Microsoft-Graph-Nodejs-Webhooks/wiki/Why-do-I-have-to-use-a-tunnel)” 113 | 114 | 对于此示例,我们使用 [ngrok](https://ngrok.com/) 创建隧道。如果要配置 ngrok: 115 | 116 | 1. 为平台“[下载](https://ngrok.com/download)”并解压 ngrok 二进制文件。 117 | 1. 键入以下命令: 118 | 119 | 120 | ```Shell 121 | ngrok http 3000 122 | ``` 123 | 124 | 1. 记下 ngrok 提供给你的 *https 公共 URL*。这是一个示例: 125 | 126 | ```http 127 | https://{NGROK_ID}.ngrok.io 128 | ``` 129 | 130 | 在下一节中将需要 `NGROK_ID` 值。 131 | 132 | ## 配置并运行 Web 应用程序 133 | 134 | 1. 使用文本编辑器打开 `constants.js`。 135 | 1. 用所注册的 Azure 应用程序的客户端 ID 替换 `ENTER_YOUR_CLIENT_ID`。 136 | 1. 用所注册的 Azure 应用程序的客户端密码替换 `ENTER_YOUR_SECRET`。 137 | 1. 将 `NGROK_ID` 替换为上一节中的*https 公共 URL* 值。 138 | ![](const) 139 | 1. 安装运行下列命令的依赖项: 140 | 141 | ```Shell 142 | npm install 143 | ``` 144 | 145 | 1. 使用下列命令启动应用程序: 146 | 147 | ```Shell 148 | npm start 149 | ``` 150 | > **注意:**也可以使应用程序等待调试程序。若要等待调试程序,请改用以下命令: 151 | > 152 | > ```Shell 153 | > npm run debug 154 | > ``` 155 | > 也可附加包含的调试程序至 Visual Studio Code 中。有关详细信息,请参阅“[使用 Visual Studio Code 调试](https://code.visualstudio.com/Docs/editor/debugging)”。 156 | 157 | 1. 打开浏览器并转到 [http://localhost:3000](http://localhost:3000)。 158 | 159 | ## 参与 160 | 161 | 如果想要参与本示例,请参阅 [CONTRIBUTING.MD](/CONTRIBUTING.md)。 162 | 163 | 此项目已采用 [Microsoft 开放源代码行为准则](https://opensource.microsoft.com/codeofconduct/)。有关详细信息,请参阅[行为准则 FAQ](https://opensource.microsoft.com/codeofconduct/faq/)。如有其他任何问题或意见,也可联系 [opencode@microsoft.com](mailto:opencode@microsoft.com)。 164 | 165 | ## 问题和意见 166 | 167 | 我们乐意倾听你有关 Microsoft Graph Webhook 示例的反馈。你可通过该存储库中的[问题](https://github.com/OfficeDev/Microsoft-Graph-NodeJs-Webhooks/issues)部分向我们发送问题和建议。 168 | 169 | Office 365 开发问题?发布到“[堆栈溢出](http://stackoverflow.com/questions/tagged/Office365+API)”。确保使用 \[Office365] 和 \[API] 标记问题或意见。 170 | 171 | ## 其他资源 172 | 173 | - [Microsoft Graph 概述](http://graph.microsoft.io/) 174 | - [订阅参考文档](https://graph.microsoft.io/en-us/docs/api-reference/beta/resources/subscription) 175 | 176 | ## 版权信息 177 | 178 | 版权所有 (c) 2019 Microsoft。保留所有权利。 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | products: 4 | - ms-graph 5 | - office-exchange-online 6 | - entra-id 7 | - office-teams 8 | languages: 9 | - nodejs 10 | - javascript 11 | description: Create Microsoft Graph webhook subscriptions for a Node.js Express app, so that it can receive notifications of changes for any resource. This sample also supports receiving change notifications with data, validating and decrypting the payload. 12 | extensions: 13 | contentType: samples 14 | technologies: 15 | - Microsoft Graph 16 | services: 17 | - Microsoft Teams 18 | - Azure AD 19 | - Office 365 20 | - Change notifications 21 | createdDate: 3/9/2016 4:12:18 PM 22 | --- 23 | # Microsoft Graph Webhooks Sample for Node.js 24 | 25 | [![Node.js CI](https://github.com/microsoftgraph/nodejs-webhooks-sample/actions/workflows/nodejs.yml/badge.svg)](https://github.com/microsoftgraph/nodejs-webhooks-sample/actions/workflows/nodejs.yml) 26 | 27 | Subscribe for [Microsoft Graph change notifications](https://learn.microsoft.com/graph/api/resources/webhooks) to be notified when your user's data changes, so you don't have to poll for changes. 28 | 29 | This sample NodeJS web application shows how to subscribe for change notifications as well as how to validate and decrypt change notifications with resource data when supported by the resource. 30 | 31 | [User-delegated authentication](https://learn.microsoft.com/graph/auth-v2-user) represents a user and the application being used when calling the Microsoft Graph. This type of authentication is best suited for scenarios when the user interacts with the application. [Application only authentication](https://learn.microsoft.com/graph/auth-v2-service) represents only the application itself when calling the Microsoft Graph, without any notion of user. This type of authentication is best suited for background services, daemons or other kind of applications users are not directly interacting with. 32 | 33 | > See the list of [permissions and authentication types](https://learn.microsoft.com/graph/api/subscription-post-subscriptions?view=graph-rest-1.0) permitted for each supported resource in Microsoft Graph. 34 | 35 | The following are common tasks that an application performs with webhooks subscriptions: 36 | 37 | - Get consent to subscribe to resources and then get an access token. 38 | - Use the access token to [create a subscription](https://learn.microsoft.com/graph/api/subscription-post-subscriptions) to a resource. 39 | - Send back a validation token to confirm the notification URL. 40 | - Listen for notifications from Microsoft Graph and respond with a 202 status code. 41 | - Request more information about changed resources using data in the notification if no data is provided with the notification. 42 | - Decrypts the resource data provided with the notification if any has been provided with the notification. 43 | 44 | ## Using the Microsoft Graph Webhooks Sample 45 | 46 | ### Prerequisites 47 | 48 | To use the Webhook sample, you need the following: 49 | 50 | - [Node.js](https://nodejs.org/) version 18 or 20. 51 | - A [work or school account](https://developer.microsoft.com/microsoft-365/dev-program). 52 | - The application ID and key from the application that you [register on the Azure Portal](#register-the-app). 53 | - A public HTTPS endpoint to receive and send HTTP requests. You can host this on Microsoft Azure or another service, or you can [use ngrok](#set-up-the-ngrok-proxy-optional) or a similar tool while testing. 54 | - [OpenSSL](https://www.openssl.org/source/) when trying change notifications with resource data. 55 | 56 | > You can install OpenSSL on windows using [chocolatey](https://chocolatey.org/install) with `choco install openssl -y` (run as administrator). 57 | 58 | ### Create your app 59 | 60 | #### Choose the tenant where you want to create your app 61 | 62 | 1. Sign in to the [Azure Active Directory admin center](https://aad.portal.azure.com) using either a work or school account. 63 | 1. If your account is present in more than one Azure AD tenant: 64 | 1. Select your profile from the menu on the top right corner of the page, and then **Switch directory**. 65 | 1. Change your session to the Azure AD tenant where you want to create your application. 66 | 67 | #### Register the app 68 | 69 | 1. Select **Azure Active Directory** in the left-hand navigation, then select [App registrations](https://go.microsoft.com/fwlink/?linkid=2083908) under **Manage**. 70 | 71 | ![A screenshot of the App registrations ](images/aad-portal-app-registrations.png) 72 | 73 | 1. Select **New registration**. On the **Register an application** page, set the values as follows. 74 | 75 | - Set **Name** to `Node.js Graph Notification Webhook Sample`. 76 | - Set **Supported account types** to **Accounts in this organizational directory only**. 77 | - Under **Redirect URI**, set the first drop-down to `Web` and set the value to `http://localhost:3000/delegated/callback`. 78 | 79 | ![A screenshot of the Register an application page](images/register-an-app.png) 80 | 81 | 1. Select **Register** to create the app. On the app's **Overview** page, copy the value of the **Application (client) ID** and **Directory (tenant) ID** and save them for later. 82 | 83 | 1. Select **Certificates & secrets** under **Manage**. Select the **New client secret** button. Enter a value in **Description** and select one of the options for **Expires** and select **Add**. 84 | 85 | 1. Copy the **Value** of the new secret **before** you leave this page. It will never be displayed again. Save the value for later. 86 | 87 | ![A screenshot of a new secret in the Client secrets list](images/copy-secret-value.png) 88 | 89 | 1. Select **API permissions** under **Manage**. 90 | 91 | 1. In the list of pages for the app, select **API permissions**, then select **Add a permission**. 92 | 93 | 1. Make sure that the **Microsoft APIs** tab is selected, then select **Microsoft Graph**. 94 | 95 | 1. Select **Application permissions**, then find and enable the **ChannelMessage.Read.All** permission. Select **Add permissions** to add the enabled permission. 96 | 97 | > **Note:** To create subscriptions for other resources you need to select different permissions as documented [here](https://learn.microsoft.com/graph/api/subscription-post-subscriptions#permissions) 98 | 99 | 1. In the **Configured permissions** list, select the ellipses (`...`) in the **User.Read** row, and select **Remove permission**. The **User.Read** permission will be requested dynamically as part of the user sign-in process. 100 | 101 | ![A screenshot of the Remove permission menu item](images/remove-configured-permission.png) 102 | 103 | 1. Select **Grant admin consent for `name of your organization`** and **Yes**. This grants consent to the permissions of the application registration you just created to the current organization. 104 | 105 | ### Set up the ngrok proxy (optional) 106 | 107 | You must expose a public HTTPS endpoint to create a subscription and receive notifications from Microsoft Graph. While testing, you can use ngrok to temporarily allow messages from Microsoft Graph to tunnel to a *localhost* port on your computer. 108 | 109 | You can use the ngrok web interface `http://127.0.0.1:4040` to inspect the HTTP traffic that passes through the tunnel. To download and learn more about using ngrok, see the [ngrok website](https://ngrok.com/). 110 | 111 | 1. Run the following command in your command-line interface (CLI) to start an ngrok session. 112 | 113 | ```Shell 114 | ngrok http 3000 115 | ``` 116 | 117 | 1. Copy the HTTPS URL that's shown in the console. You'll use this to configure your notification URL in the sample. 118 | 119 | ![The forwarding HTTPS URL in the ngrok console](images/ngrok-https-url.png) 120 | 121 | > **IMPORTANT**: Keep the console open while testing. If you close it, the tunnel also closes and you'll need to generate a new URL and update the sample. See [troubleshooting](./TROUBLESHOOTING.md) for more information about using tunnels. 122 | 123 | ### Configure and run the sample 124 | 125 | 1. Rename [sample.env](sample.env) to **.env** and open it in a text editor. 126 | 127 | 1. Replace `YOUR_CLIENT_ID_HERE` with the client ID of your registered Azure application. 128 | 129 | 1. Replace `YOUR_CLIENT_SECRET_HERE` with the client secret of your registered Azure application. 130 | 131 | 1. Replace `YOUR_TENANT_ID_HERE` with the tenant ID of your organization. This information can be found next to the client ID on the application management page, note: if you choose *Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)* replace this value for "common". 132 | 133 | 1. Replace `YOUR_NGROK_URL_HERE` with the HTTPS ngrok URL you copied earlier. 134 | 135 | 1. (Optional) - You can update the `CERTIFICATE_PATH`, `CERTIFICATE_ID`, `PRIVATE_KEY_PATH`, and `PRIVATE_KEY_PASSWORD` if desired. 136 | 137 | 1. (Optional) - If you are running the sample on Windows, you can provide a path to **openssl.exe** in the `WINDOWS_OPENSSL_PATH` value in **.env**. 138 | 139 | 1. Install the dependencies running the following command: 140 | 141 | ```Shell 142 | npm install 143 | ``` 144 | 145 | 1. Start the application with the following command: 146 | 147 | ```Shell 148 | npm start 149 | ``` 150 | 151 | > **Note:** You can also attach the debugger included in Microsoft Visual Studio Code using the included [launch.json](.vscode/launch.json). For more information, see [Node.js debugging in VS Code](https://code.visualstudio.com/docs/nodejs/nodejs-debugging). 152 | 153 | 1. Open a browser and go to [http://localhost:3000](http://localhost:3000). 154 | 155 | ### Use the app to create a subscription 156 | 157 | #### Use delegated authentication to subscribe to a user's inbox 158 | 159 | 1. Choose the **Sign in and subscribe** button and sign in with a work or school account. 160 | 161 | 1. Review and consent to the requested permissions. The subscription is created and you are redirected to a page displaying any notification being received. 162 | 163 | 1. Send an email to yourself. A notification appears showing the subject and message ID. 164 | 165 | ![A screenshot of the user inbox notifications page](images/user-inbox-notifications.png) 166 | 167 | #### Use app-only authentication to subscribe to Teams channel messages 168 | 169 | 1. If you previously subscribed to a user's inbox, choose the **Delete subscription** button to return to the home page. 170 | 171 | 1. Choose the **Subscribe** button. The subscription is created and you are redirected to a page displaying any notification being received. 172 | 173 | 1. Post a message to a channel in any team in Microsoft Teams. A notification appears showing the sender's name and the message. 174 | 175 | ![A screenshot of the Teams channel notifications page](images/teams-channel-notifications.png) 176 | 177 | ## Troubleshooting 178 | 179 | See the dedicated [troubleshooting page](./TROUBLESHOOTING.md). 180 | 181 | ## Contributing 182 | 183 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md). 184 | 185 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 186 | 187 | ## Questions and comments 188 | 189 | We'd love to get your feedback about the Microsoft Graph Webhook sample. You can send your questions and suggestions to us in the [Issues](https://github.com/microsoftgraph/nodejs-webhooks-rest-sample/issues) section of this repository. 190 | 191 | Questions about Microsoft Graph in general should be posted to [Microsoft Q&A](https://learn.microsoft.com/answers/products/graph). Make sure that your questions or comments are tagged with the relevant Microsoft Graph tag. 192 | 193 | ## Additional resources 194 | 195 | - [Microsoft Graph Webhooks sample for ASP.NET core](https://github.com/microsoftgraph/aspnetcore-webhooks-sample) 196 | - [Microsoft Graph Webhooks sample for Java Spring](https://github.com/microsoftgraph/java-spring-webhooks-sample) 197 | - [Working with Webhooks in Microsoft Graph](https://learn.microsoft.com/graph/api/resources/webhooks) 198 | - [Subscription resource](https://learn.microsoft.com/graph/api/resources/subscription) 199 | - [Microsoft Graph documentation](https://learn.microsoft.com/graph) 200 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://learn.microsoft.com/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | - Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | - Full paths of source file(s) related to the manifestation of the issue 23 | - The location of the affected source code (tag/branch/commit or direct URL) 24 | - Any special configuration required to reproduce the issue 25 | - Step-by-step instructions to reproduce the issue 26 | - Proof-of-concept or exploit code (if possible) 27 | - Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | This document covers some of the common issues you may encounter when running this sample. 4 | 5 | ## You get a 403 Forbidden response when you attempt to create a subscription 6 | 7 | Make sure that your app registration includes the required permission for Microsoft Graph (as described in the [Register the app](README.md#register-the-app) section). This permission must be set before you try to create a subscription. Otherwise you'll get an error. Then, make sure a tenant administrator has granted consent to the application. 8 | 9 | ## Error AADSTS50020 - User account from identity provider does not exist in tenant 10 | 11 | Update the *OAUTH_TENANT_ID* value in your .env file for "common" instead of the tenantid. if you still have the same error check the following article for more information: , [Troubleshoot article](https://learn.microsoft.com/en-us/troubleshoot/azure/active-directory/error-code-aadsts50020-user-account-identity-provider-does-not-exist). 12 | 13 | ## You do not receive notifications 14 | 15 | If you're using ngrok, you can use the web interface [http://127.0.0.1:4040](http://127.0.0.1:4040) to see whether the notification is being received. If you're not using ngrok, monitor the network traffic using the tools your hosting service provides, or try using ngrok. 16 | 17 | If Microsoft Graph is not sending notifications, please open a [Stack Overflow](https://stackoverflow.com/questions/tagged/MicrosoftGraph) issue tagged `MicrosoftGraph`. Include the subscription ID and the time it was created. 18 | 19 | Known issue: Occasionally the notification is received, and the retrieved message is sent to NotificationService, but the SocketIo client in this sample does not update. When this happens, it's usually the first notification after the subscription is created. 20 | 21 | ## You get a "Subscription validation request failed" response 22 | 23 | This indicates that Microsoft Graph did not receive a validation response within the expected time frame (about 10 seconds). 24 | 25 | - Make sure that you are not paused in the debugger when the validation request is received. 26 | - If you're using ngrok, make sure that you used your project's HTTP port for the tunnel (not HTTPS), and ensure that the value of `NGROK_PROXY` matches your current active ngrok session. 27 | 28 | ## You get errors while installing packages 29 | 30 | Make sure the local path where you placed the solution is not too long/deep. Moving the solution closer to the root drive resolves this issue. 31 | 32 | ## Hosting the sample without a tunnel 33 | 34 | Microsoft Graph (or any other webhook provider) needs a notification URL that it can reach to deliver notifications. The sample uses localhost as the development server. 35 | 36 | Localhost just means this host. If any webhook provider would deliver a notification to localhost, it would be delivering it to itself. Not very useful. 37 | 38 | Microsoft Graph can't deliver notifications to localhost. For this reason, we need a tunnel that can forward requests from a URL on the Internet to our localhost. 39 | 40 | There are some alternatives that you can consider to try this sample without a tunnel. 41 | 42 | ### Host the sample on a cloud service 43 | 44 | You can host the sample using a cloud service such as Microsoft Azure. Cloud services allow you to expose the notification URL to the Internet. Microsoft Graph can deliver notifications to the URL in the cloud. 45 | 46 | Note that in some cases, you'll be able to deploy the sample to a website hosted in the cloud. In other cases, you'll need to set up a virtual machine and install a development environment with the prerequisites listed in the [ReadMe](./README.md#prerequisites). 47 | 48 | See your cloud provider's documentation for details about how to host a web application or virtual machine using the cloud service. 49 | 50 | ### Application registration on Azure Portal 51 | 52 | The application in Azure must redirect your users to your application in the cloud. Add the URL to your deployed application as a redirect URI on your app registration in the Azure portal. For example: `https://yourapp.domain.com/callback`. 53 | 54 | ### Notification URL in file constants.js 55 | 56 | Update the **notificationUrl** value in the file [`constants.js`](/constants.js) file to the `/listen` path on your deployed application's domain. For example, `https://yourapp.domain.com/listen`. 57 | 58 | From here, you can run `npm install && npm start` to install dependencies and start the application. 59 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const createError = require('http-errors'); 5 | const express = require('express'); 6 | const path = require('path'); 7 | const cookieParser = require('cookie-parser'); 8 | const logger = require('morgan'); 9 | const session = require('express-session'); 10 | const flash = require('connect-flash'); 11 | const msal = require('@azure/msal-node'); 12 | require('dotenv').config(); 13 | 14 | const dbHelper = require('./helpers/dbHelper'); 15 | dbHelper.ensureDatabase(); 16 | 17 | const indexRouter = require('./routes/index'); 18 | const delegatedRouter = require('./routes/delegated'); 19 | const appOnlyRouter = require('./routes/apponly'); 20 | const listenRouter = require('./routes/listen'); 21 | const watchRouter = require('./routes/watch'); 22 | const lifecycleRouter = require('./routes/lifecycle'); 23 | 24 | const app = express(); 25 | 26 | // MSAL config 27 | const msalConfig = { 28 | auth: { 29 | clientId: process.env.OAUTH_CLIENT_ID, 30 | authority: `${process.env.OAUTH_AUTHORITY}/${process.env.OAUTH_TENANT_ID}`, 31 | clientSecret: process.env.OAUTH_CLIENT_SECRET, 32 | }, 33 | system: { 34 | loggerOptions: { 35 | loggerCallback(logLevel, message, containsPii) { 36 | console.log(message); 37 | }, 38 | piiLoggingEnabled: false, 39 | logLevel: msal.LogLevel.Error, 40 | }, 41 | }, 42 | }; 43 | 44 | // Create msal application object 45 | app.locals.msalClient = new msal.ConfidentialClientApplication(msalConfig); 46 | 47 | // Session middleware 48 | // NOTE: Uses default in-memory session store, which is not 49 | // suitable for production 50 | app.use( 51 | session({ 52 | secret: process.env.EXPRESS_SESSION_SECRET, 53 | resave: false, 54 | saveUninitialized: false, 55 | unset: 'destroy', 56 | }), 57 | ); 58 | 59 | // Flash middleware 60 | app.use(flash()); 61 | app.use(function (req, res, next) { 62 | // Read any flashed errors and save 63 | // in the response locals 64 | res.locals.errors = req.flash('error_msg'); 65 | 66 | // Check for simple error string and 67 | // convert to layout's expected format 68 | const errs = req.flash('error'); 69 | for (const err in errs) { 70 | res.locals.errors.push({ message: 'An error occurred', debug: err }); 71 | } 72 | 73 | next(); 74 | }); 75 | 76 | // view engine setup 77 | app.set('views', path.join(__dirname, 'views')); 78 | app.set('view engine', 'pug'); 79 | 80 | app.use(logger('dev')); 81 | app.use(express.json()); 82 | app.use(express.urlencoded({ extended: false })); 83 | app.use(cookieParser()); 84 | app.use(express.static(path.join(__dirname, 'public'))); 85 | 86 | app.use('/', indexRouter); 87 | app.use('/delegated', delegatedRouter); 88 | app.use('/apponly', appOnlyRouter); 89 | app.use('/listen', listenRouter); 90 | app.use('/watch', watchRouter); 91 | app.use('/lifecycle', lifecycleRouter); 92 | 93 | // catch 404 and forward to error handler 94 | app.use(function (req, res, next) { 95 | next(createError(404)); 96 | }); 97 | 98 | // error handler 99 | app.use(function (err, req, res, next) { 100 | // set locals, only providing error in development 101 | res.locals.message = err.message; 102 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 103 | 104 | // render the error page 105 | res.status(err.status || 500); 106 | res.render('error'); 107 | }); 108 | 109 | module.exports = app; 110 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('nodejs-change-notifications-sample:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /helpers/certHelper.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const pem = require('pem'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const os = require('os'); 8 | const crypto = require('crypto'); 9 | /** 10 | * Configures path to openssl.exe if needed 11 | */ 12 | function ensureOpenSsl() { 13 | if (os.platform() === 'win32') { 14 | const pathOpenSSL = process.env.OPENSSL_CONF 15 | ? process.env.OPENSSL_CONF.replace('.cfg', '.exe') 16 | : (process.env.WINDOWS_OPENSSL_PATH ?? 17 | 'C:/Program Files/OpenSSL-Win64/bin/openssl.exe'); 18 | pem.config({ pathOpenSSL: pathOpenSSL }); 19 | } 20 | } 21 | /** 22 | * @param {string} keyPath - The relative path to the file containing the private key 23 | * @returns {string} Contents of the private key file 24 | */ 25 | function getPrivateKey(keyPath) { 26 | const key = fs.readFileSync(path.join(__dirname, keyPath), 'utf8'); 27 | return key; 28 | } 29 | 30 | module.exports = { 31 | /** 32 | * Creates a self-signed certificate (public/private key pair) if none exists 33 | * @param {string} certPath - The relative path to the public key file 34 | * @param {string} keyPath - The relative path to the private key file 35 | * @param {string} password - The password to use to protect the private key 36 | */ 37 | createSelfSignedCertificateIfNotExists: async ( 38 | certPath, 39 | keyPath, 40 | password, 41 | ) => { 42 | const certFullPath = path.join(__dirname, certPath); 43 | return new Promise((resolve, reject) => { 44 | if (!fs.existsSync(certFullPath)) { 45 | ensureOpenSsl(); 46 | pem.createCertificate( 47 | { 48 | selfSigned: true, 49 | serviceKeyPassword: password, 50 | days: 365, 51 | }, 52 | (err, result) => { 53 | if (err) { 54 | console.error(err); 55 | reject(err.message); 56 | } else { 57 | fs.writeFileSync(certFullPath, result.certificate); 58 | fs.writeFileSync( 59 | path.join(__dirname, keyPath), 60 | result.serviceKey, 61 | ); 62 | resolve(true); 63 | } 64 | }, 65 | ); 66 | } else { 67 | resolve(true); 68 | } 69 | }); 70 | }, 71 | /** 72 | * Gets the certificate contents from the certificate file 73 | * @param {string} certPath = The relative path to the certificate 74 | * @returns {string} The contents of the certificate 75 | */ 76 | getSerializedCertificate: (certPath) => { 77 | const cert = fs.readFileSync(path.join(__dirname, certPath)); 78 | // Remove the markers from the string, leaving just the certificate 79 | return cert 80 | .toString() 81 | .replace(/(\r\n|\n|\r|-|BEGIN|END|CERTIFICATE|\s)/gm, ''); 82 | }, 83 | /** 84 | * Decrypts the encrypted symmetric key sent by Microsoft Graph 85 | * @param {string} encodedKey - A base64 string containing an encrypted symmetric key 86 | * @param {string} keyPath - The relative path to the private key file to decrypt with 87 | * @returns {Buffer} The decrypted symmetric key 88 | */ 89 | decryptSymmetricKey: (encodedKey, keyPath) => { 90 | const asymmetricKey = getPrivateKey(keyPath); 91 | const encryptedKey = Buffer.from(encodedKey, 'base64'); 92 | const decryptedSymmetricKey = crypto.privateDecrypt( 93 | asymmetricKey, 94 | encryptedKey, 95 | ); 96 | return decryptedSymmetricKey; 97 | }, 98 | /** 99 | * Decrypts the payload data using the one-time use symmetric key 100 | * @param {string} encryptedPayload - The base64-encoded encrypted payload 101 | * @param {Buffer} symmetricKey - The one-time use symmetric key sent by Microsoft Graph 102 | * @returns {string} - The decrypted payload 103 | */ 104 | decryptPayload: (encryptedPayload, symmetricKey) => { 105 | // Copy the initialization vector from the symmetric key 106 | const iv = Buffer.alloc(16, 0); 107 | symmetricKey.copy(iv, 0, 0, 16); 108 | 109 | // Create a decipher object 110 | const decipher = crypto.createDecipheriv('aes-256-cbc', symmetricKey, iv); 111 | 112 | // Decrypt the payload 113 | let decryptedPayload = decipher.update(encryptedPayload, 'base64', 'utf8'); 114 | decryptedPayload += decipher.final('utf8'); 115 | return decryptedPayload; 116 | }, 117 | /** 118 | * @param {string} encodedSignature - The base64-encoded signature 119 | * @param {string} signedPayload - The base64-encoded signed payload 120 | * @param {Buffer} symmetricKey - The one-time use symmetric key 121 | * @returns {boolean} - True if signature is valid, false if invalid 122 | */ 123 | verifySignature: (encodedSignature, signedPayload, symmetricKey) => { 124 | const hmac = crypto.createHmac('sha256', symmetricKey); 125 | hmac.write(signedPayload, 'base64'); 126 | return encodedSignature === hmac.digest('base64'); 127 | }, 128 | }; 129 | -------------------------------------------------------------------------------- /helpers/dbHelper.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const fs = require('fs'); 5 | const sqlite3 = require('sqlite3').verbose(); 6 | 7 | // Relative path to the subscription database 8 | const dbFile = './helpers/subscriptions.sqlite3'; 9 | 10 | module.exports = { 11 | /** 12 | * Creates and initializes the subscription database if it does not 13 | * already exist 14 | */ 15 | ensureDatabase: () => { 16 | const dbExists = fs.existsSync(dbFile); 17 | const db = new sqlite3.Database(dbFile); 18 | const createSubscriptionStatement = 19 | 'CREATE TABLE Subscription (' + 20 | 'SubscriptionId TEXT NOT NULL, ' + 21 | 'UserAccountId TEXT NOT NULL' + 22 | ')'; 23 | 24 | db.serialize(() => { 25 | if (!dbExists) { 26 | db.run(createSubscriptionStatement, (error) => { 27 | if (error) throw error; 28 | }); 29 | } 30 | }); 31 | 32 | db.close(); 33 | }, 34 | /** 35 | * Gets a single subscription by ID 36 | * @param {string} subscriptionId - The ID of the subscription to get 37 | * @returns {object} The subscription 38 | */ 39 | getSubscription: async (subscriptionId) => { 40 | const db = new sqlite3.Database(dbFile); 41 | const selectStatement = 42 | 'SELECT ' + 43 | 'SubscriptionId as subscriptionId, ' + 44 | 'UserAccountId as userAccountId ' + 45 | 'FROM Subscription ' + 46 | 'WHERE SubscriptionId = $subscriptionId'; 47 | 48 | return new Promise((resolve, reject) => { 49 | db.serialize(() => { 50 | db.get( 51 | selectStatement, 52 | { 53 | $subscriptionId: subscriptionId, 54 | }, 55 | (err, row) => { 56 | if (err) { 57 | reject(`Database error: ${err.message}`); 58 | } else { 59 | resolve(row); 60 | } 61 | }, 62 | ); 63 | }); 64 | }); 65 | }, 66 | /** 67 | * Gets all subscriptions for a user account 68 | * @param {string} userAccountId - The user account ID 69 | * @returns {Array} An array of subscriptions for the user 70 | */ 71 | getSubscriptionsByUserAccountId: async (userAccountId) => { 72 | const db = new sqlite3.Database(dbFile); 73 | const selectStatement = 74 | 'SELECT ' + 75 | 'SubscriptionId as subscriptionId, ' + 76 | 'UserAccountId as userAccountId ' + 77 | 'FROM Subscription ' + 78 | 'WHERE UserAccountId = $userAccountId'; 79 | 80 | return new Promise((resolve, reject) => { 81 | db.serialize(() => { 82 | db.all( 83 | selectStatement, 84 | { 85 | $userAccountId: userAccountId, 86 | }, 87 | (err, rows) => { 88 | if (err) { 89 | reject(`Database error: ${err.message}`); 90 | } else { 91 | resolve(rows); 92 | } 93 | }, 94 | ); 95 | }); 96 | }); 97 | }, 98 | /** 99 | * Adds a subscription to the database 100 | * @param {string} subscriptionId - The subscription ID 101 | * @param {string} userAccountId - The user account ID (use 'APP-ONLY' for subscriptions owned by the app) 102 | */ 103 | addSubscription: async (subscriptionId, userAccountId) => { 104 | const db = new sqlite3.Database(dbFile); 105 | const insertStatement = 106 | 'INSERT INTO Subscription ' + 107 | '(SubscriptionId, UserAccountId) ' + 108 | 'VALUES ($subscriptionId, $userAccountId)'; 109 | 110 | return new Promise((resolve, reject) => { 111 | db.serialize(() => { 112 | db.run( 113 | insertStatement, 114 | { 115 | $subscriptionId: subscriptionId, 116 | $userAccountId: userAccountId, 117 | }, 118 | (err) => { 119 | if (err) { 120 | reject(`Database error: ${err.message}`); 121 | } else { 122 | resolve(true); 123 | } 124 | }, 125 | ); 126 | }); 127 | }); 128 | }, 129 | /** 130 | * Deletes a subscription from the database 131 | * @param {string} subscriptionId - The ID of the subscription to delete 132 | */ 133 | deleteSubscription: async (subscriptionId) => { 134 | const db = new sqlite3.Database(dbFile); 135 | const deleteStatement = 136 | 'DELETE FROM Subscription WHERE ' + 'SubscriptionId = $subscriptionId'; 137 | 138 | return new Promise((resolve, reject) => { 139 | db.serialize(() => { 140 | db.run( 141 | deleteStatement, 142 | { 143 | $subscriptionId: subscriptionId, 144 | }, 145 | (err) => { 146 | if (err) { 147 | reject(`Database error: ${err.message}`); 148 | } else { 149 | resolve(true); 150 | } 151 | }, 152 | ); 153 | }); 154 | }); 155 | }, 156 | }; 157 | -------------------------------------------------------------------------------- /helpers/graphHelper.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | require('isomorphic-fetch'); 5 | var graph = require('@microsoft/microsoft-graph-client'); 6 | 7 | module.exports = { 8 | /** 9 | * Gets a Graph client configured to use delegated auth 10 | * @param {IConfidentialClientApplication} msalClient - The MSAL client used to retrieve user tokens 11 | * @param {string} userAccountId - The user's account ID 12 | */ 13 | getGraphClientForUser(msalClient, userAccountId) { 14 | if (!msalClient || !userAccountId) { 15 | throw new Error( 16 | `Invalid MSAL state. Client: ${ 17 | msalClient ? 'present' : 'missing' 18 | }, User Account ID: ${userAccountId ? 'present' : 'missing'}`, 19 | ); 20 | } 21 | 22 | // Initialize Graph client 23 | return graph.Client.init({ 24 | // Implement an auth provider that gets a token 25 | // from the app's MSAL instance 26 | authProvider: async (done) => { 27 | try { 28 | // Get the user's account 29 | const account = await msalClient 30 | .getTokenCache() 31 | .getAccountByHomeId(userAccountId); 32 | 33 | if (account) { 34 | // Attempt to get the token silently 35 | // This method uses the token cache and 36 | // refreshes expired tokens as needed 37 | const response = await msalClient.acquireTokenSilent({ 38 | scopes: process.env.OAUTH_SCOPES.split(','), 39 | redirectUri: process.env.OAUTH_REDIRECT_URI, 40 | account: account, 41 | }); 42 | 43 | // First param to callback is the error, 44 | // Set to null in success case 45 | done(null, response.accessToken); 46 | } 47 | } catch (err) { 48 | console.log(JSON.stringify(err, Object.getOwnPropertyNames(err))); 49 | done(err, null); 50 | } 51 | }, 52 | }); 53 | }, 54 | /** 55 | * Gets a Graph client configured to use app-only auth 56 | * @param {IConfidentialClientApplication} msalClient - The MSAL client used to retrieve app-only tokens 57 | */ 58 | getGraphClientForApp(msalClient) { 59 | if (!msalClient) { 60 | throw new Error('Invalid MSAL state. MSAL client is missing.'); 61 | } 62 | 63 | // Initialize Graph client 64 | return graph.Client.init({ 65 | // Implement an auth provider that gets a token 66 | // from the app's MSAL instance 67 | authProvider: async (done) => { 68 | try { 69 | // Get a token using client credentials 70 | const response = await msalClient.acquireTokenByClientCredential({ 71 | scopes: ['https://graph.microsoft.com/.default'], 72 | }); 73 | 74 | // First param to callback is the error, 75 | // Set to null in success case 76 | done(null, response.accessToken); 77 | } catch (err) { 78 | console.log(JSON.stringify(err, Object.getOwnPropertyNames(err))); 79 | done(err, null); 80 | } 81 | }, 82 | }); 83 | }, 84 | }; 85 | -------------------------------------------------------------------------------- /helpers/socketHelper.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const express = require('express'); 5 | const http = require('http'); 6 | const io = require('socket.io'); 7 | 8 | const socketServer = http.createServer(express); 9 | 10 | // Create a Socket.io server 11 | const ioServer = io(socketServer, { 12 | cors: { 13 | // Allow requests from the server only 14 | origin: [ 15 | process.env.OAUTH_REDIRECT_URI.substring( 16 | 0, 17 | process.env.OAUTH_REDIRECT_URI.indexOf('/', 'https://'.length), 18 | ), 19 | process.env.NGROK_PROXY, 20 | ], 21 | methods: ['GET', 'POST'], 22 | }, 23 | }); 24 | 25 | ioServer.on('connection', (socket) => { 26 | // Create rooms by subscription ID 27 | socket.on('create_room', (subscriptionId) => { 28 | socket.join(subscriptionId); 29 | }); 30 | }); 31 | 32 | // Listen on port 3001 33 | socketServer.listen(3001); 34 | console.log('Socket.io listening on port 3001'); 35 | 36 | module.exports = ioServer; 37 | -------------------------------------------------------------------------------- /helpers/tokenHelper.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const jwt = require('jsonwebtoken'); 5 | const jwksClient = require('jwks-rsa'); 6 | 7 | // Configure JSON web key set client to get keys 8 | // from well-known Microsoft identity endpoint 9 | const client = jwksClient({ 10 | jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys', 11 | }); 12 | 13 | /** 14 | * Gets the key specified in header 15 | * @param {JwtHeader} header - The header containing the key ID 16 | * @param {function} callback - The callback function 17 | */ 18 | async function getKey(header, callback) { 19 | try { 20 | const key = await client.getSigningKey(header.kid); 21 | const signingKey = key.publicKey || key.rsaPublicKey; 22 | callback(null, signingKey); 23 | } catch (err) { 24 | callback(err, null); 25 | } 26 | } 27 | 28 | module.exports = { 29 | /** 30 | * Validates a token has a valid signature and has the expected audience and issuer 31 | * @param {string} token - The token to verify 32 | * @param {string} appId - The application ID expected in the audience claim 33 | * @param {string} tenantId - The tenant ID expected in the issuer claim 34 | */ 35 | isTokenValid: (token, appId, tenantId) => { 36 | return new Promise((resolve) => { 37 | const options = { 38 | audience: [appId], 39 | issuer: [`https://sts.windows.net/${tenantId}/`], 40 | }; 41 | 42 | jwt.verify(token, getKey, options, (err) => { 43 | if (err) { 44 | console.log(`Token validation error: ${err.message}`); 45 | resolve(false); 46 | } 47 | resolve(true); 48 | }); 49 | }); 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /images/aad-portal-app-registrations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/nodejs-webhooks-sample/d500f2926617e8ecf2f83bfe1714320da9ebb9ac/images/aad-portal-app-registrations.png -------------------------------------------------------------------------------- /images/copy-secret-value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/nodejs-webhooks-sample/d500f2926617e8ecf2f83bfe1714320da9ebb9ac/images/copy-secret-value.png -------------------------------------------------------------------------------- /images/ngrok-https-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/nodejs-webhooks-sample/d500f2926617e8ecf2f83bfe1714320da9ebb9ac/images/ngrok-https-url.png -------------------------------------------------------------------------------- /images/register-an-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/nodejs-webhooks-sample/d500f2926617e8ecf2f83bfe1714320da9ebb9ac/images/register-an-app.png -------------------------------------------------------------------------------- /images/remove-configured-permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/nodejs-webhooks-sample/d500f2926617e8ecf2f83bfe1714320da9ebb9ac/images/remove-configured-permission.png -------------------------------------------------------------------------------- /images/teams-channel-notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/nodejs-webhooks-sample/d500f2926617e8ecf2f83bfe1714320da9ebb9ac/images/teams-channel-notifications.png -------------------------------------------------------------------------------- /images/user-inbox-notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/nodejs-webhooks-sample/d500f2926617e8ecf2f83bfe1714320da9ebb9ac/images/user-inbox-notifications.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-change-notifications-sample", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test": "mocha ./tests/configTests.js", 7 | "lint": "prettier --check **/**.js", 8 | "format": "prettier --write **/**.js", 9 | "start": "npm run test && node ./bin/www" 10 | }, 11 | "dependencies": { 12 | "@azure/msal-node": "^3.6.0", 13 | "@microsoft/microsoft-graph-client": "^3.0.7", 14 | "connect-flash": "^0.1.1", 15 | "cookie-parser": "~1.4.7", 16 | "debug": "^4.4.1", 17 | "dotenv": "^16.5.0", 18 | "express": "^4.21.2", 19 | "express-promise-router": "^4.1.1", 20 | "express-session": "^1.18.1", 21 | "http-errors": "^2.0.0", 22 | "isomorphic-fetch": "^3.0.0", 23 | "jsonwebtoken": "^9.0.2", 24 | "jwks-rsa": "^3.2.0", 25 | "mocha": "^11.5.0", 26 | "morgan": "^1.10.0", 27 | "pem": "^1.14.8", 28 | "pug": "^3.0.3", 29 | "socket.io": "^4.8.1", 30 | "sqlite3": "^5.1.7" 31 | }, 32 | "devDependencies": { 33 | "prettier": "^3.5.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/images/g-raph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/nodejs-webhooks-sample/d500f2926617e8ecf2f83bfe1714320da9ebb9ac/public/images/g-raph.png -------------------------------------------------------------------------------- /public/javascript/watch-client.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // Connect to the Socket.io server 5 | const socket = io('http://localhost:3001'); 6 | 7 | // Listen for notification received messages 8 | socket.on('notification_received', (notificationData) => { 9 | console.log(`Received notification: ${JSON.stringify(notificationData)}`); 10 | 11 | // Create a new table row with data from the notification 12 | const tableRow = document.createElement('tr'); 13 | 14 | if (notificationData.type == 'message') { 15 | // Email messages log subject and message ID 16 | const subjectCell = document.createElement('td'); 17 | subjectCell.innerText = notificationData.resource.subject; 18 | tableRow.appendChild(subjectCell); 19 | 20 | const idCell = document.createElement('td'); 21 | idCell.innerText = notificationData.resource.id; 22 | tableRow.appendChild(idCell); 23 | } else if (notificationData.type === 'chatMessage') { 24 | // Teams channel messages log sender and text 25 | const senderCell = document.createElement('td'); 26 | senderCell.innerText = 27 | notificationData.resource.from.user?.displayName || 'Unknown'; 28 | tableRow.appendChild(senderCell); 29 | 30 | const messageCell = document.createElement('td'); 31 | messageCell.innerText = notificationData.resource.body?.content || ''; 32 | tableRow.appendChild(messageCell); 33 | } 34 | 35 | document.getElementById('notifications').appendChild(tableRow); 36 | }); 37 | 38 | // Create a room for the subscription ID 39 | socket.emit('create_room', subscriptionId); 40 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | .wrapped-pre { 2 | word-wrap: break-word; 3 | word-break: break-all; 4 | white-space: pre-wrap; 5 | } 6 | -------------------------------------------------------------------------------- /routes/apponly.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const router = require('express-promise-router')(); 5 | const graph = require('../helpers/graphHelper'); 6 | const dbHelper = require('../helpers/dbHelper'); 7 | const certHelper = require('../helpers/certHelper'); 8 | 9 | // GET /apponly/subscribe 10 | router.get('/subscribe', async function (req, res) { 11 | const client = graph.getGraphClientForApp(req.app.locals.msalClient); 12 | 13 | // Ensure a certificate exists 14 | await certHelper.createSelfSignedCertificateIfNotExists( 15 | process.env.CERTIFICATE_PATH, 16 | process.env.PRIVATE_KEY_PATH, 17 | process.env.PRIVATE_KEY_PASSWORD, 18 | ); 19 | 20 | // If in production, use the current host to receive notifications 21 | // In development, must use an ngrok proxy 22 | const notificationHost = 23 | process.env.NODE_ENV === 'production' 24 | ? `${req.protocol}://${req.hostname}` 25 | : process.env.NGROK_PROXY; 26 | 27 | try { 28 | const existingSubscriptions = 29 | await dbHelper.getSubscriptionsByUserAccountId('APP-ONLY'); 30 | 31 | // Apps are only allowed one subscription to the /teams/getAllMessages resource 32 | // If we already had one, delete it so we can create a new one 33 | if (existingSubscriptions) { 34 | for (var existingSub of existingSubscriptions) { 35 | try { 36 | await client 37 | .api(`/subscriptions/${existingSub.subscriptionId}`) 38 | .delete(); 39 | } catch (err) { 40 | console.error(err); 41 | } 42 | 43 | await dbHelper.deleteSubscription(existingSub.subscriptionId); 44 | } 45 | } 46 | 47 | // Create the subscription 48 | const subscription = await client.api('/subscriptions').create({ 49 | changeType: 'created', 50 | notificationUrl: `${notificationHost}/listen`, 51 | lifecycleNotificationUrl: `${notificationHost}/lifecycle`, 52 | resource: '/teams/getAllMessages', 53 | clientState: process.env.SUBSCRIPTION_CLIENT_STATE, 54 | includeResourceData: true, 55 | // To get resource data, we must provide a public key that 56 | // Microsoft Graph will use to encrypt their key 57 | // See https://learn.microsoft.com/graph/webhooks-with-resource-data#creating-a-subscription 58 | encryptionCertificate: certHelper.getSerializedCertificate( 59 | process.env.CERTIFICATE_PATH, 60 | ), 61 | encryptionCertificateId: process.env.CERTIFICATE_ID, 62 | expirationDateTime: new Date(Date.now() + 3600000).toISOString(), 63 | }); 64 | 65 | // Save the subscription ID in the session 66 | req.session.subscriptionId = subscription.id; 67 | console.log( 68 | `Subscribed to Teams channel messages, subscription ID: ${subscription.id}`, 69 | ); 70 | 71 | // Add subscription to the database 72 | await dbHelper.addSubscription(subscription.id, 'APP-ONLY'); 73 | 74 | // Redirect to subscription page 75 | res.redirect('/watch'); 76 | } catch (error) { 77 | req.flash('error_msg', { 78 | message: 'Error subscribing for Teams channel message notifications', 79 | debug: JSON.stringify(error, Object.getOwnPropertyNames(error)), 80 | }); 81 | 82 | res.redirect('/'); 83 | } 84 | }); 85 | 86 | // GET /apponly/signout 87 | router.get('/signout', async function (req, res) { 88 | // Delete the subscription from database and Graph 89 | const subscriptionId = req.session.subscriptionId; 90 | const msalClient = req.app.locals.msalClient; 91 | 92 | await dbHelper.deleteSubscription(subscriptionId); 93 | 94 | const client = graph.getGraphClientForApp(msalClient); 95 | 96 | try { 97 | await client.api(`/subscriptions/${subscriptionId}`).delete(); 98 | 99 | req.session.subscriptionId = null; 100 | } catch (graphErr) { 101 | console.log(`Error deleting subscription from Graph: ${graphErr.message}`); 102 | } 103 | 104 | res.redirect('/'); 105 | }); 106 | 107 | module.exports = router; 108 | -------------------------------------------------------------------------------- /routes/delegated.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const router = require('express-promise-router')(); 5 | const graph = require('../helpers/graphHelper'); 6 | const dbHelper = require('../helpers/dbHelper'); 7 | 8 | // GET /delegated/signin 9 | router.get('/signin', async function (req, res) { 10 | // Start the authorization code flow by redirecting the 11 | // browser to Microsoft identity platforms authorization URL 12 | const urlParameters = { 13 | scopes: process.env.OAUTH_SCOPES.split(','), 14 | redirectUri: process.env.OAUTH_REDIRECT_URI, 15 | prompt: 'select_account', 16 | }; 17 | 18 | try { 19 | const authUrl = 20 | await req.app.locals.msalClient.getAuthCodeUrl(urlParameters); 21 | res.redirect(authUrl); 22 | } catch (error) { 23 | console.log(`Error: ${error}`); 24 | req.flash('error_msg', { 25 | message: 'Error getting auth URL', 26 | debug: JSON.stringify(error, Object.getOwnPropertyNames(error)), 27 | }); 28 | res.redirect('/'); 29 | } 30 | }); 31 | 32 | // GET /delegated/callback 33 | router.get('/callback', async function (req, res) { 34 | // Microsoft identity platform redirects the browser here with the 35 | // authorization result 36 | const tokenRequest = { 37 | code: req.query.code, 38 | scopes: process.env.OAUTH_SCOPES.split(','), 39 | redirectUri: process.env.OAUTH_REDIRECT_URI, 40 | }; 41 | 42 | try { 43 | const response = 44 | await req.app.locals.msalClient.acquireTokenByCode(tokenRequest); 45 | 46 | // Save the user's homeAccountId in their session 47 | req.session.userAccountId = response.account.homeAccountId; 48 | 49 | const client = graph.getGraphClientForUser( 50 | req.app.locals.msalClient, 51 | req.session.userAccountId, 52 | ); 53 | 54 | // Get the user's profile from Microsoft Graph 55 | const user = await client.api('/me').select('displayName, mail').get(); 56 | 57 | // Save user's name and email address in the session 58 | req.session.user = { 59 | name: user.displayName, 60 | email: user.mail, 61 | }; 62 | 63 | console.log(`Logged in as ${user.displayName}`); 64 | 65 | // If in production, use the current host to receive notifications 66 | // In development, must use an ngrok proxy 67 | const notificationHost = 68 | process.env.NODE_ENV === 'production' 69 | ? `${req.protocol}://${req.hostname}` 70 | : process.env.NGROK_PROXY; 71 | 72 | // Create the subscription 73 | const subscription = await client.api('/subscriptions').create({ 74 | changeType: 'created', 75 | notificationUrl: `${notificationHost}/listen`, 76 | lifecycleNotificationUrl: `${notificationHost}/lifecycle`, 77 | resource: 'me/mailFolders/inbox/messages', 78 | clientState: process.env.SUBSCRIPTION_CLIENT_STATE, 79 | includeResourceData: false, 80 | expirationDateTime: new Date(Date.now() + 3600000).toISOString(), 81 | }); 82 | 83 | // Save the subscription ID in the session 84 | req.session.subscriptionId = subscription.id; 85 | console.log( 86 | `Subscribed to user's inbox, subscription ID: ${subscription.id}`, 87 | ); 88 | 89 | // Add the subscription to the database 90 | await dbHelper.addSubscription(subscription.id, req.session.userAccountId); 91 | 92 | // Redirect to subscription page 93 | res.redirect('/watch'); 94 | } catch (error) { 95 | req.flash('error_msg', { 96 | message: 'Error completing authentication', 97 | debug: JSON.stringify(error, Object.getOwnPropertyNames(error)), 98 | }); 99 | 100 | res.redirect('/'); 101 | } 102 | }); 103 | 104 | // GET /delegated/signout 105 | router.get('/signout', async function (req, res) { 106 | // Delete the subscription from database and Graph 107 | const subscriptionId = req.session.subscriptionId; 108 | const msalClient = req.app.locals.msalClient; 109 | 110 | await dbHelper.deleteSubscription(subscriptionId); 111 | 112 | const client = graph.getGraphClientForUser( 113 | msalClient, 114 | req.session.userAccountId, 115 | ); 116 | 117 | try { 118 | await client.api(`/subscriptions/${subscriptionId}`).delete(); 119 | 120 | req.session.subscriptionId = null; 121 | } catch (graphErr) { 122 | console.log(`Error deleting subscription from Graph: ${graphErr.message}`); 123 | } 124 | 125 | try { 126 | // Remove user's account from MSAL cache 127 | const userAccount = await msalClient 128 | .getTokenCache() 129 | .getAccountByHomeId(req.session.userAccountId); 130 | 131 | await msalClient.getTokenCache().removeAccount(userAccount); 132 | 133 | req.session.userAccountId = null; 134 | } catch (msalErr) { 135 | console.log(`Error removing user from MSAL cache: ${msalErr.message}`); 136 | } 137 | 138 | res.redirect('/'); 139 | }); 140 | 141 | module.exports = router; 142 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const express = require('express'); 5 | const router = express.Router(); 6 | 7 | // GET / 8 | router.get('/', function (req, res, next) { 9 | res.render('index', { title: 'Microsoft Graph Notifications Sample' }); 10 | }); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /routes/lifecycle.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const router = require('express-promise-router')(); 5 | const graph = require('../helpers/graphHelper'); 6 | const dbHelper = require('../helpers/dbHelper'); 7 | 8 | // POST /lifecycle 9 | router.post('/', async function (req, res) { 10 | // This is the notification endpoint Microsoft Graph sends notifications to 11 | 12 | // If there is a validationToken parameter 13 | // in the query string, this is the endpoint validation 14 | // request sent by Microsoft Graph. Return the token 15 | // as plain text with a 200 response 16 | // https://learn.microsoft.com/graph/webhooks#notification-endpoint-validation 17 | if (req.query && req.query.validationToken) { 18 | res.set('Content-Type', 'text/plain'); 19 | res.send(req.query.validationToken); 20 | return; 21 | } 22 | 23 | console.log(JSON.stringify(req.body, null, 2)); 24 | 25 | for (let i = 0; i < req.body.value.length; i++) { 26 | const notification = req.body.value[i]; 27 | 28 | // Verify the client state matches the expected value 29 | // and that this is a lifecycle notification 30 | if ( 31 | notification.clientState === process.env.SUBSCRIPTION_CLIENT_STATE && 32 | notification.lifecycleEvent === 'reauthorizationRequired' 33 | ) { 34 | // Verify we have a matching subscription record in the database 35 | const subscription = await dbHelper.getSubscription( 36 | notification.subscriptionId, 37 | ); 38 | if (subscription) { 39 | // Renew the subscription 40 | await renewSubscription(subscription, req.app.locals.msalClient); 41 | } 42 | } 43 | } 44 | 45 | res.status(202).end(); 46 | }); 47 | 48 | /** 49 | * Process a non-encrypted notification 50 | * @param {object} subscription - The subscription to renew 51 | * @param {IConfidentialClientApplication} msalClient - The MSAL client to retrieve tokens for Graph requests 52 | */ 53 | async function renewSubscription(subscription, msalClient) { 54 | // Get the Graph client 55 | const client = 56 | subscription.userAccountId === 'APP-ONLY' 57 | ? graph.getGraphClientForApp(msalClient) 58 | : graph.getGraphClientForUser(msalClient, subscription.userAccountId); 59 | 60 | try { 61 | // Update the expiration on the subscription 62 | await client.api(`/subscriptions/${subscription.subscriptionId}`).update({ 63 | expirationDateTime: new Date(Date.now() + 3600000).toISOString(), 64 | }); 65 | console.log(`Renewed subscription ${subscription.subscriptionId}`); 66 | } catch (err) { 67 | console.log(`Error updating subscription ${subscription.subscriptionId}:`); 68 | console.error(err); 69 | } 70 | } 71 | 72 | module.exports = router; 73 | -------------------------------------------------------------------------------- /routes/listen.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const router = require('express-promise-router')(); 5 | const graph = require('../helpers/graphHelper'); 6 | const ioServer = require('../helpers/socketHelper'); 7 | const dbHelper = require('../helpers/dbHelper'); 8 | const tokenHelper = require('../helpers/tokenHelper'); 9 | const certHelper = require('../helpers/certHelper'); 10 | 11 | // POST /listen 12 | router.post('/', async function (req, res) { 13 | // This is the notification endpoint Microsoft Graph sends notifications to 14 | 15 | // If there is a validationToken parameter 16 | // in the query string, this is the endpoint validation 17 | // request sent by Microsoft Graph. Return the token 18 | // as plain text with a 200 response 19 | // https://learn.microsoft.com/graph/webhooks#notification-endpoint-validation 20 | if (req.query && req.query.validationToken) { 21 | res.set('Content-Type', 'text/plain'); 22 | res.send(req.query.validationToken); 23 | return; 24 | } 25 | 26 | console.log(JSON.stringify(req.body, null, 2)); 27 | 28 | // Check for validation tokens, validate them if present 29 | let areTokensValid = true; 30 | if (req.body.validationTokens) { 31 | const appId = process.env.OAUTH_CLIENT_ID; 32 | const tenantId = process.env.OAUTH_TENANT_ID; 33 | const validationResults = await Promise.all( 34 | req.body.validationTokens.map((token) => 35 | tokenHelper.isTokenValid(token, appId, tenantId), 36 | ), 37 | ); 38 | 39 | areTokensValid = validationResults.reduce((x, y) => x && y); 40 | } 41 | 42 | if (areTokensValid) { 43 | for (let i = 0; i < req.body.value.length; i++) { 44 | const notification = req.body.value[i]; 45 | 46 | // Verify the client state matches the expected value 47 | if (notification.clientState == process.env.SUBSCRIPTION_CLIENT_STATE) { 48 | // Verify we have a matching subscription record in the database 49 | const subscription = await dbHelper.getSubscription( 50 | notification.subscriptionId, 51 | ); 52 | if (subscription) { 53 | // If notification has encrypted content, process that 54 | if (notification.encryptedContent) { 55 | processEncryptedNotification(notification); 56 | } else { 57 | await processNotification( 58 | notification, 59 | req.app.locals.msalClient, 60 | subscription.userAccountId, 61 | ); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | res.status(202).end(); 69 | }); 70 | /** 71 | * Processes an encrypted notification 72 | * @param {object} notification - The notification containing encrypted content 73 | */ 74 | function processEncryptedNotification(notification) { 75 | // Decrypt the symmetric key sent by Microsoft Graph 76 | const symmetricKey = certHelper.decryptSymmetricKey( 77 | notification.encryptedContent.dataKey, 78 | process.env.PRIVATE_KEY_PATH, 79 | ); 80 | 81 | // Validate the signature on the encrypted content 82 | const isSignatureValid = certHelper.verifySignature( 83 | notification.encryptedContent.dataSignature, 84 | notification.encryptedContent.data, 85 | symmetricKey, 86 | ); 87 | 88 | if (isSignatureValid) { 89 | // Decrypt the payload 90 | const decryptedPayload = certHelper.decryptPayload( 91 | notification.encryptedContent.data, 92 | symmetricKey, 93 | ); 94 | 95 | // Send the notification to the Socket.io room 96 | emitNotification(notification.subscriptionId, { 97 | type: 'chatMessage', 98 | resource: JSON.parse(decryptedPayload), 99 | }); 100 | } 101 | } 102 | /** 103 | * Process a non-encrypted notification 104 | * @param {object} notification - The notification to process 105 | * @param {IConfidentialClientApplication} msalClient - The MSAL client to retrieve tokens for Graph requests 106 | * @param {string} userAccountId - The user's account ID 107 | */ 108 | async function processNotification(notification, msalClient, userAccountId) { 109 | // Get the message ID 110 | const messageId = notification.resourceData.id; 111 | 112 | const client = graph.getGraphClientForUser(msalClient, userAccountId); 113 | 114 | try { 115 | // Get the message from Graph 116 | const message = await client 117 | .api(`/me/messages/${messageId}`) 118 | .select('subject,id') 119 | .get(); 120 | 121 | // Send the notification to the Socket.io room 122 | emitNotification(notification.subscriptionId, { 123 | type: 'message', 124 | resource: message, 125 | }); 126 | } catch (err) { 127 | console.log(`Error getting message with ${messageId}:`); 128 | console.error(err); 129 | } 130 | } 131 | /** 132 | * Sends a notification to a Socket.io room 133 | * @param {string} subscriptionId - The subscription ID used to send to the correct room 134 | * @param {object} data - The data to send to the room 135 | */ 136 | function emitNotification(subscriptionId, data) { 137 | ioServer.to(subscriptionId).emit('notification_received', data); 138 | } 139 | 140 | module.exports = router; 141 | -------------------------------------------------------------------------------- /routes/watch.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const router = require('express-promise-router')(); 5 | const graph = require('../helpers/graphHelper'); 6 | 7 | // GET /watch 8 | router.get('/', async function (req, res) { 9 | const userAccountId = req.session.userAccountId; 10 | const subscriptionId = req.session.subscriptionId; 11 | const user = req.session.user; 12 | 13 | if (!subscriptionId) { 14 | res.redirect('/'); 15 | return; 16 | } 17 | 18 | // If there is a user account ID in the session, assume 19 | // we're watching notifications in a user's mailbox 20 | const userMode = userAccountId ? true : false; 21 | 22 | const client = userMode 23 | ? graph.getGraphClientForUser(req.app.locals.msalClient, userAccountId) 24 | : graph.getGraphClientForApp(req.app.locals.msalClient); 25 | 26 | // Get the subscription details to display on the page 27 | const subscription = await client 28 | .api(`/subscriptions/${subscriptionId}`) 29 | .get(); 30 | 31 | res.render('watch', { 32 | subscription: subscription, 33 | user: user, 34 | userMode: userMode, 35 | }); 36 | }); 37 | 38 | module.exports = router; 39 | -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | EXPRESS_SESSION_SECRET='YOUR_SECRET_VALUE_HERE' 2 | OAUTH_CLIENT_ID='YOUR_CLIENT_ID_HERE' 3 | OAUTH_CLIENT_SECRET='YOUR_CLIENT_SECRET_HERE' 4 | OAUTH_TENANT_ID='YOUR_TENANT_ID_HERE' 5 | OAUTH_REDIRECT_URI=http://localhost:3000/delegated/callback 6 | OAUTH_SCOPES='user.read,mail.read' 7 | OAUTH_AUTHORITY=https://login.microsoftonline.com 8 | NGROK_PROXY='YOUR_NGROK_URL_HERE' 9 | SUBSCRIPTION_CLIENT_STATE='cLIENTsTATEfORvALIDATION' 10 | CERTIFICATE_PATH='./certificate.pem' 11 | CERTIFICATE_ID='SelfSignedCertificate' 12 | PRIVATE_KEY_PATH='./key.pem' 13 | PRIVATE_KEY_PASSWORD='P@ssword1' 14 | WINDOWS_OPENSSL_PATH='PATH TO OPENSSL.EXE ON WINDOWS' 15 | -------------------------------------------------------------------------------- /tests/configTests.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const assert = require('assert'); 5 | require('dotenv').config(); 6 | 7 | describe('MSAL config', function () { 8 | it('should have a valid client ID', function () { 9 | assert( 10 | process.env.OAUTH_CLIENT_ID && 11 | process.env.OAUTH_CLIENT_ID.length > 0 && 12 | process.env.OAUTH_CLIENT_ID !== 'YOUR_CLIENT_ID_HERE', 13 | '\nOAUTH_CLIENT_ID is not set in .env.\n' + 14 | 'See README.md for instructions on registering an application in the Azure portal', 15 | ); 16 | }); 17 | 18 | it('should have a valid client secret', function () { 19 | assert( 20 | process.env.OAUTH_CLIENT_SECRET && 21 | process.env.OAUTH_CLIENT_SECRET.length > 0 && 22 | process.env.OAUTH_CLIENT_SECRET !== 'YOUR_CLIENT_SECRET_HERE', 23 | '\nOAUTH_CLIENT_SECRET is not set in .env.\n' + 24 | 'See README.md for instructions on registering an application in the Azure portal', 25 | ); 26 | }); 27 | 28 | it('should have a valid tenant ID', function () { 29 | assert( 30 | process.env.OAUTH_TENANT_ID && 31 | process.env.OAUTH_TENANT_ID.length > 0 && 32 | process.env.OAUTH_TENANT_ID.indexOf('YOUR_TENANT_ID_HERE') < 0, 33 | 'OAUTH_TENANT_ID is not set in .env.\n' + 34 | 'See README.md for instructions on registering an application in the Azure portal', 35 | ); 36 | }); 37 | }); 38 | 39 | describe('Notification URL', function () { 40 | it('should have a valid value in development environment', function () { 41 | assert( 42 | process.env.NODE_ENV === 'production' || 43 | (process.env.NGROK_PROXY && 44 | process.env.NGROK_PROXY.length > 0 && 45 | process.env.NGROK_PROXY.indexOf('ngrok') > 0), 46 | ); 47 | }); 48 | }); 49 | 50 | describe('Certificate config', function () { 51 | it('should have a certificate path', function () { 52 | assert( 53 | process.env.CERTIFICATE_PATH && process.env.CERTIFICATE_PATH.length > 0, 54 | ), 55 | 'CERTIFICATE_PATH is not set in .env\n' + 56 | 'Please provide a relative path and file name'; 57 | }); 58 | 59 | it('should have a certificate ID', function () { 60 | assert(process.env.CERTIFICATE_ID && process.env.CERTIFICATE_ID.length > 0), 61 | 'CERTIFICATE_ID is not set in .env\n' + 62 | 'Please provide an identifier for the certificate'; 63 | }); 64 | 65 | it('should have a private key path', function () { 66 | assert( 67 | process.env.PRIVATE_KEY_PATH && process.env.PRIVATE_KEY_PATH.length > 0, 68 | ), 69 | 'PRIVATE_KEY_PATH is not set in .env\n' + 70 | 'Please provide a relative path and file name'; 71 | }); 72 | 73 | it('should have a private key password', function () { 74 | assert( 75 | process.env.PRIVATE_KEY_PASSWORD && 76 | process.env.PRIVATE_KEY_PASSWORD.length > 0, 77 | ), 78 | 'PRIVATE_KEY_PASSWORD is not set in .env\n' + 79 | 'Please provide a password for the private key'; 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /views/error.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div(class='p-5 mb-4 bg-dark text-white rounded-3') 5 | h1 Microsoft Graph Notifications Sample 6 | p(class='fs-4') Choose one of the options below to create a subscription 7 | div(class='row align-items-md-stretch') 8 | div(class='col-md-6') 9 | div(class='h-100 p-5 bg-light border rounded-3') 10 | h2 Delegated authentication 11 | p Choose this option to sign in as a user and receive notifications when items are created in the user's Exchange Online mailbox 12 | a(class='btn btn-outline-secondary', href='/delegated/signin') Sign in and subscribe 13 | div(class='col-md-6') 14 | div(class='h-100 p-5 bg-light border rounded-3') 15 | h2 App-only authentication 16 | p Choose this option to have the application receive notifications when messages are sent to any Teams channel 17 | a(class='btn btn-outline-secondary', href='/apponly/subscribe') Subscribe 18 | -------------------------------------------------------------------------------- /views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='icon' href='/images/g-raph.png') 6 | link(rel='stylesheet', 7 | href='https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css', 8 | integrity='sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC', 9 | crossorigin='anonymous') 10 | link(rel='stylesheet', href='/stylesheets/style.css') 11 | body 12 | main 13 | div(class='container py-4') 14 | header(class='pb-3 mb-4 border-bottom') 15 | div(class='d-flex align-items-center text-dark') 16 | img(class='me-2' src='/images/g-raph.png') 17 | span(class='fs-4 flex-grow-1') Microsoft Graph Notifications Sample 18 | each error in errors 19 | div(class='alert alert-danger' role='alert') 20 | p(class='mb-3')= error.message 21 | pre(class='wrapped-pre border bg-light p-2') 22 | code= error.debug 23 | block content 24 | script(src='https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js', 25 | integrity='sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM', 26 | crossorigin='anonymous') 27 | -------------------------------------------------------------------------------- /views/watch.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1 Notifications 5 | if userMode 6 | p(class='fs-5 mb-3')= `Notifications should appear below when new messages are delivered to ${user.name}'s inbox.` 7 | else 8 | p(class='fs-5 mb-3')= `Notifications should appear below when new messages sent to any Teams channel in your organization.` 9 | div(class='d-flex') 10 | if userMode 11 | div(class='fs-4 me-2')= user.name 12 | button(class='btn btn-outline-secondary me-2' 13 | type='button' 14 | data-bs-toggle='collapse' 15 | data-bs-target='#subscriptionDisplay') Show subscription 16 | a(class='btn btn-outline-secondary' href=`${userMode ? 'delegated' : 'apponly'}/signout`) Delete subscription 17 | div(class='collapse mt-3' id='subscriptionDisplay') 18 | pre(class='wrapped-pre border bg-light p-2') 19 | code=JSON.stringify(subscription, null, 2) 20 | hr 21 | table(class='table') 22 | thead 23 | tr 24 | if userMode 25 | th Subject 26 | th ID 27 | else 28 | th Sender 29 | th Message 30 | tbody(id='notifications') 31 | 32 | script= `const subscriptionId = '${subscription.id}';` 33 | script(src='http://localhost:3001/socket.io/socket.io.js') 34 | script(src='/javascript/watch-client.js') 35 | --------------------------------------------------------------------------------