UNPKG

neo4j-client-sso

Version:

Single sign-on client (frontend) library for Neo4j products

176 lines (175 loc) 7.94 kB
"use strict"; /* * Copyright (c) "Neo4j" * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.assembleRedirectUri = exports.assembleRequestBody = exports.removeSearchParamsInBrowserHistory = exports.addSearchParamsInBrowserHistory = exports.createCodeChallenge = exports.createCodeVerifier = exports.createStateForRequest = exports.createNonce = exports.authDebug = exports.downloadAuthLogs = exports.authLog = exports.ssoLogger = void 0; const utils_1 = require("./utils"); const constants_1 = require("./constants"); const settings_1 = require("./settings"); const file_saver_1 = require("file-saver"); let externalLogging = null; const MAX_LOG_LINES = 200; const ssoLogger = (logging) => (externalLogging = logging); exports.ssoLogger = ssoLogger; const authLog = (msg, type = 'log') => { if (settings_1.isAuthDebuggingEnabled) { console[type](`${constants_1.AUTH_LOGGING_PREFIX} ${msg}`); } if (externalLogging && typeof externalLogging.logger === 'function') { const adjustedType = type === 'log' ? 'info' : type; externalLogging.logger(adjustedType, msg); } const messageNoNewlines = msg.replace('\n', ' '); const log = `${constants_1.AUTH_LOGGING_PREFIX} [${new Date().toISOString()}] '${type}' ${messageNoNewlines}`; const logs = sessionStorage.getItem(constants_1.AUTH_STORAGE_LOGS) || ''; const logsLines = logs.split('\n'); const truncatedOldLogs = logsLines.length > MAX_LOG_LINES ? logsLines.slice(1 - MAX_LOG_LINES).join('\n') : logs; sessionStorage.setItem(constants_1.AUTH_STORAGE_LOGS, `${truncatedOldLogs}${log}\n`); }; exports.authLog = authLog; const downloadAuthLogs = () => { const blob = new Blob([sessionStorage.getItem(constants_1.AUTH_STORAGE_LOGS)], { type: 'text/plain;charset=utf-8' }); (0, file_saver_1.saveAs)(blob, 'neo4j-client-sso.log'); }; exports.downloadAuthLogs = downloadAuthLogs; const authDebug = (msg, content) => { if (!settings_1.isAuthDebuggingEnabled) return; console.log(`${constants_1.AUTH_LOGGING_PREFIX} - DEBUG - ${msg}`); console.dir(content); }; exports.authDebug = authDebug; const createNonce = () => Array.from(window.crypto.getRandomValues(new Uint32Array(4)), t => t.toString(36)).join('-'); exports.createNonce = createNonce; const createStateForRequest = () => { const base = Array.from(window.crypto.getRandomValues(new Uint32Array(4)), t => t.toString(16)).join('-'); return `state-${base}`; }; exports.createStateForRequest = createStateForRequest; const createCodeVerifier = (method) => { switch (method) { case 'plain': case 'S256': const randomString = Array.from(window.crypto.getRandomValues(new Uint8Array(32)), t => String.fromCharCode(t)).join(''); return _btoaUrlSafe(randomString); case '': case null: default: throw new Error(`Unsupported or missing code verification method: ${method}`); } }; exports.createCodeVerifier = createCodeVerifier; const createCodeChallenge = (method, codeVerifier) => __awaiter(void 0, void 0, void 0, function* () { if (!codeVerifier) { throw new Error('Unable to create code challenge: Missing code verifier argument to createCodeChallenge'); } switch (method) { case 'plain': return codeVerifier; case 'S256': try { const bytes = Uint8Array.from(codeVerifier, t => t.charCodeAt(0)); const digest = yield window.crypto.subtle.digest('SHA-256', bytes); const stringFromBytes = Array.from(new Uint8Array(digest), t => String.fromCharCode(t)).join(''); return _btoaUrlSafe(stringFromBytes); } catch (err) { throw new Error(`Failed to create code challenge with error ${err}`); } case '': case null: default: throw new Error(`Unsupported or missing code challenge method: ${method}`); } }); exports.createCodeChallenge = createCodeChallenge; const addSearchParamsInBrowserHistory = (paramsToAddObj) => { if (!paramsToAddObj || !(0, utils_1.isObject)(paramsToAddObj)) return; const { hash, search, origin, pathname } = window.location; const crntHashParams = hash || undefined; const searchParams = new URLSearchParams(search); Object.entries(paramsToAddObj).forEach(([key, value]) => { if (key && value && value.length) { searchParams.set(key, value); } }); const newUrl = `${origin}${pathname}?${searchParams.toString()}${crntHashParams || ''}`; window.history.replaceState({}, '', newUrl); }; exports.addSearchParamsInBrowserHistory = addSearchParamsInBrowserHistory; const removeSearchParamsInBrowserHistory = (paramsToRemove) => { if (!paramsToRemove || !paramsToRemove.length) return; const { search, origin, pathname } = window.location; const currentUrlSearchParams = new URLSearchParams(search); const cleansedSearchParams = {}; for (const [key, value] of currentUrlSearchParams.entries()) { if (!paramsToRemove.includes(key)) { cleansedSearchParams[key] = value; } } const newUrlSearchParams = new URLSearchParams(cleansedSearchParams); const newUrl = `${origin}${pathname}?${newUrlSearchParams.toString()}`; window.history.replaceState({}, '', newUrl); }; exports.removeSearchParamsInBrowserHistory = removeSearchParamsInBrowserHistory; const assembleRequestBody = (details) => { const requestBody = []; for (const property in details) { const encodedKey = encodeURIComponent(property); const encodedValue = encodeURIComponent(details[property]); requestBody.push(encodedKey + '=' + encodedValue); } return requestBody.join('&'); }; exports.assembleRequestBody = assembleRequestBody; const assembleRedirectUri = (idpId) => { const { origin, pathname } = window.location; const path = (pathname === null || pathname === void 0 ? void 0 : pathname.length) > 1 ? pathname : ''; return `${origin}${path}?idp_id=${idpId}&auth_flow_step=redirect_uri`; }; exports.assembleRedirectUri = assembleRedirectUri; /* * Encode text safely to Base64 url * https://tools.ietf.org/html/rfc7515#appendix-C * https://tools.ietf.org/html/rfc4648#section-5 */ const _btoaUrlSafe = (text) => { if (!text) return null; return window .btoa(text) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); // remove trailing padding characters };