mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-04-01 02:42:23 -05:00
refactor: create auth utils
using more new constants
This commit is contained in:
parent
dd3a937ac9
commit
2ad3deaab7
5 changed files with 90 additions and 67 deletions
39
src/lib/auth-utils.js
Normal file
39
src/lib/auth-utils.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import {ErrorCode} from './utils';
|
||||
import {API_ERROR} from './constants';
|
||||
|
||||
export function allow_action(action) {
|
||||
return function(user, pkg, cb) {
|
||||
const ok = pkg[action].reduce(function(prev, curr) {
|
||||
if (user.name === curr || user.groups.indexOf(curr) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, false);
|
||||
|
||||
if (ok) {
|
||||
return cb(null, true);
|
||||
}
|
||||
|
||||
if (user.name) {
|
||||
cb(ErrorCode.getForbidden(`user ${user.name} is not allowed to ${action} package ${pkg.name}`));
|
||||
} else {
|
||||
cb(ErrorCode.getForbidden(`unregistered users are not allowed to ${action} package ${pkg.name}`));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getDefaultPlugins() {
|
||||
return {
|
||||
authenticate: function(user, password, cb) {
|
||||
cb(ErrorCode.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
||||
},
|
||||
|
||||
add_user: function(user, password, cb) {
|
||||
return cb(ErrorCode.getConflict(API_ERROR.BAD_USERNAME_PASSWORD));
|
||||
},
|
||||
|
||||
allow_access: allow_action('access'),
|
||||
allow_publish: allow_action('publish'),
|
||||
};
|
||||
}
|
106
src/lib/auth.js
106
src/lib/auth.js
|
@ -2,21 +2,20 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import {loadPlugin} from '../lib/plugin-loader';
|
||||
import {ErrorCode} from './utils';
|
||||
import {buildBase64Buffer, ErrorCode} from './utils';
|
||||
import {aesDecrypt, aesEncrypt, signPayload, verifyPayload} from './crypto-utils';
|
||||
|
||||
import type {Config, Logger, Callback} from '@verdaccio/types';
|
||||
import type {$Response, NextFunction} from 'express';
|
||||
import type {$RequestExtend, JWTPayload} from '../../types';
|
||||
import {API_ERROR, ROLES} from './constants';
|
||||
import {API_ERROR, HTTP_STATUS, ROLES, TOKEN_BASIC, TOKEN_BEARER} from './constants';
|
||||
import {getMatchedPackagesSpec} from './config-utils';
|
||||
|
||||
import type {IAuth} from '../../types';
|
||||
import {getDefaultPlugins} from './auth-utils';
|
||||
|
||||
const LoggerApi = require('./logger');
|
||||
/**
|
||||
* Handles the authentification, load auth plugins.
|
||||
*/
|
||||
class Auth {
|
||||
|
||||
class Auth implements IAuth {
|
||||
config: Config;
|
||||
logger: Logger;
|
||||
secret: string;
|
||||
|
@ -32,48 +31,18 @@ class Auth {
|
|||
}
|
||||
|
||||
_loadPlugin(config: Config) {
|
||||
const plugin_params = {
|
||||
const pluginOptions = {
|
||||
config,
|
||||
logger: this.logger,
|
||||
};
|
||||
|
||||
return loadPlugin(config, config.auth, plugin_params, function(p) {
|
||||
return p.authenticate || p.allow_access || p.allow_publish;
|
||||
return loadPlugin(config, config.auth, pluginOptions, function(plugin) {
|
||||
return plugin.authenticate || plugin.allow_access || plugin.allow_publish;
|
||||
});
|
||||
}
|
||||
|
||||
_applyDefaultPlugins() {
|
||||
const allow_action = function(action) {
|
||||
return function(user, pkg, cb) {
|
||||
const ok = pkg[action].reduce(function(prev, curr) {
|
||||
if (user.name === curr || user.groups.indexOf(curr) !== -1) return true;
|
||||
return prev;
|
||||
}, false);
|
||||
|
||||
if (ok) {
|
||||
return cb(null, true);
|
||||
}
|
||||
|
||||
if (user.name) {
|
||||
cb(ErrorCode.getForbidden(`user ${user.name} is not allowed to ${action} package ${pkg.name}`));
|
||||
} else {
|
||||
cb(ErrorCode.getForbidden(`unregistered users are not allowed to ${action} package ${pkg.name}`));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
this.plugins.push({
|
||||
authenticate: function(user, password, cb) {
|
||||
cb(ErrorCode.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
||||
},
|
||||
|
||||
add_user: function(user, password, cb) {
|
||||
return cb(ErrorCode.getConflict(API_ERROR.BAD_USERNAME_PASSWORD));
|
||||
},
|
||||
|
||||
allow_access: allow_action('access'),
|
||||
allow_publish: allow_action('publish'),
|
||||
});
|
||||
this.plugins.push(getDefaultPlugins());
|
||||
}
|
||||
|
||||
authenticate(user: string, password: string, cb: Callback) {
|
||||
|
@ -81,7 +50,7 @@ class Auth {
|
|||
(function next() {
|
||||
const plugin = plugins.shift();
|
||||
|
||||
if (typeof(plugin.authenticate) !== 'function') {
|
||||
if (_.isFunction(plugin.authenticate) === false) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
@ -99,12 +68,12 @@ class Auth {
|
|||
// Info: Cannot use `== false to check falsey values`
|
||||
if (!!groups && groups.length !== 0) {
|
||||
// TODO: create a better understanding of expectations
|
||||
if (typeof groups === 'string') {
|
||||
if (_.isString(groups)) {
|
||||
throw new TypeError('invalid type for function');
|
||||
}
|
||||
const isGroupValid: boolean = _.isArray(groups);
|
||||
if (!isGroupValid) {
|
||||
throw new TypeError('user groups is different than an array');
|
||||
throw new TypeError(API_ERROR.BAD_FORMAT_USER_GROUP);
|
||||
}
|
||||
|
||||
return cb(err, authenticatedUser(user, groups));
|
||||
|
@ -116,19 +85,19 @@ class Auth {
|
|||
|
||||
add_user(user: string, password: string, cb: Callback) {
|
||||
let self = this;
|
||||
let plugins = this.plugins.slice(0)
|
||||
let plugins = this.plugins.slice(0);
|
||||
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
let n = 'adduser';
|
||||
if (typeof(p[n]) !== 'function') {
|
||||
n = 'add_user';
|
||||
(function next() {
|
||||
let plugin = plugins.shift();
|
||||
let method = 'adduser';
|
||||
if (_.isFunction(plugin[method]) === false) {
|
||||
method = 'add_user';
|
||||
}
|
||||
if (typeof(p[n]) !== 'function') {
|
||||
if (_.isFunction[method] === false) {
|
||||
next();
|
||||
} else {
|
||||
// p.add_user() execution
|
||||
p[n](user, password, function(err, ok) {
|
||||
plugin[method](user, password, function(err, ok) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
@ -185,7 +154,7 @@ class Auth {
|
|||
return next();
|
||||
}
|
||||
|
||||
plugin.allow_publish(user, pkg, function(err, ok) {
|
||||
plugin.allow_publish(user, pkg, (err, ok) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
@ -220,13 +189,13 @@ class Auth {
|
|||
req.remote_user = buildAnonymousUser();
|
||||
|
||||
const authorization = req.headers.authorization;
|
||||
if (authorization == null) {
|
||||
if (_.isNil(authorization)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const parts = authorization.split(' ');
|
||||
if (parts.length !== 2) {
|
||||
return next( ErrorCode.getBadRequest('bad authorization header') );
|
||||
return next( ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER) );
|
||||
}
|
||||
|
||||
const credentials = this._parseCredentials(parts);
|
||||
|
@ -258,12 +227,12 @@ class Auth {
|
|||
_parseCredentials(parts: Array<string>) {
|
||||
let credentials;
|
||||
const scheme = parts[0];
|
||||
if (scheme.toUpperCase() === 'BASIC') {
|
||||
credentials = new Buffer(parts[1], 'base64').toString();
|
||||
this.logger.info('basic authentication is deprecated, please use JWT instead');
|
||||
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
|
||||
credentials = buildBase64Buffer(parts[1]).toString();
|
||||
this.logger.info(API_ERROR.DEPRECATED_BASIC_HEADER);
|
||||
return credentials;
|
||||
} else if (scheme.toUpperCase() === 'BEARER') {
|
||||
const token = new Buffer(parts[1], 'base64');
|
||||
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
||||
const token = buildBase64Buffer(parts[1]);
|
||||
|
||||
credentials = aesDecrypt(token, this.secret).toString('utf8');
|
||||
return credentials;
|
||||
|
@ -277,17 +246,17 @@ class Auth {
|
|||
*/
|
||||
webUIJWTmiddleware() {
|
||||
return (req: $RequestExtend, res: $Response, _next: NextFunction) => {
|
||||
if (req.remote_user !== null && req.remote_user.name !== undefined) {
|
||||
if (_.isNull(req.remote_user) === false && _.isNil(req.remote_user.name) === false) {
|
||||
return _next();
|
||||
}
|
||||
|
||||
req.pause();
|
||||
const next = function(_err) {
|
||||
const next = () => {
|
||||
req.resume();
|
||||
return _next();
|
||||
};
|
||||
|
||||
const token = (req.headers.authorization || '').replace('Bearer ', '');
|
||||
const token = (req.headers.authorization || '').replace(`${TOKEN_BEARER} `, '');
|
||||
if (!token) {
|
||||
return next();
|
||||
}
|
||||
|
@ -329,7 +298,7 @@ class Auth {
|
|||
try {
|
||||
decoded = verifyPayload(token, this.secret);
|
||||
} catch (err) {
|
||||
throw ErrorCode.getCode(401, err.message);
|
||||
throw ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, err.message);
|
||||
}
|
||||
|
||||
return decoded;
|
||||
|
@ -351,7 +320,7 @@ function buildAnonymousUser() {
|
|||
return {
|
||||
name: undefined,
|
||||
// groups without '$' are going to be deprecated eventually
|
||||
groups: ['$all', '$anonymous', '@all', '@anonymous'],
|
||||
groups: [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONUMOUS],
|
||||
real_groups: [],
|
||||
};
|
||||
}
|
||||
|
@ -362,7 +331,12 @@ function buildAnonymousUser() {
|
|||
*/
|
||||
function authenticatedUser(name: string, pluginGroups: Array<any>) {
|
||||
const isGroupValid: boolean = _.isArray(pluginGroups);
|
||||
const groups = (isGroupValid ? pluginGroups : []).concat([ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL]);
|
||||
const groups = (isGroupValid ? pluginGroups : []).concat([
|
||||
ROLES.$ALL,
|
||||
ROLES.$AUTH,
|
||||
ROLES.DEPRECATED_ALL,
|
||||
ROLES.DEPRECATED_AUTH,
|
||||
ROLES.ALL]);
|
||||
|
||||
return {
|
||||
name,
|
||||
|
|
|
@ -7,6 +7,7 @@ import {ErrorCode} from './utils';
|
|||
|
||||
import type {PackageList} from '@verdaccio/types';
|
||||
import type {MatchedPackage} from '../../types';
|
||||
|
||||
const BLACKLIST = {
|
||||
all: true,
|
||||
anonymous: true,
|
||||
|
|
|
@ -29,10 +29,12 @@ export const DEFAULT_UPLINK = 'npmjs';
|
|||
|
||||
export const ROLES = {
|
||||
$ALL: '$all',
|
||||
ALL: 'all',
|
||||
$AUTH: '$authenticated',
|
||||
$ANONYMOUS: '$anonymous',
|
||||
DEPRECATED_ALL: '@all',
|
||||
DEPRECATED_AUTH: '@authenticated',
|
||||
ALL: 'all',
|
||||
DEPRECATED_ANONUMOUS: '@anonymous',
|
||||
};
|
||||
|
||||
export const HTTP_STATUS = {
|
||||
|
@ -80,7 +82,10 @@ export const API_ERROR = {
|
|||
FILE_NOT_FOUND: 'File not found',
|
||||
BAD_STATUS_CODE: 'bad status code',
|
||||
PACKAGE_EXIST: 'this package is already present',
|
||||
BAD_AUTH_HEADER: 'bad authorization header',
|
||||
WEB_DISABLED: 'Web interface is disabled in the config file',
|
||||
DEPRECATED_BASIC_HEADER: 'basic authentication is deprecated, please use JWT instead',
|
||||
BAD_FORMAT_USER_GROUP: 'user groups is different than an array',
|
||||
};
|
||||
|
||||
export const APP_ERROR = {
|
||||
|
|
|
@ -29,6 +29,10 @@ export function getUserAgent(): string {
|
|||
return `${pkgName}/${pkgVersion}`;
|
||||
}
|
||||
|
||||
export function buildBase64Buffer(payload: string): Buffer {
|
||||
return new Buffer(payload, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a package.
|
||||
* @return {Boolean} whether the package is valid or not
|
||||
|
|
Loading…
Add table
Reference in a new issue