@atlassian/atlassian-connect-express
Version:
Library for building Atlassian Add-ons on top of Express
114 lines (103 loc) • 3.74 kB
JavaScript
const jose = require("jose");
const _ = require("lodash");
function authenticate(addon) {
if (!addon.config.appId()) {
throw new Error(
"In order to use Forge authentication middleware, the app ID must be configured under" +
"\n`appId` in config.json, or via the `AC_APP_ID` environment variable." +
"\nThe app ID can be found in your `manifest.yml`"
);
}
return async function (req, res, next) {
function sendError(msg) {
const code = 401;
addon.logger.error("Authentication failed:", code, msg);
if (addon.config.expressErrorHandling()) {
next({
code,
message: msg
});
} else {
res.status(code).send(_.escape(msg));
}
}
function getJwksUrl(forgeInvocationToken) {
const commercialStageBaseUrl = "https://api.stg.atlassian.com/";
const agcProdBaseUrl = "https://api.atlassian-us-gov-mod.com/";
const agcStageBaseUrl = "https://api.stg.atlassian-us-gov-mod.com/";
const decodedClaims = jose.decodeJwt(forgeInvocationToken, {
complete: true
});
let apiBaseUrl;
if (decodedClaims && decodedClaims.app && decodedClaims.app.apiBaseUrl) {
apiBaseUrl = decodedClaims.app.apiBaseUrl;
} else {
apiBaseUrl = "";
}
if (apiBaseUrl.startsWith(commercialStageBaseUrl)) {
return "https://forge.cdn.stg.atlassian-dev.net/.well-known/jwks.json";
} else if (apiBaseUrl.startsWith(agcProdBaseUrl)) {
return "https://forge.cdn.prod.atlassian-dev-us-gov-mod.net/.well-known/jwks.json";
} else if (apiBaseUrl.startsWith(agcStageBaseUrl)) {
return "https://forge.cdn.stg.atlassian-dev-us-gov-mod.net/.well-known/jwks.json";
} else {
return "https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json";
}
}
const verifyToken = async (req, appId) => {
const ariPrefix = "ari:cloud:ecosystem::app/";
const appIdList = (Array.isArray(appId) ? appId : appId.split(" ")).map(
id => (id.startsWith(ariPrefix) ? id : ariPrefix + id)
);
let forgeInvocationToken;
const authHeader = req.headers.authorization;
if (authHeader) {
if (authHeader.toLowerCase().startsWith("bearer")) {
[, forgeInvocationToken] = authHeader.split(" ");
}
}
if (!forgeInvocationToken) {
throw new Error("No valid auth token provided in the request");
}
const jwksUrl = getJwksUrl(forgeInvocationToken);
const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));
const payload = await jose.jwtVerify(forgeInvocationToken, JWKS, {
audience: appIdList,
issuer: "forge/invocation-token",
clockTolerance: addon.config.clockTolerance()
});
return payload;
};
try {
const payload = await verifyToken(req, addon.config.appId());
const innerPayload = payload.payload || {};
const { installationId, apiBaseUrl } = innerPayload.app;
const {
"x-forge-oauth-system": appToken,
"x-forge-oauth-user": userToken
} = req.headers;
await addon.forgeAppTokenCache.setForgeAppToken(
installationId,
appToken,
apiBaseUrl
);
req.context = req.context || {};
req.context.forge = {};
Object.assign(req.context.forge, {
tokens: {
app: appToken,
user: userToken
},
...innerPayload,
apiBaseUrl
});
next();
} catch (err) {
console.error("Error while verifying Forge Invocation Token:", err);
sendError("Forge Invocation Token verification was unsuccessful");
}
};
}
module.exports = {
authenticate
};