From 1f9b87ac71bbd4f2561fe140b9175b6ed72f6294 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 12:17:44 +0100 Subject: [PATCH 01/15] first commit --- examples/3rd-party-oauth-login/.gitignore | 24 + .../3rd-party-oauth-login/APP_SUBMISSION.md | 5 + examples/3rd-party-oauth-login/README.md | 106 +++ examples/3rd-party-oauth-login/app.html | 38 + examples/3rd-party-oauth-login/index.html | 42 + examples/3rd-party-oauth-login/jsconfig.json | 7 + examples/3rd-party-oauth-login/package.json | 16 + examples/3rd-party-oauth-login/src/app.js | 91 ++ .../src/assets/congratulations.png | Bin 0 -> 40308 bytes .../src/assets/style.css | 22 + .../src/assets/welcome.png | Bin 0 -> 72236 bytes .../src/backend/.sample.env | 1 + .../3rd-party-oauth-login/src/backend/app.js | 132 +++ .../src/backend/package-lock.json | 851 ++++++++++++++++++ .../src/backend/package.json | 20 + .../src/backend/store.json | 4 + examples/3rd-party-oauth-login/src/index.js | 8 + examples/3rd-party-oauth-login/vite.config.js | 29 + 18 files changed, 1396 insertions(+) create mode 100644 examples/3rd-party-oauth-login/.gitignore create mode 100644 examples/3rd-party-oauth-login/APP_SUBMISSION.md create mode 100644 examples/3rd-party-oauth-login/README.md create mode 100644 examples/3rd-party-oauth-login/app.html create mode 100644 examples/3rd-party-oauth-login/index.html create mode 100644 examples/3rd-party-oauth-login/jsconfig.json create mode 100644 examples/3rd-party-oauth-login/package.json create mode 100644 examples/3rd-party-oauth-login/src/app.js create mode 100644 examples/3rd-party-oauth-login/src/assets/congratulations.png create mode 100644 examples/3rd-party-oauth-login/src/assets/style.css create mode 100644 examples/3rd-party-oauth-login/src/assets/welcome.png create mode 100644 examples/3rd-party-oauth-login/src/backend/.sample.env create mode 100644 examples/3rd-party-oauth-login/src/backend/app.js create mode 100644 examples/3rd-party-oauth-login/src/backend/package-lock.json create mode 100644 examples/3rd-party-oauth-login/src/backend/package.json create mode 100644 examples/3rd-party-oauth-login/src/backend/store.json create mode 100644 examples/3rd-party-oauth-login/src/index.js create mode 100644 examples/3rd-party-oauth-login/vite.config.js diff --git a/examples/3rd-party-oauth-login/.gitignore b/examples/3rd-party-oauth-login/.gitignore new file mode 100644 index 000000000..b05b37f28 --- /dev/null +++ b/examples/3rd-party-oauth-login/.gitignore @@ -0,0 +1,24 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.next + +# testing +/coverage + +# misc +.DS_Store +*.pem +.idea + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +dist diff --git a/examples/3rd-party-oauth-login/APP_SUBMISSION.md b/examples/3rd-party-oauth-login/APP_SUBMISSION.md new file mode 100644 index 000000000..0d3d2f0cc --- /dev/null +++ b/examples/3rd-party-oauth-login/APP_SUBMISSION.md @@ -0,0 +1,5 @@ +## Submission to Miro Marketplace + +Congrats! You have finished building your app & you'd like to publish it for +users. You can submit your app on the +[Miro Marketplace](https://developers.miro.com/docs/submit-your-app) for review. diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md new file mode 100644 index 000000000..c08befb9d --- /dev/null +++ b/examples/3rd-party-oauth-login/README.md @@ -0,0 +1,106 @@ +# 3rd Party OAuth Login + +This app allows you to login to a 3rd party service using (you will need to provide OAuth URL) and tracks the logged in status via local storage. + +# 👨🏻‍💻 App Demo + +# 📒 Table of Contents + +- [Included Features](#features) +- [Tools and Technologies](#tools) +- [Prerequisites](#prerequisites) +- [Associated Developer Tutorial](#tutorial) +- [Run the app locally](#run) +- [Folder Structure](#folder) +- [Contributing](#contributing) +- [License](#license) + +# ⚙️ Included Features + +- [Miro Web SDK](https://developers.miro.com/docs/web-sdk-reference) + - [miro.board.createStickyNote()](https://developers.miro.com/docs/websdk-reference-board#createstickynote) + - [miro.board.viewport.zoomTo()](https://developers.miro.com/docs/viewport_viewport#zoomto) + +# 🛠️ Tools and Technologies + +- [Express](https://expressjs.com/) +- [Node.js](https://nodejs.org/en) +- [Vite](https://vitejs.dev/) +- [ngrok](https://ngrok.com/) + +# ✅ Prerequisites + +- You have a [Miro account](https://miro.com/signup/). +- You're [signed in to Miro](https://miro.com/login/). +- Your Miro account has a [Developer team](https://developers.miro.com/docs/create-a-developer-team). +- Your development environment includes [Node.js 14.13](https://nodejs.org/en/download) or a later version. +- All examples use `npm` as a package manager and `npx` as a package runner. + + + +# 🏃🏽‍♂️ Run the app locally + +1. Run `npm install` to install dependencies. +2. Run `npm start` to start developing. \ + Your URL should be similar to this example: + ``` + http://localhost:3000 + ``` +3. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, + rename it to `.env` and then save the file. +4. Run `npm install` in the `backend` directory. +5. Run `node app.js` in the `backend` directory. +6. If you need to use something like NGrok for your redirectURL (I had to do + this to go through the OAuth process for my Slack app) run `ngrok http 4000`. +7. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service should be: + `https://bced-81-59-0-206.ngrok-free.app/redirect` +8. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ + In the app manifest editor, configure the app as follows, and then click save: + +```yaml +# See https://developers.miro.com/docs/app-manifest on how to use this +appName: 3rd-party-oauth-login +sdkVersion: SDK_V2 +sdkUri: http://localhost:3000 +scopes: + - boards:read + - boards:write +``` + +9. Go back to your app home page, and under the `Permissions` section, you will see a blue button that says `Install app and get OAuth token`. Click that button. Then click on `Add` as shown in the video below. In the video we install a different app, but the process is the same regardless of the app. + +> ⚠️ We recommend to install your app on a [developer team](https://developers.miro.com/docs/create-a-developer-team) while you are developing or testing apps.⚠️ + +https://github.com/miroapp/app-examples/assets/10428517/1e6862de-8617-46ef-b265-97ff1cbfe8bf + +10. Go to your developer team, and open your boards. +11. Click on the plus icon from the bottom section of your left sidebar. If you hover over it, it will say `More apps`. +12. Search for your app `Calendar` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. + +https://github.com/horeaporutiu/app-examples-template/assets/10428517/b23d9c4c-e785-43f9-a72e-fa5d82c7b019 + +# 🗂️ Folder structure + +``` +. +├── src <-- main logic for the app +│ └── app.js <-- handles the button clicks and front end interaction +│ └── index.js <-- handles clicking on app icon in Miro +│ └── style.css <-- CSS styles for the app. +│ └── backend <-- The node.js express backend +│ └── app.js <-- handles redirect and also passes OAuth URL to front end +│ └── .sample.env <-- Env variable for your OAuth URL. rename it to .env +└── index.html <-- The app entry point. +└── app.html <-- The app entry point. +``` + +# 🫱🏻‍🫲🏽 Contributing + +If you want to contribute to this example, or any other Miro Open Source project, please review [Miro's contributing guide](https://github.com/miroapp/app-examples/blob/main/CONTRIBUTING.md). + +# 🪪 License + +[MIT License](https://github.com/miroapp/app-examples/blob/main/LICENSE). diff --git a/examples/3rd-party-oauth-login/app.html b/examples/3rd-party-oauth-login/app.html new file mode 100644 index 000000000..56d70266b --- /dev/null +++ b/examples/3rd-party-oauth-login/app.html @@ -0,0 +1,38 @@ + + + + + + + 3rd Party Oauth Login + + +
+
+
+ +
+
+

