diff --git a/package.json b/package.json index 5c2a935d9..8073efb58 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,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", @@ -129,7 +128,8 @@ "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", diff --git a/src/api/web/endpoint/user.js b/src/api/web/endpoint/user.js index bf459306d..2c57f6b75 100644 --- a/src/api/web/endpoint/user.js +++ b/src/api/web/endpoint/user.js @@ -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; diff --git a/src/lib/auth.js b/src/lib/auth.js index 68d7f2975..5087d5b8a 100644 --- a/src/lib/auth.js +++ b/src/lib/auth.js @@ -296,11 +296,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) { @@ -308,18 +309,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(); diff --git a/src/webui/src/components/Header/index.js b/src/webui/src/components/Header/index.js index 397fed3d9..7aec24afa 100644 --- a/src/webui/src/components/Header/index.js +++ b/src/webui/src/components/Header/index.js @@ -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}); } } diff --git a/src/webui/src/components/PackageSidebar/index.jsx b/src/webui/src/components/PackageSidebar/index.jsx index b4830a83b..a95f602bc 100644 --- a/src/webui/src/components/PackageSidebar/index.jsx +++ b/src/webui/src/components/PackageSidebar/index.jsx @@ -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 diff --git a/src/webui/src/modules/detail/index.jsx b/src/webui/src/modules/detail/index.jsx index 744b24ce8..acd93be16 100644 --- a/src/webui/src/modules/detail/index.jsx +++ b/src/webui/src/modules/detail/index.jsx @@ -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({ diff --git a/src/webui/src/modules/home/index.js b/src/webui/src/modules/home/index.js index d055cf623..8fcc17083 100644 --- a/src/webui/src/modules/home/index.js +++ b/src/webui/src/modules/home/index.js @@ -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 }); diff --git a/src/webui/utils/api.js b/src/webui/utils/api.js index 7995bc4d2..084738dfd 100644 --- a/src/webui/utils/api.js +++ b/src/webui/utils/api.js @@ -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(); diff --git a/test/webui/components/PackageSidebar/packageSidebar.spec.js b/test/webui/components/PackageSidebar/packageSidebar.spec.js index 3db1a4f09..e64059511 100644 --- a/test/webui/components/PackageSidebar/packageSidebar.spec.js +++ b/test/webui/components/PackageSidebar/packageSidebar.spec.js @@ -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(' component', () => { const wrapper = mount(); const { loadPackageData } = wrapper.instance(); expect(console.error).toBeCalled(); - loadPackageData().then(response => { + loadPackageData().catch(response => { expect(response).toBeUndefined(); expect(wrapper.state()).toEqual({ failed: true }); }); diff --git a/test/webui/components/__mocks__/api.js b/test/webui/components/__mocks__/api.js index d89c2b875..2c8e2cf3e 100644 --- a/test/webui/components/__mocks__/api.js +++ b/test/webui/components/__mocks__/api.js @@ -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; diff --git a/test/webui/components/header.spec.js b/test/webui/components/header.spec.js index 82de9d3e2..e567375cf 100644 --- a/test/webui/components/header.spec.js +++ b/test/webui/components/header.spec.js @@ -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('
component shallow', () => { @@ -81,15 +80,15 @@ describe('
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); }); }); diff --git a/test/webui/components/store/login.js b/test/webui/components/store/login.js index d69821465..35f6897fc 100644 --- a/test/webui/components/store/login.js +++ b/test/webui/components/store/login.js @@ -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'); } }); } diff --git a/test/webui/components/store/logo.js b/test/webui/components/store/logo.js index 0e721525d..74162df22 100644 --- a/test/webui/components/store/logo.js +++ b/test/webui/components/store/logo.js @@ -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); } diff --git a/tools/webpack.dev.config.babel.js b/tools/webpack.dev.config.babel.js index ef063a0e9..cfd989e4f 100644 --- a/tools/webpack.dev.config.babel.js +++ b/tools/webpack.dev.config.babel.js @@ -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, diff --git a/tools/webpack.prod.config.babel.js b/tools/webpack.prod.config.babel.js index 9107562f9..610bd630f 100644 --- a/tools/webpack.prod.config.babel.js +++ b/tools/webpack.prod.config.babel.js @@ -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: { diff --git a/yarn.lock b/yarn.lock index 9e28deee6..5ec019813 100644 --- a/yarn.lock +++ b/yarn.lock @@ -601,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" @@ -3600,12 +3593,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" @@ -9123,7 +9110,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"