0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-16 21:56:25 -05:00

fix(api): force authenticate on login (#1347)

When a user has a valid token and tries to login with other credentials the endpoint returns 201.

The reason was if another user logged previously and had a valid token stored in the terminal. We must authenticate any user that tries to log in even if the token stored is valid.

We must check credentials again and return a new token, if the credentials are wrong we reject the login. Furthermore, the new token will update the list of groups.
This commit is contained in:
Juan Picado @jotadeveloper 2019-06-13 06:58:43 +02:00 committed by GitHub
parent 192fb77169
commit 85c1bd1f76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 19 deletions

View file

@ -8,9 +8,10 @@ import Cookies from 'cookies';
import { ErrorCode } from '../../../lib/utils';
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '../../../lib/constants';
import { createSessionToken, getApiToken, getAuthenticatedMessage, validatePassword } from '../../../lib/auth-utils';
import { createRemoteUser, createSessionToken, getApiToken, getAuthenticatedMessage, validatePassword } from '../../../lib/auth-utils';
import logger from '../../../lib/logger';
import type { Config } from '@verdaccio/types';
import type { Config, RemoteUser } from '@verdaccio/types';
import type { $Response, Router } from 'express';
import type { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '../../../../types';
@ -22,17 +23,26 @@ export default function(route: Router, auth: IAuth, config: Config) {
});
});
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', async function(req: $RequestExtend, res: $Response, next: $NextFunctionVer) {
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req: $RequestExtend, res: $Response, next: $NextFunctionVer) {
const { name, password } = req.body;
const remoteName = req.remote_user.name;
if (_.isNil(req.remote_user.name) === false) {
const token = name && password ? await getApiToken(auth, config, req.remote_user, password) : undefined;
if (_.isNil(remoteName) === false && _.isNil(name) === false && remoteName === name) {
auth.authenticate(name, password, async function callbackAuthenticate(err, groups) {
if (err) {
logger.logger.trace({ name, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD));
}
res.status(HTTP_STATUS.CREATED);
const restoredRemoteUser: RemoteUser = createRemoteUser(name, groups);
const token = await getApiToken(auth, config, restoredRemoteUser, password);
return next({
ok: getAuthenticatedMessage(req.remote_user.name),
token,
res.status(HTTP_STATUS.CREATED);
return next({
ok: getAuthenticatedMessage(req.remote_user.name),
token,
});
});
} else {
if (validatePassword(password) === false) {

View file

@ -1,6 +1,7 @@
// @flow
import {HEADER_TYPE, HEADERS, HTTP_STATUS} from '../../../src/lib/constants';
import {HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BEARER} from '../../../src/lib/constants';
import {buildToken} from "../../../src/lib/utils";
export function getPackage(
request: any,
@ -19,6 +20,24 @@ export function getPackage(
});
}
export function loginUserToken(request: any,
user: string,
credentials: any,
token: string,
statusCode: number = HTTP_STATUS.CREATED) {
// $FlowFixMe
return new Promise((resolve) => {
request.put(`/-/user/org.couchdb.user:${user}`)
.send(credentials)
.set('authorization', buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(statusCode)
.end(function(err, res) {
return resolve([err, res]);
});
});
}
export function addUser(request: any, user: string, credentials: any,
statusCode: number = HTTP_STATUS.CREATED) {
// $FlowFixMe
@ -50,7 +69,7 @@ export function getProfile(request: any, token: string, statusCode: number = HTT
// $FlowFixMe
return new Promise((resolve) => {
request.get(`/-/npm/v1/user`)
.set('authorization', `Bearer ${token}`)
.set('authorization', buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(statusCode)
.end(function(err, res) {

View file

@ -7,12 +7,12 @@ import rimraf from 'rimraf';
import endPointAPI from '../../../src/api/index';
import {HEADERS, HTTP_STATUS, HEADER_TYPE} from '../../../src/lib/constants';
import {HEADERS, HTTP_STATUS, HEADER_TYPE, TOKEN_BASIC, TOKEN_BEARER, API_ERROR} from '../../../src/lib/constants';
import {mockServer} from './mock';
import {DOMAIN_SERVERS} from '../../functional/config.functional';
import {parseConfigFile} from '../../../src/lib/utils';
import {buildToken, parseConfigFile} from '../../../src/lib/utils';
import {parseConfigurationFile} from '../__helper';
import {addUser, getPackage} from './__api-helper';
import {addUser, getPackage, loginUserToken} from './__api-helper';
import {setup} from '../../../src/lib/logger';
import {buildUserBuffer} from '../../../src/lib/auth-utils';
@ -65,17 +65,19 @@ describe('endpoint user auth JWT unit test', () => {
expect(err).toBeNull();
expect(res.body.ok).toBeDefined();
expect(res.body.token).toBeDefined();
const token = res.body.token;
const { token } = res.body;
expect(typeof token).toBe('string');
expect(res.body.ok).toMatch(`user '${credentials.name}' created`);
// testing JWT auth headers with token
// we need it here, because token is required
const [err1, resp1] = await getPackage(request(app), `Bearer ${token}`, 'vue');
const [err1, resp1] = await getPackage(request(app), buildToken(TOKEN_BEARER, token), 'vue');
expect(err1).toBeNull();
expect(resp1.body).toBeDefined();
expect(resp1.body.name).toMatch('vue');
const [err2, resp2] = await getPackage(request(app), `Bearer fake`, 'vue', HTTP_STATUS.UNAUTHORIZED);
const [err2, resp2] = await getPackage(request(app), buildToken(TOKEN_BEARER, 'fake'), 'vue', HTTP_STATUS.UNAUTHORIZED);
expect(err2).toBeNull();
expect(resp2.statusCode).toBe(HTTP_STATUS.UNAUTHORIZED);
expect(resp2.body.error).toMatch(FORBIDDEN_VUE);
@ -88,27 +90,49 @@ describe('endpoint user auth JWT unit test', () => {
await addUser(request(app), credentials.name, credentials);
// it should fails conflict 409
await addUser(request(app), credentials.name, credentials, HTTP_STATUS.CONFLICT);
// npm will try to sign in sending credentials via basic auth header
const token = buildUserBuffer(credentials.name, credentials.password).toString('base64');
request(app).put(`/-/user/org.couchdb.user:${credentials.name}/-rev/undefined`)
.send(credentials)
.set('authorization', `Basic ${token}`)
.set('authorization', buildToken(TOKEN_BASIC, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
expect(err).toBeNull();
expect(res.body.ok).toBeDefined();
expect(res.body.token).toBeDefined();
done();
});
});
test('should fails on try to access with corrupted token', async (done) => {
const [err2, resp2] = await getPackage(request(app), `Bearer fake`, 'vue', HTTP_STATUS.UNAUTHORIZED);
const [err2, resp2] = await getPackage(request(app), buildToken(TOKEN_BEARER, 'fake'), 'vue', HTTP_STATUS.UNAUTHORIZED);
expect(err2).toBeNull();
expect(resp2.statusCode).toBe(HTTP_STATUS.UNAUTHORIZED);
expect(resp2.body.error).toMatch(FORBIDDEN_VUE);
done();
});
test('should fails on login if user credentials are invalid even if jwt valid token is provided', async (done) => {
const credentials = { name: 'newFailsUser', password: 'secretPass' };
const [err, res] = await addUser(request(app), credentials.name, credentials);
expect(err).toBeNull();
expect(res.body.ok).toBeDefined();
expect(res.body.token).toBeDefined();
const { token } = res.body;
expect(typeof token).toBe('string');
expect(res.body.ok).toMatch(`user '${credentials.name}' created`);
// we login when token is valid
const newCredentials = { name: 'newFailsUser', password: 'BAD_PASSWORD' };
const [err2, resp2] = await loginUserToken(request(app), newCredentials.name, newCredentials, token, HTTP_STATUS.UNAUTHORIZED);
expect(err2).toBeNull();
expect(resp2.statusCode).toBe(HTTP_STATUS.UNAUTHORIZED);
expect(resp2.body.error).toMatch(API_ERROR.BAD_USERNAME_PASSWORD);
done();
});
});