+ You're about to learn how to go through OAuth of a 3rd party tool + and then return back as a logged in user. +

+
+
+

Click below to login!

+
+ +
+

User Login Status

+

+
+
+
+ + + + diff --git a/examples/3rd-party-oauth-login/index.html b/examples/3rd-party-oauth-login/index.html new file mode 100644 index 000000000..041e06723 --- /dev/null +++ b/examples/3rd-party-oauth-login/index.html @@ -0,0 +1,42 @@ + + + + + + + + 3rd Party Oauth Login + + +
+
+
+ +
+
+

Great, your app is running locally

+

+ You can now create your Developer team to get your app running in + Miro. +

+
+ +
+

+ To see your app, open it in a app panel on Miro.com, or preview it + at this url +

+
+
+
+ + + diff --git a/examples/3rd-party-oauth-login/jsconfig.json b/examples/3rd-party-oauth-login/jsconfig.json new file mode 100644 index 000000000..d23878618 --- /dev/null +++ b/examples/3rd-party-oauth-login/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "typeRoots": ["./node_modules/@types", "./node_modules/@mirohq"] + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/examples/3rd-party-oauth-login/package.json b/examples/3rd-party-oauth-login/package.json new file mode 100644 index 000000000..3754f9145 --- /dev/null +++ b/examples/3rd-party-oauth-login/package.json @@ -0,0 +1,16 @@ +{ + "name": "3rd-party-oauth-login", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "start": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "dependencies": { + "mirotone": "5" + }, + "devDependencies": { + "vite": "3.0.3" + } +} diff --git a/examples/3rd-party-oauth-login/src/app.js b/examples/3rd-party-oauth-login/src/app.js new file mode 100644 index 000000000..6f996e83b --- /dev/null +++ b/examples/3rd-party-oauth-login/src/app.js @@ -0,0 +1,91 @@ +/* eslint-disable no-undef */ +import "./assets/style.css"; + +var loginBtn = document.getElementById("loginButton"); +var loginText = document.getElementById("loginText"); + +// Attach click event listener to the button +loginBtn.addEventListener("click", startOAuthFlow); + +// Add an event listener to the DOMContentLoaded event +document.addEventListener("DOMContentLoaded", function () { + // Call the initialize function when the DOM content is loaded + initialize(); +}); + +// Define a function containing the code you want to run each time index.html is opened +async function initialize() { + await isLoggedIn(); +} + +// This function checks the local storage for the user's id. Local storage is just for demo purposes. +// It is recommended to implement your own storage for a production application. +// The function then displays the user login status on the UI. +async function isLoggedIn() { + try { + // Call the miro.board.getUserInfo() method to get the user's information + const userInfo = await miro.board.getUserInfo(); + //parse userInfo call for the user's id + const currentUserId = userInfo.id; + const response = await fetch("src/backend/store.json"); + + // Parse the JSON response + const data = await response.json(); + + // Check if the currentId exists in the loggedInUserIds array + const isLoggedIn = data.loggedInUserIds.includes(currentUserId); + var statusParagraph = document.getElementById("loginStatus"); + + if (isLoggedIn) { + statusParagraph.textContent = + "✅ User is logged in ✅ If you want to run this flow again, delete " + + "your user id from the store.json file."; + // Hide the login button and login text if the user is logged in + loginBtn.style.display = "none"; + loginText.style.display = "none"; + } else { + statusParagraph.textContent = "❌ User is not logged in ❌"; + } + return; + } catch (error) { + console.error("Error fetching or parsing JSON file:", error); + return false; // Handle errors gracefully + } +} + +async function startOAuthFlow() { + try { + const token = await miro.board.getIdToken(); + // Make a fetch request to your backend endpoint + const response = await fetch("http://localhost:4000/", { + method: "GET", // Send as GET request + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, // Include JWT token in Authorization header + }, + }); + let OAuthURL = await response.json(); + window.open(OAuthURL, "_blank"); + } catch (error) { + console.error("Error fetching data:", error); + } +} + +// Add an event listener to receive messages from the backend, it will be called after the OAuth flow is completed +window.addEventListener("message", async function (event) { + try { + console.log("event"); + console.log(event); + console.log(event.data); + const redirectSuccess = event.data; + if (redirectSuccess) { + // await isLoggedIn(); + } else { + var statusParagraph = document.getElementById("loginStatus"); + statusParagraph.textContent = + "❌ Redirect unsucessful, delete your userID from store.json and try again ❌"; + } + } catch (error) { + console.log(error); + } +}); diff --git a/examples/3rd-party-oauth-login/src/assets/congratulations.png b/examples/3rd-party-oauth-login/src/assets/congratulations.png new file mode 100644 index 0000000000000000000000000000000000000000..1c2dfd03deb01178fa5c56d4992aa9574bce6e55 GIT binary patch literal 40308 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 literal 0 HcmV?d00001 diff --git a/examples/3rd-party-oauth-login/src/backend/.sample.env b/examples/3rd-party-oauth-login/src/backend/.sample.env new file mode 100644 index 000000000..ca3a97d99 --- /dev/null +++ b/examples/3rd-party-oauth-login/src/backend/.sample.env @@ -0,0 +1 @@ +OAuthURL="https://slack.com/oauth/v2/authorize?client_id=661161.......&scope=chat:write&user_scope=" \ No newline at end of file diff --git a/examples/3rd-party-oauth-login/src/backend/app.js b/examples/3rd-party-oauth-login/src/backend/app.js new file mode 100644 index 000000000..313fdb80d --- /dev/null +++ b/examples/3rd-party-oauth-login/src/backend/app.js @@ -0,0 +1,132 @@ +//Create express app +const express = require("express"); +const app = express(); + +//use fs and path to write to local file (store.json) where we keep our logged in users +const fs = require("fs"); +const path = require("path"); + +//use this to decode the JWT token sent from Miro WebSDK front end +const jwt = require("jsonwebtoken"); + +//Use config dotenv to make enable use of environmental variables +require("dotenv").config(); + +//Define port and host +const port = "4000"; +const host = "localhost"; + +// Import the cors middleware +const cors = require("cors"); + +let currentUser = ""; + +// Use cors middleware to allow requests from frontend server +app.use(cors({ origin: "http://localhost:3000" })); + +// Function to set the current user from the JWT token +async function setCurrentUser(authHeader) { + try { + const token = authHeader.authorization.split(" ")[1]; + // Secret key is the Miro app client secret found in Miro App Settings page + const secretKey = process.env.clientSecret; + // Verify and decode the JWT token + jwt.verify(token, secretKey, (err, decoded) => { + //return currentUser + currentUser = decoded.user; + }); + } catch (error) { + console.log(error); + } +} + +// This route is used to send over the OAuth URL to the front end. +// When the user clicks on Login button, the front end will initiate the OAuth flow +// by opening the OAuth URL in a new window. +app.get("/", async (req, res) => { + try { + res.set("Content-Type", "application/json"); //ensure we can parse the response as JSON + await setCurrentUser(req.headers); + res.json(process.env.OAuthURL); + } catch (error) { + console.error("An error occurred:", error); + } +}); + +// This route is used to handle the redirect URL from the 3rd party service +app.get("/redirect", async (req, res) => { + try { + //if the status code is 200, the OAuth flow was successful and then we can add this Miro user to the loggedInUserIds array + if (res.statusCode === 200) { + //now you can use this code to go through the OAuth flow + //exchange it for an access token for the 3rd party of your choice + const code = req.query.code; + + // Add the user ID to the loggedInUserIds array + await addUserId(currentUser); + + res.send(` +

