@guardian/pan-domain-node
Version:
NodeJs implementation of Guardian pan-domain auth verification
167 lines (166 loc) • 6.34 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.PanDomainAuthentication = void 0;
exports.createCookie = createCookie;
exports.verifyUser = verifyUser;
const cookie = __importStar(require("cookie"));
const utils_1 = require("./utils");
const api_1 = require("./api");
const fetch_public_key_1 = require("./fetch-public-key");
const client_s3_1 = require("@aws-sdk/client-s3");
const credential_providers_1 = require("@aws-sdk/credential-providers");
function createCookie(user, privateKey) {
let queryParams = [];
queryParams.push("firstName=" + user.firstName);
queryParams.push("lastName=" + user.lastName);
queryParams.push("email=" + user.email);
user.avatarUrl && queryParams.push("avatarUrl=" + user.avatarUrl);
queryParams.push("system=" + user.authenticatingSystem);
queryParams.push("authedIn=" + user.authenticatedIn.join(","));
queryParams.push("expires=" + user.expires.toString());
queryParams.push("multifactor=" + String(user.multifactor));
const combined = queryParams.join("&");
const queryParamsString = Buffer.from(combined).toString('base64');
const signature = (0, utils_1.sign)(combined, privateKey);
return queryParamsString + "." + signature;
}
function verifyUser(pandaCookie, publicKey, currentTime, validateUser) {
if (!pandaCookie) {
return {
success: false,
reason: 'no-cookie'
};
}
const parsedCookie = (0, utils_1.parseCookie)(pandaCookie);
if (!parsedCookie) {
return {
success: false,
reason: 'invalid-cookie'
};
}
const { data, signature } = parsedCookie;
if (!(0, utils_1.verifySignature)(data, signature, publicKey)) {
return {
success: false,
reason: 'invalid-cookie'
};
}
const currentTimestampInMillis = currentTime.getTime();
try {
const user = (0, utils_1.parseUser)(data);
const isExpired = user.expires < currentTimestampInMillis;
if (isExpired) {
const gracePeriodEndsAtEpochTimeMillis = user.expires + api_1.gracePeriodInMillis;
if (gracePeriodEndsAtEpochTimeMillis < currentTimestampInMillis) {
return {
success: false,
reason: 'expired-cookie'
};
}
else {
return {
success: true,
shouldRefreshCredentials: true,
mustRefreshByEpochTimeMillis: gracePeriodEndsAtEpochTimeMillis,
user
};
}
}
if (!validateUser(user)) {
return {
success: false,
reason: 'invalid-user',
user
};
}
return {
success: true,
shouldRefreshCredentials: false,
user
};
}
catch (error) {
console.error(error);
return {
success: false,
reason: 'unknown'
};
}
}
class PanDomainAuthentication {
constructor(cookieName, region, bucket, keyFile, validateUser, credentialsProvider = (0, credential_providers_1.fromNodeProviderChain)()) {
this.keyCacheTimeInMillis = 60 * 1000; // 1 minute
this.cookieName = cookieName;
this.region = region;
this.bucket = bucket;
this.keyFile = keyFile;
this.validateUser = validateUser;
const standardAwsConfig = {
region: region,
credentials: credentialsProvider,
};
this.s3Client = new client_s3_1.S3(standardAwsConfig);
this.publicKey = (0, fetch_public_key_1.fetchPublicKey)(this.s3Client, bucket, keyFile);
this.keyUpdateTimer = setInterval(() => this.getPublicKey(), this.keyCacheTimeInMillis);
}
stop() {
if (this.keyUpdateTimer) {
clearInterval(this.keyUpdateTimer);
this.keyUpdateTimer = undefined;
}
}
getPublicKey() {
return this.publicKey.then(({ key, lastUpdated }) => {
const now = new Date();
const diff = now.getTime() - lastUpdated.getTime();
if (diff > this.keyCacheTimeInMillis) {
this.publicKey = (0, fetch_public_key_1.fetchPublicKey)(this.s3Client, this.bucket, this.keyFile);
return this.publicKey.then(({ key }) => key);
}
else {
return key;
}
});
}
verify(requestCookies) {
return this.getPublicKey().then(publicKey => {
const cookies = cookie.parse(requestCookies !== null && requestCookies !== void 0 ? requestCookies : '');
const pandaCookie = cookies[this.cookieName];
return verifyUser(pandaCookie, publicKey, new Date(), this.validateUser);
});
}
}
exports.PanDomainAuthentication = PanDomainAuthentication;