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
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.15.0",
"description": "Plugin for Insomnia to provide Cognito JWT token from AWS",
"main": "./src/plugin.js",
"type": "commonjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand All @@ -24,7 +25,12 @@
"description": "Plugin for Insomnia to provide Cognito JWT token from AWS"
},
"dependencies": {
"amazon-cognito-identity-js": "^5.2.7",
"crypto-js": "^3.3.0",
"jwt-decode": "^2.2.0"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"jest": "^27.5.1"
}
}
199 changes: 103 additions & 96 deletions src/plugin.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,89 @@
const jwtDecode = require("jwt-decode")
const CryptoJS = require("crypto-js")
const {
CognitoUserPool,
CognitoUser,
AuthenticationDetails,
} = require("amazon-cognito-identity-js");
const CryptoJS = require("crypto-js");
const jwtDecode = require("jwt-decode");

// Get JWT Token from Cognito
const session = async ({
Username,
Password,
Region,
ClientId,
TokenType,
ClientSecret,
}) => {
const domain = Region.split('_')[0] // backward compatible with Pool
const requestBody = {
AuthFlow: "USER_PASSWORD_AUTH",
ClientId,
AuthParameters: {
USERNAME: Username,
PASSWORD: Password,
}
}
if (ClientSecret) {
const hash = secretHash(ClientSecret, Username, ClientId)
requestBody.AuthParameters.SECRET_HASH = hash
}

const request = {
method: "post",
headers: {
"content-type": "application/x-amz-json-1.1",
"x-amz-target": "AWSCognitoIdentityProviderService.InitiateAuth",
},
body: JSON.stringify(requestBody),
}
if (TokenType === 'raw_request') { return JSON.stringify(request) }
const response = await fetch(`https://cognito-idp.${domain}.amazonaws.com`, request)

if (response.status !== 200) {
console.log(
"Looks like there was a problem. Status Code: " + response.status
)
return "Error in getting session: " + JSON.stringify({ request, status: response.status })
}
const { AuthenticationResult } = await response.json()
return AuthenticationResult
? TokenType === "id"
? AuthenticationResult.IdToken
: AuthenticationResult.AccessToken
: undefined
}

const secretHash = (ClientSecret, Username, ClientId) => (
CryptoJS.enc.Base64.stringify(
CryptoJS.HmacSHA256([Username, ClientId].join(''), ClientSecret)))
const cognitoAuth = async (authData) => {
const {
Username: E2E_USERNAME,
Password: E2E_PASSWORD,
Region,
ClientId: COGNITO_CLIENT_ID,
UserPoolId: COGNITO_USER_POOL_ID,
TokenType,
ClientSecret,
} = authData;
var poolData = {
UserPoolId: COGNITO_USER_POOL_ID,
ClientId: COGNITO_CLIENT_ID,
};
const userPool = new CognitoUserPool(poolData);
const cognitoUserData = {
Username: E2E_USERNAME,
Pool: userPool,
};
const cognitoUser = new CognitoUser(cognitoUserData);
cognitoUser.setAuthenticationFlowType("USER_SRP_AUTH");
const authDetails = new AuthenticationDetails({
Username: E2E_USERNAME,
Password: E2E_PASSWORD,
});
const authUser = await new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authDetails, {
onSuccess: resolve,
onFailure: reject,
});
});
if (TokenType === "id") return authUser.getIdToken().getJwtToken();
return authUser.getAccessToken().getJwtToken();
};

// Validate if the token has expired
const validToken = (token) => {
const now = Date.now().valueOf() / 1000
const now = Date.now().valueOf() / 1000;
try {
const data = jwtDecode(token)
const data = jwtDecode(token);
if (typeof data.exp !== "undefined" && data.exp < now) {
return false
return false;
}
if (typeof data.nbf !== "undefined" && data.nbf > now) {
return false
return false;
}
return true
return true;
} catch (err) {
return false
return false;
}
}
};

// Encode our token
const base64url = (source) => {
encodedSource = CryptoJS.enc.Base64.stringify(source)
encodedSource = encodedSource.replace(/=+$/, "")
encodedSource = encodedSource.replace(/\+/g, "-")
encodedSource = encodedSource.replace(/\//g, "_")
return encodedSource
}

var encodedSource = CryptoJS.enc.Base64.stringify(source);
encodedSource = encodedSource.replace(/=+$/, "");
encodedSource = encodedSource.replace(/\+/g, "-");
encodedSource = encodedSource.replace(/\//g, "_");
return encodedSource;
};
// Create a fake token to keep in store, so we don't query for same wrong values
const errorToken = (error) => {
const header = {
alg: "HS256",
typ: "JWT",
}
const stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header))
const encodedHeader = base64url(stringifiedHeader)
};
const stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
const encodedHeader = base64url(stringifiedHeader);
// If error we keep it for 1 min
const exp = Date.now().valueOf() / 1000 + 60
const exp = Date.now().valueOf() / 1000 + 60;
const data = {
error,
exp,
}
const stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data))
const encodedData = base64url(stringifiedData)
return encodedHeader + "." + encodedData
}
};
const stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
const encodedData = base64url(stringifiedData);
return encodedHeader + "." + encodedData;
};

// Main run function
const run = async (
Expand All @@ -105,55 +92,68 @@ const run = async (
Password,
Region,
ClientId,
UserPoolId,
TokenType,
ClientSecret,
ClientSecret
) => {
if (!Username) {
throw new Error("Username attribute is required")
throw new Error("Username attribute is required");
}
if (!Password) {
throw new Error("Password attribute is required")
throw new Error("Password attribute is required");
}
if (!Region) {
throw new Error("Region attribute is required")
throw new Error("Region attribute is required");
}
if (!ClientId) {
throw new Error("ClientId attribute is required")
throw new Error("ClientId attribute is required");
}
if (!UserPoolId) {
throw new Error("UserPoolId attribute is required");
}
if (!TokenType) {
TokenType = "access"
TokenType = "access";
}

const key = [Username, Password, Region, ClientId, TokenType, ClientSecret].join("::")
const token = await context.store.getItem(key)
const key = [
Username,
Password,
Region,
ClientId,
UserPoolId,
TokenType,
ClientSecret,
].join("::");
const token = await context.store.getItem(key);
if (token && validToken(token)) {
if (jwtDecode(token).error) {
// Display error
return jwtDecode(token).error
return jwtDecode(token).error;
}
// JWT token is still valid, reuse it
return token
return token;
} else {
// Compute a new token
try {
const token = await session({
const token = await cognitoAuth({
Username,
Password,
Region,
ClientId,
UserPoolId,
TokenType,
ClientSecret,
})
await context.store.setItem(key, token)
return token
});
await context.store.setItem(key, token);
return token;
} catch (error) {
// To keep thing simle we create a fake JWT token with error message
const token = errorToken(error.message)
await context.store.setItem(key, token)
return error.message
// To keep thing simple we create a fake JWT token with error message
const token = errorToken(error.message);
await context.store.setItem(key, token);
return error.message;
}
}
}
};

module.exports.templateTags = [
{
Expand Down Expand Up @@ -181,6 +181,11 @@ module.exports.templateTags = [
type: "string",
validate: (arg) => (arg ? "" : "Required"),
},
{
displayName: "UserPoolId",
type: "string",
validate: (arg) => (arg ? "" : "Required"),
},
{
displayName: "TokenType",
type: "enum",
Expand All @@ -207,4 +212,6 @@ module.exports.templateTags = [
],
run,
},
]
];

module.exports.run = run;