Bruno - Scriptmania π©βπ»π§βπ» #385
Replies: 23 comments 54 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}`); |
Beta Was this translation helpful? Give feedback.
-
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); |
Beta Was this translation helpful? Give feedback.
-
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:
|
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Call a service for every json file in a folder
|
Beta Was this translation helpful? Give feedback.
-
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(); |
Beta Was this translation helpful? Give feedback.
-
The much awaited collection level scripting is now available in |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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); |
Beta Was this translation helpful? Give feedback.
-
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()); |
Beta Was this translation helpful? Give feedback.
-
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.bru
ResolveVariables.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;
} |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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'
}) |
Beta Was this translation helpful? Give feedback.
-
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'
}); |
Beta Was this translation helpful? Give feedback.
-
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.js Coding
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 = auth Inside your pre-request script: const auth = require('./cognito-auth');
await auth.login(); Next Steps
|
Beta Was this translation helpful? Give feedback.
-
Simple Loop iteration over a single requestPre Request
Post Response
Output
Requires 1.4 onward for bru.setNextRequest() |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
@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? |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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
|
Beta Was this translation helpful? Give feedback.
-
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(); |
Beta Was this translation helpful? Give feedback.
-
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:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Generate HTML reportsHey, here a simple tip |
Beta Was this translation helpful? Give feedback.
-
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!
Beta Was this translation helpful? Give feedback.
All reactions