0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-04-01 02:42:23 -05:00

Merge branch 'master' into 628-auth-user-zero

This commit is contained in:
Christopher Kelley 2018-03-26 09:21:33 -04:00
commit 78bb95c0c2
No known key found for this signature in database
GPG key ID: 9D168CEE284C92BA
25 changed files with 142 additions and 183 deletions

View file

@ -55,13 +55,14 @@ The above line will pull the latest prebuilt image from dockerhub, if you haven'
If you have [build an image locally](#build-your-own-docker-image) use `verdaccio` as the last argument.
You can use `-v` to mount `conf` and `storage` to the hosts filesystem:
You can use `-v` to bind mount `conf` and `storage` to the hosts filesystem:
```bash
V_PATH=/path/for/verdaccio; docker run -it --rm --name verdaccio -p 4873:4873 \
-v $V_PATH/conf:/verdaccio/conf \
-v $V_PATH/storage:/verdaccio/storage \
verdaccio/verdaccio
```
>Note: Verdaccio runs as a non-root user (uid=101, gid=101) inside the container, if you use bind mount to override default, you need to make sure the mount directory is assigned to the right user. In above example, you need to run `sudo chown -R 101:101 /opt/verdaccio` otherwise you will get permission errors at runtime. [Use docker volume](https://docs.docker.com/storage/volumes/) is recommended over using bind mount.
### Docker and custom port configuration
Any `host:port` configured in `conf/config.yaml` under `listen` is currently ignored when using docker.

View file

@ -27,6 +27,7 @@
"compression": "1.7.2",
"cookies": "0.7.1",
"cors": "2.8.4",
"date-fns": "1.29.0",
"express": "4.16.2",
"global": "4.3.2",
"handlebars": "4.0.11",
@ -51,7 +52,6 @@
"@commitlint/config-conventional": "6.1.3",
"@commitlint/travis-cli": "6.1.3",
"@verdaccio/types": "2.0.2",
"axios": "0.18.0",
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-eslint": "8.2.2",
@ -126,10 +126,11 @@
"supertest": "3.0.0",
"url-loader": "0.6.2",
"verdaccio-auth-memory": "0.0.4",
"verdaccio-memory": "0.0.3",
"verdaccio-memory": "0.0.6",
"webpack": "3.10.0",
"webpack-dev-server": "2.11.1",
"webpack-merge": "4.1.2"
"webpack-merge": "4.1.2",
"whatwg-fetch": "2.0.3"
},
"keywords": [
"private",
@ -147,10 +148,10 @@
"prepublish": "in-publish && npm run build:webui && npm run code:build || not-in-publish",
"flow": "flow",
"pretest": "npm run code:build",
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest --maxWorkers 2",
"test": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC jest --maxWorkers 2",
"test:e2e": "cross-env BABEL_ENV=registry jest --config ./jest.e2e.config.js --maxWorkers 2",
"test:all": "npm run test && npm run test:e2e",
"test:unit": "cross-env NODE_ENV=test BABEL_ENV=test jest '(/test/unit.*\\.spec|/test/webui/.*\\.spec)\\.js' --maxWorkers 2",
"test:unit": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC jest '(/test/unit.*\\.spec|/test/webui/.*\\.spec)\\.js' --maxWorkers 2",
"test:func": "cross-env NODE_ENV=test BABEL_ENV=test jest '(/test/functional.*\\.func)\\.js' --maxWorkers 2",
"pre:ci": "npm run lint && npm run build:webui",
"commitmsg": "commitlint -e $GIT_PARAMS",

View file

@ -4,7 +4,6 @@ import HTTPError from 'http-errors';
import type {Config} from '@verdaccio/types';
import type {Router} from 'express';
import type {IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer} from '../../../../types';
// import {combineBaseUrl, getWebProtocol} from '../../../lib/utils';
function addUserAuthApi(route: Router, auth: IAuth, config: Config) {
route.post('/login', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
@ -21,14 +20,6 @@ function addUserAuthApi(route: Router, auth: IAuth, config: Config) {
}
});
});
// FIXME: this will be re-implemented
// route.post('/-/logout', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
// const base = combineBaseUrl(getWebProtocol(req), req.get('host'), config.url_prefix);
// res.cookies.set('token', '');
// res.redirect(base);
// });
}
export default addUserAuthApi;

View file

