UNPKG

@teamwork/get-bearer-token

Version:

CLI tool to obtain bearer tokens for Teamwork API using OAuth flow

171 lines (148 loc) 4.92 kB
import express from "express"; import path from "path"; import { fileURLToPath } from "url"; import axios from "axios"; import { exchangeCodeForToken, setAuthData, getAuthData, isAuthenticated, clearAuthData, buildAuthUrl } from "./auth.js"; export async function startServer({ port = 8123, version = '0.0.0' } = {}) { const app = express(); // Resolve __dirname in ESM const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Enable JSON body parsing app.use(express.json()); // Login redirect - opens Teamwork launchpad in the browser app.get("/api/auth/login", (req, res) => { const authUrl = buildAuthUrl(); res.redirect(authUrl); }); // App-specific login redirect - for generating app tokens app.get("/api/auth/app-login", (req, res) => { const { clientId } = req.query; if (!clientId) return res.status(400).json({ error: "clientId is required" }); const authUrl = buildAuthUrl(clientId); res.redirect(authUrl); }); // Generic proxy for Teamwork API calls - injects auth header const proxyPrefix = "/api/proxy/teamwork/"; app.all(`${proxyPrefix}{*path}`, async (req, res) => { if (!isAuthenticated()) { return res.status(401).json({ error: "Not authenticated" }); } const authData = getAuthData(); const baseUrl = authData.installation.url; const targetPath = req.originalUrl.slice(proxyPrefix.length); const targetUrl = `${baseUrl}${targetPath}`; try { const response = await axios({ method: req.method, url: targetUrl, data: req.body, headers: { "Authorization": `Bearer ${authData.access_token}`, "Content-Type": "application/json", "Accept": "application/json", }, }); res.status(response.status).json(response.data); } catch (error) { const status = error.response?.status || 500; const data = error.response?.data || { error: error.message }; res.status(status).json(data); } }); // Serve static files app.use("/", express.static(path.join(__dirname, "public"))); // API endpoint to exchange code for token app.post("/api/auth/token", async (req, res) => { const { code } = req.body; if (!code) { return res.status(400).json({ error: "Code is required" }); } try { // Exchange code for access token via Teamwork Launchpad const data = await exchangeCodeForToken(code); // Store auth data in memory setAuthData(data); res.json({ success: true, data, version }); } catch (error) { console.error("Token exchange error:", error.message); res.status(500).json({ error: "Failed to exchange code for token", details: error.message, }); } }); // API endpoint to get current auth status app.get("/api/auth/status", (req, res) => { if (isAuthenticated()) { const authData = getAuthData(); res.json({ authenticated: true, installation: authData.installation, user: authData.user, accessToken: authData.access_token, version, }); } else { res.json({ authenticated: false, version }); } }); // API endpoint to logout app.post("/api/auth/logout", (req, res) => { clearAuthData(); res.json({ success: true }); }); // API endpoint to exchange code for app-specific token app.post("/api/auth/app-token", async (req, res) => { const { code, clientId, clientSecret, redirectUri } = req.body; if (!code || !clientId || !clientSecret) { return res.status(400).json({ error: "code, clientId, and clientSecret are required" }); } try { const authData = getAuthData(); if (!authData || !authData.installation) { return res.status(401).json({ error: "Not authenticated" }); } const baseUrl = authData.installation.url; // Exchange code for access token using the app's credentials const response = await axios.post( `${baseUrl}launchpad/v1/token.json`, { code, client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri, }, { headers: { "Content-Type": "application/json", Accept: "application/json", }, } ); res.json({ success: true, data: response.data }); } catch (error) { console.error("App token exchange error:", error.response?.data || error.message); res.status(500).json({ error: "Failed to exchange code for app token", details: error.response?.data || error.message, }); } }); return new Promise((resolve, reject) => { const server = app.listen(port, (err) => { if (!err) { resolve(server); } else { reject(err); } }); }); }