Status Code: ${res.statusCode}

+

Auth successful! This window will close in 10 seconds.

+

Code returned: ${code}

+

User marked as logged in in local storage (store.json) file.

+ + `); + } else { + res.send(` +

Status Code: ${res.statusCode}

+

Auth failed! Check your redirect URL. The window will close in 10 seconds.

+ + `); + } + } catch (error) { + console.error("An error occurred:", error); + res.status(500).send("Internal server error"); + } +}); + +// Route to check if user is logged in +// app.get('/api/user/isLoggedIn', (req, res) => { +// try { +// const userId = req.query.userId; + +// if (loggedInUsers.has(userId)) { +// res.json({ loggedIn: true }); +// } else { +// res.json({ loggedIn: false }); +// } +// } catch (error) { +// console.error('An error occurred:', error); +// } +// }); + +// Function to add a user ID to the loggedInUserIds array +async function addUserId(userId) { + try { + const filePath = path.join(__dirname, "store.json"); + // Read the existing JSON data from the file + const existingData = fs.readFileSync(filePath, "utf8"); + const jsonData = JSON.parse(existingData); + + // Add the new user ID to the array + jsonData.loggedInUserIds.push(userId); + + // Write the updated JSON data back to the file + fs.writeFileSync(filePath, JSON.stringify(jsonData, null, 4)); + } catch (error) { + console.error("Error adding user ID to JSON file:", error); + } +} + +app.listen(port, host); +console.log(`Running on http://${host}:${port}`); diff --git a/examples/3rd-party-oauth-login/src/backend/package-lock.json b/examples/3rd-party-oauth-login/src/backend/package-lock.json new file mode 100644 index 000000000..0485a7511 --- /dev/null +++ b/examples/3rd-party-oauth-login/src/backend/package-lock.json @@ -0,0 +1,851 @@ +{ + "name": "oauth-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "oauth-backend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.18.3", + "jsonwebtoken": "^9.0.2" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", + "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/examples/3rd-party-oauth-login/src/backend/package.json b/examples/3rd-party-oauth-login/src/backend/package.json new file mode 100644 index 000000000..e58e035d3 --- /dev/null +++ b/examples/3rd-party-oauth-login/src/backend/package.json @@ -0,0 +1,20 @@ +{ + "name": "oauth-backend", + "version": "1.0.0", + "description": "Node.js server to handle redirect URL from OAuth2.0 flow", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node.js" + ], + "author": "Horea Porutiu", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.18.3", + "jsonwebtoken": "^9.0.2" + } +} diff --git a/examples/3rd-party-oauth-login/src/backend/store.json b/examples/3rd-party-oauth-login/src/backend/store.json new file mode 100644 index 000000000..44440493b --- /dev/null +++ b/examples/3rd-party-oauth-login/src/backend/store.json @@ -0,0 +1,4 @@ +{ + "loggedInUserIds": [ + ] +} \ No newline at end of file diff --git a/examples/3rd-party-oauth-login/src/index.js b/examples/3rd-party-oauth-login/src/index.js new file mode 100644 index 000000000..2ff328283 --- /dev/null +++ b/examples/3rd-party-oauth-login/src/index.js @@ -0,0 +1,8 @@ +/* eslint-disable no-undef */ +export async function init() { + miro.board.ui.on("icon:click", async () => { + await miro.board.ui.openPanel({ url: "app.html" }); + }); +} + +init(); diff --git a/examples/3rd-party-oauth-login/vite.config.js b/examples/3rd-party-oauth-login/vite.config.js new file mode 100644 index 000000000..22007b17b --- /dev/null +++ b/examples/3rd-party-oauth-login/vite.config.js @@ -0,0 +1,29 @@ +import path from "path"; +import fs from "fs"; +import dns from "dns"; +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/server-options.html#server-host +dns.setDefaultResultOrder("verbatim"); + +// make sure vite picks up all html files in root, needed for vite build +const allHtmlEntries = fs + .readdirSync(".") + .filter((file) => path.extname(file) === ".html") + .reduce((acc, file) => { + acc[path.basename(file, ".html")] = path.resolve(__dirname, file); + + return acc; + }, {}); + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + rollupOptions: { + input: allHtmlEntries, + }, + }, + server: { + port: 3000, + }, +}); From 94db32fd3642661e45f55bc5b479be073570f505 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 12:20:02 +0100 Subject: [PATCH 02/15] readme fix --- examples/3rd-party-oauth-login/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index c08befb9d..88c277ba8 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -18,8 +18,6 @@ This app allows you to login to a 3rd party service using (you will need to prov # ⚙️ Included Features
- [Miro Web SDK](https://developers.miro.com/docs/web-sdk-reference) - - [miro.board.createStickyNote()](https://developers.miro.com/docs/websdk-reference-board#createstickynote) - - [miro.board.viewport.zoomTo()](https://developers.miro.com/docs/viewport_viewport#zoomto) # 🛠️ Tools and Technologies @@ -78,7 +76,7 @@ https://github.com/miroapp/app-examples/assets/10428517/1e6862de-8617-46ef-b265- 10. Go to your developer team, and open your boards. 11. Click on the plus icon from the bottom section of your left sidebar. If you hover over it, it will say `More apps`. -12. Search for your app `Calendar` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. +12. Search for your app `3rd-party-oauth-login` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. https://github.com/horeaporutiu/app-examples-template/assets/10428517/b23d9c4c-e785-43f9-a72e-fa5d82c7b019 From d7f9139bbaf8e1d7d5022596f7f460552666c423 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:33:02 +0100 Subject: [PATCH 03/15] rm getIdToken example --- examples/3rd-party-oauth-login/README.md | 15 ++- .../3rd-party-oauth-login/app-manifest.yaml | 6 ++ examples/3rd-party-oauth-login/app.html | 12 ++- examples/3rd-party-oauth-login/src/app.js | 98 ++++++++++--------- .../src/backend/.sample.env | 4 +- .../3rd-party-oauth-login/src/backend/app.js | 92 +++++------------ .../src/backend/package-lock.json | 72 ++++++++++++++ .../src/backend/package.json | 6 +- .../src/backend/store.json | 4 - 9 files changed, 179 insertions(+), 130 deletions(-) create mode 100644 examples/3rd-party-oauth-login/app-manifest.yaml delete mode 100644 examples/3rd-party-oauth-login/src/backend/store.json diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index 88c277ba8..bf91163c8 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -4,12 +4,13 @@ This app allows you to login to a 3rd party service using (you will need to prov # 👨🏻‍💻 App Demo +https://github.com/miroapp/app-examples/assets/10428517/fef43c9f-d528-4787-8c66-c94e0f88a03d + # 📒 Table of Contents - [Included Features](#features) - [Tools and Technologies](#tools) - [Prerequisites](#prerequisites) -- [Associated Developer Tutorial](#tutorial) - [Run the app locally](#run) - [Folder Structure](#folder) - [Contributing](#contributing) @@ -18,6 +19,7 @@ This app allows you to login to a 3rd party service using (you will need to prov # ⚙️ Included Features - [Miro Web SDK](https://developers.miro.com/docs/web-sdk-reference) + - [miro.board.ui.openPanel(options)](https://developers.miro.com/docs/ui_boardui#openpanel) # 🛠️ Tools and Technologies @@ -34,11 +36,6 @@ This app allows you to login to a 3rd party service using (you will need to prov - Your development environment includes [Node.js 14.13](https://nodejs.org/en/download) or a later version. - All examples use `npm` as a package manager and `npx` as a package runner. - - # 🏃🏽‍♂️ Run the app locally 1. Run `npm install` to install dependencies. @@ -47,20 +44,20 @@ This app allows you to login to a 3rd party service using (you will need to prov ``` http://localhost:3000 ``` -3. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, +3. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, clientId, and clientSecret and rename it to `.env` and then save the file. 4. Run `npm install` in the `backend` directory. 5. Run `node app.js` in the `backend` directory. 6. If you need to use something like NGrok for your redirectURL (I had to do this to go through the OAuth process for my Slack app) run `ngrok http 4000`. -7. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service should be: +7. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service (for me it was in the App Settings in Slack) should be: `https://bced-81-59-0-206.ngrok-free.app/redirect` 8. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ In the app manifest editor, configure the app as follows, and then click save: ```yaml # See https://developers.miro.com/docs/app-manifest on how to use this -appName: 3rd-party-oauth-login +appName: 3rd Party Oauth Login sdkVersion: SDK_V2 sdkUri: http://localhost:3000 scopes: diff --git a/examples/3rd-party-oauth-login/app-manifest.yaml b/examples/3rd-party-oauth-login/app-manifest.yaml new file mode 100644 index 000000000..2280ea40d --- /dev/null +++ b/examples/3rd-party-oauth-login/app-manifest.yaml @@ -0,0 +1,6 @@ +# See https://developers.miro.com/docs/app-manifest on how to use this +appName: 3rd Party Oauth Login +sdkUri: "http://localhost:3000" +scopes: + - boards:read + - boards:write diff --git a/examples/3rd-party-oauth-login/app.html b/examples/3rd-party-oauth-login/app.html index 56d70266b..00a87337d 100644 --- a/examples/3rd-party-oauth-login/app.html +++ b/examples/3rd-party-oauth-login/app.html @@ -15,8 +15,15 @@

You're about to learn how to go through OAuth of a 3rd party tool - and then return back as a logged in user. + and then return back as a logged in user. You'll need + an app in a 3rd party tool (outside of Miro), with the + following:

+
    +
  • clientID
  • +
  • clientSecret
  • +
  • OAuth URL
  • +

Click below to login!

@@ -30,6 +37,9 @@

Click below to login!

User Login Status

+ + Logout from 3rd party tool + diff --git a/examples/3rd-party-oauth-login/src/app.js b/examples/3rd-party-oauth-login/src/app.js index 6f996e83b..22141f2dc 100644 --- a/examples/3rd-party-oauth-login/src/app.js +++ b/examples/3rd-party-oauth-login/src/app.js @@ -1,11 +1,14 @@ /* eslint-disable no-undef */ +// check eslint global config import "./assets/style.css"; var loginBtn = document.getElementById("loginButton"); +var logoutBtn = document.getElementById("logoutButton"); var loginText = document.getElementById("loginText"); -// Attach click event listener to the button +// Attach click event listeners loginBtn.addEventListener("click", startOAuthFlow); +logoutBtn.addEventListener("click", handleLogout); // Add an event listener to the DOMContentLoaded event document.addEventListener("DOMContentLoaded", function () { @@ -15,77 +18,84 @@ document.addEventListener("DOMContentLoaded", function () { // Define a function containing the code you want to run each time index.html is opened async function initialize() { + logoutBtn.style.display = "none"; await isLoggedIn(); } -// This function checks the local storage for the user's id. Local storage is just for demo purposes. -// It is recommended to implement your own storage for a production application. -// The function then displays the user login status on the UI. -async function isLoggedIn() { +// This function displays the user login status on the UI +async function displayLoginStatus(loggedIn) { try { - // Call the miro.board.getUserInfo() method to get the user's information - const userInfo = await miro.board.getUserInfo(); - //parse userInfo call for the user's id - const currentUserId = userInfo.id; - const response = await fetch("src/backend/store.json"); - - // Parse the JSON response - const data = await response.json(); - - // Check if the currentId exists in the loggedInUserIds array - const isLoggedIn = data.loggedInUserIds.includes(currentUserId); var statusParagraph = document.getElementById("loginStatus"); - if (isLoggedIn) { + if (loggedIn) { statusParagraph.textContent = - "✅ User is logged in ✅ If you want to run this flow again, delete " + - "your user id from the store.json file."; - // Hide the login button and login text if the user is logged in + "✅ User is logged in. " + + "🔄 If you want to run this flow again, use the logout button."; + + // Hide the login button and show the logout button loginBtn.style.display = "none"; loginText.style.display = "none"; + logoutBtn.style.display = "block"; } else { statusParagraph.textContent = "❌ User is not logged in ❌"; + loginBtn.style.display = "block"; } return; + } catch (error) { + console.log(error); + } +} + +// This function checks the local storage for the user's id. Local storage is just for demo purposes. +// It is recommended to implement your own storage for a production application. +async function isLoggedIn() { + try { + // Check if the user is logged in by checking the local storage on the browser + const loggedIn = localStorage.isLoggedIn; + + // Check if the currentId exists in the loggedInUserIds array + await displayLoginStatus(loggedIn); + return; } catch (error) { console.error("Error fetching or parsing JSON file:", error); return false; // Handle errors gracefully } } +//this function opens a new browser tab to start the OAuth process +//we then add an event listener to the window to listen for the message from the backend for once the OAuth flow is completed async function startOAuthFlow() { try { - const token = await miro.board.getIdToken(); - // Make a fetch request to your backend endpoint - const response = await fetch("http://localhost:4000/", { - method: "GET", // Send as GET request - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, // Include JWT token in Authorization header - }, - }); + const response = await fetch("http://localhost:4000"); let OAuthURL = await response.json(); window.open(OAuthURL, "_blank"); + + // Add an event listener to receive messages from the backend, it will be called after the OAuth flow is completed + window.addEventListener("message", async function (event) { + const redirectSuccess = event.data.redirectSuccess; + if (typeof redirectSuccess === "undefined") { + return; + } else if (redirectSuccess) { + this.localStorage.isLoggedIn = true; + await isLoggedIn(); + } else { + console.log("Redirect failed"); + return; + } + }); } catch (error) { console.error("Error fetching data:", error); } } -// Add an event listener to receive messages from the backend, it will be called after the OAuth flow is completed -window.addEventListener("message", async function (event) { +// Handle the logout by clearing the local storage and updating the UI to display the login button +async function handleLogout() { try { - console.log("event"); - console.log(event); - console.log(event.data); - const redirectSuccess = event.data; - if (redirectSuccess) { - // await isLoggedIn(); - } else { - var statusParagraph = document.getElementById("loginStatus"); - statusParagraph.textContent = - "❌ Redirect unsucessful, delete your userID from store.json and try again ❌"; - } + await localStorage.clear(); + logoutBtn.style.display = "none"; + await displayLoginStatus(false); + return; } catch (error) { - console.log(error); + console.error("Error fetching data:", error); } -}); +} diff --git a/examples/3rd-party-oauth-login/src/backend/.sample.env b/examples/3rd-party-oauth-login/src/backend/.sample.env index ca3a97d99..178928a83 100644 --- a/examples/3rd-party-oauth-login/src/backend/.sample.env +++ b/examples/3rd-party-oauth-login/src/backend/.sample.env @@ -1 +1,3 @@ -OAuthURL="https://slack.com/oauth/v2/authorize?client_id=661161.......&scope=chat:write&user_scope=" \ No newline at end of file +OAuthURL=https://slack.com/oauth/v2/authorize?client_id=81938855590.6792758974006&scope=chat:write&user_scope=' +clientSecret='' +clientId='' \ No newline at end of file diff --git a/examples/3rd-party-oauth-login/src/backend/app.js b/examples/3rd-party-oauth-login/src/backend/app.js index 313fdb80d..96e815b7f 100644 --- a/examples/3rd-party-oauth-login/src/backend/app.js +++ b/examples/3rd-party-oauth-login/src/backend/app.js @@ -1,13 +1,7 @@ //Create express app const express = require("express"); const app = express(); - -//use fs and path to write to local file (store.json) where we keep our logged in users -const fs = require("fs"); -const path = require("path"); - -//use this to decode the JWT token sent from Miro WebSDK front end -const jwt = require("jsonwebtoken"); +const axios = require("axios"); //Use config dotenv to make enable use of environmental variables require("dotenv").config(); @@ -19,34 +13,15 @@ const host = "localhost"; // Import the cors middleware const cors = require("cors"); -let currentUser = ""; - // Use cors middleware to allow requests from frontend server app.use(cors({ origin: "http://localhost:3000" })); -// Function to set the current user from the JWT token -async function setCurrentUser(authHeader) { - try { - const token = authHeader.authorization.split(" ")[1]; - // Secret key is the Miro app client secret found in Miro App Settings page - const secretKey = process.env.clientSecret; - // Verify and decode the JWT token - jwt.verify(token, secretKey, (err, decoded) => { - //return currentUser - currentUser = decoded.user; - }); - } catch (error) { - console.log(error); - } -} - // This route is used to send over the OAuth URL to the front end. // When the user clicks on Login button, the front end will initiate the OAuth flow // by opening the OAuth URL in a new window. app.get("/", async (req, res) => { try { res.set("Content-Type", "application/json"); //ensure we can parse the response as JSON - await setCurrentUser(req.headers); res.json(process.env.OAuthURL); } catch (error) { console.error("An error occurred:", error); @@ -54,6 +29,7 @@ app.get("/", async (req, res) => { }); // This route is used to handle the redirect URL from the 3rd party service +// Since I used Ngrok in this example, the full route was https://1111-11-11-111-11.ngrok-free.app/redirect in Slack App Settings app.get("/redirect", async (req, res) => { try { //if the status code is 200, the OAuth flow was successful and then we can add this Miro user to the loggedInUserIds array @@ -61,20 +37,33 @@ app.get("/redirect", async (req, res) => { //now you can use this code to go through the OAuth flow //exchange it for an access token for the 3rd party of your choice const code = req.query.code; - - // Add the user ID to the loggedInUserIds array - await addUserId(currentUser); + //show how to exchange access code for access token. Note this is for demo purposes only. This is the endpoint for a Slack app. + //In a production app, it is advised to pass your client id and secret using the HTTP Basic auth scheme. + const tokenResponse = await axios.post( + "https://slack.com/api/oauth.v2.access", + { + code: code, + client_id: process.env.clientId, // Pass in your client ID in the .env file + client_secret: process.env.clientSecret, // Your client Secret in the .env file + grant_type: "authorization_code", + }, + { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }, + ); + + //now you can implement logic to use 3rd party APIs and use the access token for authentication res.send(`

Status Code: ${res.statusCode}

Auth successful! This window will close in 10 seconds.

-

Code returned: ${code}

-

User marked as logged in in local storage (store.json) file.

+

Access Token: ${tokenResponse.data.access_token}

+

User marked as logged in in (browser) localStorage.

`); } else { @@ -82,10 +71,10 @@ app.get("/redirect", async (req, res) => {

Status Code: ${res.statusCode}

Auth failed! Check your redirect URL. The window will close in 10 seconds.

`); } @@ -95,38 +84,5 @@ app.get("/redirect", async (req, res) => { } }); -// Route to check if user is logged in -// app.get('/api/user/isLoggedIn', (req, res) => { -// try { -// const userId = req.query.userId; - -// if (loggedInUsers.has(userId)) { -// res.json({ loggedIn: true }); -// } else { -// res.json({ loggedIn: false }); -// } -// } catch (error) { -// console.error('An error occurred:', error); -// } -// }); - -// Function to add a user ID to the loggedInUserIds array -async function addUserId(userId) { - try { - const filePath = path.join(__dirname, "store.json"); - // Read the existing JSON data from the file - const existingData = fs.readFileSync(filePath, "utf8"); - const jsonData = JSON.parse(existingData); - - // Add the new user ID to the array - jsonData.loggedInUserIds.push(userId); - - // Write the updated JSON data back to the file - fs.writeFileSync(filePath, JSON.stringify(jsonData, null, 4)); - } catch (error) { - console.error("Error adding user ID to JSON file:", error); - } -} - app.listen(port, host); console.log(`Running on http://${host}:${port}`); diff --git a/examples/3rd-party-oauth-login/src/backend/package-lock.json b/examples/3rd-party-oauth-login/src/backend/package-lock.json index 0485a7511..19c277b7c 100644 --- a/examples/3rd-party-oauth-login/src/backend/package-lock.json +++ b/examples/3rd-party-oauth-login/src/backend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.6.7", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.18.3", @@ -32,6 +33,21 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -86,6 +102,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -154,6 +181,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -293,6 +328,38 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -636,6 +703,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", diff --git a/examples/3rd-party-oauth-login/src/backend/package.json b/examples/3rd-party-oauth-login/src/backend/package.json index e58e035d3..92dd3f6af 100644 --- a/examples/3rd-party-oauth-login/src/backend/package.json +++ b/examples/3rd-party-oauth-login/src/backend/package.json @@ -1,5 +1,5 @@ { - "name": "oauth-backend", + "name": "3rd-Party-OAuth-Login-Server", "version": "1.0.0", "description": "Node.js server to handle redirect URL from OAuth2.0 flow", "main": "app.js", @@ -12,9 +12,9 @@ "author": "Horea Porutiu", "license": "ISC", "dependencies": { + "axios": "^1.6.7", "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.18.3", - "jsonwebtoken": "^9.0.2" + "express": "^4.18.3" } } diff --git a/examples/3rd-party-oauth-login/src/backend/store.json b/examples/3rd-party-oauth-login/src/backend/store.json deleted file mode 100644 index 44440493b..000000000 --- a/examples/3rd-party-oauth-login/src/backend/store.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "loggedInUserIds": [ - ] -} \ No newline at end of file From 4cca3ad3f34d5bd7795ed1b657638d6aa85a1561 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:36:15 +0100 Subject: [PATCH 04/15] add OAuth URL instructions --- examples/3rd-party-oauth-login/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index bf91163c8..7e290f763 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -45,7 +45,7 @@ https://github.com/miroapp/app-examples/assets/10428517/fef43c9f-d528-4787-8c66- http://localhost:3000 ``` 3. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, clientId, and clientSecret and - rename it to `.env` and then save the file. + rename it to `.env` and then save the file. For me, I had to go into my Slack App settings -> Manage Distribution and then check all the boxes, and then I was able to find the "Sharable URL" that I used as my "redirectURL". Also this likely requires the app to have some scopes, so you would have to add that in the OAuth and permission page of the app settings. 4. Run `npm install` in the `backend` directory. 5. Run `node app.js` in the `backend` directory. 6. If you need to use something like NGrok for your redirectURL (I had to do From 00f69c4acb7b1f22cb57160f410236176c07b68d Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:37:57 +0100 Subject: [PATCH 05/15] add OAuth URL instructions --- examples/3rd-party-oauth-login/README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index 7e290f763..0ab9929b1 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -45,15 +45,12 @@ https://github.com/miroapp/app-examples/assets/10428517/fef43c9f-d528-4787-8c66- http://localhost:3000 ``` 3. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, clientId, and clientSecret and - rename it to `.env` and then save the file. For me, I had to go into my Slack App settings -> Manage Distribution and then check all the boxes, and then I was able to find the "Sharable URL" that I used as my "redirectURL". Also this likely requires the app to have some scopes, so you would have to add that in the OAuth and permission page of the app settings. -4. Run `npm install` in the `backend` directory. -5. Run `node app.js` in the `backend` directory. -6. If you need to use something like NGrok for your redirectURL (I had to do - this to go through the OAuth process for my Slack app) run `ngrok http 4000`. -7. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service (for me it was in the App Settings in Slack) should be: - `https://bced-81-59-0-206.ngrok-free.app/redirect` -8. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ - In the app manifest editor, configure the app as follows, and then click save: + rename it to `.env` and then save the file. + +> For me, I had to go into my Slack App settings -> Basic Settings for the clientId and ClientSecret. I had to go to App Settings -> Manage Distribution and then check all the boxes, and then I was able to find the "Sharable URL" that I used as my "redirectURL". Also this likely requires the app to have some scopes, so you would have to add that in the OAuth and permission page of the app settings. 4. Run `npm install` in the `backend` directory. 5. Run `node app.js` in the `backend` directory. 6. If you need to use something like NGrok for your redirectURL (I had to do +> this to go through the OAuth process for my Slack app) run `ngrok http 4000`. 7. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service (for me it was in the App Settings in Slack) should be: +> `https://bced-81-59-0-206.ngrok-free.app/redirect` 8. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ +> In the app manifest editor, configure the app as follows, and then click save: ```yaml # See https://developers.miro.com/docs/app-manifest on how to use this From abcfe84bc4a459f6d80f9a97d16e94c1cf77ab26 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:40:25 +0100 Subject: [PATCH 06/15] add OAuth URL instructions --- examples/3rd-party-oauth-login/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index 0ab9929b1..cf500c9ce 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -44,13 +44,18 @@ https://github.com/miroapp/app-examples/assets/10428517/fef43c9f-d528-4787-8c66- ``` http://localhost:3000 ``` -3. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, clientId, and clientSecret and +3. Run `npm install` in the `backend` directory. +4. Run `node app.js` in the `backend` directory. +5. If you need to use something like Ngrok for your redirectURL (I had to do this to go through the OAuth process for my Slack app) run `ngrok http 4000`. +6. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service (for me it was in the App Settings in Slack) should be: + `https://bced-81-59-0-206.ngrok-free.app/redirect` +7. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, clientId, and clientSecret and rename it to `.env` and then save the file. -> For me, I had to go into my Slack App settings -> Basic Settings for the clientId and ClientSecret. I had to go to App Settings -> Manage Distribution and then check all the boxes, and then I was able to find the "Sharable URL" that I used as my "redirectURL". Also this likely requires the app to have some scopes, so you would have to add that in the OAuth and permission page of the app settings. 4. Run `npm install` in the `backend` directory. 5. Run `node app.js` in the `backend` directory. 6. If you need to use something like NGrok for your redirectURL (I had to do -> this to go through the OAuth process for my Slack app) run `ngrok http 4000`. 7. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service (for me it was in the App Settings in Slack) should be: -> `https://bced-81-59-0-206.ngrok-free.app/redirect` 8. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ -> In the app manifest editor, configure the app as follows, and then click save: +> For me, I had to go into my Slack App settings -> Basic Settings for the clientId and ClientSecret. I had to go to App Settings -> Manage Distribution and then check all the boxes, and then I was able to find the "Sharable URL" that I used as my "redirectURL". Also this likely requires the app to have some scopes, so you would have to add that in the OAuth and permission page of the app settings. + +8. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ + > In the app manifest editor, configure the app as follows, and then click save: ```yaml # See https://developers.miro.com/docs/app-manifest on how to use this From 540520238755284b773c8352812a756ec08d3f44 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:41:40 +0100 Subject: [PATCH 07/15] add OAuth URL instructions --- examples/3rd-party-oauth-login/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index cf500c9ce..afe17e610 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -39,13 +39,13 @@ https://github.com/miroapp/app-examples/assets/10428517/fef43c9f-d528-4787-8c66- # 🏃🏽‍♂️ Run the app locally 1. Run `npm install` to install dependencies. -2. Run `npm start` to start developing. \ +2. Run `npm run start` to start the front end. We have a separate server for the backend. \ Your URL should be similar to this example: ``` http://localhost:3000 ``` -3. Run `npm install` in the `backend` directory. -4. Run `node app.js` in the `backend` directory. +3. Run `npm install` in the `src/backend` directory. +4. Run `node app.js` in the `src/backend` directory. 5. If you need to use something like Ngrok for your redirectURL (I had to do this to go through the OAuth process for my Slack app) run `ngrok http 4000`. 6. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service (for me it was in the App Settings in Slack) should be: `https://bced-81-59-0-206.ngrok-free.app/redirect` From f7e8e42562336eddf1427078e6c8b5bbccf0c0d6 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:42:16 +0100 Subject: [PATCH 08/15] add OAuth URL instructions --- examples/3rd-party-oauth-login/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index afe17e610..8ef8d55a4 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -55,7 +55,7 @@ https://github.com/miroapp/app-examples/assets/10428517/fef43c9f-d528-4787-8c66- > For me, I had to go into my Slack App settings -> Basic Settings for the clientId and ClientSecret. I had to go to App Settings -> Manage Distribution and then check all the boxes, and then I was able to find the "Sharable URL" that I used as my "redirectURL". Also this likely requires the app to have some scopes, so you would have to add that in the OAuth and permission page of the app settings. 8. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ - > In the app manifest editor, configure the app as follows, and then click save: + In the app manifest editor, configure the app as follows, and then click save: ```yaml # See https://developers.miro.com/docs/app-manifest on how to use this From 1ea8d044003bb6c283cd88a5d238efb3b74f5935 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:44:42 +0100 Subject: [PATCH 09/15] small formatting --- examples/3rd-party-oauth-login/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index 8ef8d55a4..fe0b3fdcc 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -75,7 +75,7 @@ https://github.com/miroapp/app-examples/assets/10428517/1e6862de-8617-46ef-b265- 10. Go to your developer team, and open your boards. 11. Click on the plus icon from the bottom section of your left sidebar. If you hover over it, it will say `More apps`. -12. Search for your app `3rd-party-oauth-login` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. +12. Search for your app `3rd Party OAuth Login` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. https://github.com/horeaporutiu/app-examples-template/assets/10428517/b23d9c4c-e785-43f9-a72e-fa5d82c7b019 From 3d33bd7a8fc08253d7cb30257c52d2b91866622a Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:46:46 +0100 Subject: [PATCH 10/15] small formatting --- examples/3rd-party-oauth-login/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index fe0b3fdcc..e1c5dc7e5 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -2,7 +2,7 @@ This app allows you to login to a 3rd party service using (you will need to provide OAuth URL) and tracks the logged in status via local storage. -# 👨🏻‍💻 App Demo +# 👨🏻‍💻 App Demo 🔊(Sound On)🔊 https://github.com/miroapp/app-examples/assets/10428517/fef43c9f-d528-4787-8c66-c94e0f88a03d From f7c9fa68e5fbdf2770e113a3ccf31bdb0149484e Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Fri, 15 Mar 2024 17:48:28 +0100 Subject: [PATCH 11/15] small formatting --- .../3rd-party-oauth-login/src/backend/app.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/3rd-party-oauth-login/src/backend/app.js b/examples/3rd-party-oauth-login/src/backend/app.js index 96e815b7f..4583b32ff 100644 --- a/examples/3rd-party-oauth-login/src/backend/app.js +++ b/examples/3rd-party-oauth-login/src/backend/app.js @@ -60,10 +60,10 @@ app.get("/redirect", async (req, res) => {