@ -308,11 +308,12 @@ class Auth {
/**
* JWT middleware for WebUI
* @return {Function}
*/
jwtMiddleware() {
return (req: $RequestExtend, res: $Response, _next: NextFunction) => {
if (req.remote_user !== null && req.remote_user.name !== undefined) return _next();
if (req.remote_user !== null && req.remote_user.name !== undefined) {
return _next();
}
req.pause();
const next = function(_err) {
@ -320,18 +321,22 @@ class Auth {
return _next();
};
req.remote_user = buildAnonymousUser();
let token = (req.headers.authorization || '').replace('Bearer ', '');
if (!token) return next();
const token = (req.headers.authorization || '').replace('Bearer ', '');
if (!token) {
return next();
}
let decoded;
try {
decoded = this.decode_token(token);
} catch (err) {/**/}
} catch (err) {
// FIXME: intended behaviour, do we want it?
}
if (decoded) {
req.remote_user = authenticatedUser(decoded.user, decoded.group);
} else {
req.remote_user = buildAnonymousUser();
}
next();

View file

@ -1,17 +1,16 @@
import React from 'react';
import {Button, Dialog, Input, Alert} from 'element-react';
import isString from 'lodash/isString';
import get from 'lodash/get';
import isNumber from 'lodash/isNumber';
import {Link} from 'react-router-dom';
import API from '../../../utils/api';
import storage from '../../../utils/storage';
import {getRegistryURL} from '../../../utils/url';
import classes from './header.scss';
import './logo.png';
import {getRegistryURL} from '../../../utils/url';
export default class Header extends React.Component {
state = {
@ -43,10 +42,8 @@ export default class Header extends React.Component {
}
componentWillMount() {
API.get('logo')
.then((response) => {
this.setState({logo: response.data});
})
API.request('logo')
.then((response) => response.text().then((logo) => this.setState({logo})))
.catch((error) => {
throw new Error(error);
});
@ -62,26 +59,27 @@ export default class Header extends React.Component {
}
try {
let resp = await API.post(`login`, {
data: {
username: this.state.username,
password: this.state.password
const credentials = {
username: this.state.username,
password: this.state.password
};
let resp = await API.request(`login`, 'POST', {
body: JSON.stringify(credentials),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
});
}).then((response) => response.json());
storage.setItem('token', resp.data.token);
storage.setItem('username', resp.data.username);
storage.setItem('token', resp.token);
storage.setItem('username', resp.username);
location.reload();
} catch (e) {
const errorObj = {
title: 'Unable to login',
type: 'error'
};
if (get(e, 'response.status', 0) === 401) {
errorObj.description = e.response.data.error;
} else {
errorObj.description = e.message;
}
errorObj.description = e.message;
this.setState({loginError: errorObj});
}
}

View file

@ -32,7 +32,9 @@ export default class PackageSidebar extends React.Component {
let packageMeta;
try {
packageMeta = (await API.get(`sidebar/${packageName}`)).data;
packageMeta = await API.request(`sidebar/${packageName}`, 'GET').then(function(response) {
return response.json();
});
} catch (err) {
this.setState({
failed: true

View file

@ -1,9 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import format from 'date-fns/format';
import Module from '../../Module';
import datetime from '../../../../../utils/datetime';
import classes from './style.scss';
const TIMEFORMAT = 'YYYY/MM/DD, HH:mm:ss';
export default class LastSync extends React.Component {
static propTypes = {
packageMeta: PropTypes.object.isRequired
@ -19,15 +21,15 @@ export default class LastSync extends React.Component {
}
});
return lastUpdate ? datetime(lastUpdate) : '';
const time = format(new Date(lastUpdate), TIMEFORMAT);
return lastUpdate ? time : '';
}
get recentReleases() {
let recentReleases = Object.keys(this.props.packageMeta.time).map((version) => {
return {
version,
time: datetime(this.props.packageMeta.time[version])
};
const time = format(new Date(this.props.packageMeta.time[version]), TIMEFORMAT);
return {version, time};
});
return recentReleases.slice(recentReleases.length - 3, recentReleases.length).reverse();

View file

@ -47,9 +47,9 @@ export default class Detail extends React.Component {
});
try {
const resp = await API.get(`package/readme/${packageName}`);
const resp = await API.request(`package/readme/${packageName}`, 'GET').then((response) => response.text());
this.setState({
readMe: resp.data
readMe: resp
});
} catch (err) {
this.setState({

View file

@ -52,11 +52,11 @@ export default class Home extends React.Component {
async loadPackages() {
try {
this.req = await API.get('packages');
this.req = await API.request('packages', 'GET').then((response) => response.json());
if (this.state.query === '') {
this.setState({
packages: this.req.data,
packages: this.req,
loading: false
});
}
@ -71,12 +71,12 @@ export default class Home extends React.Component {
async searchPackage(query) {
try {
this.req = await API.get(`/search/${query}`);
this.req = await API.request(`/search/${query}`, 'GET').then((response) => response.json());
// Implement cancel feature later
if (this.state.query === query) {
this.setState({
packages: this.req.data,
packages: this.req,
fistTime: false,
loading: false
});

View file

@ -1,34 +1,34 @@
import storage from './storage';
import axios from 'axios';
class API {
constructor() {
['get', 'delete', 'post', 'put', 'patch'].map((method) => {
this[method] = (url, options = {}) => {
if (!window.VERDACCIO_API_URL) {
throw new Error('VERDACCIO_API_URL is not defined!');
request(url, method = 'GET', options = {}) {
if (!window.VERDACCIO_API_URL) {
throw new Error('VERDACCIO_API_URL is not defined!');
}
const token = storage.getItem('token');
if (token) {
if (!options.headers) options.headers = {};
options.headers.authorization = token;
}
if (!['http://', 'https://', '//'].some((prefix) => url.startsWith(prefix))) {
url = window.VERDACCIO_API_URL + url;
}
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
const token = storage.getItem('token');
if (token) {
if (!options.headers) options.headers = {};
options.headers.authorization = token;
}
if (!['http://', 'https://', '//'].some((prefix) => url.startsWith(prefix))) {
url = window.VERDACCIO_API_URL + url;
}
return axios.request({
method,
url,
...options
});
};
});
}
return fetch(url, {
method,
...options
}).then(handleErrors);
}
}
export default new API();

View file

@ -1,16 +0,0 @@
/**
* Date time in LocaleString
* @param {string} input
* @returns {string}
*/
export default function datetime(input) {
const date = new Date(input);
return date.toLocaleString('en-GB', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
});
}

View file

@ -7,7 +7,7 @@ web:
store:
memory:
cache: true
limit: 10
auth:
auth-memory:

View file

@ -7,7 +7,7 @@ web:
store:
memory:
cache: true
limit: 10
auth:
auth-memory:

View file

@ -12,6 +12,7 @@ import type {IProxy} from '../../types';
setup([]);
describe('UpStorge', () => {
jest.setTimeout(10000);
const uplinkDefault = {
url: 'https://registry.npmjs.org/'

View file

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<PackageSidebar /> : <LastSync /> should load the LastSync component and match snapshot 1`] = `"<div class=\\"module releasesModule\\"><h2 class=\\"moduleTitle\\">Last Sync<span>Dec 14, 2017, 3:43 PM</span></h2><div><ul><li class=\\"last-sync-item\\"><span>2.7.1</span><span>Dec 14, 2017, 3:43 PM</span></li><li class=\\"last-sync-item\\"><span>2.7.0</span><span>Dec 5, 2017, 11:25 PM</span></li><li class=\\"last-sync-item\\"><span>2.6.6</span><span>Nov 8, 2017, 10:47 PM</span></li></ul></div></div>"`;
exports[`<PackageSidebar /> : <LastSync /> should load the LastSync component and match snapshot 1`] = `"<div class=\\"module releasesModule\\"><h2 class=\\"moduleTitle\\">Last Sync<span>2017/12/14, 15:43:52</span></h2><div><ul><li class=\\"last-sync-item\\"><span>2.7.1</span><span>2017/12/14, 15:43:27</span></li><li class=\\"last-sync-item\\"><span>2.7.0</span><span>2017/12/05, 23:25:06</span></li><li class=\\"last-sync-item\\"><span>2.6.6</span><span>2017/11/08, 22:47:16</span></li></ul></div></div>"`;

View file

@ -6,24 +6,20 @@ import React from 'react';
import { mount, shallow } from 'enzyme';
import LastSync from '../../../../src/webui/src/components/PackageSidebar/modules/LastSync';
import { packageMeta } from '../store/packageMeta';
jest.mock(
'../../../../src/webui/utils/datetime',
() => require('../__mocks__/datetime').default
);
console.error = jest.fn();
describe.skip('<PackageSidebar /> : <LastSync />', () => {
describe('<PackageSidebar /> : <LastSync />', () => {
it('should load the component and check getters: lastUpdate, recentReleases with package data', () => {
const wrapper = mount(<LastSync packageMeta={packageMeta} />);
const instance = wrapper.instance();
const result = [
{ time: 'Dec 14, 2017, 3:43 PM', version: '2.7.1' },
{ time: 'Dec 5, 2017, 11:25 PM', version: '2.7.0' },
{ time: 'Nov 8, 2017, 10:47 PM', version: '2.6.6' }
{ time: '2017/12/14, 15:43:27', version: '2.7.1' },
{ time: '2017/12/05, 23:25:06', version: '2.7.0' },
{ time: '2017/11/08, 22:47:16', version: '2.6.6' }
];
expect(instance.lastUpdate).toEqual('Dec 14, 2017, 3:43 PM');
const lastUpdated = '2017/12/14, 15:43:52';
expect(instance.lastUpdate).toEqual(lastUpdated);
expect(instance.recentReleases).toEqual(result);
});

View file

@ -8,7 +8,7 @@ import PackageSidebar from '../../../../src/webui/src/components/PackageSidebar'
import { packageMeta } from '../store/packageMeta';
jest.mock('../../../../src/webui/utils/api', () => ({
get: require('../__mocks__/api').default.get
request: require('../__mocks__/api').default.request,
}));
console.error = jest.fn();
@ -18,7 +18,7 @@ describe('<PackageSidebar /> component', () => {
const wrapper = mount(<PackageSidebar />);
const { loadPackageData } = wrapper.instance();
expect(console.error).toBeCalled();
loadPackageData().then(response => {
loadPackageData().catch(response => {
expect(response).toBeUndefined();
expect(wrapper.state()).toEqual({ failed: true });
});

View file

@ -10,29 +10,36 @@ import { packageMeta } from '../store/packageMeta';
* @param {string} endpoint
* @returns {Promise}
*/
const register = (method = 'get', endpoint, config = {}) => {
const register = (url, method = 'get', options = {}) => {
if (endpoint === 'login' && method === 'post') {
return login(config);
if (url === 'login' && method.toLocaleLowerCase() === 'post') {
return login(options);
}
if (endpoint === 'logo' && method === 'get') {
if (url === 'logo' && method.toLocaleLowerCase() === 'get') {
return logo();
}
if (endpoint === 'sidebar/verdaccio' && method === 'get') {
return Promise.resolve({ data: packageMeta });
if (url === 'sidebar/verdaccio' && method.toLocaleLowerCase() === 'get') {
return new Promise(function(resolve) {
resolve({
json: function() {
return packageMeta;
}
});
});
}
return Promise.reject({ status: 404, data: 'Not found' });
throw Error('Not found');
};
/**
* Bind API methods
*/
const API = ['get', 'post'].reduce((api, method) => {
api[method] = register.bind(null, method);
return api;
}, {});
class API {
request() {
return register.call(null, ...arguments);
}
}
export default API;
export default new API;

View file

@ -1,17 +0,0 @@
/**
* Date time in LocaleString
* @param {string} input
* @returns {string}
*/
export default function datetime(input) {
const date = new Date(input);
return date.toLocaleString('en-GB', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
timeZone: 'Europe/London'
});
}

View file

@ -8,8 +8,7 @@ import { BrowserRouter } from 'react-router-dom';
import storage from '../../../src/webui/utils/storage';
jest.mock('../../../src/webui/utils/api', () => ({
get: require('./__mocks__/api').default.get,
post: require('./__mocks__/api').default.post
request: require('./__mocks__/api').default.request,
}));
describe('<Header /> component shallow', () => {
@ -81,15 +80,15 @@ describe('<Header /> component shallow', () => {
it('handleSubmit - login should failed with 401', () => {
const HeaderWrapper = wrapper.find(Header).dive();
const handleSubmit = HeaderWrapper.instance().handleSubmit;
const error = {
const errorObject = {
title: 'Unable to login',
type: 'error',
description: 'Unauthorized'
};
HeaderWrapper.setState({ username: 'sam', password: '12345' });
handleSubmit().then(() => {
expect(HeaderWrapper.state('loginError')).toEqual(error);
handleSubmit().catch((error) => {
expect(HeaderWrapper.state('loginError')).toEqual(errorObject);
});
});

View file

@ -5,21 +5,20 @@
*/
export default function(config) {
return new Promise(function(resolve, reject) {
if (config.data.username === 'sam' && config.data.password === '1234') {
resolve({
status: 200,
data: {
username: 'sam',
token: 'TEST_TOKEN'
}
const body = JSON.parse(config.body);
if (body.username === 'sam' && body.password === '1234') {
return new Promise(function(resolve) {
resolve({
json: function() {
return {
username: 'sam',
token: 'TEST_TOKEN'
}
}
});
});
} else {
reject({
response: {
status: 401,
data: { error: 'Unauthorized' }
}
});
throw Error('Unauthorized');
}
});
}

View file

@ -4,7 +4,7 @@
*/
export default function() {
const response = {
data: 'http://xyz.com/image.jpg'
url: 'http://xyz.com/image.jpg'
};
return Promise.resolve(response);
}

View file

@ -10,6 +10,7 @@ export default {
...baseConfig,
entry: {
main: [
'whatwg-fetch',
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:4872',
'webpack/hot/only-dev-server',
@ -22,6 +23,8 @@ export default {
publicPath: '/',
},
devtool: 'cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
__DEBUG__: true,

View file

@ -9,7 +9,7 @@ import getPackageVersion from './getPackageVersion';
const prodConf = {
entry: {
main: ['babel-polyfill', `${env.SRC_ROOT}/webui/src/index.js`],
main: ['babel-polyfill', 'whatwg-fetch', `${env.SRC_ROOT}/webui/src/index.js`],
},
module: {

View file

@ -231,14 +231,10 @@
lodash "4.17.5"
mkdirp "0.5.1"
"@verdaccio/streams@1.0.0":
"@verdaccio/streams@1.0.0", "@verdaccio/streams@^1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@verdaccio/streams/-/streams-1.0.0.tgz#d5d24c6747208728b9fd16b908e3932c3fb1f864"
"@verdaccio/streams@^0.0.2":
version "0.0.2"
resolved "https://registry.npmjs.org/@verdaccio/streams/-/streams-0.0.2.tgz#72cd65449e657b462a1ca094f663cad9ea872427"
"@verdaccio/types@2.0.2":
version "2.0.2"
resolved "https://registry.npmjs.org/@verdaccio/types/-/types-2.0.2.tgz#2a60faa458bbb5eaf3cdb6db1ef95c8d2e2fa5ed"
@ -605,13 +601,6 @@ aws4@^1.2.1, aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
axios@0.18.0:
version "0.18.0"
resolved "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
dependencies:
follow-redirects "^1.3.0"
is-buffer "^1.1.5"
babel-cli@6.26.0:
version "6.26.0"
resolved "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1"
@ -2513,6 +2502,10 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
date-fns@1.29.0:
version "1.29.0"
resolved "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@ -3604,12 +3597,6 @@ flow-runtime@0.17.0:
version "0.17.0"
resolved "https://registry.npmjs.org/flow-runtime/-/flow-runtime-0.17.0.tgz#ff57dd22bd7b0682c7beff20c3590f6a4a8286e3"
follow-redirects@^1.3.0:
version "1.4.1"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa"
dependencies:
debug "^3.1.0"
for-in@^0.1.3:
version "0.1.8"
resolved "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
@ -8944,11 +8931,11 @@ verdaccio-htpasswd@0.1.4:
crypto "^1.0.1"
unix-crypt-td-js "^1.0.0"
verdaccio-memory@0.0.3:
version "0.0.3"
resolved "https://registry.npmjs.org/verdaccio-memory/-/verdaccio-memory-0.0.3.tgz#8ba6b51f2cf93d67958fedad5feb3f1e19933e48"
verdaccio-memory@0.0.6:
version "0.0.6"
resolved "https://registry.npmjs.org/verdaccio-memory/-/verdaccio-memory-0.0.6.tgz#9521701710c2b768e00e6bcc23d9ce9faca4109e"
dependencies:
"@verdaccio/streams" "^0.0.2"
"@verdaccio/streams" "^1.0.0"
http-errors "1.6.2"
memory-fs "^0.4.1"
@ -9127,7 +9114,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
dependencies:
iconv-lite "0.4.19"
whatwg-fetch@>=0.10.0:
whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0:
version "2.0.3"
resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"