Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -32,6 +32,7 @@ entity MyEntity {
### 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
107 changes: 76 additions & 31 deletions lib/helper.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,57 @@
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)
if (!res.ok) {
const data = await res.json().catch(() => null)
const err = new Error(`HTTP ${res.status} ${res.statusText}`)
err.response = { status: res.status, data }
throw err
}
const data = await res.json().catch(() => null)
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 @@ -135,18 +181,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 @@ -274,23 +320,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 @@ -339,40 +386,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