Access Token: ${tokenResponse.data.access_token}

User marked as logged in in (browser) localStorage.

`); } else { @@ -71,10 +71,10 @@ app.get("/redirect", async (req, res) => {

Status Code: ${res.statusCode}

Auth failed! Check your redirect URL. The window will close in 10 seconds.

`); } From 5f670a63aea39c4cbc59d449c3b895f3c6768872 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Mon, 18 Mar 2024 10:06:56 +0100 Subject: [PATCH 12/15] adding edits from mettin feedback --- .../3rd-party-oauth-login/APP_SUBMISSION.md | 2 +- examples/3rd-party-oauth-login/README.md | 24 +++++++++---------- examples/3rd-party-oauth-login/src/app.js | 12 +++++----- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/3rd-party-oauth-login/APP_SUBMISSION.md b/examples/3rd-party-oauth-login/APP_SUBMISSION.md index 0d3d2f0cc..b18121a26 100644 --- a/examples/3rd-party-oauth-login/APP_SUBMISSION.md +++ b/examples/3rd-party-oauth-login/APP_SUBMISSION.md @@ -1,5 +1,5 @@ ## Submission to Miro Marketplace Congrats! You have finished building your app & you'd like to publish it for -users. You can submit your app on the +users. You can submit your app to the [Miro Marketplace](https://developers.miro.com/docs/submit-your-app) for review. diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index e1c5dc7e5..23568b160 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -1,6 +1,6 @@ # 3rd Party OAuth Login -This app allows you to login to a 3rd party service using (you will need to provide OAuth URL) and tracks the logged in status via local storage. +This app allows you to login to a 3rd party service using OAuth (you will need to provide an OAuth URL) and tracks the logged in status via local storage. # 👨🏻‍💻 App Demo 🔊(Sound On)🔊 @@ -44,17 +44,17 @@ https://github.com/miroapp/app-examples/assets/10428517/fef43c9f-d528-4787-8c66- ``` http://localhost:3000 ``` -3. Run `npm install` in the `src/backend` directory. -4. Run `node app.js` in the `src/backend` directory. -5. If you need to use something like Ngrok for your redirectURL (I had to do this to go through the OAuth process for my Slack app) run `ngrok http 4000`. -6. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service (for me it was in the App Settings in Slack) should be: +3. Run `cd src/backend`. +4. Run `npm install`. +5. Run `node app.js`. +6. If you need to use something like ngrok for your redirectURL (I had to do this to go through the OAuth process for my Slack app) run `ngrok http 4000`. +7. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your redirect URL in the other service (for me it was in the App Settings in Slack) should be: `https://bced-81-59-0-206.ngrok-free.app/redirect` -7. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, clientId, and clientSecret and - rename it to `.env` and then save the file. +8. Go into `src/backend` and fill in the `.sample.env` file with your OAuthURL, clientId, and clientSecret and rename it to `.env` and then save the file. > For me, I had to go into my Slack App settings -> Basic Settings for the clientId and ClientSecret. I had to go to App Settings -> Manage Distribution and then check all the boxes, and then I was able to find the "Sharable URL" that I used as my "redirectURL". Also this likely requires the app to have some scopes, so you would have to add that in the OAuth and permission page of the app settings. -8. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ +9. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ In the app manifest editor, configure the app as follows, and then click save: ```yaml @@ -67,15 +67,15 @@ scopes: - boards:write ``` -9. Go back to your app home page, and under the `Permissions` section, you will see a blue button that says `Install app and get OAuth token`. Click that button. Then click on `Add` as shown in the video below. In the video we install a different app, but the process is the same regardless of the app. +10. Go back to your app home page, and under the `Permissions` section, you will see a blue button that says `Install app and get OAuth token`. Click that button. Then click on `Add` as shown in the video below. In the video we install a different app, but the process is the same regardless of the app. > ⚠️ We recommend to install your app on a [developer team](https://developers.miro.com/docs/create-a-developer-team) while you are developing or testing apps.⚠️ https://github.com/miroapp/app-examples/assets/10428517/1e6862de-8617-46ef-b265-97ff1cbfe8bf -10. Go to your developer team, and open your boards. -11. Click on the plus icon from the bottom section of your left sidebar. If you hover over it, it will say `More apps`. -12. Search for your app `3rd Party OAuth Login` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. +11. Go to your developer team, and open your boards. +12. Click on the plus icon from the bottom section of your left sidebar. If you hover over it, it will say `More apps`. +13. Search for your app `3rd Party OAuth Login` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. https://github.com/horeaporutiu/app-examples-template/assets/10428517/b23d9c4c-e785-43f9-a72e-fa5d82c7b019 diff --git a/examples/3rd-party-oauth-login/src/app.js b/examples/3rd-party-oauth-login/src/app.js index 22141f2dc..719e1d015 100644 --- a/examples/3rd-party-oauth-login/src/app.js +++ b/examples/3rd-party-oauth-login/src/app.js @@ -2,9 +2,9 @@ // check eslint global config import "./assets/style.css"; -var loginBtn = document.getElementById("loginButton"); -var logoutBtn = document.getElementById("logoutButton"); -var loginText = document.getElementById("loginText"); +const loginBtn = document.getElementById("loginButton"); +const logoutBtn = document.getElementById("logoutButton"); +const loginText = document.getElementById("loginText"); // Attach click event listeners loginBtn.addEventListener("click", startOAuthFlow); @@ -25,7 +25,7 @@ async function initialize() { // This function displays the user login status on the UI async function displayLoginStatus(loggedIn) { try { - var statusParagraph = document.getElementById("loginStatus"); + const statusParagraph = document.getElementById("loginStatus"); if (loggedIn) { statusParagraph.textContent = @@ -51,7 +51,7 @@ async function displayLoginStatus(loggedIn) { async function isLoggedIn() { try { // Check if the user is logged in by checking the local storage on the browser - const loggedIn = localStorage.isLoggedIn; + const loggedIn = localStorage.getItem("isLoggedIn") === "true"; // Check if the currentId exists in the loggedInUserIds array await displayLoginStatus(loggedIn); @@ -67,7 +67,7 @@ async function isLoggedIn() { async function startOAuthFlow() { try { const response = await fetch("http://localhost:4000"); - let OAuthURL = await response.json(); + const OAuthURL = await response.json(); window.open(OAuthURL, "_blank"); // Add an event listener to receive messages from the backend, it will be called after the OAuth flow is completed From e8eeffcd2eb7c6dbb763e85c1f13a84aaf914e4d Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Mon, 18 Mar 2024 10:24:14 +0100 Subject: [PATCH 13/15] add slack in title --- examples/3rd-party-oauth-login/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index 23568b160..65fa586cb 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -1,4 +1,4 @@ -# 3rd Party OAuth Login +# 3rd Party OAuth Login (Slack example) This app allows you to login to a 3rd party service using OAuth (you will need to provide an OAuth URL) and tracks the logged in status via local storage. From 598cc0c4212e23549aeea26ddb79103b8fb78b77 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Mon, 18 Mar 2024 15:25:46 +0100 Subject: [PATCH 14/15] clarify summary --- examples/3rd-party-oauth-login/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index 65fa586cb..d8bc5d110 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -1,6 +1,7 @@ # 3rd Party OAuth Login (Slack example) -This app allows you to login to a 3rd party service using OAuth (you will need to provide an OAuth URL) and tracks the logged in status via local storage. +This app allows you to login to a 3rd party service using OAuth (you will need to provide an OAuth URL). The app tracks if the user +has logged in or not via [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). # 👨🏻‍💻 App Demo 🔊(Sound On)🔊 From 7e757caf808209d74df87d8d87ff2e926361870f Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Mon, 18 Mar 2024 15:27:02 +0100 Subject: [PATCH 15/15] clarify summary --- examples/3rd-party-oauth-login/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/3rd-party-oauth-login/README.md b/examples/3rd-party-oauth-login/README.md index d8bc5d110..63d86447b 100644 --- a/examples/3rd-party-oauth-login/README.md +++ b/examples/3rd-party-oauth-login/README.md @@ -1,7 +1,6 @@ # 3rd Party OAuth Login (Slack example) -This app allows you to login to a 3rd party service using OAuth (you will need to provide an OAuth URL). The app tracks if the user -has logged in or not via [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). +This app allows you to login to a 3rd party service using OAuth (you will need to provide an OAuth URL). The app tracks if the user has logged in or not via [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). # 👨🏻‍💻 App Demo 🔊(Sound On)🔊