import express from 'express' import bodyParser from 'body-parser' import * as jwt from 'atlassian-jwt' import fetch from 'node-fetch' import fs from 'fs/promises' import descriptor from './atlassian-connect.json' const { CLIENT_SECRET = '', PUBLIC_URL = '', } = process.env const app = express() app.use(bodyParser.json()) app.get('/', (req, res) => { console.log('/') res.redirect('/atlassian-connect.json') }) app.post('/uninstalled', (req, res) => { console.log('uninstalled') console.log(req.body) res.sendStatus(200) }) app.post('/installed', (req, res) => { console.log('installed') console.log(req.body) // store at least { clientKey } res.sendStatus(200) }) app.get('/atlassian-connect.json', (req, res) => { console.log('descriptor') descriptor.baseUrl = PUBLIC_URL res.json(descriptor) }) app.listen(3000, () => { console.log('listening on port 3000') }) const qs = (params: Record) => { const sp = new URLSearchParams(params) return sp.toString() } const getAccessToken = async (key: string, clientKey: string, secret: string) => { const baseUrl = 'https://bitbucket.org' const url = 'https://bitbucket.org/site/oauth2/access_token' const opts = { method: 'post' as 'post', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: { grant_type: 'urn:bitbucket:oauth2:jwt', }, } const req = jwt.fromMethodAndPathAndBody(opts.method, url, opts.body) const qsh = jwt.createQueryStringHash(req, true, baseUrl) const now = Math.floor(Date.now() / 1000) const exp = now + (3 * 60) // expires in 3 minutes const tokenData = { iss: key, iat: now, // the time the token is generated exp, // token expiry time sub: clientKey, // clientKey from /installed qsh, } const jwtToken = jwt.encodeSymmetric(tokenData, secret) const options = { method: opts.method, headers: { ...opts.headers, Authorization: `JWT ${jwtToken}` }, body: qs(opts.body), } const res = await fetch(url, options) if (res.status !== 200) { throw new Error(`Failed to get access token: ${res.status}`) } return res.json() } const downloadRepo = async (owner: string, name: string, branch: string, accessToken: string) => { const url = `https://bitbucket.org/${owner}/${name}/get/${branch}.zip` const res = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}`, }, }) if (res.status !== 200) { throw new Error(`Failed to download repo: ${res.status}`) } const zip = await res.buffer() return zip } const go = async (clientKey: string, repoOwner: string, repoName: string, branchName: string) => { const token = await getAccessToken(descriptor.key, clientKey, CLIENT_SECRET) const zipBuffer = await downloadRepo(repoOwner, repoName, branchName, token.access_token) await fs.writeFile(`${repoOwner}-${repoName}-${branchName}.zip`, zipBuffer) } // go().then(console.log).catch(console.error)