Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
### Fixed

- Querying from content column while using object storage now returns expected result.
- Replaced `axios` with the built-in `fetch` API, removing an external dependency

## Version 3.12.1

Expand Down
106 changes: 75 additions & 31 deletions lib/helper.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,56 @@
const axios = require("axios")
const https = require("https")
const crypto = require("crypto")
const stream = require("stream/promises")
const cds = require("@sap/cds")
const LOG = cds.log("attachments")
const { extname } = require("path")

async function _fetch(url, options = {}) {
const res = await fetch(url, options)
const data = await res.json().catch(() => null)
if (!res.ok) {
const err = new Error(`HTTP ${res.status} ${res.statusText}`)
err.response = { status: res.status, data }
throw err
}
return data
Comment thread
KoblerS marked this conversation as resolved.
}
Comment thread
KoblerS marked this conversation as resolved.

async function _fetchWithAgent(url, { agent, ...options }) {
return new Promise((resolve, reject) => {
const parsed = new URL(url)
const reqOptions = {
hostname: parsed.hostname,
port: parsed.port || 443,
path: parsed.pathname + parsed.search,
method: options.method || "GET",
headers: options.headers || {},
agent,
}
const req = https.request(reqOptions, (res) => {
const chunks = []
res.on("data", (chunk) => chunks.push(chunk))
res.on("end", () => {
let data
try {
data = JSON.parse(Buffer.concat(chunks).toString())
} catch {
data = null
}
if (res.statusCode >= 400) {
const err = new Error(`HTTP ${res.statusCode}`)
err.response = { status: res.statusCode, data }
return reject(err)
}
resolve(data)
})
})
req.on("error", reject)
if (options.body) req.write(options.body)
req.end()
})
}

/**
* Validates the presence of required Service Manager credentials
* @param {*} serviceManagerCreds - Service Manager credentials object
Expand Down Expand Up @@ -129,18 +174,18 @@ async function fetchObjectStoreBinding(tenantID, token) {
endpoint: `${sm_url}/v1/service_bindings`,
labelQuery: `service eq 'OBJECT_STORE' and tenant_id eq '${tenantID}'`,
})
const response = await axios.get(`${sm_url}/v1/service_bindings`, {
params: {
labelQuery: `service eq 'OBJECT_STORE' and tenant_id eq '${tenantID}'`,
},
const params = new URLSearchParams({
labelQuery: `service eq 'OBJECT_STORE' and tenant_id eq '${tenantID}'`,
})
const response = await _fetch(`${sm_url}/v1/service_bindings?${params}`, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})

return response.data?.items || []
return response?.items || []
}

/**
Expand Down Expand Up @@ -268,23 +313,24 @@ async function fetchTokenWithClientSecret(url, clientid, clientsecret) {
"Content-Type": "application/x-www-form-urlencoded",
}

const response = await axios.post(`${url}/oauth/token`, null, {
const params = new URLSearchParams({
grant_type: "client_credentials",
client_id: clientid,
client_secret: clientsecret,
})
const response = await _fetch(`${url}/oauth/token?${params}`, {
method: "POST",
headers,
params: {
grant_type: "client_credentials",
client_id: clientid,
client_secret: clientsecret,
},
})
Comment thread
KoblerS marked this conversation as resolved.

const duration = Date.now() - startTime
LOG.debug("OAuth token fetched successfully", {
clientid,
duration,
tokenType: response.data?.token_type,
tokenType: response?.token_type,
})

return response.data.access_token
return response.access_token
} catch (error) {
const duration = Date.now() - startTime
const suggestion =
Expand Down Expand Up @@ -333,40 +379,38 @@ async function fetchTokenWithMTLS(certURL, clientid, certificate, key) {
client_id: clientid,
}).toString()

const options = {
const agent = new https.Agent({
cert: certificate,
key: key,
})

const response = await _fetchWithAgent(`${certURL}/oauth/token`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
url: `${certURL}/oauth/token`,
method: "POST",
data: requestBody,
httpsAgent: new https.Agent({
cert: certificate,
key: key,
}),
}

const response = await axios(options)
const duration = Date.now() - startTime

if (!response.data?.access_token) {
body: requestBody,
agent,
})
if (!response?.access_token) {
LOG.error(
"MTLS token response missing access_token",
null,
"Check MTLS certificate/key validity and Service Manager configuration",
{ clientid, duration, responseData: response.data },
{ clientid, duration: Date.now() - startTime, responseData: response },
)
throw new Error("Access token not found in MTLS token response")
}

const duration = Date.now() - startTime
LOG.debug("MTLS token fetched successfully", {
clientid,
duration,
tokenType: response.data.token_type,
tokenType: response.token_type,
})

return response.data.access_token
return response.access_token
} catch (error) {
const duration = Date.now() - startTime

Expand Down
Loading
Loading