Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 3rd party oauth example #265

Merged
merged 16 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions examples/3rd-party-oauth-login/.gitignore
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions examples/3rd-party-oauth-login/APP_SUBMISSION.md
Original file line number Diff line number Diff line change
@@ -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
horeaporutiu marked this conversation as resolved.
Show resolved Hide resolved
[Miro Marketplace](https://developers.miro.com/docs/submit-your-app) for review.
103 changes: 103 additions & 0 deletions examples/3rd-party-oauth-login/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# 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.
horeaporutiu marked this conversation as resolved.
Show resolved Hide resolved

# 👨🏻‍💻 App Demo 🔊(Sound On)🔊

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)
- [Run the app locally](#run)
- [Folder Structure](#folder)
- [Contributing](#contributing)
- [License](#license)

# ⚙️ Included Features <a name="features"></a>

- [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 <a name="tools"></a>

- [Express](https://expressjs.com/)
- [Node.js](https://nodejs.org/en)
- [Vite](https://vitejs.dev/)
- [ngrok](https://ngrok.com/)

# ✅ Prerequisites <a name="prerequisites"></a>

- 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 <a name="run"></a>

1. Run `npm install` to install dependencies.
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 `src/backend` directory.
4. Run `node app.js` in the `src/backend` directory.
mettin marked this conversation as resolved.
Show resolved Hide resolved
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`.
mettin marked this conversation as resolved.
Show resolved Hide resolved
6. Your ngrok forwarding address should look something like: `https://bced-81-59-0-206.ngrok-free.app`. Then your <b>redirect URL</b> 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.
horeaporutiu marked this conversation as resolved.
Show resolved Hide resolved

> 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
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. <b>In the video we install a different app, but the process is the same regardless of the app.</b>

> ⚠️ 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. <b>In the video we search for a different app, but the process is the same regardless of the app.</b>

https://github.com/horeaporutiu/app-examples-template/assets/10428517/b23d9c4c-e785-43f9-a72e-fa5d82c7b019

# 🗂️ Folder structure <a name="folder"></a>

```
.
├── 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 <a name="contributing"></a>

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 <a name="license"></a>

[MIT License](https://github.com/miroapp/app-examples/blob/main/LICENSE).
6 changes: 6 additions & 0 deletions examples/3rd-party-oauth-login/app-manifest.yaml
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions examples/3rd-party-oauth-login/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://miro.com/app/static/sdk/v2/miro.js"></script>
<title>3rd Party Oauth Login</title>
</head>
<body>
<div id="root">
<div class="grid wrapper">
<div class="cs1 ce12">
<img src="/src/assets/congratulations.png" alt="" />
</div>
<div class="cs1 ce12">
<p>
You're about to learn how to go through OAuth of a 3rd party tool
and then return back as a logged in user. You'll need
<b>an app in a 3rd party tool</b> (outside of Miro), with the
following:
</p>
<ul>
<li><b>clientID</b></li>
<li><b>clientSecret</b></li>
<li><b>OAuth URL</b></li>
</ul>
</div>
<div id="loginText" class="cs1 ce12">
<h1>Click below to login!</h1>
</div>
<div id="textHeaderPanel" class="cs1 ce12">
<a id="loginButton" class="button button-primary" target="_blank">
Login to 3rd party tool
</a>
</div>
<div id="statusContainer" class="cs1 ce12">
<h1>User Login Status</h1>
<p id="loginStatus"></p>
</div>
<a id="logoutButton" class="button button-primary" target="_blank">
Logout from 3rd party tool
</a>
</div>
</div>

<script type="module" src="/src/app.js"></script>
</body>
</html>
42 changes: 42 additions & 0 deletions examples/3rd-party-oauth-login/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/src/assets/style.css" />
<script src="https://miro.com/app/static/sdk/v2/miro.js"></script>
<title>3rd Party Oauth Login</title>
</head>
<body>
<div id="root">
<div class="grid container">
<div class="cs1 ce12">
<img src="/src/assets/welcome.png" alt="" />
</div>
<div class="cs1 ce12">
<h1>Great, your app is running locally</h1>
<p>
You can now create your Developer team to get your app running in
Miro.
</p>
</div>
<div class="cs1 ce12">
<a
class="button button-primary"
href="https://developers.miro.com/docs/create-a-developer-team"
target="_blank"
>
Create a Developer team
</a>
</div>
<div class="cs1 ce12">
<p>
To see your app, open it in a app panel on Miro.com, or preview it
at <a href="/app.html" class="link link-primary">this url</a>
</p>
</div>
</div>
</div>
<script type="module" src="/src/index.js"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions examples/3rd-party-oauth-login/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./node_modules/@mirohq"]
},
"include": ["src"],
"exclude": ["node_modules"]
}
16 changes: 16 additions & 0 deletions examples/3rd-party-oauth-login/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
101 changes: 101 additions & 0 deletions examples/3rd-party-oauth-login/src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* 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");
mettin marked this conversation as resolved.
Show resolved Hide resolved

// Attach click event listeners
loginBtn.addEventListener("click", startOAuthFlow);
logoutBtn.addEventListener("click", handleLogout);

// 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() {
logoutBtn.style.display = "none";
await isLoggedIn();
}

// This function displays the user login status on the UI
async function displayLoginStatus(loggedIn) {
try {
var statusParagraph = document.getElementById("loginStatus");
mettin marked this conversation as resolved.
Show resolved Hide resolved

if (loggedIn) {
statusParagraph.textContent =
"✅ 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;
mettin marked this conversation as resolved.
Show resolved Hide resolved

// 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 response = await fetch("http://localhost:4000");
let OAuthURL = await response.json();
mettin marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}

// Handle the logout by clearing the local storage and updating the UI to display the login button
async function handleLogout() {
try {
await localStorage.clear();
logoutBtn.style.display = "none";
await displayLoginStatus(false);
return;
} catch (error) {
console.error("Error fetching data:", error);
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions examples/3rd-party-oauth-login/src/assets/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@import "mirotone/dist/styles.css";

*,
*:before,
*:after {
box-sizing: border-box;
}

body {
display: flex;
}

#root {
width: 100%;
overflow: auto;
padding: var(--space-medium);
}

img {
max-width: 100%;
height: auto;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions examples/3rd-party-oauth-login/src/backend/.sample.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OAuthURL=https://slack.com/oauth/v2/authorize?client_id=81938855590.6792758974006&scope=chat:write&user_scope='
clientSecret=''
clientId=''
Loading
Loading