diff --git a/.eslintignore b/.eslintignore index 6a0f1134b..e4c97f862 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,6 +13,9 @@ Dockerfile *.html *.scss *.png +*.json +*.name +*.tgz *.jpg *.sh **/partials/** diff --git a/.eslintrc b/.eslintrc index afbb0e594..28db2d51c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,102 +1,21 @@ { "extends": [ - "eslint:recommended", - "google", - "plugin:react/recommended", - "plugin:jest/recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/typescript", - "plugin:jsx-a11y/recommended", - "prettier" + "@verdaccio" ], - "plugins": ["import", "jest", "jsx-a11y", "react-hooks"], - "env": { - "es6": true, - "node": true, - "jest": true - }, - "globals": { - "__APP_VERSION__": true - }, - "parserOptions": { - "allowImportExportEverywhere": true, - "sourceType": "module", - "ecmaVersion": 11, - "ecmaFeatures": { - "impliedStrict": true, - "jsx": true - } - }, - "settings": { - "import/resolver": { - "node": { - "extensions": [".js", ".jsx", ".ts", ".tsx"] - } - } - }, - "parser": "@typescript-eslint/parser", "rules": { - "curly": ["error", "all"], - "react/prop-types": 0, - "jest/no-export": 0, - "jest/no-test-callback": 0, - "jest/expect-expect": 0, - "jest/no-try-expect": 0, - "jest/no-done-callback": "off", - "jest/no-conditional-expect": "off", - "keyword-spacing": "off", - "no-tabs": "off", - "no-useless-escape": "off", - "padded-blocks": "off", - "require-jsdoc": "off", - "valid-jsdoc": "off", - "import/order": ["error"], - "eol-last": "error", - "no-irregular-whitespace": "error", - "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], - "no-trailing-spaces": "error", - "camelcase": "off", - "guard-for-in": "error", - "new-cap": "error", - "max-len": ["error", 180], - "no-console": ["error", { "allow": ["warn"] }], - "no-constant-condition": "error", - "no-debugger": "error", - "no-empty": "error", - "no-fallthrough": "error", - "no-invalid-this": "error", - "no-new-require": "error", - "no-undef": "error", - "no-unreachable": "error", - "no-var": "error", - "one-var": "error", - "prefer-rest-params": "error", - "prefer-spread": "error", - "handle-callback-err": 0, - "prefer-const": 0, - "@typescript-eslint/camelcase": 0, - "@typescript-eslint/ban-ts-ignore": 0, "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/ban-ts-ignore": 0, "@typescript-eslint/no-inferrable-types": 0, "@typescript-eslint/no-empty-function": 0, - "@typescript-eslint/no-this-alias": 0, + "@typescript-eslint/no-this-alias": ["warn"], "@typescript-eslint/no-use-before-define": 0, - "@typescript-eslint/array-type": ["error"], + "@typescript-eslint/array-type": ["warn"], "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/indent": 0, - "@typescript-eslint/ban-ts-comment": 0, - "@typescript-eslint/ban-types": 0, - "@typescript-eslint/explicit-module-boundary-types": 0, - - // rules to fix - "no-unused-vars": ["warn", { "vars": "all", "args": "none" }], - "jest/no-identical-title": ["warn"], - "prefer-promise-reject-errors": ["warn"], - "jest/no-disabled-tests": ["warn"], - "jest/no-commented-out-tests": ["warn"], - "@typescript-eslint/prefer-optional-chain": ["warn"], - "@typescript-eslint/explicit-member-accessibility": ["warn"], - "@typescript-eslint/no-unused-vars": ["warn"] + "@typescript-eslint/interface-name-prefix": 0, + "import/order": 0, + "handle-callback-err": 1, + "prefer-const": 1, + "prefer-promise-reject-errors": 1 } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bb7d84d0..b9d23b953 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,16 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: + paths: + - .github/workflows/ci.yml + - 'packages/**' + - 'jest/**' + - 'package.json' + - 'lerna.json' jobs: ci: diff --git a/.github/workflows/docker-publish-pre-check.yml b/.github/workflows/docker-publish-pre-check.yml index 8a4a232d3..e4ee0dc79 100644 --- a/.github/workflows/docker-publish-pre-check.yml +++ b/.github/workflows/docker-publish-pre-check.yml @@ -1,12 +1,6 @@ name: Docker & Publish Pre-check on: - push: - paths: - - 'packages/**' - - 'docker-bin/**' - - 'package.json' - - 'lerna.json' pull_request: paths: - .github/workflows/docker-publish-pre-check.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 394a75a77..a3bcee1e2 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -2,6 +2,8 @@ name: Docker publish to GitHub registry on: push: + branches: + - master paths: - .github/workflows/docker-publish.yml - 'packages/**' diff --git a/debug/DEBUGGING.md b/debug/DEBUGGING.md new file mode 100644 index 000000000..4c9e03232 --- /dev/null +++ b/debug/DEBUGGING.md @@ -0,0 +1,23 @@ +# Debuging Documentation + + +## Debugging tests + + + +### Running a single test + +```bash +yarn test test/integration/package.spec.ts --runInBand +``` + +Using `--runInBand` allows you to see the `console.log` prints, without that `jest` hidde them. + +#### Display additional information + +You can take advance of `debug` module used by many dependencies, eg: + +* `supertest`: `DEBUG=superagent yarn test test/integration/package.spec.ts --runInBand` +* `express`: `DEBUG=express:* yarn test test/integration/package.spec.ts --runInBand` +* `nock`: `DEBUG=nock yarn test test/integration/package.spec.ts --runInBand` +* All of if: `DEBUG=* yarn test test/integration/package.spec.ts --runInBand` diff --git a/package.json b/package.json index bd7949d6a..32dfcf2f8 100644 --- a/package.json +++ b/package.json @@ -21,18 +21,19 @@ "@commitlint/cli": "8.3.5", "@commitlint/config-conventional": "8.2.0", "@octokit/rest": "17.0.0", - "@types/async": "3.0.3", - "@types/express": "4.17.1", + "@types/async": "3.2.3", + "@types/express": "4.17.6", "@types/http-errors": "1.6.3", - "@types/jest": "25.2.3", - "@types/lodash": "4.14.151", + "@types/jest": "26.0.1", + "@types/lodash": "4.14.156", "@types/mime": "2.0.2", "@types/minimatch": "3.0.3", - "@types/node": "12.12.21", + "@types/node": "14.0.14", "@types/request": "2.48.3", "@types/semver": "7.2.0", - "@verdaccio/babel-preset": "9.4.0", - "@verdaccio/eslint-config": "9.0.0", + "@types/supertest": "2.0.9", + "@verdaccio/babel-preset": "9.6.1", + "@verdaccio/eslint-config": "9.3.2", "@verdaccio/types": "9.5.0", "codecov": "3.6.1", "cross-env": "7.0.2", @@ -42,22 +43,22 @@ "get-stdin": "7.0.0", "husky": "2.7.0", "in-publish": "2.0.0", - "jest": "26.0.1", - "jest-environment-node": "26.0.1", - "jest-junit": "10.0.0", + "jest": "26.1.0", + "jest-environment-node": "26.1.0", + "jest-junit": "11.0.1", "kleur": "3.0.3", - "lerna": "3.21.0", + "lerna": "3.22.1", "lint-staged": "8.2.1", - "nock": "11.7.2", + "nock": "12.0.3", "prettier": "1.19.1", - "rimraf": "3.0.0", + "rimraf": "3.0.2", "selfsigned": "1.10.7", - "standard-version": "7.0.1", + "standard-version": "8.0.0", "supertest": "4.0.2", - "typescript": "3.9.2", - "verdaccio": "4.6.2", - "verdaccio-auth-memory": "9.3.0", - "verdaccio-memory": "9.3.0" + "typescript": "3.9.5", + "verdaccio": "4.7.1", + "verdaccio-auth-memory": "9.7.0", + "verdaccio-memory": "9.7.0" }, "scripts": { "bootstrap": "lerna bootstrap", @@ -73,7 +74,7 @@ "release:prerelease": "lerna version --conventional-commits --conventional-prerelease --preid next --registry http://localhost:4873", "release:publish": "lerna publish from-git", "release:publish-prerelease": "lerna publish from-git --pre-dist-tag next", - "lint": "eslint \"packages/**/@(src|tests)/**\"", + "lint": "eslint \"packages/**/@(src|tests|test)/**\"", "test": "lerna run test --concurrency 1", "test:e2e:cli": "cross-env NODE_ENV=test jest --config ./test/e2e-cli/jest.config.e2e.cli.js --passWithNoTests", "website:lint": "cd website && yarn lint", diff --git a/packages/api/__mocks__/@verdaccio/logger/index.js b/packages/api/__mocks__/@verdaccio/logger/index.js new file mode 100644 index 000000000..722dd194c --- /dev/null +++ b/packages/api/__mocks__/@verdaccio/logger/index.js @@ -0,0 +1,19 @@ +const setup = jest.fn(); +const logger = { + child: jest.fn(() => ({ + debug: jest.fn(), + trace: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + })), + debug: jest.fn(), + trace: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), +}; + +export { setup, logger } diff --git a/packages/api/package.json b/packages/api/package.json index 52ebfbd98..37b915d36 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -24,11 +24,12 @@ "license": "MIT", "dependencies": { "@verdaccio/auth": "5.0.0-alpha.0", - "@verdaccio/commons-api": "9.4.0", + "@verdaccio/commons-api": "9.6.1", "@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/hooks": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/middleware": "5.0.0-alpha.0", + "@verdaccio/store": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0", "body-parser": "1.19.0", "cookies": "0.8.0", @@ -38,7 +39,8 @@ }, "devDependencies": { "@verdaccio/dev-types": "5.0.0-alpha.0", - "@verdaccio/types": "9.3.0" + "@verdaccio/types": "9.5.0", + "express": "4.17.1" }, "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" } diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index c3f994681..7ddf2a253 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -37,7 +37,6 @@ export default function(config: Config, auth: IAuth, storage: IStorageHandler): app.param('_rev', match(/^-rev$/)); app.param('org_couchdb_user', match(/^org\.couchdb\.user:/)); app.param('anything', match(/.*/)); - app.use(auth.apiJWTmiddleware()); app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' })); // @ts-ignore diff --git a/packages/api/src/package.ts b/packages/api/src/package.ts index dd358c289..425b8c2a7 100644 --- a/packages/api/src/package.ts +++ b/packages/api/src/package.ts @@ -15,7 +15,7 @@ const downloadStream = (packageName: string, filename: string, storage: any, req }); stream.on('error', function(err): void { - return res.report_error(err); + return res.locals.report_error(err); }); res.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM); diff --git a/packages/api/src/publish.ts b/packages/api/src/publish.ts index 3712cf301..bab18dfcb 100644 --- a/packages/api/src/publish.ts +++ b/packages/api/src/publish.ts @@ -139,7 +139,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I storage.mergeTags(packageName, tags, cb); }; - const afterChange = function(error, okMessage, metadata): void { + const afterChange = function(error, okMessage, metadata: Package): void { const metadataCopy: Package = { ...metadata }; const { _attachments, versions } = metadataCopy; @@ -326,7 +326,7 @@ export function uploadPackageTarball(storage: IStorageHandler) { }); stream.on('error', function(err) { - return res.report_error(err); + return res.locals.report_error(err); }); stream.on('success', function() { diff --git a/packages/api/src/user.ts b/packages/api/src/user.ts index 10b09681d..187d568af 100644 --- a/packages/api/src/user.ts +++ b/packages/api/src/user.ts @@ -74,16 +74,4 @@ export default function(route: Router, auth: IAuth, config: Config): void { ok: API_MESSAGE.LOGGED_OUT, }); }); - - // placeholder 'cause npm require to be authenticated to publish - // we do not do any real authentication yet - route.post('/_session', Cookies.express(), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void { - res.cookies.set('AuthSession', String(Math.random()), createSessionToken()); - - next({ - ok: true, - name: 'somebody', - roles: [], - }); - }); } diff --git a/packages/api/test/.eslintrc b/packages/api/test/.eslintrc new file mode 100644 index 000000000..ff8b4a8b8 --- /dev/null +++ b/packages/api/test/.eslintrc @@ -0,0 +1,8 @@ +{ + "rules": { + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/explicit-member-accessibility": 0, + "@typescript-eslint/no-unused-vars": 2, + "no-console": 0 + } +} diff --git a/packages/api/test/integration/_helper.ts b/packages/api/test/integration/_helper.ts new file mode 100644 index 000000000..19a8898df --- /dev/null +++ b/packages/api/test/integration/_helper.ts @@ -0,0 +1,70 @@ +import path from "path"; +import express, {Application} from 'express'; +import supertest from 'supertest'; + +import {parseConfigFile} from '@verdaccio/utils'; +import { Config } from '@verdaccio/config'; +import { Storage } from '@verdaccio/store'; +import { final, handleError, errorReportingMiddleware } from '@verdaccio/middleware'; +import { Auth } from '@verdaccio/auth'; +import apiEndpoints from '../../src'; +import {IAuth} from "@verdaccio/dev-types"; +import {HEADER_TYPE, HTTP_STATUS, generatePackageMetadata} from "@verdaccio/dev-commons"; +import {HEADERS} from "@verdaccio/commons-api"; + +const getConf = (conf) => { + const configPath = path.join(__dirname, 'config', conf); + + return parseConfigFile(configPath); +}; + +export async function initializeServer(configName): Promise { + const app = express(); + const config = new Config(getConf(configName)); + const storage = new Storage(config); + await storage.init(config, []); + const auth: IAuth = new Auth(config); + // @ts-ignore + app.use(errorReportingMiddleware); + // @ts-ignore + app.use(apiEndpoints(config, auth, storage)); + // @ts-ignore + app.use(handleError); + // @ts-ignore + app.use(final); + + app.use(function(request, response) { + response.status(590); + console.log('respo', response); + response.json({error: 'cannot handle this'}); + }); + + return app; +} + +export function publishVersion(app, configFile, pkgName, version): supertest.Test { + const pkgMetadata = generatePackageMetadata(pkgName, version); + + return supertest(app) + .put(`/${encodeURIComponent(pkgName)}`) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify(pkgMetadata)) + .set('accept', HEADERS.GZIP) + .set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON); +} + +export async function publishTaggedVersion(app, configFile, pkgName: string, version: string, tag: string) { + const pkgMetadata = generatePackageMetadata(pkgName, version, { + [tag]: version + }); + + return supertest(app) + .put(`/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/-tag/${encodeURIComponent(tag)}`) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify(pkgMetadata)) + .expect(HTTP_STATUS.CREATED) + .set('accept', HEADERS.GZIP) + .set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON); +} diff --git a/packages/api/test/integration/config/package.yaml b/packages/api/test/integration/config/package.yaml new file mode 100644 index 000000000..eb4758b26 --- /dev/null +++ b/packages/api/test/integration/config/package.yaml @@ -0,0 +1,30 @@ +store: + memory: + limit: 1000 + +auth: + auth-memory: + users: + test: + name: test + password: test + +web: + enable: true + title: verdaccio + +publish: + allow_offline: false + +uplinks: + +logs: { type: stdout, format: pretty, level: trace } + +packages: + '@*/*': + access: $anonymous + publish: $anonymous + '**': + access: $anonymous + publish: $anonymous +_debug: true diff --git a/packages/api/test/integration/config/ping.yaml b/packages/api/test/integration/config/ping.yaml new file mode 100644 index 000000000..4ecd6c5fa --- /dev/null +++ b/packages/api/test/integration/config/ping.yaml @@ -0,0 +1,30 @@ +store: + memory: + limit: 10 + +auth: + auth-memory: + users: +web: + enable: true + title: verdaccio + +uplinks: + +logs: { type: stdout, format: pretty, level: trace } + +packages: + '@*/*': + access: $all + publish: $all + unpublish: $all + proxy: npmjs + 'verdaccio': + access: $all + publish: $all + '**': + access: $all + publish: $all + unpublish: $all + proxy: npmjs +_debug: true diff --git a/packages/api/test/integration/config/publish.yaml b/packages/api/test/integration/config/publish.yaml new file mode 100644 index 000000000..eb4758b26 --- /dev/null +++ b/packages/api/test/integration/config/publish.yaml @@ -0,0 +1,30 @@ +store: + memory: + limit: 1000 + +auth: + auth-memory: + users: + test: + name: test + password: test + +web: + enable: true + title: verdaccio + +publish: + allow_offline: false + +uplinks: + +logs: { type: stdout, format: pretty, level: trace } + +packages: + '@*/*': + access: $anonymous + publish: $anonymous + '**': + access: $anonymous + publish: $anonymous +_debug: true diff --git a/packages/api/test/integration/config/user.yaml b/packages/api/test/integration/config/user.yaml new file mode 100644 index 000000000..2389e1b7b --- /dev/null +++ b/packages/api/test/integration/config/user.yaml @@ -0,0 +1,36 @@ +store: + memory: + limit: 1000 + +auth: + auth-memory: + users: + test: + name: test + password: test + +web: + enable: true + title: verdaccio + +uplinks: + npmjs: + url: https://registry.npmjs.org/ + +logs: { type: stdout, format: pretty, level: trace } + +packages: + '@*/*': + access: $all + publish: $all + unpublish: $all + proxy: npmjs + 'verdaccio': + access: $all + publish: $all + '**': + access: $all + publish: $all + unpublish: $all + proxy: npmjs +_debug: true diff --git a/packages/api/test/integration/config/whoami.yaml b/packages/api/test/integration/config/whoami.yaml new file mode 100644 index 000000000..2389e1b7b --- /dev/null +++ b/packages/api/test/integration/config/whoami.yaml @@ -0,0 +1,36 @@ +store: + memory: + limit: 1000 + +auth: + auth-memory: + users: + test: + name: test + password: test + +web: + enable: true + title: verdaccio + +uplinks: + npmjs: + url: https://registry.npmjs.org/ + +logs: { type: stdout, format: pretty, level: trace } + +packages: + '@*/*': + access: $all + publish: $all + unpublish: $all + proxy: npmjs + 'verdaccio': + access: $all + publish: $all + '**': + access: $all + publish: $all + unpublish: $all + proxy: npmjs +_debug: true diff --git a/packages/api/test/integration/package.spec.ts b/packages/api/test/integration/package.spec.ts new file mode 100644 index 000000000..1bda9bddf --- /dev/null +++ b/packages/api/test/integration/package.spec.ts @@ -0,0 +1,83 @@ +import supertest from 'supertest'; + +import {initializeServer, publishTaggedVersion, publishVersion} from './_helper'; +import { HTTP_STATUS } from '@verdaccio/commons-api'; +import {HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons'; +import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types"; + +const mockApiJWTmiddleware = jest.fn(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: 'foo', groups: [], real_groups: []} + _next(); + } +); + +jest.mock('@verdaccio/auth', () => ({ + Auth: class { + apiJWTmiddleware() { + return mockApiJWTmiddleware(); + } + allow_access (_d, f_, cb) { + cb(null, true) + } + allow_publish (_d, f_, cb) { + cb(null, true) + } + } +})); + +describe('package', () => { + let app; + beforeAll(async () => { + app = await initializeServer('package.yaml'); + await publishVersion(app, 'package.yaml', 'foo', '1.0.0'); + }); + + test('should return a package', async (done) => { + + return supertest(app) + .get('/foo') + .set('Accept', HEADERS.JSON) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.OK) + .then(response => { + expect(response.body.name).toEqual('foo'); + done(); + }); + }); + + test('should return a package by version', async (done) => { + return supertest(app) + .get('/foo/1.0.0') + .set('Accept', HEADERS.JSON) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.OK) + .then(response => { + expect(response.body.name).toEqual('foo'); + done(); + }); + }); + + // TODO: investigate the 404 + test.skip('should return a package by dist-tag', async (done) => { + await publishVersion(app, 'package.yaml', 'foo-tagged', '1.0.0'); + await publishTaggedVersion(app, 'package.yaml', 'foo-tagged', '1.0.1', 'test'); + return supertest(app) + .get('/foo-tagged/1.0.0') + .set('Accept', HEADERS.JSON) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.CREATED) + .then(response => { + expect(response.body.name).toEqual('foo'); + done(); + }); + }); + + test('should return 404', async () => { + return supertest(app) + .get('/404-not-found') + .set('Accept', HEADERS.JSON) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.NOT_FOUND); + }); +}); diff --git a/packages/api/test/integration/ping.spec.ts b/packages/api/test/integration/ping.spec.ts new file mode 100644 index 000000000..08901d586 --- /dev/null +++ b/packages/api/test/integration/ping.spec.ts @@ -0,0 +1,16 @@ +import supertest from 'supertest'; + +import {initializeServer } from './_helper'; +import { HTTP_STATUS } from '@verdaccio/commons-api'; +import {HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons'; + +describe('ping', () => { + test('should return the reply the ping', async () => { + return supertest(await initializeServer('ping.yaml')) + .get('/-/ping') + .set('Accept', HEADERS.JSON) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.OK) + .then(response => expect(response.body).toEqual({})); + }); +}); diff --git a/packages/api/test/integration/publish.spec.ts b/packages/api/test/integration/publish.spec.ts new file mode 100644 index 000000000..31ac8c1b4 --- /dev/null +++ b/packages/api/test/integration/publish.spec.ts @@ -0,0 +1,179 @@ +import {initializeServer, publishVersion} from './_helper'; +import { HTTP_STATUS } from '@verdaccio/commons-api'; +import {API_ERROR, API_MESSAGE, generatePackageMetadata, HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons'; +import {$RequestExtend, $ResponseExtend} from '@verdaccio/dev-types'; +import supertest from "supertest"; + +const mockApiJWTmiddleware = jest.fn(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: 'foo', groups: [], real_groups: []} + _next(); + } +); + +jest.setTimeout(50000000); + +jest.mock('@verdaccio/auth', () => ({ + Auth: class { + apiJWTmiddleware() { + return mockApiJWTmiddleware(); + } + allow_access (_d, f_, cb) { + cb(null, true) + } + allow_publish (_d, f_, cb) { + cb(null, true) + } + + allow_unpublish (_d, f_, cb) { + cb(null, true) + } + } +})); + +// const mockStorage = jest.fn(() => { +// const { Storage } = jest.requireActual('@verdaccio/store'); +// return { +// Storage: class extends Storage { +// addPackage(name, metadata, cb) { +// super.addPackage(name, metadata, cb); +// } +// } +// }; +// }); + +// jest.mock('@verdaccio/store', () => { +// const { Storage } = jest.requireActual('@verdaccio/store'); +// return ({ +// Storage: class extends Storage { +// addPackage(name, metadata, cb) { +// // super.addPackage(name, metadata, cb); +// return mockStorage(name, metadata, cb); +// } +// } +// }) +// }); + +describe('publish', () => { + describe('handle invalid publish formats', () => { + const pkgName = 'test'; + const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0'); + test('should fail on publish a bad _attachments package', async (done) => { + const app = await initializeServer('publish.yaml'); + return supertest(app) + .put(`/${encodeURIComponent(pkgName)}`) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify(Object.assign({}, pkgMetadata, { + _attachments: {} + }))) + .set('accept', HEADERS.GZIP) + .expect(HTTP_STATUS.BAD_REQUEST) + .then(response => { + console.log("response.body", response.body); + expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL); + done(); + }); + }); + + test('should fail on publish a bad versions package', async (done) => { + const app = await initializeServer('publish.yaml'); + return supertest(app) + .put(`/${encodeURIComponent(pkgName)}`) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify(Object.assign({}, pkgMetadata, { + versions: '' + }))) + .set('accept', HEADERS.GZIP) + .expect(HTTP_STATUS.BAD_REQUEST) + .then(response => { + console.log("response.body", response.body); + expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL); + done(); + }); + }); + }); + + describe('publish a package', () => { + test('should publish a package', async (done) => { + const app = await initializeServer('publish.yaml'); + return publishVersion(app, 'publish.yaml', 'foo', '1.0.0') + .expect(HTTP_STATUS.CREATED) + .then(response => { + expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED); + done(); + }); + }); + + test('should publish a new package', async (done) => { + const pkgName = 'test'; + const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0'); + const app = await initializeServer('publish.yaml'); + return supertest(app) + .put(`/${encodeURIComponent(pkgName)}`) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify(Object.assign({}, pkgMetadata, { + _attachments: null + }))) + .set('accept', HEADERS.GZIP) + .expect(HTTP_STATUS.CREATED) + .then(response => { + expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED); + done(); + }); + }); + + test('should publish a new package with no readme', async (done) => { + const pkgName = 'test'; + const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0'); + const app = await initializeServer('publish.yaml'); + return supertest(app) + .put(`/${encodeURIComponent(pkgName)}`) + .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON) + .send(JSON.stringify(Object.assign({}, pkgMetadata, { + versions: { + ['1.0.0'] : { + readme: null + } + } + }))) + .set('accept', HEADERS.GZIP) + .expect(HTTP_STATUS.CREATED) + .then(response => { + expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED); + done(); + }); + }); + + }); + + + test('should fails on publish a duplicated package', async (done) => { + const app = await initializeServer('publish.yaml'); + await publishVersion(app, 'publish.yaml', 'foo', '1.0.0'); + return publishVersion(app, 'publish.yaml', 'foo', '1.0.0') + .expect(HTTP_STATUS.CONFLICT) + .then(response => { + console.log("response.body", response.body); + expect(response.body.error).toEqual(API_ERROR.PACKAGE_EXIST); + done(); + }); + }); + + describe('unpublish a package', () => { + let app; + + beforeEach(async () => { + app = await initializeServer('publish.yaml'); + await publishVersion(app, 'publish.yaml', 'foo', '1.0.0'); + }) + + test('should unpublish a package', () => { + + }) + }); + + describe('star a package', () => { + + }); + +}); diff --git a/packages/api/test/integration/user.spec.ts b/packages/api/test/integration/user.spec.ts new file mode 100644 index 000000000..f58b45646 --- /dev/null +++ b/packages/api/test/integration/user.spec.ts @@ -0,0 +1,237 @@ +import supertest from 'supertest'; + +import { initializeServer } from './_helper'; +import { HTTP_STATUS, API_ERROR } from '@verdaccio/commons-api'; +import {HEADERS, HEADER_TYPE, API_MESSAGE} from '@verdaccio/dev-commons'; +import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types"; +import {getBadRequest, getConflict, getUnauthorized} from "@verdaccio/commons-api/lib"; +import _ from "lodash"; + +const mockApiJWTmiddleware = jest.fn(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: 'test', groups: [], real_groups: []}; + _next(); + } +); + +const mockAuthenticate = jest.fn(() => (_name, _password, callback): void => { + return callback(null, ['all']); + } +); + +const mockAddUser = jest.fn(() => (_name, _password, callback): void => { + return callback(getConflict(API_ERROR.USERNAME_ALREADY_REGISTERED)); + } +); + +jest.mock('@verdaccio/auth', () => ({ + getApiToken: () => 'token', + Auth: class { + apiJWTmiddleware() { + return mockApiJWTmiddleware(); + } + allow_access (_d, f_, cb) { + cb(null, true); + } + add_user (name, password, callback) { + mockAddUser()(name, password, callback); + } + authenticate (_name, _password, callback) { + mockAuthenticate()(_name, _password, callback); + } + } +})); + +describe('user', () => { + const credentials = { name: 'test', password: 'test' }; + + test('should test add a new user', async (done) => { + mockApiJWTmiddleware.mockImplementationOnce(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: undefined}; + _next(); + } + ); + + mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => { + return callback(null, true); + } + ); + supertest(await initializeServer('user.yaml')) + .put(`/-/user/org.couchdb.user:newUser`) + .send({ + name: 'newUser', + password: 'newUser' + }) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.CREATED) + .end(function(err, res) { + if (err) { + return done(err); + } + expect(res.body.ok).toBeDefined(); + expect(res.body.token).toBeDefined(); + const token = res.body.token; + expect(typeof token).toBe('string'); + expect(res.body.ok).toMatch(`user 'newUser' created`); + done(); + }); + }); + + test('should test fails on add a existing user with login', async (done) => { + mockApiJWTmiddleware.mockImplementationOnce(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: undefined}; + _next(); + } + ); + supertest(await initializeServer('user.yaml')) + .put('/-/user/org.couchdb.user:jotaNew') + .send(credentials) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.CONFLICT) + .end(function(err, res) { + if (err) { + return done(err); + } + expect(res.body.error).toBeDefined(); + expect(res.body.error).toMatch(API_ERROR.USERNAME_ALREADY_REGISTERED); + done(); + }); + }); + + test('should log in as existing user', async (done) => { + supertest(await initializeServer('user.yaml')) + .put(`/-/user/org.couchdb.user:${credentials.name}`) + .send(credentials) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.CREATED) + .end((err, res) => { + if (err) { + return done(err); + } + + expect(res.body).toBeTruthy(); + expect(res.body.ok).toMatch(`you are authenticated as \'${credentials.name}\'`); + done(); + }); + }); + + test('should test fails add a new user with missing name', async (done) => { + mockApiJWTmiddleware.mockImplementationOnce(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: undefined }; + _next(); + } + ); + mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => { + return callback(getBadRequest(API_ERROR.USERNAME_PASSWORD_REQUIRED)); + } + ); + const credentialsShort = _.cloneDeep(credentials); + delete credentialsShort.name; + + supertest(await initializeServer('user.yaml')) + .put(`/-/user/org.couchdb.user:${credentials.name}`) + .send(credentialsShort) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.BAD_REQUEST) + .end(function(err, res) { + if (err) { + return done(err); + } + + expect(res.body.error).toBeDefined(); + expect(res.body.error).toMatch(API_ERROR.USERNAME_PASSWORD_REQUIRED); + done(); + }); + }); + + test('should test fails add a new user with missing password', async (done) => { + mockApiJWTmiddleware.mockImplementationOnce(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: undefined}; + _next(); + } + ); + const credentialsShort = _.cloneDeep(credentials); + delete credentialsShort.password; + + supertest(await initializeServer('user.yaml')) + .put(`/-/user/org.couchdb.user:${credentials.name}`) + .send(credentialsShort) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.BAD_REQUEST) + .end(function(err, res) { + if (err) { + return done(err); + } + + expect(res.body.error).toBeDefined(); + // FIXME: message is not 100% accurate + // eslint-disable-next-line new-cap + expect(res.body.error).toMatch(API_ERROR.PASSWORD_SHORT()); + done(); + }); + }); + + test('should test fails add a new user with wrong password', async (done) => { + mockApiJWTmiddleware.mockImplementationOnce(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: 'test'}; + _next(); + } + ); + mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => { + return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD)); + } + ); + const credentialsShort = _.cloneDeep(credentials); + credentialsShort.password = 'failPassword'; + + supertest(await initializeServer('user.yaml')) + .put('/-/user/org.couchdb.user:test') + .send(credentialsShort) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.UNAUTHORIZED) + .end(function(err, res) { + if (err) { + return done(err); + } + + expect(res.body.error).toBeDefined(); + expect(res.body.error).toMatch(API_ERROR.BAD_USERNAME_PASSWORD); + done(); + }); + }); + + test('should be able to logout an user', async (done) => { + mockApiJWTmiddleware.mockImplementationOnce(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: 'test'}; + _next(); + } + ); + mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => { + return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD)); + } + ); + const credentialsShort = _.cloneDeep(credentials); + credentialsShort.password = 'failPassword'; + + supertest(await initializeServer('user.yaml')) + .delete('/-/user/token/someSecretToken') + .send(credentialsShort) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.OK) + .end(function(err, res) { + if (err) { + return done(err); + } + + expect(res.body.ok).toMatch(API_MESSAGE.LOGGED_OUT); + done(); + }); + }); + +}); diff --git a/packages/api/test/integration/whoami.spec.ts b/packages/api/test/integration/whoami.spec.ts new file mode 100644 index 000000000..13227fa90 --- /dev/null +++ b/packages/api/test/integration/whoami.spec.ts @@ -0,0 +1,53 @@ +import supertest from 'supertest'; + +import {initializeServer } from './_helper'; +import { HTTP_STATUS } from '@verdaccio/commons-api'; +import { HEADERS} from '@verdaccio/dev-commons'; +import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types"; + +const mockApiJWTmiddleware = jest.fn(() => + (req: $RequestExtend, res: $ResponseExtend, _next): void => { + req.remote_user = { name: 'foo', groups: [], real_groups: []} + _next(); + } +); + +jest.mock('@verdaccio/auth', () => ({ + Auth: class { + apiJWTmiddleware() { + return mockApiJWTmiddleware(); + } + allow_access (_d, f_, cb) { + cb(null, true) + } + } +})); + +describe('whoami', () => { + test.skip('should test referer /whoami endpoint', async (done) => { + return supertest(await initializeServer('whoami.yaml')) + .get('/whoami') + .set('referer', 'whoami') + .expect(HTTP_STATUS.OK) + .end(done); + }); + + test.skip('should test no referer /whoami endpoint', async (done) => { + return supertest(await initializeServer('whoami.yaml')) + .get('/whoami') + .expect(HTTP_STATUS.NOT_FOUND) + .end(done); + }); + + + test('should return the logged username', async () => { + return supertest(await initializeServer('whoami.yaml')) + .get('/-/whoami') + .set('Accept', HEADERS.JSON) + .expect('Content-Type', HEADERS.JSON_CHARSET) + .expect(HTTP_STATUS.OK) + .then(response => { + expect(response.body.username).toEqual('foo'); + }); + }); +}); diff --git a/packages/api/test/__snapshots__/publish.spec.ts.snap b/packages/api/test/unit/__snapshots__/publish.spec.ts.snap similarity index 100% rename from packages/api/test/__snapshots__/publish.spec.ts.snap rename to packages/api/test/unit/__snapshots__/publish.spec.ts.snap diff --git a/packages/api/test/publish.spec.ts b/packages/api/test/unit/publish.spec.ts similarity index 98% rename from packages/api/test/publish.spec.ts rename to packages/api/test/unit/publish.spec.ts index f535770c9..1cff6b6e5 100644 --- a/packages/api/test/publish.spec.ts +++ b/packages/api/test/unit/publish.spec.ts @@ -1,4 +1,4 @@ -import { addVersion, uploadPackageTarball, removeTarball, unPublishPackage, publishPackage } from '../src/publish'; +import { addVersion, uploadPackageTarball, removeTarball, unPublishPackage, publishPackage } from '../../src/publish'; import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons'; const REVISION_MOCK = '15-e53a77096b0ee33e'; @@ -82,7 +82,7 @@ describe('Publish endpoints - upload package tarball', () => { pipe: jest.fn(), on: jest.fn(), }; - res = { status: jest.fn(), report_error: jest.fn() }; + res = { status: jest.fn(), locals: { report_error: jest.fn() }}; next = jest.fn(); }); diff --git a/packages/api/test/validate.api.params.middleware.spec.ts b/packages/api/test/unit/validate.api.params.middleware.spec.ts similarity index 90% rename from packages/api/test/validate.api.params.middleware.spec.ts rename to packages/api/test/unit/validate.api.params.middleware.spec.ts index d0387155a..e91da244a 100644 --- a/packages/api/test/validate.api.params.middleware.spec.ts +++ b/packages/api/test/unit/validate.api.params.middleware.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable curly */ // ensure that all arguments are validated import path from 'path'; import fs from 'fs'; @@ -13,7 +14,7 @@ import fs from 'fs'; */ describe('api endpoint app.param()', () => { let m; - const requirePath = path.normalize(path.join(__dirname, '../src/index.ts')); + const requirePath = path.normalize(path.join(__dirname, '../../src/index.ts')); const source = fs.readFileSync(requirePath, 'utf8'); const very_scary_regexp = /\n\s*app\.(\w+)\s*\(\s*(("[^"]*")|('[^']*'))\s*,/g; const appParams = {}; diff --git a/packages/auth/package.json b/packages/auth/package.json index 345249668..99b6e8d25 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -23,7 +23,7 @@ }, "license": "MIT", "dependencies": { - "@verdaccio/commons-api": "9.4.0", + "@verdaccio/commons-api": "9.6.1", "@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/loaders": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 869112cfe..76e578b81 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -31,7 +31,7 @@ "@verdaccio/config": "5.0.0-alpha.0", "@verdaccio/node-api": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0", - "commander": "3.0.2", + "commander": "5.1.0", "envinfo": "7.4.0", "kleur": "3.0.3", "semver": "7.3.2" diff --git a/packages/commons/src/helpers/pkg.ts b/packages/commons/src/helpers/pkg.ts index 734ae4217..d17fd45b7 100644 --- a/packages/commons/src/helpers/pkg.ts +++ b/packages/commons/src/helpers/pkg.ts @@ -1,12 +1,16 @@ import { Package } from "@verdaccio/types"; -export function generatePackageMetadata(pkgName: string, version = '1.0.0'): Package { +export interface DistTags { + [key: string]: string; +} + +export function generatePackageMetadata(pkgName: string, version = '1.0.0', distTags: DistTags = {['latest']: version}): Package { // @ts-ignore return { "_id": pkgName, "name": pkgName, "dist-tags": { - "latest": version + ...distTags }, "versions": { [version]: { diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 41f08d4a3..87cd572b2 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -15,14 +15,14 @@ "license": "MIT", "homepage": "https://verdaccio.org", "dependencies": { - "@verdaccio/commons-api": "9.4.0", + "@verdaccio/commons-api": "9.6.1", "@verdaccio/logger": "5.0.0-alpha.0", "handlebars": "4.5.3", "request": "2.87.0" }, "devDependencies": { "@verdaccio/dev-commons": "5.0.0-alpha.0", - "@verdaccio/types": "9.3.0" + "@verdaccio/types": "9.5.0" }, "scripts": { "clean": "rimraf ./build", diff --git a/packages/logger-prettify/package.json b/packages/logger-prettify/package.json index e51ec67a4..d521333f2 100644 --- a/packages/logger-prettify/package.json +++ b/packages/logger-prettify/package.json @@ -24,7 +24,7 @@ "build": "npm run build:js && npm run build:types" }, "dependencies": { - "@verdaccio/commons-api": "9.4.0", + "@verdaccio/commons-api": "9.6.1", "@verdaccio/dev-commons": "5.0.0-alpha.0", "dayjs": "1.8.19", "fast-safe-stringify": "2.0.7", diff --git a/packages/logger/package.json b/packages/logger/package.json index 66c3f89b5..4abbde61b 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@types/pino": "6.0.1", - "@verdaccio/types": "9.3.0" + "@verdaccio/types": "9.5.0" }, "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" } diff --git a/packages/middleware/package.json b/packages/middleware/package.json index 4b8645444..864c31d63 100644 --- a/packages/middleware/package.json +++ b/packages/middleware/package.json @@ -22,7 +22,7 @@ "build": "npm run build:js && npm run build:types" }, "dependencies": { - "@verdaccio/commons-api": "9.4.0", + "@verdaccio/commons-api": "9.6.1", "@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0", diff --git a/packages/middleware/src/middleware.ts b/packages/middleware/src/middleware.ts index b11262f04..f34b2a2fc 100644 --- a/packages/middleware/src/middleware.ts +++ b/packages/middleware/src/middleware.ts @@ -1,3 +1,4 @@ + import _ from 'lodash'; import { @@ -13,6 +14,7 @@ import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IAuth } from '@verda import { Config, Package, RemoteUser } from '@verdaccio/types'; import { logger } from '@verdaccio/logger'; import { VerdaccioError } from '@verdaccio/commons-api'; +import {HttpError} from "http-errors"; export function match(regexp: RegExp): any { return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer, value: string): void { @@ -110,8 +112,7 @@ export function allow(auth: IAuth): Function { const packageName = req.params.scope ? `@${req.params.scope}/${req.params.package}` : req.params.package; const packageVersion = req.params.filename ? getVersionFromTarball(req.params.filename) : undefined; const remote: RemoteUser = req.remote_user; - logger.trace({ action, user: remote.name }, `[middleware/allow][@{action}] allow for @{user}`); - + logger.trace({ action, user: remote?.name }, `[middleware/allow][@{action}] allow for @{user}`); auth['allow_' + action]({ packageName, packageVersion }, remote, function(error, allowed): void { req.resume(); if (error) { @@ -148,7 +149,8 @@ export function final(body: FinalBody, req: $RequestExtend, res: $ResponseExtend if (typeof body === 'object' && _.isNil(body) === false) { if (typeof (body as MiddlewareError).error === 'string') { - res._verdaccio_error = (body as MiddlewareError).error; + res.locals._verdaccio_error = (body as MiddlewareError).error; + // res._verdaccio_error = (body as MiddlewareError).error; } body = JSON.stringify(body, undefined, ' ') + '\n'; } @@ -181,121 +183,122 @@ export const LOG_STATUS_MESSAGE = "@{status}, user: @{user}(@{remoteIP}), req: ' export const LOG_VERDACCIO_ERROR = `${LOG_STATUS_MESSAGE}, error: @{!error}`; export const LOG_VERDACCIO_BYTES = `${LOG_STATUS_MESSAGE}, bytes: @{bytes.in}/@{bytes.out}`; -export function log(config: Config) { - return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void { - // logger - req.log = logger.child({ sub: 'in' }); +export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void { + // logger + req.log = logger.child({ sub: 'in' }); - const _auth = req.headers.authorization; - if (_.isNil(_auth) === false) { - req.headers.authorization = ''; - } + const _auth = req.headers.authorization; + if (_.isNil(_auth) === false) { + req.headers.authorization = ''; + } - const _cookie = req.headers.cookie; - if (_.isNil(_cookie) === false) { - req.headers.cookie = ''; + const _cookie = req.headers.cookie; + if (_.isNil(_cookie) === false) { + req.headers.cookie = ''; + } + + req.url = req.originalUrl; + req.log.info({ req: req, ip: req.ip }, "@{ip} requested '@{req.method} @{req.url}'"); + req.originalUrl = req.url; + + if (_.isNil(_auth) === false) { + req.headers.authorization = _auth; + } + + if (_.isNil(_cookie) === false) { + req.headers.cookie = _cookie; + } + + let bytesin = 0; + req.on('data', function(chunk): void { + bytesin += chunk.length; + }); + + let bytesout = 0; + const _write = res.write; + // FIXME: res.write should return boolean + // @ts-ignore + res.write = function(buf): boolean { + bytesout += buf.length; + /* eslint prefer-rest-params: "off" */ + // @ts-ignore + _write.apply(res, arguments); + }; + + const log = function(): void { + const forwardedFor = req.headers['x-forwarded-for']; + const remoteAddress = req.connection.remoteAddress; + const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress; + let message; + if (res.locals._verdaccio_error) { + message = LOG_VERDACCIO_ERROR; + } else { + message = LOG_VERDACCIO_BYTES; } req.url = req.originalUrl; - // avoid log noise data from static content - if (req.originalUrl.match(/static/) === null) { - req.log.info({req: req, ip: req.ip}, "@{ip} requested '@{req.method} @{req.url}'"); - } + req.log.warn( + { + request: { + method: req.method, + url: req.url, + }, + level: 35, // http + user: (req.remote_user && req.remote_user.name) || null, + remoteIP, + status: res.statusCode, + error: res.locals._verdaccio_error, + bytes: { + in: bytesin, + out: bytesout, + }, + }, + message + ); req.originalUrl = req.url; + }; - if (_.isNil(_auth) === false) { - req.headers.authorization = _auth; - } + req.on('close', function(): void { + log(); + }); - if (_.isNil(_cookie) === false) { - req.headers.cookie = _cookie; - } - - let bytesin = 0; - if (config?.experiments?.bytesin_off !== true) { - req.on('data', function(chunk): void { - bytesin += chunk.length; - }); - } - - let bytesout = 0; - const _write = res.write; - // FIXME: res.write should return boolean - // @ts-ignore - res.write = function(buf): boolean { + const _end = res.end; + res.end = function(buf): void { + if (buf) { bytesout += buf.length; - /* eslint prefer-rest-params: "off" */ - // @ts-ignore - _write.apply(res, arguments); - }; - - let logHasBeenCalled = false; - const log = function(): void { - if (logHasBeenCalled) { - return; - } - logHasBeenCalled = true; - - const forwardedFor = req.headers['x-forwarded-for']; - const remoteAddress = req.connection.remoteAddress; - const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress; - let message; - if (res._verdaccio_error) { - message = LOG_VERDACCIO_ERROR; - } else { - message = LOG_VERDACCIO_BYTES; - } - - req.url = req.originalUrl; - // avoid log noise data from static content - if (req.url.match(/static/) === null) { - req.log.warn( - { - request: { - method: req.method, - url: req.url, - }, - level: 35, // http - user: (req.remote_user && req.remote_user.name) || null, - remoteIP, - status: res.statusCode, - error: res._verdaccio_error, - bytes: { - in: bytesin, - out: bytesout, - }, - }, - message - ); - req.originalUrl = req.url; - } } + /* eslint prefer-rest-params: "off" */ + // @ts-ignore + _end.apply(res, arguments); + log(); + }; + next(); +} - req.on('close', function(): void { - log(); - }); - - const _end = res.end; - res.end = function(buf): void { - if (buf) { - bytesout += buf.length; - } - /* eslint prefer-rest-params: "off" */ - // @ts-ignore - _end.apply(res, arguments); - log(); - }; - next(); +export function handleError(err: HttpError, req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) { + if (_.isError(err)) { + if (err.code === 'ECONNABORT' && res.statusCode === HTTP_STATUS.NOT_MODIFIED) { + return next(); + } + if (_.isFunction(res.locals.report_error) === false) { + // in case of very early error this middleware may not be loaded before error is generated + // fixing that + errorReportingMiddleware(req, res, _.noop); + } + res.locals.report_error(err); + } else { + // Fall to Middleware.final + return next(err); } } // Middleware export function errorReportingMiddleware(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void { - res.report_error = - res.report_error || + res.locals.report_error = + res.locals.report_error || function(err: VerdaccioError): void { if (err.status && err.status >= HTTP_STATUS.BAD_REQUEST && err.status < 600) { - if (!res.headersSent) { + if (_.isNil(res.headersSent) === false) { res.status(err.status); next({ error: err.message || API_ERROR.UNKNOWN_ERROR }); } diff --git a/packages/middleware/types/custom.d.ts b/packages/middleware/types/custom.d.ts index 733e88c70..fa554d09c 100644 --- a/packages/middleware/types/custom.d.ts +++ b/packages/middleware/types/custom.d.ts @@ -1,4 +1,7 @@ +// + import { Logger, RemoteUser } from "@verdaccio/types"; +import * as http from "http"; declare global { namespace Express { @@ -7,10 +10,11 @@ declare global { log: Logger; } - export interface Response { - report_error: any; - _verdaccio_error: any; - socket?: any; - } + // FIXME: + // export interface Response extends http.ServerResponse, Express.Response { + // report_error: any; + // _verdaccio_error: any; + // socket?: any; + // } } } diff --git a/packages/proxy/package.json b/packages/proxy/package.json index c21280560..838f45226 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -24,16 +24,16 @@ }, "dependencies": { "@verdaccio/dev-commons": "5.0.0-alpha.0", - "@verdaccio/local-storage": "9.5.0", + "@verdaccio/local-storage": "9.6.1", "@verdaccio/logger": "5.0.0-alpha.0", - "@verdaccio/streams": "9.5.0", + "@verdaccio/streams": "9.6.1", "@verdaccio/utils": "5.0.0-alpha.0", "JSONStream": "1.3.5", "request": "2.87.0" }, "devDependencies": { "@verdaccio/dev-types": "5.0.0-alpha.0", - "@verdaccio/types": "9.3.0" + "@verdaccio/types": "9.5.0" }, "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" } diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index d4232a6f3..9071176f8 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -85,12 +85,12 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler): any { if (err.code === 'ECONNABORT' && res.statusCode === HTTP_STATUS.NOT_MODIFIED) { return next(); } - if (_.isFunction(res.report_error) === false) { + if (_.isFunction(res.locals.report_error) === false) { // in case of very early error this middleware may not be loaded before error is generated // fixing that errorReportingMiddleware(req, res, _.noop); } - res.report_error(err); + res.locals.report_error(err); } else { // Fall to Middleware.final return next(err); diff --git a/packages/server/test/.eslintrc b/packages/server/test/.eslintrc new file mode 100644 index 000000000..ff8b4a8b8 --- /dev/null +++ b/packages/server/test/.eslintrc @@ -0,0 +1,8 @@ +{ + "rules": { + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/explicit-member-accessibility": 0, + "@typescript-eslint/no-unused-vars": 2, + "no-console": 0 + } +} diff --git a/packages/server/test/api/index.spec.ts b/packages/server/test/api/index.spec.ts index aa8fcfe3e..961488a1d 100644 --- a/packages/server/test/api/index.spec.ts +++ b/packages/server/test/api/index.spec.ts @@ -5,7 +5,6 @@ import path from 'path'; import endPointAPI from '@verdaccio/server'; import { HEADERS, - API_ERROR, HTTP_STATUS, HEADER_TYPE, API_MESSAGE, @@ -85,67 +84,7 @@ describe('endpoint unit test', () => { }); describe('Registry API Endpoints', () => { - - describe('should test ping api', () => { - test('should test endpoint /-/ping', (done) => { - request(app) - .get('/-/ping') - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) - .expect(HTTP_STATUS.OK) - .end(function(err) { - if (err) { - return done(err); - } - done(); - }); - }); - }); - - describe('should test whoami api', () => { - test('should test referer /whoami endpoint', (done) => { - request(app) - .get('/whoami') - .set('referer', 'whoami') - .expect(HTTP_STATUS.OK) - .end(done); - }); - - test('should test no referer /whoami endpoint', (done) => { - request(app) - .get('/whoami') - .expect(HTTP_STATUS.NOT_FOUND) - .end(done); - }); - - test('should test /-/whoami endpoint', (done) => { - request(app) - .get('/-/whoami') - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) - .expect(HTTP_STATUS.OK) - .end(function(err) { - if (err) { - return done(err); - } - done(); - }); - }); - - test('should test /whoami endpoint', (done) => { - request(app) - .get('/-/whoami') - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) - .expect(HTTP_STATUS.OK) - .end(function(err) { - if (err) { - return done(err); - } - done(); - }); - }); - }); - describe('should test user api', () => { - describe('should test authorization headers with tokens only errors', () => { test('should fails on protected endpoint /-/auth-package bad format', (done) => { request(app) @@ -219,106 +158,6 @@ describe('endpoint unit test', () => { }); }); }); - - test('should test fails add a new user with missing name', (done) => { - - const credentialsShort = _.cloneDeep(credentials); - delete credentialsShort.name; - - request(app) - .put(`/-/user/org.couchdb.user:${credentials.name}`) - .send(credentialsShort) - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) - .expect(HTTP_STATUS.BAD_REQUEST) - .end(function(err, res) { - if (err) { - return done(err); - } - - expect(res.body.error).toBeDefined(); - expect(res.body.error).toMatch(API_ERROR.USERNAME_PASSWORD_REQUIRED); - done(); - }); - }); - - test('should test fails add a new user with missing password', (done) => { - - const credentialsShort = _.cloneDeep(credentials); - delete credentialsShort.password; - - request(app) - .put(`/-/user/org.couchdb.user:${credentials.name}`) - .send(credentialsShort) - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) - .expect(HTTP_STATUS.BAD_REQUEST) - .end(function(err, res) { - if (err) { - return done(err); - } - - expect(res.body.error).toBeDefined(); - // FIXME: message is not 100% accurate - /* eslint new-cap: 0 */ - expect(res.body.error).toMatch(API_ERROR.PASSWORD_SHORT()); - done(); - }); - }); - - test('should test add a new user with login', (done) => { - const newCredentials = _.cloneDeep(credentials); - newCredentials.name = 'jotaNew'; - - request(app) - .put('/-/user/org.couchdb.user:jotaNew') - .send(newCredentials) - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) - .expect(HTTP_STATUS.CREATED) - .end(function(err, res) { - if (err) { - return done(err); - } - expect(res.body).toBeTruthy(); - done(); - }); - }); - - test('should test fails on add a existing user with login', (done) => { - request(app) - .put('/-/user/org.couchdb.user:jotaNew') - .send(credentials) - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) - .expect(HTTP_STATUS.CONFLICT) - .end(function(err, res) { - if (err) { - return done(err); - } - expect(res.body.error).toBeDefined(); - expect(res.body.error).toMatch(API_ERROR.USERNAME_ALREADY_REGISTERED); - done(); - }); - }); - - test('should test fails add a new user with wrong password', (done) => { - - const credentialsShort = _.cloneDeep(credentials); - credentialsShort.password = 'failPassword'; - - request(app) - .put('/-/user/org.couchdb.user:jota') - .send(credentialsShort) - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) - .expect(HTTP_STATUS.UNAUTHORIZED) - .end(function(err, res) { - if (err) { - return done(err); - } - - expect(res.body.error).toBeDefined(); - expect(res.body.error).toMatch(/unauthorized/); - done(); - }); - }); - }); describe('should test package api', () => { diff --git a/packages/server/types/custom.d.ts b/packages/server/types/custom.d.ts index 733e88c70..9c7e201cc 100644 --- a/packages/server/types/custom.d.ts +++ b/packages/server/types/custom.d.ts @@ -6,11 +6,5 @@ declare global { remote_user: RemoteUser; log: Logger; } - - export interface Response { - report_error: any; - _verdaccio_error: any; - socket?: any; - } } } diff --git a/packages/store/package.json b/packages/store/package.json index d82d50ae9..51f22e97d 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -23,20 +23,20 @@ "build": "npm run build:js && npm run build:types" }, "dependencies": { - "@verdaccio/commons-api": "9.4.0", + "@verdaccio/commons-api": "9.6.1", "@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/loaders": "5.0.0-alpha.0", - "@verdaccio/local-storage": "9.5.0", + "@verdaccio/local-storage": "9.6.1", "@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/proxy": "5.0.0-alpha.0", - "@verdaccio/streams": "9.5.0", + "@verdaccio/streams": "9.6.1", "@verdaccio/utils": "5.0.0-alpha.0", "async": "3.1.1", "lodash": "4.17.15", "semver": "7.1.2" }, "devDependencies": { - "@verdaccio/types": "9.3.0" + "@verdaccio/types": "9.5.0" }, "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" } diff --git a/packages/utils/package.json b/packages/utils/package.json index b6322a4a0..ec9c3752e 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -16,7 +16,7 @@ "homepage": "https://verdaccio.org", "dependencies": { "@verdaccio/dev-commons": "5.0.0-alpha.0", - "@verdaccio/readme": "9.5.0", + "@verdaccio/readme": "9.6.1", "js-yaml": "3.13.1", "jsonwebtoken": "8.5.1", "minimatch": "3.0.4", diff --git a/packages/utils/test/.eslintrc b/packages/utils/test/.eslintrc new file mode 100644 index 000000000..fa58451d3 --- /dev/null +++ b/packages/utils/test/.eslintrc @@ -0,0 +1,8 @@ +{ + "rules": { + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/explicit-member-accessibility": 0, + "no-console": 0, + "@typescript-eslint/no-unused-vars": 1 + } +} diff --git a/packages/web/package.json b/packages/web/package.json index 3eb248f74..148711ac0 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@verdaccio/dev-types": "5.0.0-alpha.0", - "@verdaccio/types": "9.3.0" + "@verdaccio/types": "9.5.0" }, "scripts": { "clean": "rimraf ./build", diff --git a/packages/web/types/custom.d.ts b/packages/web/types/custom.d.ts index 733e88c70..9c7e201cc 100644 --- a/packages/web/types/custom.d.ts +++ b/packages/web/types/custom.d.ts @@ -6,11 +6,5 @@ declare global { remote_user: RemoteUser; log: Logger; } - - export interface Response { - report_error: any; - _verdaccio_error: any; - socket?: any; - } } } diff --git a/tsconfig.json b/tsconfig.json index 1e4432661..da7d75c93 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "module": "commonjs", "declaration": true, "noImplicitAny": false, - "incremental": true, + "incremental": false, "strict": true, "strictNullChecks": true, "resolveJsonModule": true, diff --git a/yarn.lock b/yarn.lock index 809eb90e7..89370ea2f 100644 Binary files a/yarn.lock and b/yarn.lock differ