Bruno - Scriptmania π©βπ»π§βπ» #385
Replies: 25 comments 63 replies
Basic AuthBruno already supports GUI to configure Basic Auth in const username = "bruno";
const password = "della";
const authString = `${username}:${password}`;
const encodedAuthString = require('btoa')(authString);
req.setHeader("Authorization", `Basic ${encodedAuthString}`); |
File UploadsBruno {
"version": "1",
"name": "bruno-testbench",
"type": "collection",
"scripts": {
"filesystemAccess": {
"allow": true
}
}
}
And the use this in your pre-request scripts const fs = require('fs');
const path = require('path');
const attachmentFilename = "debug-data.bin";
const attachmentPath = path.join(bru.cwd(), attachmentFilename);
const attachment = fs.readFileSync(attachmentPath, "base64");
const attachmentLength = attachment.length;
req.setHeader("Content-Type", "application/octet-stream");
req.setHeader("Content-Disposition", `attachment; filename="${attachmentFilename}"`);
req.setHeader("Content-Length", attachmentLength);
req.setBody(attachment); |
XPath parsingYou can use XPath to query XML data. Install 2 packages in the collection folder: Then you can use the following snippet to work with XML responses: |
CookiesIn your login api const cookies = res.getHeader('set-cookie');
if(cookies) {
bru.setVar("cookie", cookies.join('; '));
}In your subsequent requests const cookie = bru.getVar("cookie");
if(cookie) {
req.setHeader("Cookie", cookie)
}Cookie support will arrive soon in Bruno by default. Until then, please use this workaround. |
Call a service for every json file in a folder |
|
Using a script for logging in to OAuth2 server and setting the Bearer-Token to the header. Use the script in pre-request scripts. Folder structure: tools.js const axios = require('axios');
const moment = require('moment');
const tools = {
log: function(str) {
let now = moment().format('YYYY-MM-DDTHH:mm:ss.SSS');
console.log(now + ' ' + str);
},
setAuth: function(tokenKey = 'mapperToken') {
req.setHeader('Authorization', 'Bearer ' + bru.getVar(tokenKey));
this.log("set Token to Authheader");
},
login: async function(tokenUrlKey = 'tokenUrl', clientIdKey = 'clientId', clientSecretKey = 'clientSecret', tokenKey = 'token') {
let now = moment();
let collectionVarnameForExpiryTime = 'tokenExpiry_ForToken_' + tokenKey + '_FromURL_' + bru.getEnvVar(tokenUrlKey);
let expiryTime = !!bru.getVar(collectionVarnameForExpiryTime) ? moment(bru.getVar(collectionVarnameForExpiryTime)) : now;
let difference = expiryTime.diff(now, 'milliseconds');
this.log('Token expires in milliseconds: ' + difference);
let tokenUrl = bru.getEnvVar(tokenUrlKey);
let clientId = bru.getEnvVar(clientIdKey);
let clientSecret = bru.getEnvVar(clientSecretKey);
// if expiring within 10 seconds
if (difference < 10000 || !bru.getVar(collectionVarnameForExpiryTime) || !bru.getVar('activeToken') || bru.getVar('activeToken') != (clientId + '_' + tokenUrl) ) {
this.log('Old token belongs to different environment or old token expired, requesting new one...');
try {
let resp = await axios({
method: 'POST',
url: tokenUrl,
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
data: {
"grant_type": "client_credentials",
"client_id": encodeURIComponent(clientId),
"client_secret": encodeURIComponent(clientSecret)
}
});
bru.setVar(tokenKey, resp.data.access_token);
bru.setVar(collectionVarnameForExpiryTime, moment().add(resp.data.expires_in, 'seconds').format());
bru.setVar('activeToken', clientId + '_' + tokenUrl);
this.log('new token set.');
} catch (error) {
this.log('login error: ' + error.message);
console.error(error);
throw error;
}
}
this.setAuth(tokenKey);
}
}
module.exports = tools;Now you can use the defined method in every request. E.G. in pre-requests-scripts. So everytime the token is checked and if it is expired, a new token is requested and stored to the collection variables. const tools = require('./tools/tools');
await tools.login(); |
Upload a PDF filePre Request scriptconst fs = require("fs");
const path = require("path");
const FormData = require("form-data");
const cwd = bru.cwd();
const url = bru.getEnvVar("host") + "/api/controlador/documentos";
const attachmentFilename = "doc.pdf";
const attachmentPath = path.join(cwd,'relative/path/to/doc.pdf');
const attachment = fs.readFileSync(attachmentPath);
const formData = new FormData();
formData.append('doc', attachment, { filename: attachmentFilename });
req.setBody(formData);bruno.json{
"version": "1",
"name": "my_collection",
"type": "collection",
"scripts": {
"moduleWhitelist": ["form-data"],
"filesystemAccess": {
"allow": true
}
}
}package.json{
"name": "my_collection",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"form-data": "^4.0.0"
}
}Important: run |
LoggingIn case you'd like to keep. track of request and responses. bruno.json {
"version": "1",
"name": "my_collection",
"type": "collection",
"scripts": {
"filesystemAccess": {
"allow": true
}
}
}Collection Level Post response script const fs = require('fs');
const path = require('path');
const moment = require('moment');
const safeStringify = function (obj) {
try {
return JSON.stringify(obj, null, 2);
} catch (err) {
return obj
}
};
const request = {
url: req.getUrl(),
headers: req.getHeaders(),
method: req.getMethod(),
body: req.getBody()
};
const response = {
status: res.getStatus(),
headers: res.getHeaders(),
body: res.getBody()
};
const now = moment();
const formattedDate = now.format('YYYYMMDD-HHmmss');
const filename = path.join(bru.cwd(), 'logs', `${formattedDate}.json`);
const content = safeStringify({request, response}, null, 2);
fs.writeFileSync(filename, content); |
Different User Tokens Across Different EnvsFolder Structure|-- folder
| |-- auth.bru
| |-- request.bru
|-- bruno.json
|-- environments
| |-- dev.bru
| |-- qa.bru
|-- token-store.js
class TokenStore {
constructor() {
this.tokens = {
dev: null,
qa: null,
prod: null
}
}
saveToken(token, env) {
this.tokens[env] = token;
}
populateTokenInRequest(req, env) {
const token = `Bearer ${this.tokens[env]}`;
req.setHeader('Authorization', token);
}
}
return new TokenStore();Inside your const tokenStore = require('./token-store');
const body = res.getBody();
const token = body.access_token;
tokenStore.saveToken(token, bru.getEnvName());Inside your const tokenStore = require('./token-store');
tokenStore.populateTokenInRequest(req, bru.getEnvName()); |
Automatic Request Chaining to resolve var usagesThe following is a setup that will analyze a requests url for any bruno.json{
"version": "1",
"name": "Example",
"type": "collection",
"scripts": {
"filesystemAccess": {
"allow": true
}
}
}Collection.bruResolveVariables.js// Shoutout to ChatGPT for helping me with regex
const fs = require('fs');
const path = require('path');
const pattern = /\{\{([^\{\}]+)\}\}/g; // Match text between '{{' and '}}'.
/** @type {Map<String, String>} */
let varRequestMap = null;
let match;
while ((match = pattern.exec(req.url)) !== null) {
const varKey = match[1];
const varValue = bru.getVar(varKey);
if (varValue) {
console.log(varKey + ": " + varValue);
}
else {
console.log(varKey + " is not defined.");
if (!varRequestMap) {
varRequestMap = BuildDependencyMap();
}
const dependsOnRequest = varRequestMap.get(varKey);
if (dependsOnRequest) {
console.log("Enqueuing: " + dependsOnRequest)
bru.setNextRequest(dependsOnRequest)
}
else {
console.log("Could not determine which request should provide: " + varKey)
}
}
}
function BuildDependencyMap() {
const varRequestMap = new Map();
console.log("Building dependency map")
const directoryPath = bru.cwd();
const files = fs.readdirSync(directoryPath);
const bruFiles = files.filter(file => path.extname(file) === '.bru' && path.basename(file) !== "collection.bru");
const setVarPattern = /bru\.setVar\(['"]([^'"]+)['"]/g; // Match all first parameters of bru.SetVar, in double or single quotes
const namePattern = /name:\s*([^\n\r]+)/;
bruFiles.forEach(file => {
const filePath = path.join(directoryPath, file);
const data = fs.readFileSync(filePath, 'utf8');
console.log(`Analyzing ${file}`);
const name = data.match(namePattern)[1];
console.log("Name: " + name);
const matches = [...data.matchAll(setVarPattern)];
const parameters = matches.map(match => match[1]);
if (parameters.length > 0) {
console.log(`${file} provides the following vars:`)
console.log(parameters)
parameters.forEach(parameter => varRequestMap.set(parameter, name));
}
});
return varRequestMap;
} |
|
hello guys I wrote a pre request script: I use environment variable but i get an error: What does this error mean? And have anybody an idea to solve i? thanks |
|
Attached is another variant for OAuth2 Client Credentials handling in the collection. YouTube Video explaining it:Coding:const axios = require('axios');
const btoa = require('btoa');
const moment = require('moment');
const calculateTokenExpiryInMilliseconds = (tokenExpiryVarName) => {
const currentTime = moment()
const expiryTime = moment(bru.getVar(tokenExpiryVarName)) || currentTime
return expiryTime.diff(currentTime, 'milliseconds')
}
const authenticateClient = async (config) => {
const {
tokenEndpointVarName,
clientIdVarName,
clientSecretVarName,
bearerTokenVarName
} = config;
if (!bearerTokenVarName) {
throw new Error('Missing environment variable name for storing the bearer token');
}
const expiryDifferenceInMilliseconds = calculateTokenExpiryInMilliseconds(`expiryFor_${bearerTokenVarName}`)
const oneMinuteInMilliseconds = 60000
if (expiryDifferenceInMilliseconds >= oneMinuteInMilliseconds &&
bru.getVar('lastRequestedEnvironmentName') === bru.getEnvName()) {
console.log('Taking already existing token.')
return
}
console.log('Requesting new token.')
const envVariables = {
[tokenEndpointVarName]: bru.getEnvVar(tokenEndpointVarName),
[clientIdVarName]: bru.getEnvVar(clientIdVarName),
[clientSecretVarName]: bru.getEnvVar(clientSecretVarName)
}
const missingVariables = Object.entries(envVariables).filter(([key, value]) => !value).map(([key]) => key)
if (missingVariables.length > 0) {
throw new Error(`Missing required environment variables: ${missingVariables.join(', ')}`)
}
try {
const tokenData = (await axios.post(envVariables[tokenEndpointVarName], { grant_type: "client_credentials" }, { headers: { "Authorization": `Basic ${btoa(`${envVariables[clientIdVarName]}:${envVariables[clientSecretVarName]}`)}`, "Content-Type": "application/x-www-form-urlencoded" } })).data
bru.setVar(bearerTokenVarName, tokenData.access_token)
bru.setVar(`expiryFor_${bearerTokenVarName}`, moment().add(tokenData.expires_in, 'seconds').format())
bru.setVar('lastRequestedEnvironmentName', bru.getEnvName())
} catch (error) {
throw error
}
}
await authenticateClient({
tokenEndpointVarName: 'tokenUrl',
clientIdVarName: 'clientId',
clientSecretVarName: 'clientSecret',
bearerTokenVarName: 'bearerToken2'
}) |
|
Attached is a variant for OAuth2 Authorization Code handling in the collection. YouTube Video explaining it:Coding:const axios = require('axios');
const btoa = require('btoa');
const moment = require('moment');
const calculateTokenExpiryMilliseconds = (tokenExpiryVarName) => {
const currentTime = moment();
const expiryTime = moment(bru.getVar(tokenExpiryVarName)) || currentTime;
return expiryTime.diff(currentTime, 'milliseconds');
};
const exchangeAuthorizationCodeForToken = async (clientId, clientSecret, tokenEndpoint, authorizationCode, redirectUri) => {
const response = await axios.post(tokenEndpoint, {
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: redirectUri
}, {
headers: {
"Authorization": `Basic ${btoa(`${clientId}:${clientSecret}`)}`,
"Content-Type": "application/x-www-form-urlencoded"
}
});
return response.data;
};
const storeTokenAndExpiry = (tokenData, tokenVarName) => {
bru.setVar(tokenVarName, tokenData.access_token);
bru.setVar(`expiryFor_${tokenVarName}`, moment().add(tokenData.expires_in, 'seconds').format());
console.log('New token stored successfully.');
};
const authenticateClient = async (config) => {
const {
tokenEndpointVarName,
authorizeEndpointVarName,
clientIdVarName,
clientSecretVarName,
bearerTokenVarName,
authorizationCodeVarName,
redirectUriVarName
} = config;
const expiryDifferenceMs = calculateTokenExpiryMilliseconds(`expiryFor_${bearerTokenVarName}`);
const oneMinuteInMilliseconds = 60000;
if (expiryDifferenceMs >= oneMinuteInMilliseconds && bru.getVar(`lastRequestedEnvironmentName`) === bru.getEnvName()) {
console.log(`Token in variable ${bearerTokenVarName} is still valid.`);
return;
}
const envVariables = {
[tokenEndpointVarName]: bru.getEnvVar(tokenEndpointVarName),
[authorizeEndpointVarName]: bru.getEnvVar(authorizeEndpointVarName),
[clientIdVarName]: bru.getEnvVar(clientIdVarName),
[clientSecretVarName]: bru.getEnvVar(clientSecretVarName),
[redirectUriVarName]: bru.getEnvVar(redirectUriVarName),
};
const missingVariables = Object.entries(envVariables)
.filter(([key, value]) => !value)
.map(([key]) => key);
if (missingVariables.length > 0) {
throw new Error(`Missing required environment variables: ${missingVariables.join(', ')}. Please ensure these variables are correctly set in your environment.`);
}
const authorizationCode = bru.getEnvVar(authorizationCodeVarName);
const authorizationCodeUrl = `${envVariables[authorizeEndpointVarName]}?client_id=${envVariables[clientIdVarName]}&response_type=code&redirect_uri=${envVariables[redirectUriVarName]}`;
if (!authorizationCode) {
throw new Error(`No authorization code provided. Navigate to the url ${authorizationCodeUrl} and grab the code from the URL.`);
}
try {
const tokenData = await exchangeAuthorizationCodeForToken(
envVariables[clientIdVarName],
envVariables[clientSecretVarName],
envVariables[tokenEndpointVarName],
authorizationCode,
envVariables[redirectUriVarName]
);
storeTokenAndExpiry(tokenData, bearerTokenVarName);
bru.setVar(`lastRequestedEnvironmentName`, bru.getEnvName());
} catch (error) {
throw new Error(`Error: It seems like on of the input values is incorrect. Most probably the authorization code is invalid. Try to grab a new one from ${authorizationCodeUrl} and store it as part of the environment variable ${authorizationCodeVarName}. The original error is: ${error}`);
}
}
await authenticateClient({
tokenEndpointVarName: 'tokenUrl',
authorizeEndpointVarName: 'authorizeUrl',
clientIdVarName: 'clientId',
clientSecretVarName: 'clientSecret',
bearerTokenVarName: 'bearerToken',
authorizationCodeVarName: 'authorizationCode',
redirectUriVarName: 'redirectUri'
}); |
AWS Cognito AuthA small implementation of AWS Cognito allowing you to fetch the JWT Token automatically and inject the token in the Authorization header based on insomnia-plugin-aws-cognito-token Folder structure|-- folder
| |-- request.bru
|-- bruno.json
|-- environments
| |-- dev.bru
| |-- prod.bru
|-- cognito-auth.jsCoding
const fetch = require('node-fetch')
const username = bru.getEnvVar('username')
const password = bru.getEnvVar('password')
const auth = {
login: async function () {
const baseUrl = bru.getEnvVar('base-url')
const cognitoInfosResponse = await fetch(`${baseUrl}/infos`)
const { CognitoRegion, CognitoAppClientID } = await cognitoInfosResponse.json()
const requestBody = {
AuthFlow: 'USER_PASSWORD_AUTH',
ClientId: CognitoAppClientID,
AuthParameters: {
USERNAME: username,
PASSWORD: password,
},
}
const request = {
method: 'post',
headers: {
'content-type': 'application/x-amz-json-1.1',
'x-amz-target': 'AWSCognitoIdentityProviderService.InitiateAuth',
},
body: JSON.stringify(requestBody),
}
const authResponse = await fetch(`https://cognito-idp.${CognitoRegion}.amazonaws.com`, request)
const { AuthenticationResult } = await authResponse.json()
const token = AuthenticationResult.IdToken
req.setHeader('Authorization', `Bearer ${token}`)
},
}
module.exports = authInside your pre-request script: const auth = require('./cognito-auth');
await auth.login();Next Steps
|
Simple Loop iteration over a single requestPre Request Post Response Output Requires 1.4 onward for bru.setNextRequest() |
|
Is there a way to be able to use the package jsonwebtoken? Every other library I have installed has worked well, but I need to sign some information prior to sending it to an api, but if I include jsonwebtoken, I get the error |
|
@tsteckenborn @masewo Is there a way to upload custom auth variants? Or is that just a different way of phrasing "pre request" script for the collection/request? |
|
Hey. I'am looking for a way to dynamically generate parameters for my requests. ive made project level libs directory with class (had to install faker as external lib) const { faker } = require('@faker-js/faker');
const { v4: uuidv4 } = require('uuid');
function fakeUUids(count) {
let uuids = [];
for (let i = 1; i <= count; i++) {
uuids.push(uuidv4());
}
return uuids;
}
function fakeNames(count) {
let names = [];
for (let i = 0; i < count; i++) {
names.push(faker.person.firstName());
}
return names;
}
function fakeLastNames(count) {
let lastNames = [];
for (let i = 0; i < count; i++) {
lastNames.push(faker.person.lastName());
}
return lastNames;
}
function fakePhones(count, mask) {
let phones = [];
for (let i = 0; i < count; i++) {
phones.push(faker.phone.number(mask));
}
return phones;
}
module.exports = {
fakeUUids,
fakeNames,
fakeLastNames,
fakePhones
}Modified collections bruno.json with Added to collection pre request script const {fakeUUids} = require('../libs/fake');
const {fakeNames} = require('../libs/fake');
const {fakePhones} = require('../libs/fake');
const uuids = fakeUUids(3);
uuids.forEach((uuid, i) => bru.setVar(`fakeUuid${i}`, uuid));
const names = fakeNames(2);
names.forEach((name, i) => bru.setVar(`fakeName${i}`, name));
const phones = fakePhones(3, '+998#######');
phones.forEach((phone, i) => bru.setVar(`fakePhone${i}`, phone));And can now access them in query/body parameters like {{fakeUuid1}}, {{fakeName0}}. Often I need more than one value. |
|
I needed to support MFA via MSAL in my collection. I kept getting errors tyring to use the MSAL js library, but I was able to get it to work via a PowerShell script. I am by no means a JavaScript or PowerShell developer, so it's probably not as elegant as it could be, but I thought I would still share. Pre request script:Power shell script:Bruno.json |
|
Anybody know how to use oracledb module (it works fine when run in node,js ), but not in bruno scripting: //------------------------------------------------------------ const oracledb = require('oracledb'); oracledb.outFormat = oracledb.OBJECT; async function run() { const connection = await oracledb.getConnection ({ } run(); |
|
Tbh I'm not sure it ever worked properly, I think I was overeager about
sharing it.. I think there was an issue about it, but on my phone ATM so
can't find it easily
β¦On Tue, Sep 3, 2024, 4:59β―PM Benjamin Steimer ***@***.***> wrote:
Hi @CoenraadS <https://github.com/CoenraadS>, I'm wondering if this still
works.
image.png (view on web)
<https://github.com/user-attachments/assets/f6772c08-b03f-4cc1-8182-dde8d56b72ea>
The request which should provide the variable won't get called.
β
Reply to this email directly, view it on GitHub
<#385 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABKVVYJXMFZ53CVLE5O4ZCTZUVM5LAVCNFSM6AAAAAA5UAXIUKVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTANJSHAYDMMQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
Has anyone solved a problem of setting a script that can be shared across multiple collections? |
TRPC-ifylittle script that can be used to get trpc to work nicer
function getBodyWithVars() {
return JSON.parse(
JSON.stringify(req.getBody()).replace(/{{(.*?)}}/g, (match, group1) => {
return (
bru.getVar(group1) ||
bru.getEnvVar(group1) ||
bru.getProcessEnv(group1) ||
"NULL"
);
}),
);
}
/**
* Walks `obj`, finds all ISO-8601 dateβstrings,
* and returns { json: <same>, meta: { values: { path: ["Date"] } } }
*/
function customSuperjson(obj) {
const isoDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
const values = {};
const json = walk(obj, []);
return { json, meta: { values } };
function walk(node, path) {
if (Array.isArray(node)) {
return node.map((v, i) => walk(v, path.concat(i)));
}
if (node !== null && typeof node === "object") {
const out = {};
for (const [k, v] of Object.entries(node)) {
out[k] = walk(v, path.concat(k));
}
return out;
}
if (typeof node === "string" && isoDate.test(node)) {
// record the path
const key = path.join(".");
values[key] = ["Date"];
return node;
}
return node;
}
}
const trpcify = () => {
if (req.getMethod() === "GET") {
const input = getBodyWithVars();
console.log(input);
req.setBody();
const encoded = encodeURIComponent(JSON.stringify(customSuperjson(input)));
const currentUrl = req.getUrl();
req.setUrl(`${currentUrl}?input=${encoded}`);
}
if (req.getMethod() === "POST") {
const input = getBodyWithVars();
req.setBody(customSuperjson(input));
}
};
trpcify();require("./trpcify"); |
















Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
A place to showcase your scripting superpowers β‘
Bruno offers scripting support to help you to add additional functionality to the tool such as data generation, validation and integration with other tools and systems, including sending intermediate requests, parsing response data, updating environment variables, etc.
Apart from the commonly used libraries, Bruno also supports installing any npm module to use in your scripting workflows.
Scripting is very powerful and can be used to extend the functionality of Bruno in many ways.
Let's use this space to showcase novel and interesting that you have used scripting in Bruno.
This will help others to learn and get inspired to use scripting in their own workflows.
Let the hacking begin!
All reactions