mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
eca62597e8
* fix: E409 username is already registered (adduser) * update tests
362 lines
11 KiB
TypeScript
362 lines
11 KiB
TypeScript
import _ from 'lodash';
|
|
import path from 'path';
|
|
import { describe, expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
Config as AppConfig,
|
|
ROLES,
|
|
createAnonymousRemoteUser,
|
|
createRemoteUser,
|
|
parseConfigFile,
|
|
} from '@verdaccio/config';
|
|
import { getDefaultConfig } from '@verdaccio/config';
|
|
import { API_ERROR, CHARACTER_ENCODING, VerdaccioError, errorUtils } from '@verdaccio/core';
|
|
import { logger, setup } from '@verdaccio/logger';
|
|
import { aesDecrypt, verifyPayload } from '@verdaccio/signature';
|
|
import { Config, RemoteUser } from '@verdaccio/types';
|
|
import { getAuthenticatedMessage } from '@verdaccio/utils';
|
|
|
|
import {
|
|
ActionsAllowed,
|
|
AllowActionCallbackResponse,
|
|
Auth,
|
|
allow_action,
|
|
getApiToken,
|
|
getDefaultPlugins,
|
|
} from '../src';
|
|
|
|
setup({});
|
|
|
|
const parseConfigurationFile = (conf) => {
|
|
const { name, ext } = path.parse(conf);
|
|
const format = ext.startsWith('.') ? ext.substring(1) : 'yaml';
|
|
|
|
return path.join(__dirname, `./partials/config/${format}/security/${name}.${format}`);
|
|
};
|
|
|
|
describe('Auth utilities', () => {
|
|
vi.setConfig({ testTimeout: 20000 });
|
|
|
|
const parseConfigurationSecurityFile = (name) => {
|
|
return parseConfigurationFile(`security/${name}`);
|
|
};
|
|
|
|
function getConfig(configFileName: string, secret: string) {
|
|
const conf = parseConfigFile(parseConfigurationSecurityFile(configFileName));
|
|
// @ts-ignore
|
|
const secConf = _.merge(getDefaultConfig(), conf);
|
|
// @ts-expect-error
|
|
secConf.secret = secret;
|
|
const config: Config = new AppConfig(secConf);
|
|
|
|
return config;
|
|
}
|
|
|
|
async function getTokenByConfiguration(
|
|
configFileName: string,
|
|
username: string,
|
|
password: string,
|
|
secret = '12345',
|
|
methodToSpy: string,
|
|
methodNotBeenCalled: string
|
|
): Promise<string> {
|
|
const config: Config = getConfig(configFileName, secret);
|
|
const auth: Auth = new Auth(config, logger);
|
|
await auth.init();
|
|
// @ts-ignore
|
|
const spy = vi.spyOn(auth, methodToSpy);
|
|
// @ts-ignore
|
|
const spyNotCalled = vi.spyOn(auth, methodNotBeenCalled);
|
|
const user: RemoteUser = {
|
|
name: username,
|
|
real_groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
groups: ['company-role1', 'company-role2'],
|
|
};
|
|
const token = await getApiToken(auth, config, user, password);
|
|
expect(spy).toHaveBeenCalled();
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
expect(spyNotCalled).not.toHaveBeenCalled();
|
|
expect(token).toBeDefined();
|
|
|
|
return token as string;
|
|
}
|
|
|
|
const verifyJWT = (token: string, user: string, password: string, secret: string) => {
|
|
const payload = verifyPayload(token, secret);
|
|
expect(payload.name).toBe(user);
|
|
expect(payload.groups).toBeDefined();
|
|
expect(payload.groups).toEqual([
|
|
'company-role1',
|
|
'company-role2',
|
|
'test',
|
|
'$all',
|
|
'$authenticated',
|
|
'@all',
|
|
'@authenticated',
|
|
'all',
|
|
]);
|
|
expect(payload.real_groups).toBeDefined();
|
|
expect(payload.real_groups).toEqual([
|
|
'test',
|
|
'$all',
|
|
'$authenticated',
|
|
'@all',
|
|
'@authenticated',
|
|
'all',
|
|
]);
|
|
};
|
|
|
|
const verifyAES = (token: string, user: string, password: string, secret: string) => {
|
|
// @ts-ignore
|
|
const payload = aesDecrypt(token, secret).toString(CHARACTER_ENCODING.UTF8);
|
|
const content = payload.split(':');
|
|
|
|
expect(content[0]).toBe(user);
|
|
expect(content[0]).toBe(password);
|
|
};
|
|
|
|
describe('getDefaultPlugins', () => {
|
|
test('authentication should fail by default (default)', () => {
|
|
const plugin = getDefaultPlugins({ trace: vi.fn() });
|
|
plugin.authenticate('foo', 'bar', (error: any) => {
|
|
expect(error).toEqual(errorUtils.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
});
|
|
});
|
|
|
|
test('add user should not fail by default (default)', () => {
|
|
const plugin = getDefaultPlugins({ trace: vi.fn() });
|
|
// @ts-ignore
|
|
plugin.adduser('foo', 'bar', (error: any, access: any) => {
|
|
expect(error).toEqual(null);
|
|
expect(access).toEqual(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('allow_action', () => {
|
|
describe('access/publish/unpublish and anonymous', () => {
|
|
const packageAccess = {
|
|
name: 'foo',
|
|
version: undefined,
|
|
access: ['foo'],
|
|
unpublish: false,
|
|
};
|
|
|
|
// const type = 'access';
|
|
test.each(['access', 'publish', 'unpublish'])(
|
|
'should restrict %s to anonymous users',
|
|
(type) => {
|
|
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
createAnonymousRemoteUser(),
|
|
{
|
|
...packageAccess,
|
|
[type]: ['foo'],
|
|
},
|
|
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
expect(error).not.toBeNull();
|
|
expect(allowed).toBeUndefined();
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
test.each(['access', 'publish', 'unpublish'])(
|
|
'should allow %s to anonymous users',
|
|
(type) => {
|
|
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
createAnonymousRemoteUser(),
|
|
{
|
|
...packageAccess,
|
|
[type]: [ROLES.$ANONYMOUS],
|
|
},
|
|
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
expect(error).toBeNull();
|
|
expect(allowed).toBe(true);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
test.each(['access', 'publish', 'unpublish'])(
|
|
'should allow %s only if user is anonymous if the logged user has groups',
|
|
(type) => {
|
|
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
{
|
|
...packageAccess,
|
|
[type]: [ROLES.$ANONYMOUS],
|
|
},
|
|
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
expect(error).not.toBeNull();
|
|
expect(allowed).toBeUndefined();
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
test.each(['access', 'publish', 'unpublish'])(
|
|
'should allow %s only if user is anonymous match any other groups',
|
|
(type) => {
|
|
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
{
|
|
...packageAccess,
|
|
[type]: ['admin', 'some-other-group', ROLES.$ANONYMOUS],
|
|
},
|
|
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
expect(error).toBeNull();
|
|
expect(allowed).toBe(true);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
test.each(['access', 'publish', 'unpublish'])(
|
|
'should not allow %s anonymous if other groups are defined and does not match',
|
|
(type) => {
|
|
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
{
|
|
...packageAccess,
|
|
[type]: ['bla-bla-group', 'some-other-group', ROLES.$ANONYMOUS],
|
|
},
|
|
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
expect(error).not.toBeNull();
|
|
expect(allowed).toBeUndefined();
|
|
}
|
|
);
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('createRemoteUser', () => {
|
|
test('create remote user', () => {
|
|
expect(createRemoteUser('test', [])).toEqual({
|
|
name: 'test',
|
|
real_groups: [],
|
|
groups: ['$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
});
|
|
});
|
|
test('create remote user with groups', () => {
|
|
expect(createRemoteUser('test', ['group1', 'group2'])).toEqual({
|
|
name: 'test',
|
|
real_groups: ['group1', 'group2'],
|
|
groups: ['group1', 'group2', '$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
});
|
|
});
|
|
test('create anonymous remote user', () => {
|
|
expect(createAnonymousRemoteUser()).toEqual({
|
|
name: undefined,
|
|
real_groups: [],
|
|
groups: ['$all', '$anonymous', '@all', '@anonymous'],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getApiToken test', () => {
|
|
test('should sign token with aes and security missing', async () => {
|
|
const token = await getTokenByConfiguration(
|
|
'security-missing',
|
|
'test',
|
|
'test',
|
|
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
'aesEncrypt',
|
|
'jwtEncrypt'
|
|
);
|
|
|
|
verifyAES(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
expect(_.isString(token)).toBeTruthy();
|
|
});
|
|
|
|
test('should sign token with aes and security empty', async () => {
|
|
const token = await getTokenByConfiguration(
|
|
'security-empty',
|
|
'test',
|
|
'test',
|
|
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
'aesEncrypt',
|
|
'jwtEncrypt'
|
|
);
|
|
|
|
verifyAES(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
expect(_.isString(token)).toBeTruthy();
|
|
});
|
|
|
|
test('should sign token with aes', async () => {
|
|
const token = await getTokenByConfiguration(
|
|
'security-basic',
|
|
'test',
|
|
'test',
|
|
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
'aesEncrypt',
|
|
'jwtEncrypt'
|
|
);
|
|
|
|
verifyAES(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
expect(_.isString(token)).toBeTruthy();
|
|
});
|
|
|
|
test('should sign token with legacy and jwt disabled', async () => {
|
|
const token = await getTokenByConfiguration(
|
|
'security-no-legacy',
|
|
'test',
|
|
'test',
|
|
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
'aesEncrypt',
|
|
'jwtEncrypt'
|
|
);
|
|
|
|
expect(_.isString(token)).toBeTruthy();
|
|
verifyAES(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
});
|
|
|
|
test('should sign token with legacy enabled and jwt enabled', async () => {
|
|
const token = await getTokenByConfiguration(
|
|
'security-jwt-legacy-enabled',
|
|
'test',
|
|
'test',
|
|
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
'jwtEncrypt',
|
|
'aesEncrypt'
|
|
);
|
|
|
|
verifyJWT(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
expect(_.isString(token)).toBeTruthy();
|
|
});
|
|
|
|
test('should sign token with jwt enabled', async () => {
|
|
const token = await getTokenByConfiguration(
|
|
'security-jwt',
|
|
'test',
|
|
'test',
|
|
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
'jwtEncrypt',
|
|
'aesEncrypt'
|
|
);
|
|
|
|
expect(_.isString(token)).toBeTruthy();
|
|
verifyJWT(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
});
|
|
|
|
test('should sign with jwt whether legacy is disabled', async () => {
|
|
const token = await getTokenByConfiguration(
|
|
'security-legacy-disabled',
|
|
'test',
|
|
'test',
|
|
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
'jwtEncrypt',
|
|
'aesEncrypt'
|
|
);
|
|
|
|
expect(_.isString(token)).toBeTruthy();
|
|
verifyJWT(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
});
|
|
});
|
|
|
|
describe('getAuthenticatedMessage test', () => {
|
|
test('should sign token with jwt enabled', () => {
|
|
expect(getAuthenticatedMessage('test')).toBe("you are authenticated as 'test'");
|
|
});
|
|
});
|
|
});
|