UNPKG

@guardian/pan-domain-node

Version:

NodeJs implementation of Guardian pan-domain auth verification

167 lines (166 loc) 6.34 kB
"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;