2026-04-27 04:25:52 +00:00

115 lines
3.7 KiB
JavaScript

const config = require('../config')
const crypto = require('crypto');
const axios = require('axios');
function base64url(buffer) {
return Buffer.from(buffer)
.toString('base64')
.replace(/=+$/, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
/**
* Generates a signed JWT (HS256) for Bus auth.
* @param {string} jwtSecret - The shared secret to sign the token.
* @param {string} issuer - The issuer URL (repositorioURL).
* @returns {string} Signed JWT token.
*/
function generateBusJwt(jwtSecret, issuer) {
const header = { typ: 'JWT', alg: 'HS256' };
const currentTimestamp = Math.floor(Date.now() / 1000);
const data = {
iss: issuer,
iat: currentTimestamp,
exp: currentTimestamp + 6000000,
aud: 'aud',
sub: 'sub',
name: 'name',
ident: 'ident',
role: 'role',
};
const encodedHeader = base64url(JSON.stringify(header));
const encodedData = base64url(JSON.stringify(data));
const token = `${encodedHeader}.${encodedData}`;
const signature = crypto
.createHmac('sha256', jwtSecret)
.update(token)
.digest('base64')
.replace(/=+$/, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
return `${token}.${signature}`;
}
/**
* Authenticates against the Bus and returns the access token.
* @param {string} busURL - Base URL of the Bus (e.g. http://bus-host).
* @param {string} jwtSecret - The shared secret to sign the JWT.
* @param {string} issuer - The issuer URL (repositorioURL).
* @param {string} scope - OAuth scopes (e.g. "MedicationRequest/*.read,MedicationRequest/*.write").
* @returns {Promise<string>} The access token returned by the Bus.
*/
async function getBusToken(busURL, jwtSecret, issuer, scope) {
const clientAssertion = generateBusJwt(jwtSecret, issuer);
const response = await axios.post(`${busURL}/bus-auth/v2/auth`, {
grantType: 'client_credentials',
scope,
clientAssertionType: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
clientAssertion,
});
return response.data.accessToken;
}
/**
* Creates an axios instance pre-configured for the Bus FHIR API.
* All requests will include the Authorization and Content-Type headers.
* @param {string} busURL - Base URL of the Bus.
* @param {string} token - Bearer access token obtained from getBusToken.
* @returns {import('axios').AxiosInstance}
*/
function createBusRequest(token, axiosConfig = {}, busUrl = null, url = null) {
const instance = axios.create({
...(!!url ? { url: url } : !!busUrl ? { baseURL: busURL } : {}),
...axiosConfig,
headers: {
'Content-Type': 'application/fhir+json',
'Authorization': `Bearer ${token}`,
...(axiosConfig.headers || {}),
},
});
if (config.debug) {
instance.interceptors.request.use((req) => {
const url = req.baseURL + req.url + (req.params ? '?' + new URLSearchParams(req.params).toString() : '');
console.log(`[${new Date().toISOString()}] --> ${req.method.toUpperCase()} ${url}`);
return req;
});
instance.interceptors.response.use(
(res) => {
console.log(`[${new Date().toISOString()}] <-- ${res.status} ${res.config.baseURL}${res.config.url}`);
return res;
},
(err) => {
const res = err.response;
if (res) {
console.error(`[${new Date().toISOString()}] <-- ${res.status} ${res.config.baseURL}${res.config.url}`);
}
return Promise.reject(err);
}
);
}
return instance;
}
module.exports = { getBusToken, createBusRequest };