2018-08-21 08:05:34 +02:00
|
|
|
import _ from 'lodash';
|
2020-03-03 23:59:19 +01:00
|
|
|
|
2020-09-17 06:48:16 +02:00
|
|
|
import {
|
|
|
|
API_ERROR,
|
|
|
|
ROLES,
|
|
|
|
TIME_EXPIRATION_7D,
|
|
|
|
DEFAULT_MIN_LIMIT_PASSWORD,
|
|
|
|
} from '@verdaccio/dev-commons';
|
|
|
|
import {
|
|
|
|
RemoteUser,
|
|
|
|
AllowAccess,
|
|
|
|
PackageAccess,
|
|
|
|
Callback,
|
|
|
|
Config,
|
|
|
|
Security,
|
|
|
|
APITokenOptions,
|
|
|
|
JWTOptions,
|
|
|
|
IPluginAuth,
|
|
|
|
} from '@verdaccio/types';
|
2020-03-08 09:19:12 +01:00
|
|
|
import { VerdaccioError } from '@verdaccio/commons-api';
|
2020-03-03 23:59:19 +01:00
|
|
|
|
2020-03-08 10:13:56 +01:00
|
|
|
import { ErrorCode } from './utils';
|
2018-07-03 07:54:24 +02:00
|
|
|
|
2020-09-17 06:48:16 +02:00
|
|
|
export interface CookieSessionToken {
|
|
|
|
expires: Date;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function validatePassword(
|
|
|
|
password: string,
|
|
|
|
minLength: number = DEFAULT_MIN_LIMIT_PASSWORD
|
|
|
|
): boolean {
|
2018-10-12 11:07:55 +02:00
|
|
|
return typeof password === 'string' && password.length >= minLength;
|
|
|
|
}
|
|
|
|
|
2020-03-08 09:19:12 +01:00
|
|
|
/**
|
|
|
|
* All logged users will have by default the group $all and $authenticate
|
|
|
|
*/
|
2020-09-17 06:48:16 +02:00
|
|
|
export const defaultLoggedUserRoles = [
|
|
|
|
ROLES.$ALL,
|
|
|
|
ROLES.$AUTH,
|
|
|
|
ROLES.DEPRECATED_ALL,
|
|
|
|
ROLES.DEPRECATED_AUTH,
|
|
|
|
ROLES.ALL,
|
|
|
|
];
|
2020-03-08 09:19:12 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
export const defaultNonLoggedUserRoles = [
|
|
|
|
ROLES.$ALL,
|
|
|
|
ROLES.$ANONYMOUS,
|
|
|
|
// groups without '$' are going to be deprecated eventually
|
|
|
|
ROLES.DEPRECATED_ALL,
|
2020-08-13 23:27:00 +02:00
|
|
|
ROLES.DEPRECATED_ANONYMOUS,
|
2020-03-08 09:19:12 +01:00
|
|
|
];
|
|
|
|
|
2018-08-21 08:05:34 +02:00
|
|
|
/**
|
|
|
|
* Create a RemoteUser object
|
|
|
|
* @return {Object} { name: xx, pluginGroups: [], real_groups: [] }
|
|
|
|
*/
|
2019-07-16 08:40:01 +02:00
|
|
|
export function createRemoteUser(name: string, pluginGroups: string[]): RemoteUser {
|
2018-09-21 17:34:12 +02:00
|
|
|
const isGroupValid: boolean = Array.isArray(pluginGroups);
|
2020-03-08 09:19:12 +01:00
|
|
|
const groups = (isGroupValid ? pluginGroups : []).concat([...defaultLoggedUserRoles]);
|
2018-08-21 08:05:34 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
name,
|
|
|
|
groups,
|
2021-03-31 23:22:32 +02:00
|
|
|
real_groups: pluginGroups,
|
2018-08-21 08:05:34 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds an anonymous remote user in case none is logged in.
|
|
|
|
* @return {Object} { name: xx, groups: [], real_groups: [] }
|
|
|
|
*/
|
|
|
|
export function createAnonymousRemoteUser(): RemoteUser {
|
|
|
|
return {
|
|
|
|
name: undefined,
|
2020-03-08 09:19:12 +01:00
|
|
|
groups: [...defaultNonLoggedUserRoles],
|
2021-03-31 23:22:32 +02:00
|
|
|
real_groups: [],
|
2018-08-21 08:05:34 +02:00
|
|
|
};
|
|
|
|
}
|
2018-07-17 20:33:51 +02:00
|
|
|
|
2020-03-08 09:19:12 +01:00
|
|
|
export type AllowActionCallbackResponse = boolean | undefined;
|
2020-09-17 06:48:16 +02:00
|
|
|
export type AllowActionCallback = (
|
|
|
|
error: VerdaccioError | null,
|
|
|
|
allowed?: AllowActionCallbackResponse
|
|
|
|
) => void;
|
|
|
|
export type AllowAction = (
|
|
|
|
user: RemoteUser,
|
|
|
|
pkg: AuthPackageAllow,
|
|
|
|
callback: AllowActionCallback
|
|
|
|
) => void;
|
2020-03-08 09:19:12 +01:00
|
|
|
export interface AuthPackageAllow extends PackageAccess, AllowAccess {
|
|
|
|
// TODO: this should be on @verdaccio/types
|
|
|
|
unpublish: boolean | string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ActionsAllowed = 'publish' | 'unpublish' | 'access';
|
|
|
|
|
2020-04-13 14:34:26 +02:00
|
|
|
export function allow_action(action: ActionsAllowed, logger): AllowAction {
|
2020-09-17 06:48:16 +02:00
|
|
|
return function allowActionCallback(
|
|
|
|
user: RemoteUser,
|
|
|
|
pkg: AuthPackageAllow,
|
|
|
|
callback: AllowActionCallback
|
|
|
|
): void {
|
2020-08-13 23:27:00 +02:00
|
|
|
logger.trace({ remote: user.name }, `[auth/allow_action]: user: @{user.name}`);
|
2018-10-01 07:06:30 +02:00
|
|
|
const { name, groups } = user;
|
2020-03-08 09:19:12 +01:00
|
|
|
const groupAccess = pkg[action] as string[];
|
2020-08-13 23:27:00 +02:00
|
|
|
const hasPermission = groupAccess.some((group) => name === group || groups.includes(group));
|
|
|
|
logger.trace(
|
|
|
|
{ pkgName: pkg.name, hasPermission, remote: user.name, groupAccess },
|
|
|
|
`[auth/allow_action]: hasPermission? @{hasPermission} for user: @{user}`
|
|
|
|
);
|
2018-07-03 07:54:24 +02:00
|
|
|
|
2018-07-15 00:30:47 +02:00
|
|
|
if (hasPermission) {
|
2020-08-13 23:27:00 +02:00
|
|
|
logger.trace({ remote: user.name }, `auth/allow_action: access granted to: @{user}`);
|
2018-07-15 00:30:47 +02:00
|
|
|
return callback(null, true);
|
2018-07-03 07:54:24 +02:00
|
|
|
}
|
|
|
|
|
2018-07-15 00:30:47 +02:00
|
|
|
if (name) {
|
2020-09-17 06:48:16 +02:00
|
|
|
callback(
|
|
|
|
ErrorCode.getForbidden(`user ${name} is not allowed to ${action} package ${pkg.name}`)
|
|
|
|
);
|
2018-07-03 07:54:24 +02:00
|
|
|
} else {
|
2020-09-17 06:48:16 +02:00
|
|
|
callback(
|
|
|
|
ErrorCode.getUnauthorized(`authorization required to ${action} package ${pkg.name}`)
|
|
|
|
);
|
2018-07-03 07:54:24 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-08-10 13:38:06 +02:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2020-04-13 14:34:26 +02:00
|
|
|
export function handleSpecialUnpublish(logger): any {
|
2020-08-13 23:27:00 +02:00
|
|
|
return function (user: RemoteUser, pkg: AuthPackageAllow, callback: AllowActionCallback): void {
|
2019-07-16 08:40:01 +02:00
|
|
|
const action = 'unpublish';
|
2019-08-10 13:38:06 +02:00
|
|
|
// verify whether the unpublish prop has been defined
|
|
|
|
const isUnpublishMissing: boolean = _.isNil(pkg[action]);
|
2020-08-13 23:27:00 +02:00
|
|
|
const hasGroups: boolean = isUnpublishMissing ? false : (pkg[action] as string[]).length > 0;
|
2020-09-17 06:48:16 +02:00
|
|
|
logger.trace(
|
|
|
|
{ user: user.name, name: pkg.name, hasGroups },
|
|
|
|
`fallback unpublish for @{name} has groups: @{hasGroups} for @{user}`
|
|
|
|
);
|
2020-03-03 23:59:19 +01:00
|
|
|
|
2019-08-10 13:38:06 +02:00
|
|
|
if (isUnpublishMissing || hasGroups === false) {
|
2019-02-24 23:20:25 +01:00
|
|
|
return callback(null, undefined);
|
|
|
|
}
|
2020-03-03 23:59:19 +01:00
|
|
|
|
2020-09-17 06:48:16 +02:00
|
|
|
logger.trace(
|
|
|
|
{ user: user.name, name: pkg.name, action, hasGroups },
|
|
|
|
`allow_action for @{action} for @{name} has groups: @{hasGroups} for @{user}`
|
|
|
|
);
|
2020-04-13 14:34:26 +02:00
|
|
|
return allow_action(action, logger)(user, pkg, callback);
|
2019-02-24 23:20:25 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-04-13 14:34:26 +02:00
|
|
|
export function getDefaultPlugins(logger: any): IPluginAuth<Config> {
|
2018-07-03 07:54:24 +02:00
|
|
|
return {
|
2020-03-03 23:59:19 +01:00
|
|
|
authenticate(user: string, password: string, cb: Callback): void {
|
2018-07-03 07:54:24 +02:00
|
|
|
cb(ErrorCode.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
|
|
},
|
|
|
|
|
2020-03-08 09:19:12 +01:00
|
|
|
adduser(user: string, password: string, cb: Callback): void {
|
2018-07-03 07:54:24 +02:00
|
|
|
return cb(ErrorCode.getConflict(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
|
|
},
|
|
|
|
|
2019-07-16 08:40:01 +02:00
|
|
|
// FIXME: allow_action and allow_publish should be in the @verdaccio/types
|
|
|
|
// @ts-ignore
|
2020-04-13 14:34:26 +02:00
|
|
|
allow_access: allow_action('access', logger),
|
2019-07-16 08:40:01 +02:00
|
|
|
// @ts-ignore
|
2020-04-13 14:34:26 +02:00
|
|
|
allow_publish: allow_action('publish', logger),
|
|
|
|
allow_unpublish: handleSpecialUnpublish(logger),
|
2018-07-03 07:54:24 +02:00
|
|
|
};
|
|
|
|
}
|
2018-08-21 08:05:34 +02:00
|
|
|
|
|
|
|
export function createSessionToken(): CookieSessionToken {
|
|
|
|
const tenHoursTime = 10 * 60 * 60 * 1000;
|
|
|
|
|
|
|
|
return {
|
|
|
|
// npmjs.org sets 10h expire
|
2021-03-31 23:22:32 +02:00
|
|
|
expires: new Date(Date.now() + tenHoursTime),
|
2018-08-21 08:05:34 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaultWebTokenOptions: JWTOptions = {
|
|
|
|
sign: {
|
2019-07-16 08:40:01 +02:00
|
|
|
// The expiration token for the website is 7 days
|
2021-03-31 23:22:32 +02:00
|
|
|
expiresIn: TIME_EXPIRATION_7D,
|
2018-08-21 08:05:34 +02:00
|
|
|
},
|
2021-03-31 23:22:32 +02:00
|
|
|
verify: {},
|
2018-08-21 08:05:34 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const defaultApiTokenConf: APITokenOptions = {
|
2021-03-31 23:22:32 +02:00
|
|
|
legacy: true,
|
2018-08-21 08:05:34 +02:00
|
|
|
};
|
|
|
|
|
2019-07-16 08:40:01 +02:00
|
|
|
export const defaultSecurity: Security = {
|
|
|
|
web: defaultWebTokenOptions,
|
2021-03-31 23:22:32 +02:00
|
|
|
api: defaultApiTokenConf,
|
2019-07-16 08:40:01 +02:00
|
|
|
};
|
2018-08-21 08:05:34 +02:00
|
|
|
|
|
|
|
export function getAuthenticatedMessage(user: string): string {
|
|
|
|
return `you are authenticated as '${user}'`;
|
|
|
|
}
|
|
|
|
|
2019-07-16 08:40:01 +02:00
|
|
|
export function buildUserBuffer(name: string, password: string): Buffer {
|
|
|
|
return Buffer.from(`${name}:${password}`, 'utf8');
|
2018-08-21 08:05:34 +02:00
|
|
|
}
|