0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-01-20 22:52:46 -05:00
verdaccio/packages/utils/src/auth-utils.ts

210 lines
5.6 KiB
TypeScript
Raw Normal View History

import _ from 'lodash';
2020-03-03 23:59:19 +01: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';
export interface CookieSessionToken {
expires: Date;
}
export function validatePassword(
password: string,
minLength: number = DEFAULT_MIN_LIMIT_PASSWORD
): boolean {
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
*/
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,
ROLES.DEPRECATED_ANONYMOUS,
2020-03-08 09:19:12 +01:00
];
/**
* Create a RemoteUser object
* @return {Object} { name: xx, pluginGroups: [], real_groups: [] }
*/
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]);
return {
name,
groups,
real_groups: pluginGroups,
};
}
/**
* 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],
real_groups: [],
};
}
2018-07-17 20:33:51 +02:00
2020-03-08 09:19:12 +01:00
export type AllowActionCallbackResponse = boolean | undefined;
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';
export function allow_action(action: ActionsAllowed, logger): AllowAction {
return function allowActionCallback(
user: RemoteUser,
pkg: AuthPackageAllow,
callback: AllowActionCallback
): void {
logger.trace({ remote: user.name }, `[auth/allow_action]: user: @{user.name}`);
const { name, groups } = user;
2020-03-08 09:19:12 +01:00
const groupAccess = pkg[action] as string[];
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-15 00:30:47 +02:00
if (hasPermission) {
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-15 00:30:47 +02:00
if (name) {
callback(
ErrorCode.getForbidden(`user ${name} is not allowed to ${action} package ${pkg.name}`)
);
} else {
callback(
ErrorCode.getUnauthorized(`authorization required to ${action} package ${pkg.name}`)
);
}
};
}
/**
*
*/
export function handleSpecialUnpublish(logger): any {
return function (user: RemoteUser, pkg: AuthPackageAllow, callback: AllowActionCallback): void {
const action = 'unpublish';
// verify whether the unpublish prop has been defined
const isUnpublishMissing: boolean = _.isNil(pkg[action]);
const hasGroups: boolean = isUnpublishMissing ? false : (pkg[action] as string[]).length > 0;
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
if (isUnpublishMissing || hasGroups === false) {
return callback(null, undefined);
}
2020-03-03 23:59:19 +01:00
logger.trace(
{ user: user.name, name: pkg.name, action, hasGroups },
`allow_action for @{action} for @{name} has groups: @{hasGroups} for @{user}`
);
return allow_action(action, logger)(user, pkg, callback);
};
}
export function getDefaultPlugins(logger: any): IPluginAuth<Config> {
return {
2020-03-03 23:59:19 +01:00
authenticate(user: string, password: string, cb: Callback): void {
cb(ErrorCode.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
},
2020-03-08 09:19:12 +01:00
adduser(user: string, password: string, cb: Callback): void {
return cb(ErrorCode.getConflict(API_ERROR.BAD_USERNAME_PASSWORD));
},
// FIXME: allow_action and allow_publish should be in the @verdaccio/types
// @ts-ignore
allow_access: allow_action('access', logger),
// @ts-ignore
allow_publish: allow_action('publish', logger),
allow_unpublish: handleSpecialUnpublish(logger),
};
}
export function createSessionToken(): CookieSessionToken {
const tenHoursTime = 10 * 60 * 60 * 1000;
return {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + tenHoursTime),
};
}
const defaultWebTokenOptions: JWTOptions = {
sign: {
// The expiration token for the website is 7 days
expiresIn: TIME_EXPIRATION_7D,
},
verify: {},
};
const defaultApiTokenConf: APITokenOptions = {
legacy: true,
};
export const defaultSecurity: Security = {
web: defaultWebTokenOptions,
api: defaultApiTokenConf,
};
export function getAuthenticatedMessage(user: string): string {
return `you are authenticated as '${user}'`;
}
export function buildUserBuffer(name: string, password: string): Buffer {
return Buffer.from(`${name}:${password}`, 'utf8');
}