Skip to content

Commit

Permalink
add asap auth
Browse files Browse the repository at this point in the history
  • Loading branch information
abhijeetborole committed Sep 27, 2023
1 parent 95edf42 commit fab2f96
Show file tree
Hide file tree
Showing 5 changed files with 1,038 additions and 0 deletions.
220 changes: 220 additions & 0 deletions lib/authorizer/asap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
var jose = require('jose'),
uuid = require('uuid'),
nodeForge = require('node-forge'),
AUTHORIZATION = 'Authorization',
AUTHORIZATION_PREFIX = 'Bearer ',
ASAP_PARAMETERS = [
'alg',
'kid',
'iss',
'exp',
'aud',
'privateKey',
'claims'
],
DEFAULT_EXPIRY = '1h',
DEFAULT_ALGORITHM = 'RS256',
// No synchronous algorithms supported in ASAP, Ref:
// https://s2sauth.bitbucket.io/spec/#overview
ALGORITHMS_SUPPORTED = {
RS256: 'RS256',
RS384: 'RS384',
RS512: 'RS512',
PS256: 'PS256',
PS384: 'PS384',
PS512: 'PS512',
ES256: 'ES256',
ES384: 'ES384',
ES512: 'ES512'
},

// eslint-disable-next-line no-useless-escape
DATA_URI_PATTERN = /^data:application\/pkcs8;kid=([\w.\-\+/]+);base64,([a-zA-Z0-9+/=]+)$/;

function removeNewlines (str) {
return str.replace(/\n/g, '');
}


function trimStringDoubleQuotes (str) {
return str.replace(/^"(.*)"$/, '$1');
}

function parsePrivateKey (keyId, privateKey) {
privateKey = trimStringDoubleQuotes(privateKey);
var uriDecodedPrivateKey = decodeURIComponent(privateKey),
match,
privateKeyDerBuffer,
privateKeyAsn1,
privateKeyInfo,
pkcs1pem,
base64pkcs1,
privateKeyInfoPKCS1,
privateKeyInfoPKCS8,
asn1pkcs1privateKey,
pkcs8pem;

if (!uriDecodedPrivateKey.startsWith('data:')) {
return uriDecodedPrivateKey;
}

match = DATA_URI_PATTERN.exec(uriDecodedPrivateKey);

if (!match) {
throw new Error('Malformed Data URI');
}

if (keyId !== match[1]) {
throw new Error('Supplied key id does not match the one included in data uri.');
}

// Convert DER to PEM if needed
// Create a private key from the DER buffer
privateKeyDerBuffer = nodeForge.util.decode64(match[2]);
privateKeyAsn1 = nodeForge.asn1.fromDer(privateKeyDerBuffer);
privateKeyInfo = nodeForge.pki.privateKeyFromAsn1(privateKeyAsn1);
pkcs1pem = nodeForge.pki.privateKeyToPem(privateKeyInfo);
base64pkcs1 = pkcs1pem.toString('base64').trim();

// convert the PKCS#1 key generated to PKCS#8 format
privateKeyInfoPKCS1 = nodeForge.pki.privateKeyFromPem(base64pkcs1);
asn1pkcs1privateKey = nodeForge.pki.privateKeyToAsn1(privateKeyInfoPKCS1);
privateKeyInfoPKCS8 = nodeForge.pki.wrapRsaPrivateKey(asn1pkcs1privateKey);
pkcs8pem = nodeForge.pki.privateKeyInfoToPem(privateKeyInfoPKCS8);

return pkcs8pem.toString('base64').trim();
}

/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'asap',
version: '1.0.0'
},
updates: [
{
property: AUTHORIZATION,
type: 'header'
}
]
},

/**
* Initializes an item (extracts parameters from intermediate requests if any, etc)
* before the actual authorization step.
*
* @param {AuthInterface} auth -
* @param {Response} response -
* @param {AuthHandlerInterface~authInitHookCallback} done -
*/
init: function (auth, response, done) {
done(null);
},

/**
* Verifies whether the request has valid basic auth credentials (which is always).
* Sanitizes the auth parameters if needed.
*
* @param {AuthInterface} auth -
* @param {AuthHandlerInterface~authPreHookCallback} done -
*/
pre: function (auth, done) {
done(null, true);
},

/**
* Verifies whether the basic auth succeeded.
*
* @param {AuthInterface} auth -
* @param {Response} response -
* @param {AuthHandlerInterface~authPostHookCallback} done -
*/
post: function (auth, response, done) {
done(null, true);
},

/**
* Signs a request.
*
* @param {AuthInterface} auth -
* @param {Request} request -
* @param {AuthHandlerInterface~authSignHookCallback} done -
*/
sign: function (auth, request, done) {
var params = auth.get(ASAP_PARAMETERS),
claims = params.claims || {},

// Give priority to the claims object, if present
issuer = claims.iss || params.iss,

// Atlassian wants subject to fall back to issuer if not present
subject = claims.sub || params.sub || issuer,
audience = claims.aud || params.aud,

// Default to a uuid, this is mandatory in ASAP
jwtTokenId = claims.jti || uuid.v4(),
issuedAt = claims.iat,
expiry = params.exp || DEFAULT_EXPIRY,
privateKey = params.privateKey && removeNewlines(params.privateKey),
kid = params.kid,

// Atlassian's internal tool for generating keys uses RS256 by default
alg = params.alg || DEFAULT_ALGORITHM;

if (typeof claims === 'string') {
const trimmedClaims = claims.trim();

claims = trimmedClaims && JSON.parse(trimmedClaims);
}
// Validation
if (!kid || !issuer || !audience || !jwtTokenId || !privateKey || !kid) {
return done(new Error('One or more of required claims missing'));
}

if (!ALGORITHMS_SUPPORTED[alg]) {
return done(new Error('invalid algorithm'));
}

try {
privateKey = parsePrivateKey(kid, privateKey);
}
catch (err) {
return done(new Error('Failed to parse private key.'));
}

jose.importPKCS8(privateKey, alg)
.then((signKey) => {
return new jose.SignJWT(claims)
.setProtectedHeader({ alg, kid })

// This will be system generated if not present
.setIssuedAt(issuedAt)
.setIssuer(issuer)
.setSubject(subject)
.setJti(jwtTokenId)
.setAudience(audience)
.setExpirationTime(expiry)
.sign(signKey);
})
.then((token) => {
request.removeHeader(AUTHORIZATION, { ignoreCase: true });

request.addHeader({
key: AUTHORIZATION,
value: AUTHORIZATION_PREFIX + token,
system: true
});

return done();
})
.catch(() => {
done(new Error('Failed to sign request with key.'));
});
}
};
1 change: 1 addition & 0 deletions lib/authorizer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ _.forEach({
hawk: require('./hawk'),
oauth1: require('./oauth1'),
oauth2: require('./oauth2'),
asap: require('./asap'),
ntlm: require('./ntlm'),
apikey: require('./apikey'),
edgegrid: require('./edgegrid'),
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"js-sha512": "0.8.0",
"lodash": "4.17.21",
"mime-types": "2.1.35",
"node-forge": "1.3.1",
"node-oauth1": "1.3.0",
"performance-now": "2.1.0",
"postman-collection": "4.2.0",
Expand Down
Loading

0 comments on commit fab2f96

Please sign in to comment.