budibase/packages/server/src/sdk/app/oauth2/utils.ts

116 lines
3.0 KiB
TypeScript

import fetch, { RequestInit } from "node-fetch"
import { HttpError } from "koa"
import { get } from "../oauth2"
import { Document, OAuth2CredentialsMethod } from "@budibase/types"
import { cache, context, docIds } from "@budibase/backend-core"
interface OAuth2LogDocument extends Document {
lastUsage: number
}
const { DocWritethrough } = cache.docWritethrough
async function fetchToken(config: {
url: string
clientId: string
clientSecret: string
method: OAuth2CredentialsMethod
}) {
const fetchConfig: RequestInit = {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "client_credentials",
}),
redirect: "follow",
}
if (config.method === OAuth2CredentialsMethod.HEADER) {
fetchConfig.headers = {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(
`${config.clientId}:${config.clientSecret}`,
"utf-8"
).toString("base64")}`,
}
} else {
fetchConfig.body = new URLSearchParams({
grant_type: "client_credentials",
client_id: config.clientId,
client_secret: config.clientSecret,
})
}
const resp = await fetch(config.url, fetchConfig)
return resp
}
const trackUsage = async (id: string) => {
const writethrough = new DocWritethrough<OAuth2LogDocument>(
context.getAppDB(),
docIds.generateOAuth2LogID(id)
)
await writethrough.patch({
lastUsage: Date.now(),
})
}
// TODO: check if caching is worth
export async function generateToken(id: string) {
const config = await get(id)
if (!config) {
throw new HttpError(`oAuth config ${id} count not be found`)
}
const resp = await fetchToken(config)
const jsonResponse = await resp.json()
if (!resp.ok) {
const message = jsonResponse.error_description ?? resp.statusText
throw new Error(`Error fetching oauth2 token: ${message}`)
}
await trackUsage(id)
return `${jsonResponse.token_type} ${jsonResponse.access_token}`
}
export async function validateConfig(config: {
url: string
clientId: string
clientSecret: string
method: OAuth2CredentialsMethod
}): Promise<{ valid: boolean; message?: string }> {
try {
const resp = await fetchToken(config)
const jsonResponse = await resp.json()
if (!resp.ok) {
const message = jsonResponse.error_description ?? resp.statusText
return { valid: false, message }
}
return { valid: true }
} catch (e: any) {
return { valid: false, message: e.message }
}
}
export async function getLastUsages(ids: string[]) {
const docs = await context
.getAppDB()
.getMultiple<OAuth2LogDocument>(ids.map(docIds.generateOAuth2LogID), {
allowMissing: true,
})
const result = ids.reduce<Record<string, number>>((acc, id) => {
const doc = docs.find(d => d._id === docIds.generateOAuth2LogID(id))
if (doc) {
acc[id] = doc.lastUsage
}
return acc
}, {})
return result
}