UNPKG

verdaccio

Version:

A lightweight private npm proxy registry

283 lines (237 loc) 7.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validatePassword = validatePassword; exports.createRemoteUser = createRemoteUser; exports.createAnonymousRemoteUser = createAnonymousRemoteUser; exports.allow_action = allow_action; exports.handleSpecialUnpublish = handleSpecialUnpublish; exports.getDefaultPlugins = getDefaultPlugins; exports.createSessionToken = createSessionToken; exports.getSecurity = getSecurity; exports.getAuthenticatedMessage = getAuthenticatedMessage; exports.buildUserBuffer = buildUserBuffer; exports.isAESLegacy = isAESLegacy; exports.getApiToken = getApiToken; exports.parseAuthTokenHeader = parseAuthTokenHeader; exports.parseBasicPayload = parseBasicPayload; exports.parseAESCredentials = parseAESCredentials; exports.verifyJWTPayload = verifyJWTPayload; exports.isAuthHeaderValid = isAuthHeaderValid; exports.getMiddlewareCredentials = getMiddlewareCredentials; exports.expireReasons = void 0; var _lodash = _interopRequireDefault(require("lodash")); var _utils = require("./utils"); var _constants = require("./constants"); var _cryptoUtils = require("./crypto-utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * @prettier * */ function validatePassword(password, minLength = _constants.DEFAULT_MIN_LIMIT_PASSWORD) { return typeof password === 'string' && password.length >= minLength; } /** * Create a RemoteUser object * @return {Object} { name: xx, pluginGroups: [], real_groups: [] } */ function createRemoteUser(name, pluginGroups) { const isGroupValid = Array.isArray(pluginGroups); const groups = (isGroupValid ? pluginGroups : []).concat([_constants.ROLES.$ALL, _constants.ROLES.$AUTH, _constants.ROLES.DEPRECATED_ALL, _constants.ROLES.DEPRECATED_AUTH, _constants.ROLES.ALL]); return { name, groups, real_groups: pluginGroups }; } /** * Builds an anonymous remote user in case none is logged in. * @return {Object} { name: xx, groups: [], real_groups: [] } */ function createAnonymousRemoteUser() { return { name: undefined, // groups without '$' are going to be deprecated eventually groups: [_constants.ROLES.$ALL, _constants.ROLES.$ANONYMOUS, _constants.ROLES.DEPRECATED_ALL, _constants.ROLES.DEPRECATED_ANONYMOUS], real_groups: [] }; } function allow_action(action) { return function (user, pkg, callback) { const { name, groups } = user; const hasPermission = pkg[action].some(group => name === group || groups.includes(group)); if (hasPermission) { return callback(null, true); } if (name) { callback(_utils.ErrorCode.getForbidden(`user ${name} is not allowed to ${action} package ${pkg.name}`)); } else { callback(_utils.ErrorCode.getUnauthorized(`authorization required to ${action} package ${pkg.name}`)); } }; } function handleSpecialUnpublish() { return function (user, pkg, callback) { const action = 'unpublish'; const hasSupport = _lodash.default.isNil(pkg[action]) === false ? pkg[action] : false; if (hasSupport === false) { return callback(null, undefined); } return allow_action(action)(user, pkg, callback); }; } function getDefaultPlugins() { return { authenticate(user, password, cb) { cb(_utils.ErrorCode.getForbidden(_constants.API_ERROR.BAD_USERNAME_PASSWORD)); }, add_user(user, password, cb) { return cb(_utils.ErrorCode.getConflict(_constants.API_ERROR.BAD_USERNAME_PASSWORD)); }, allow_access: allow_action('access'), allow_publish: allow_action('publish'), allow_unpublish: handleSpecialUnpublish() }; } function createSessionToken() { const tenHoursTime = 10 * 60 * 60 * 1000; return { // npmjs.org sets 10h expire expires: new Date(Date.now() + tenHoursTime) }; } const defaultWebTokenOptions = { sign: { expiresIn: _constants.TIME_EXPIRATION_7D }, verify: {} }; const defaultApiTokenConf = { legacy: true, sign: {} }; function getSecurity(config) { const defaultSecurity = { web: defaultWebTokenOptions, api: defaultApiTokenConf }; if (_lodash.default.isNil(config.security) === false) { return _lodash.default.merge(defaultSecurity, config.security); } return defaultSecurity; } function getAuthenticatedMessage(user) { return `you are authenticated as '${user}'`; } function buildUserBuffer(name, password) { return Buffer.from(`${name}:${password}`, _constants.CHARACTER_ENCODING.UTF8); } function isAESLegacy(security) { const { legacy, jwt } = security.api; return _lodash.default.isNil(legacy) === false && _lodash.default.isNil(jwt) && legacy === true; } async function getApiToken(auth, config, remoteUser, aesPassword) { const security = getSecurity(config); if (isAESLegacy(security)) { // fallback all goes to AES encryption return await new Promise(resolve => { resolve(auth.aesEncrypt(buildUserBuffer(remoteUser.name, aesPassword)).toString('base64')); }); } else { // i am wiling to use here _.isNil but flow does not like it yet. const { jwt } = security.api; if (jwt && jwt.sign) { return await auth.jwtEncrypt(remoteUser, jwt.sign); } else { return await new Promise(resolve => { resolve(auth.aesEncrypt(buildUserBuffer(remoteUser.name, aesPassword)).toString('base64')); }); } } } function parseAuthTokenHeader(authorizationHeader) { const parts = authorizationHeader.split(' '); const [scheme, token] = parts; return { scheme, token }; } function parseBasicPayload(credentials) { const index = credentials.indexOf(':'); if (index < 0) { return; } const user = credentials.slice(0, index); const password = credentials.slice(index + 1); return { user, password }; } function parseAESCredentials(authorizationHeader, secret) { const { scheme, token } = parseAuthTokenHeader(authorizationHeader); // basic is deprecated and should not be enforced if (scheme.toUpperCase() === _constants.TOKEN_BASIC.toUpperCase()) { const credentials = (0, _utils.convertPayloadToBase64)(token).toString(); return credentials; } else if (scheme.toUpperCase() === _constants.TOKEN_BEARER.toUpperCase()) { const tokenAsBuffer = (0, _utils.convertPayloadToBase64)(token); const credentials = (0, _cryptoUtils.aesDecrypt)(tokenAsBuffer, secret).toString('utf8'); return credentials; } } const expireReasons = ['JsonWebTokenError', 'TokenExpiredError']; exports.expireReasons = expireReasons; function verifyJWTPayload(token, secret) { try { const payload = (0, _cryptoUtils.verifyPayload)(token, secret); return payload; } catch (error) { // #168 this check should be removed as soon AES encrypt is removed. if (expireReasons.includes(error.name)) { // it might be possible the jwt configuration is enabled and // old tokens fails still remains in usage, thus // we return an anonymous user to force log in. return createAnonymousRemoteUser(); } else { throw _utils.ErrorCode.getCode(_constants.HTTP_STATUS.UNAUTHORIZED, error.message); } } } function isAuthHeaderValid(authorization) { return authorization.split(' ').length === 2; } function getMiddlewareCredentials(security, secret, authorizationHeader) { if (isAESLegacy(security)) { const credentials = parseAESCredentials(authorizationHeader, secret); if (!credentials) { return; } const parsedCredentials = parseBasicPayload(credentials); if (!parsedCredentials) { return; } return parsedCredentials; } else { const { scheme, token } = parseAuthTokenHeader(authorizationHeader); if (_lodash.default.isString(token) && scheme.toUpperCase() === _constants.TOKEN_BEARER.toUpperCase()) { return verifyJWTPayload(token, secret); } } }