mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-30 22:34:10 -05:00
feat: migrate yarn workspaces (#1546)
This commit is contained in:
parent
73585f0262
commit
a70454c7b2
451 changed files with 78127 additions and 7204 deletions
143
.circleci/config.yml
Normal file
143
.circleci/config.yml
Normal file
|
@ -0,0 +1,143 @@
|
|||
version: 2.1
|
||||
|
||||
executors:
|
||||
node_latest_browser:
|
||||
docker:
|
||||
- image: circleci/node:latest-browsers
|
||||
node_latest:
|
||||
docker:
|
||||
- image: circleci/node:13
|
||||
node_lts_12:
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
default_executor: node_latest
|
||||
|
||||
aliases:
|
||||
- &repo_path
|
||||
~/verdaccio
|
||||
- &defaults
|
||||
working_directory: *repo_path
|
||||
- &yarn_cache_key
|
||||
yarn-sha-{{ checksum "yarn.lock" }}
|
||||
- &coverage_key
|
||||
coverage-{{ .Branch }}-{{ .Revision }}
|
||||
- &ignore_non_dev_branches
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
branches:
|
||||
ignore:
|
||||
- gh-pages
|
||||
- /release\/.*/
|
||||
|
||||
commands:
|
||||
restore_repo:
|
||||
description: Restore repository from workspace
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: *repo_path
|
||||
run_test:
|
||||
description: Run test and functional test
|
||||
steps:
|
||||
- run:
|
||||
name: Test
|
||||
command: yarn run test
|
||||
- store_test_results:
|
||||
path: reports/
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
<<: *defaults
|
||||
executor: default_executor
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: *yarn_cache_key
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: yarn
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: yarn bootstrap
|
||||
- run:
|
||||
name: Prepare CI
|
||||
command: yarn lint
|
||||
- run:
|
||||
name: Clean project
|
||||
command: yarn clean
|
||||
- run:
|
||||
name: Build project
|
||||
command: yarn build
|
||||
- save_cache:
|
||||
key: *yarn_cache_key
|
||||
paths:
|
||||
- ~/.yarn
|
||||
- ~/.cache/yarn
|
||||
- node_modules
|
||||
- persist_to_workspace:
|
||||
root: *repo_path
|
||||
paths:
|
||||
- ./*
|
||||
|
||||
test_node_latest:
|
||||
<<: *defaults
|
||||
executor: node_latest
|
||||
steps:
|
||||
- restore_repo
|
||||
- run_test
|
||||
- save_cache:
|
||||
key: *coverage_key
|
||||
paths:
|
||||
- coverage
|
||||
test_node_lts_12:
|
||||
<<: *defaults
|
||||
executor: node_lts_12
|
||||
steps:
|
||||
- restore_repo
|
||||
- run_test
|
||||
# test_e2e_cli:
|
||||
# <<: *defaults
|
||||
# executor: node_latest
|
||||
# steps:
|
||||
# - restore_repo
|
||||
# - run:
|
||||
# name: Test End-to-End ClI
|
||||
# command: yarn run test:e2e:cli
|
||||
|
||||
coverage:
|
||||
<<: *defaults
|
||||
executor: default_executor
|
||||
steps:
|
||||
- restore_repo
|
||||
- restore_cache:
|
||||
key: *coverage_key
|
||||
- run:
|
||||
name: Publish coverage
|
||||
command: yarn run coverage:publish
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
workflow:
|
||||
jobs:
|
||||
- prepare:
|
||||
<<: *ignore_non_dev_branches
|
||||
- test_node_latest:
|
||||
requires:
|
||||
- prepare
|
||||
<<: *ignore_non_dev_branches
|
||||
- test_node_lts_12:
|
||||
requires:
|
||||
- prepare
|
||||
<<: *ignore_non_dev_branches
|
||||
# - test_e2e_cli:
|
||||
# requires:
|
||||
# - prepare
|
||||
# - test_node_latest
|
||||
# - test_node_lts_12
|
||||
# <<: *ignore_non_dev_branches
|
||||
# - coverage:
|
||||
# requires:
|
||||
# - test_e2e_cli
|
||||
# <<: *ignore_non_dev_branches
|
|
@ -15,8 +15,7 @@ Dockerfile
|
|||
*.png
|
||||
*.jpg
|
||||
*.sh
|
||||
*.ico
|
||||
test/unit/partials/
|
||||
**/partials/**
|
||||
**/fixtures/**
|
||||
types/custom.d.ts
|
||||
docker-examples/
|
||||
LICENSE
|
||||
**/mock/store/**
|
||||
|
|
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
|
@ -9,21 +9,25 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node_version: [12, 14, 15]
|
||||
node_version: [8, 10, 12, 13]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.3
|
||||
- uses: actions/checkout@v2.3.1
|
||||
- name: Use Node ${{ matrix.node_version }}
|
||||
uses: actions/setup-node@v2.1.5
|
||||
uses: actions/setup-node@v2.1.1
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
node_version: ${{ matrix.node_version }}
|
||||
- name: Install
|
||||
run: yarn install --immutable
|
||||
- name: Build
|
||||
run: yarn code:build
|
||||
run: yarn
|
||||
- name: Bootstrap Lerna
|
||||
run: yarn bootstrap
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
- name: Clean
|
||||
run: yarn clean
|
||||
- name: Build
|
||||
run: yarn build
|
||||
- name: Test
|
||||
run: yarn test
|
||||
|
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2.3.3
|
||||
- name: Build
|
||||
run: docker build .
|
||||
run: yarn docker
|
||||
env:
|
||||
VERDACCIO_BUILD_REGISTRY: https://registry.verdaccio.org
|
||||
|
||||
|
|
29
.github/workflows/release-canary.yml
vendored
Normal file
29
.github/workflows/release-canary.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
name: Canary Release to Verdaccio
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node (latest)
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node_version: 13
|
||||
- name: Install
|
||||
run: yarn install --no-lockfile
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
- name: Clean
|
||||
run: yarn clean
|
||||
- name: Build
|
||||
run: yarn build
|
||||
- name: Test
|
||||
run: yarn test
|
||||
- name: Publish
|
||||
run: |
|
||||
echo "//registry.verdaccio.org/:_authToken=${{ secrets.VERDACCIO_TOKEN }}" > .npmrc
|
||||
git update-index --assume-unchanged .npmrc
|
||||
yarn publish:canary
|
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
|
@ -1,32 +0,0 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.3
|
||||
- name: Use Node (latest)
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Install
|
||||
run: yarn install
|
||||
- name: Build
|
||||
run: yarn code:build
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
- name: Publish
|
||||
run: sh scripts/publish.sh
|
||||
env:
|
||||
REGISTRY_AUTH_TOKEN: ${{ secrets.REGISTRY_AUTH_TOKEN }}
|
||||
REGISTRY_URL: registry.npmjs.org
|
||||
- name: Create release notes
|
||||
run: sh scripts/github-release.sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
25
.gitignore
vendored
25
.gitignore
vendored
|
@ -5,41 +5,22 @@ build/
|
|||
|
||||
### Test
|
||||
|
||||
test/unit/partials/store/test-*-storage/*
|
||||
test/unit/partials/store/*-storage/*
|
||||
test/unit/partials/store/storage_default_storage/*
|
||||
.verdaccio-db.json
|
||||
.sinopia-db.json
|
||||
|
||||
###
|
||||
!bin/verdaccio
|
||||
test-storage*
|
||||
access-storage*
|
||||
.verdaccio_test_env
|
||||
node_modules
|
||||
package-lock.json
|
||||
npm_test-fails-add-tarball*
|
||||
yarn-error.log
|
||||
|
||||
# Istanbul
|
||||
|
||||
# jest
|
||||
reports/
|
||||
coverage/
|
||||
.nyc*
|
||||
|
||||
.idea/
|
||||
|
||||
|
||||
# React
|
||||
bundle.js
|
||||
bundle.js.map
|
||||
__tests__
|
||||
|
||||
# Compiled script
|
||||
static/*
|
||||
|
||||
# This is the Yarn build state; it's local to each clone
|
||||
/.yarn/build-state.yml
|
||||
|
||||
# This is the Yarn install state cache, it can be rebuilt anytime
|
||||
/.yarn/install-state.gz
|
||||
.history
|
||||
packages/partials
|
||||
|
|
41
.stylelintrc
41
.stylelintrc
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"processors": ["stylelint-processor-styled-components"],
|
||||
"extends": [
|
||||
"stylelint-config-recommended"
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": true,
|
||||
"block-no-empty": true,
|
||||
"color-named": "always-where-possible",
|
||||
"comment-no-empty": true,
|
||||
"declaration-block-no-duplicate-properties": [
|
||||
true,
|
||||
{
|
||||
ignore: ["consecutive-duplicates-with-different-values"]
|
||||
}
|
||||
],
|
||||
"declaration-block-no-shorthand-property-overrides": true,
|
||||
"font-family-no-duplicate-names": true,
|
||||
"color-no-invalid-hex": true,
|
||||
"font-family-no-missing-generic-family-keyword": true,
|
||||
"function-calc-no-unspaced-operator": true,
|
||||
"function-linear-gradient-no-nonstandard-direction": true,
|
||||
"keyframe-declaration-no-important": true,
|
||||
"property-no-vendor-prefix": true,
|
||||
"media-feature-name-no-unknown": true,
|
||||
"no-descending-specificity": [true, { "severity": "warning" }],
|
||||
"no-duplicate-at-import-rules": true,
|
||||
"no-duplicate-selectors": true,
|
||||
"no-empty-source": true,
|
||||
"no-extra-semicolons": true,
|
||||
"no-invalid-double-slash-comments": true,
|
||||
"property-no-unknown": true,
|
||||
"selector-pseudo-class-no-unknown": true,
|
||||
"selector-pseudo-element-no-unknown": true,
|
||||
"selector-type-no-unknown": [true, { "severity": "warning" }],
|
||||
"string-no-newline": true,
|
||||
"unit-no-unknown": true
|
||||
}
|
||||
|
||||
}
|
||||
|
2
.yarnrc
Normal file
2
.yarnrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
save-prefix ""
|
||||
registry "https://registry.verdaccio.org"
|
23
Dockerfile
23
Dockerfile
|
@ -1,4 +1,4 @@
|
|||
FROM --platform=${BUILDPLATFORM:-linux/amd64} node:14.16.1-alpine as builder
|
||||
FROM node:12.18.3-alpine as builder
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
VERDACCIO_BUILD_REGISTRY=https://registry.verdaccio.org
|
||||
|
@ -12,18 +12,14 @@ RUN apk --no-cache add openssl ca-certificates wget && \
|
|||
WORKDIR /opt/verdaccio-build
|
||||
COPY . .
|
||||
|
||||
RUN yarn config set npmRegistryServer $VERDACCIO_BUILD_REGISTRY && \
|
||||
yarn config set enableProgressBars false && \
|
||||
yarn config set enableTelemetry false && \
|
||||
yarn install && \
|
||||
RUN yarn config set registry $VERDACCIO_BUILD_REGISTRY && \
|
||||
yarn install --production=false && \
|
||||
yarn build && \
|
||||
yarn lint && \
|
||||
yarn code:docker-build && \
|
||||
yarn cache clean && \
|
||||
yarn workspaces focus --production
|
||||
yarn install --production=true
|
||||
|
||||
|
||||
|
||||
FROM node:14.16.1-alpine
|
||||
FROM node:12.18.3-alpine
|
||||
LABEL maintainer="https://github.com/verdaccio/verdaccio"
|
||||
|
||||
ENV VERDACCIO_APPDIR=/opt/verdaccio \
|
||||
|
@ -42,10 +38,11 @@ RUN mkdir -p /verdaccio/storage /verdaccio/plugins /verdaccio/conf
|
|||
|
||||
COPY --from=builder /opt/verdaccio-build .
|
||||
|
||||
ADD conf/docker.yaml /verdaccio/conf/config.yaml
|
||||
RUN ls packages/config/src/conf
|
||||
ADD packages/config/src/conf/docker.yaml /verdaccio/conf/config.yaml
|
||||
|
||||
RUN adduser -u $VERDACCIO_USER_UID -S -D -h $VERDACCIO_APPDIR -g "$VERDACCIO_USER_NAME user" -s /sbin/nologin $VERDACCIO_USER_NAME && \
|
||||
chmod -R +x $VERDACCIO_APPDIR/bin $VERDACCIO_APPDIR/docker-bin && \
|
||||
chmod -R +x $VERDACCIO_APPDIR/packages/verdaccio/bin $VERDACCIO_APPDIR/docker-bin && \
|
||||
chown -R $VERDACCIO_USER_UID:root /verdaccio/storage && \
|
||||
chmod -R g=u /verdaccio/storage /etc/passwd
|
||||
|
||||
|
@ -57,4 +54,4 @@ VOLUME /verdaccio/storage
|
|||
|
||||
ENTRYPOINT ["uid_entrypoint"]
|
||||
|
||||
CMD $VERDACCIO_APPDIR/bin/verdaccio --config /verdaccio/conf/config.yaml --listen $VERDACCIO_PROTOCOL://0.0.0.0:$VERDACCIO_PORT
|
||||
CMD $VERDACCIO_APPDIR/packages/verdaccio/bin/verdaccio --config /verdaccio/conf/config.yaml --listen $VERDACCIO_PROTOCOL://0.0.0.0:$VERDACCIO_PORT
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../build/lib/cli');
|
4
debug/.babelrc
Normal file
4
debug/.babelrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"presets": [["@verdaccio"]],
|
||||
"debug": true
|
||||
}
|
2
debug/bootstrap.js
vendored
2
debug/bootstrap.js
vendored
|
@ -3,4 +3,4 @@
|
|||
require('@babel/register')({
|
||||
extensions: [".ts", ".js"]
|
||||
});
|
||||
require('../src/lib/cli');
|
||||
require('../packages/cli/src/index');
|
||||
|
|
1
debug/debug.js
Normal file
1
debug/debug.js
Normal file
|
@ -0,0 +1 @@
|
|||
require('@verdaccio/cli');
|
9
debug/package.json
Normal file
9
debug/package.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "debug",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@verdaccio/babel-preset": "^8.2.0",
|
||||
"@verdaccio/eslint-config": "^8.2.0"
|
||||
}
|
||||
}
|
14
lerna.json
Normal file
14
lerna.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": "5.0.0-alpha.0",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"stream": true,
|
||||
"command": {
|
||||
"publish": {
|
||||
"message": "chore: publish release %s"
|
||||
}
|
||||
}
|
||||
}
|
242
package.json
242
package.json
|
@ -1,7 +1,4 @@
|
|||
{
|
||||
"name": "verdaccio",
|
||||
"version": "5.0.0",
|
||||
"description": "A lightweight private npm proxy registry",
|
||||
"author": {
|
||||
"name": "Verdaccio Maintainers",
|
||||
"email": "verdaccio.npm@gmail.com"
|
||||
|
@ -11,205 +8,80 @@
|
|||
"url": "git://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"homepage": "https://verdaccio.org",
|
||||
"main": "build/index.js",
|
||||
"bin": "./bin/verdaccio",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/verdaccio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/commons-api": "10.0.0",
|
||||
"@verdaccio/local-storage": "10.0.1",
|
||||
"@verdaccio/readme": "10.0.0",
|
||||
"@verdaccio/streams": "10.0.0",
|
||||
"@verdaccio/ui-theme": "3.0.1",
|
||||
"JSONStream": "1.3.5",
|
||||
"async": "3.2.0",
|
||||
"body-parser": "1.19.0",
|
||||
"clipanion": "3.0.0-rc.11",
|
||||
"compression": "1.7.4",
|
||||
"cookies": "0.8.0",
|
||||
"cors": "2.8.5",
|
||||
"dayjs": "1.10.4",
|
||||
"debug": "^4.3.1",
|
||||
"envinfo": "7.7.4",
|
||||
"express": "4.17.1",
|
||||
"fast-safe-stringify": "^2.0.7",
|
||||
"handlebars": "4.7.7",
|
||||
"http-errors": "1.8.0",
|
||||
"js-yaml": "4.0.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"kleur": "4.1.4",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "6.0.0",
|
||||
"lunr-mutable-indexes": "2.3.2",
|
||||
"marked": "2.0.1",
|
||||
"memoizee": "0.4.15",
|
||||
"mime": "2.5.2",
|
||||
"minimatch": "3.0.4",
|
||||
"mkdirp": "1.0.4",
|
||||
"mv": "2.1.1",
|
||||
"pino": "6.11.2",
|
||||
"pkginfo": "0.4.1",
|
||||
"prettier-bytes": "^1.0.3",
|
||||
"pretty-ms": "^5.0.0",
|
||||
"request": "2.88.0",
|
||||
"semver": "7.3.4",
|
||||
"validator": "13.5.2",
|
||||
"verdaccio-audit": "10.0.0",
|
||||
"verdaccio-htpasswd": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.13.0",
|
||||
"@babel/core": "7.13.8",
|
||||
"@babel/node": "7.13.0",
|
||||
"@babel/plugin-proposal-class-properties": "7.13.0",
|
||||
"@babel/plugin-proposal-decorators": "7.13.5",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.12.13",
|
||||
"@babel/plugin-proposal-function-sent": "7.12.13",
|
||||
"@babel/plugin-proposal-json-strings": "7.13.8",
|
||||
"@babel/plugin-proposal-numeric-separator": "7.12.13",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.13.8",
|
||||
"@babel/plugin-proposal-throw-expressions": "7.12.13",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/plugin-syntax-import-meta": "7.10.4",
|
||||
"@babel/plugin-transform-async-to-generator": "7.13.0",
|
||||
"@babel/plugin-transform-classes": "7.13.0",
|
||||
"@babel/plugin-transform-runtime": "7.13.9",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "7.13.9",
|
||||
"@babel/preset-typescript": "7.13.0",
|
||||
"@babel/register": "7.13.8",
|
||||
"@babel/runtime": "7.13.9",
|
||||
"@commitlint/cli": "12.0.1",
|
||||
"@commitlint/config-conventional": "12.0.1",
|
||||
"@octokit/rest": "16.43.2",
|
||||
"@types/async": "3.2.4",
|
||||
"@commitlint/cli": "8.3.5",
|
||||
"@commitlint/config-conventional": "8.2.0",
|
||||
"@octokit/rest": "16.28.9",
|
||||
"@types/async": "3.0.3",
|
||||
"@types/bunyan": "1.8.6",
|
||||
"@types/express": "4.17.6",
|
||||
"@types/http-errors": "1.8.0",
|
||||
"@types/jest": "26.0.14",
|
||||
"@types/lodash": "4.14.167",
|
||||
"@types/express": "4.17.1",
|
||||
"@types/http-errors": "1.6.3",
|
||||
"@types/jest": "24.0.25",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/mime": "2.0.1",
|
||||
"@types/minimatch": "3.0.3",
|
||||
"@types/node": "14.14.37",
|
||||
"@types/pino": "6.3.6",
|
||||
"@types/request": "2.48.5",
|
||||
"@types/semver": "7.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "4.13.0",
|
||||
"@typescript-eslint/parser": "4.13.0",
|
||||
"@verdaccio/eslint-config": "^8.5.0",
|
||||
"@verdaccio/types": "^9.7.2",
|
||||
"all-contributors-cli": "6.20.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-jest": "26.6.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||
"codecov": "3.8.1",
|
||||
"cross-env": "7.0.3",
|
||||
"detect-secrets": "1.0.6",
|
||||
"eslint": "7.19.0",
|
||||
"eslint-config-google": "0.14.0",
|
||||
"eslint-config-prettier": "7.2.0",
|
||||
"eslint-plugin-babel": "5.3.1",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jest": "24.1.3",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-react": "7.22.0",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"eslint-plugin-verdaccio": "9.6.1",
|
||||
"fs-extra": "9.1.0",
|
||||
"get-stdin": "8.0.0",
|
||||
"@types/node": "12.12.21",
|
||||
"@types/request": "2.48.3",
|
||||
"@types/semver": "6.2.0",
|
||||
"@types/express-serve-static-core": "4.17.1",
|
||||
"@verdaccio/babel-preset": "^8.5.0",
|
||||
"@verdaccio/eslint-config": "^9.0.0",
|
||||
"@verdaccio/types": "^9.0.0",
|
||||
"codecov": "3.6.1",
|
||||
"cross-env": "6.0.3",
|
||||
"detect-secrets": "1.0.5",
|
||||
"eslint": "6.8.0",
|
||||
"fs-extra": "8.1.0",
|
||||
"get-stdin": "7.0.0",
|
||||
"kleur": "3.0.3",
|
||||
"husky": "2.7.0",
|
||||
"in-publish": "2.0.1",
|
||||
"jest": "25.5.4",
|
||||
"jest-environment-node": "25.5.0",
|
||||
"jest-junit": "9.0.0",
|
||||
"in-publish": "2.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"jest-environment-node": "^24.9.0",
|
||||
"jest-junit": "^9.0.0",
|
||||
"lerna": "^3.18.4",
|
||||
"lint-staged": "8.2.1",
|
||||
"lockfile-lint": "4.3.7",
|
||||
"nock": "12.0.3",
|
||||
"node-mocks-http": "^1.10.1",
|
||||
"prettier": "2.2.1",
|
||||
"puppeteer": "5.5.0",
|
||||
"rimraf": "3.0.2",
|
||||
"selfsigned": "1.10.8",
|
||||
"standard-version": "9.1.1",
|
||||
"supertest": "6.1.1",
|
||||
"typescript": "4.1.3",
|
||||
"verdaccio-auth-memory": "10.0.0",
|
||||
"verdaccio-memory": "10.0.0"
|
||||
"nock": "^11.7.2",
|
||||
"prettier": "^1.19.1",
|
||||
"rimraf": "3.0.0",
|
||||
"selfsigned": "1.10.7",
|
||||
"standard-version": "^7.0.1",
|
||||
"supertest": "^4.0.2",
|
||||
"typescript": "^3.7.5",
|
||||
"verdaccio-auth-memory": "^8.5.0",
|
||||
"verdaccio-memory": "^8.5.0",
|
||||
"verdaccio": "^4.4.0"
|
||||
},
|
||||
"keywords": [
|
||||
"private",
|
||||
"package",
|
||||
"repository",
|
||||
"registry",
|
||||
"enterprise",
|
||||
"modules",
|
||||
"proxy",
|
||||
"server",
|
||||
"verdaccio"
|
||||
],
|
||||
"scripts": {
|
||||
"release": "standard-version -a -s",
|
||||
"prepublish": "in-publish && yarn run code:build || not-in-publish",
|
||||
"type-check": "tsc --noEmit",
|
||||
"type-check:watch": "yarn run type-check -- --watch",
|
||||
"pretest": "yarn run code:build",
|
||||
"format": "prettier --single-quote --trailing-comma none --write \"{src,test}/**/*.ts\"",
|
||||
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\" --debug-check",
|
||||
"test": "yarn run test:unit",
|
||||
"test:clean": "npx jest --clearCache",
|
||||
"test:unit": "cross-env NODE_ENV=test TZ=UTC FORCE_COLOR=1 jest --config ./jest.config.js --maxWorkers 2 --passWithNoTests",
|
||||
"test:functional": "cross-env NODE_ENV=test jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index* --passWithNoTests",
|
||||
"test:e2e:cli": "cross-env NODE_ENV=test jest --config ./test/e2e-cli/jest.config.e2e.cli.js --passWithNoTests",
|
||||
"test:e2e": "yarn jest --config ./test/jest.config.e2e.js",
|
||||
"test:all": "yarn run test && yarn run test:functional && yarn run test:e2e & yarn run test:e2e:pkg",
|
||||
"pre:ci": "yarn run lint",
|
||||
"coverage:publish": "codecov",
|
||||
"lint": "yarn run type-check && yarn run lint:ts",
|
||||
"lint:ts": "eslint \"**/*.{js,jsx,ts,tsx}\"",
|
||||
"lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts verdaccio npm yarn",
|
||||
"start": "yarn babel-node --extensions \".ts,.tsx\" src/lib/cli",
|
||||
"start:debug": "yarn node debug/bootstrap.js",
|
||||
"code:build": "yarn babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps inline",
|
||||
"code:docker-build": "yarn babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\"",
|
||||
"docker": "docker build -t verdaccio/verdaccio:pr-2122 . --no-cache",
|
||||
"docker:run": "docker run -it --rm -p 4873:4873 verdaccio/verdaccio:local"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged",
|
||||
"commit-msg": "commitlint -e $GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"relative": true,
|
||||
"linters": {
|
||||
"*": [
|
||||
"eslint .",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"ignore": [
|
||||
"*.json"
|
||||
]
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"debug": "node debug/bootstrap.js",
|
||||
"dev": "cross-env BABEL_ENV=registry babel-node --extensions \".ts,.tsx\" packages/cli/src",
|
||||
"clean": "lerna run clean",
|
||||
"build": "lerna run build",
|
||||
"docker": "docker build -t verdaccio/verdaccio:local . --no-cache",
|
||||
"release": "lerna version --conventional-commits",
|
||||
"version:canary": "lerna version --conventional-commits --conventional-prerelease --no-commit-hooks --exact --no-changelog --yes --preid alpha",
|
||||
"publish:canary": "lerna publish from-package --canary --yes --no-git-reset --no-git-tag-version --no-push --force-publish --concurrency 1",
|
||||
"release:from-prerelease": "lerna version --conventional-commits --conventional-graduate",
|
||||
"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)/**\"",
|
||||
"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"
|
||||
},
|
||||
"license": "MIT",
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"@commitlint/config-conventional"
|
||||
]
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/verdaccio",
|
||||
"logo": "https://opencollective.com/verdaccio/logo.txt"
|
||||
}
|
||||
}
|
||||
|
|
3
packages/api/.babelrc
Normal file
3
packages/api/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": [["@verdaccio"]]
|
||||
}
|
9
packages/api/jest.config.js
Normal file
9
packages/api/jest.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
transform: {
|
||||
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
|
||||
},
|
||||
verbose: true,
|
||||
collectCoverage: true,
|
||||
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
|
||||
};
|
43
packages/api/package.json
Normal file
43
packages/api/package.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "@verdaccio/api",
|
||||
"version": "5.0.0-alpha.0",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"homepage": "https://verdaccio.org",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "npm run build:js && npm run build:types"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@verdaccio/commons-api": "^9.0.0",
|
||||
"@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/utils": "5.0.0-alpha.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"cookies": "^0.8.0",
|
||||
"express": "4.17.1",
|
||||
"lodash": "^4.17.15",
|
||||
"mime": "^2.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/dev-types": "5.0.0-alpha.0",
|
||||
"@verdaccio/types": "^9.0.0"
|
||||
},
|
||||
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
|
||||
}
|
78
packages/api/src/dist-tags.ts
Normal file
78
packages/api/src/dist-tags.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import mime from 'mime';
|
||||
import _ from 'lodash';
|
||||
import{ Router } from 'express';
|
||||
|
||||
import { media, allow } from '@verdaccio/middleware';
|
||||
import { API_MESSAGE, HTTP_STATUS, DIST_TAGS } from '@verdaccio/dev-commons';
|
||||
import { VerdaccioError } from '@verdaccio/commons-api';
|
||||
import { Package } from '@verdaccio/types';
|
||||
|
||||
// @ts-ignore
|
||||
import{ IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
|
||||
|
||||
export default function(route: Router, auth: IAuth, storage: IStorageHandler): void {
|
||||
const can = allow(auth);
|
||||
const tag_package_version = function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): $NextFunctionVer {
|
||||
if (_.isString(req.body) === false) {
|
||||
return next('route');
|
||||
}
|
||||
|
||||
const tags = {};
|
||||
tags[req.params.tag] = req.body;
|
||||
storage.mergeTags(req.params.package, tags, function(err: Error): $NextFunctionVer {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({ ok: API_MESSAGE.TAG_ADDED });
|
||||
});
|
||||
};
|
||||
|
||||
// tagging a package
|
||||
route.put('/:package/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
|
||||
|
||||
route.post('/-/package/:package/dist-tags/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
|
||||
|
||||
route.put('/-/package/:package/dist-tags/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
|
||||
|
||||
route.delete('/-/package/:package/dist-tags/:tag', can('publish'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const tags = {};
|
||||
tags[req.params.tag] = null;
|
||||
storage.mergeTags(req.params.package, tags, function(err: VerdaccioError): $NextFunctionVer {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: API_MESSAGE.TAG_REMOVED,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
route.get('/-/package/:package/dist-tags', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
storage.getPackage({
|
||||
name: req.params.package,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
callback: function(err: VerdaccioError, info: Package): $NextFunctionVer {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
next(info[DIST_TAGS]);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
route.post('/-/package/:package/dist-tags', can('publish'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
storage.mergeTags(req.params.package, req.body, function(err: VerdaccioError): $NextFunctionVer {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: API_MESSAGE.TAG_UPDATED,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,31 +1,26 @@
|
|||
import { Config } from '@verdaccio/types';
|
||||
import _ from 'lodash';
|
||||
|
||||
import express from 'express';
|
||||
|
||||
import { match, validateName, validatePackage, encodeScopePackage, antiLoop } from '@verdaccio/middleware';
|
||||
import { IAuth, IStorageHandler } from '@verdaccio/dev-types';
|
||||
import { Config } from '@verdaccio/types';
|
||||
import bodyParser from 'body-parser';
|
||||
import { IAuth, IStorageHandler } from '../../../types';
|
||||
import whoami from './api/whoami';
|
||||
import ping from './api/ping';
|
||||
import user from './api/user';
|
||||
import distTags from './api/dist-tags';
|
||||
import publish from './api/publish';
|
||||
import search from './api/search';
|
||||
import pkg from './api/package';
|
||||
import stars from './api/stars';
|
||||
import profile from './api/v1/profile';
|
||||
import token from './api/v1/token';
|
||||
|
||||
import v1Search from './api/v1/search';
|
||||
import whoami from './whoami';
|
||||
import ping from './ping';
|
||||
import user from './user';
|
||||
import distTags from './dist-tags';
|
||||
import publish from './publish';
|
||||
import search from './search';
|
||||
import pkg from './package';
|
||||
import stars from './stars';
|
||||
import profile from './v1/profile';
|
||||
import token from './v1/token';
|
||||
import v1Search from './api/v1/search'
|
||||
|
||||
const {
|
||||
match,
|
||||
validateName,
|
||||
validatePackage,
|
||||
encodeScopePackage,
|
||||
antiLoop
|
||||
} = require('../middleware');
|
||||
const { match, validateName, validatePackage, encodeScopePackage, antiLoop } = require('../middleware');
|
||||
|
||||
export default function (config: Config, auth: IAuth, storage: IStorageHandler) {
|
||||
export default function(config: Config, auth: IAuth, storage: IStorageHandler) {
|
||||
/* eslint new-cap:off */
|
||||
const app = express.Router();
|
||||
/* eslint new-cap:off */
|
||||
|
@ -49,6 +44,7 @@ export default function (config: Config, auth: IAuth, storage: IStorageHandler)
|
|||
|
||||
app.use(auth.apiJWTmiddleware());
|
||||
app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' }));
|
||||
// @ts-ignore
|
||||
app.use(antiLoop(config));
|
||||
// encode / in a scoped package name to be matched as a single parameter in routes
|
||||
app.use(encodeScopePackage);
|
||||
|
@ -64,7 +60,7 @@ export default function (config: Config, auth: IAuth, storage: IStorageHandler)
|
|||
stars(app, storage);
|
||||
|
||||
if (_.get(config, 'experiments.search') === true) {
|
||||
v1Search(app, auth, storage);
|
||||
v1Search(app, auth, storage)
|
||||
}
|
||||
|
||||
if (_.get(config, 'experiments.token') === true) {
|
74
packages/api/src/package.ts
Normal file
74
packages/api/src/package.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import _ from 'lodash';
|
||||
import { Router } from 'express';
|
||||
|
||||
import { allow } from '@verdaccio/middleware';
|
||||
import { convertDistRemoteToLocalTarballUrls, getVersion, ErrorCode } from '@verdaccio/utils';
|
||||
import { HEADERS, DIST_TAGS, API_ERROR } from '@verdaccio/dev-commons';
|
||||
import { Config, Package } from '@verdaccio/types';
|
||||
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
|
||||
|
||||
const downloadStream = (packageName: string, filename: string, storage: any, req: $RequestExtend, res: $ResponseExtend): void => {
|
||||
const stream = storage.getTarball(packageName, filename);
|
||||
|
||||
stream.on('content-length', function(content): void {
|
||||
res.header('Content-Length', content);
|
||||
});
|
||||
|
||||
stream.on('error', function(err): void {
|
||||
return res.report_error(err);
|
||||
});
|
||||
|
||||
res.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM);
|
||||
stream.pipe(res);
|
||||
};
|
||||
|
||||
export default function(route: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
|
||||
const can = allow(auth);
|
||||
// TODO: anonymous user?
|
||||
route.get('/:package/:version?', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const getPackageMetaCallback = function(err, metadata: Package): void {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
metadata = convertDistRemoteToLocalTarballUrls(metadata, req, config.url_prefix);
|
||||
|
||||
let queryVersion = req.params.version;
|
||||
if (_.isNil(queryVersion)) {
|
||||
return next(metadata);
|
||||
}
|
||||
|
||||
let version = getVersion(metadata, queryVersion);
|
||||
if (_.isNil(version) === false) {
|
||||
return next(version);
|
||||
}
|
||||
|
||||
if (_.isNil(metadata[DIST_TAGS]) === false) {
|
||||
if (_.isNil(metadata[DIST_TAGS][queryVersion]) === false) {
|
||||
queryVersion = metadata[DIST_TAGS][queryVersion];
|
||||
version = getVersion(metadata, queryVersion);
|
||||
if (_.isNil(version) === false) {
|
||||
return next(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${req.params.version}`));
|
||||
};
|
||||
|
||||
storage.getPackage({
|
||||
name: req.params.package,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
callback: getPackageMetaCallback,
|
||||
});
|
||||
});
|
||||
|
||||
route.get('/:scopedPackage/-/:scope/:filename', can('access'), function(req: $RequestExtend, res: $ResponseExtend): void {
|
||||
const { scopedPackage, filename } = req.params;
|
||||
|
||||
downloadStream(scopedPackage, filename, storage, req, res);
|
||||
});
|
||||
|
||||
route.get('/:package/-/:filename', can('access'), function(req: $RequestExtend, res: $ResponseExtend): void {
|
||||
downloadStream(req.params.package, req.params.filename, storage, req, res);
|
||||
});
|
||||
}
|
|
@ -1,10 +1,5 @@
|
|||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '../../../../types';
|
||||
import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '@verdaccio/dev-types';
|
||||
|
||||
export default function (route: Router): void {
|
||||
route.get(
|
|
@ -1,21 +1,18 @@
|
|||
import Path from 'path';
|
||||
import _ from 'lodash';
|
||||
import buildDebug from 'debug';
|
||||
import Path from 'path';
|
||||
import mime from 'mime';
|
||||
|
||||
import { Router } from 'express';
|
||||
|
||||
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
|
||||
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
|
||||
import {validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation} from '@verdaccio/utils';
|
||||
import { media, expectJson, allow } from '@verdaccio/middleware';
|
||||
import { notify } from '@verdaccio/hooks';
|
||||
import { Config, Callback, MergeTags, Version, Package } from '@verdaccio/types';
|
||||
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '../../../lib/constants';
|
||||
import { validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation } from '../../../lib/utils';
|
||||
import { media, expectJson, allow } from '../../middleware';
|
||||
import { notify } from '../../../lib/notify';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
|
||||
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
|
||||
import { logger } from '../../../lib/logger';
|
||||
import { isPublishablePackage } from '../../../lib/storage-utils';
|
||||
import star from './star';
|
||||
|
||||
const debug = buildDebug('verdaccio:publish');
|
||||
import {isPublishablePackage} from "./utils";
|
||||
|
||||
export default function publish(router: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
|
||||
const can = allow(auth);
|
||||
|
@ -106,18 +103,20 @@ export default function publish(router: Router, auth: IAuth, storage: IStorageHa
|
|||
*/
|
||||
export function publishPackage(storage: IStorageHandler, config: Config, auth: IAuth): any {
|
||||
const starApi = star(storage);
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const packageName = req.params.package;
|
||||
debug('publishing or updating a new version for %o', packageName);
|
||||
|
||||
logger.debug({packageName} , `publishing or updating a new version for @{packageName}`);
|
||||
|
||||
/**
|
||||
* Write tarball of stream data from package clients.
|
||||
*/
|
||||
const createTarball = function (filename: string, data, cb: Callback): void {
|
||||
const createTarball = function(filename: string, data, cb: Callback): void {
|
||||
const stream = storage.addTarball(packageName, filename);
|
||||
stream.on('error', function (err) {
|
||||
stream.on('error', function(err) {
|
||||
cb(err);
|
||||
});
|
||||
stream.on('success', function () {
|
||||
stream.on('success', function() {
|
||||
cb();
|
||||
});
|
||||
// this is dumb and memory-consuming, but what choices do we have?
|
||||
|
@ -129,18 +128,18 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
|||
/**
|
||||
* Add new package version in storage
|
||||
*/
|
||||
const createVersion = function (version: string, metadata: Version, cb: Callback): void {
|
||||
const createVersion = function(version: string, metadata: Version, cb: Callback): void {
|
||||
storage.addVersion(packageName, version, metadata, null, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add new tags in storage
|
||||
*/
|
||||
const addTags = function (tags: MergeTags, cb: Callback): void {
|
||||
const addTags = function(tags: MergeTags, cb: Callback): void {
|
||||
storage.mergeTags(packageName, tags, cb);
|
||||
};
|
||||
|
||||
const afterChange = function (error, okMessage, metadata): void {
|
||||
const afterChange = function(error, okMessage, metadata): void {
|
||||
const metadataCopy: Package = { ...metadata };
|
||||
|
||||
const { _attachments, versions } = metadataCopy;
|
||||
|
@ -161,7 +160,8 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
|||
// npm-registry-client 0.3+ embeds tarball into the json upload
|
||||
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
|
||||
// issue https://github.com/rlidwka/sinopia/issues/31, dealing with it here:
|
||||
const isInvalidBodyFormat = isObject(_attachments) === false || hasDiffOneKey(_attachments) || isObject(versions) === false || hasDiffOneKey(versions);
|
||||
const isInvalidBodyFormat = isObject(_attachments) === false || hasDiffOneKey(_attachments) ||
|
||||
isObject(versions) === false || hasDiffOneKey(versions);
|
||||
|
||||
if (isInvalidBodyFormat) {
|
||||
// npm is doing something strange again
|
||||
|
@ -177,7 +177,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
|||
// at this point document is either created or existed before
|
||||
const [firstAttachmentKey] = Object.keys(_attachments);
|
||||
|
||||
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function (error) {
|
||||
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function(error) {
|
||||
if (error) {
|
||||
return next(error);
|
||||
}
|
||||
|
@ -187,12 +187,12 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
|||
|
||||
versionMetadataToPublish.readme = _.isNil(versionMetadataToPublish.readme) === false ? String(versionMetadataToPublish.readme) : '';
|
||||
|
||||
createVersion(versionToPublish, versionMetadataToPublish, function (error) {
|
||||
createVersion(versionToPublish, versionMetadataToPublish, function(error) {
|
||||
if (error) {
|
||||
return next(error);
|
||||
}
|
||||
|
||||
addTags(metadataCopy[DIST_TAGS], async function (error) {
|
||||
addTags(metadataCopy[DIST_TAGS], async function(error) {
|
||||
if (error) {
|
||||
return next(error);
|
||||
}
|
||||
|
@ -218,26 +218,27 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
|||
const metadata = validateMetadata(req.body, packageName);
|
||||
// treating deprecation as updating a package
|
||||
if (req.params._rev || isRelatedToDeprecation(req.body)) {
|
||||
debug('updating a new version for %o', packageName);
|
||||
logger.debug({packageName} , `updating a new version for @{packageName}`);
|
||||
// we check unpublish permissions, an update is basically remove versions
|
||||
const remote = req.remote_user;
|
||||
auth.allow_unpublish({ packageName }, remote, (error) => {
|
||||
auth.allow_unpublish({packageName}, remote, (error) => {
|
||||
if (error) {
|
||||
logger.error({ packageName }, `not allowed to unpublish a version for @{packageName}`);
|
||||
logger.debug({packageName} , `not allowed to unpublish a version for @{packageName}`);
|
||||
return next(error);
|
||||
}
|
||||
storage.changePackage(packageName, metadata, req.params.revision, function (error) {
|
||||
|
||||
storage.changePackage(packageName, metadata, req.params.revision, function(error) {
|
||||
afterChange(error, API_MESSAGE.PKG_CHANGED, metadata);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
debug('adding a new version for %o', packageName);
|
||||
storage.addPackage(packageName, metadata, function (error) {
|
||||
logger.debug({packageName} , `adding a new version for @{packageName}`);
|
||||
storage.addPackage(packageName, metadata, function(error) {
|
||||
afterChange(error, API_MESSAGE.PKG_CREATED, metadata);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({ packageName }, 'error on publish, bad package data for @{packageName}');
|
||||
logger.error({packageName}, 'error on publish, bad package data for @{packageName}');
|
||||
return next(ErrorCode.getBadData(API_ERROR.BAD_PACKAGE_DATA));
|
||||
}
|
||||
};
|
||||
|
@ -247,10 +248,11 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
|||
* un-publish a package
|
||||
*/
|
||||
export function unPublishPackage(storage: IStorageHandler) {
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const packageName = req.params.package;
|
||||
debug('unpublishing %o', packageName);
|
||||
storage.removePackage(packageName, function (err) {
|
||||
|
||||
logger.debug({packageName} , `unpublishing @{packageName}`);
|
||||
storage.removePackage(packageName, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
@ -264,16 +266,18 @@ export function unPublishPackage(storage: IStorageHandler) {
|
|||
* Delete tarball
|
||||
*/
|
||||
export function removeTarball(storage: IStorageHandler) {
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const packageName = req.params.package;
|
||||
const { filename, revision } = req.params;
|
||||
debug('removing a tarball for %o-%o-%o', packageName, filename, revision);
|
||||
storage.removeTarball(packageName, filename, revision, function (err) {
|
||||
const {filename, revision} = req.params;
|
||||
|
||||
logger.debug({packageName, filename, revision} , `removing a tarball for @{packageName}-@{tarballName}-@{revision}`);
|
||||
storage.removeTarball(packageName, filename, revision, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
debug('success remove tarball for %o-%o-%o', packageName, filename, revision);
|
||||
|
||||
logger.debug({packageName, filename, revision} , `success remove tarball for @{packageName}-@{tarballName}-@{revision}`);
|
||||
return next({ ok: API_MESSAGE.TARBALL_REMOVED });
|
||||
});
|
||||
};
|
||||
|
@ -282,11 +286,11 @@ export function removeTarball(storage: IStorageHandler) {
|
|||
* Adds a new version
|
||||
*/
|
||||
export function addVersion(storage: IStorageHandler) {
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const { version, tag } = req.params;
|
||||
const packageName = req.params.package;
|
||||
|
||||
storage.addVersion(packageName, version, req.body, tag, function (error) {
|
||||
storage.addVersion(packageName, version, req.body, tag, function(error) {
|
||||
if (error) {
|
||||
return next(error);
|
||||
}
|
||||
|
@ -303,29 +307,29 @@ export function addVersion(storage: IStorageHandler) {
|
|||
* uploadPackageTarball
|
||||
*/
|
||||
export function uploadPackageTarball(storage: IStorageHandler) {
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const packageName = req.params.package;
|
||||
const stream = storage.addTarball(packageName, req.params.filename);
|
||||
req.pipe(stream);
|
||||
|
||||
// checking if end event came before closing
|
||||
let complete = false;
|
||||
req.on('end', function () {
|
||||
req.on('end', function() {
|
||||
complete = true;
|
||||
stream.done();
|
||||
});
|
||||
|
||||
req.on('close', function () {
|
||||
req.on('close', function() {
|
||||
if (!complete) {
|
||||
stream.abort();
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', function (err) {
|
||||
return res.locals.report_error(err);
|
||||
stream.on('error', function(err) {
|
||||
return res.report_error(err);
|
||||
});
|
||||
|
||||
stream.on('success', function () {
|
||||
stream.on('success', function() {
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: API_MESSAGE.TARBALL_UPLOADED,
|
|
@ -1,19 +1,15 @@
|
|||
import { HEADERS } from '../../../lib/constants';
|
||||
import { HEADERS } from '@verdaccio/dev-commons';
|
||||
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
export default function (route, auth, storage): void {
|
||||
export default function(route, auth, storage): void {
|
||||
// searching packages
|
||||
route.get('/-/all(/since)?', function (req, res) {
|
||||
route.get('/-/all(/since)?', function(req, res) {
|
||||
let received_end = false;
|
||||
let response_finished = false;
|
||||
let processing_pkgs = 0;
|
||||
let firstPackage = true;
|
||||
|
||||
res.status(200);
|
||||
res.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET);
|
||||
res.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
|
||||
/*
|
||||
* Offical NPM registry (registry.npmjs.org) no longer return whole database,
|
||||
|
@ -37,7 +33,7 @@ export default function (route, auth, storage): void {
|
|||
if (!respShouldBeArray) {
|
||||
res.set('Date', 'Mon, 10 Oct 1983 00:12:48 GMT');
|
||||
}
|
||||
const check_finish = function (): void {
|
||||
const check_finish = function(): void {
|
||||
if (!received_end) {
|
||||
return;
|
||||
}
|
||||
|
@ -66,7 +62,7 @@ export default function (route, auth, storage): void {
|
|||
stream.on('data', function each(pkg) {
|
||||
processing_pkgs++;
|
||||
|
||||
auth.allow_access({ packageName: pkg.name }, req.remote_user, function (err, allowed) {
|
||||
auth.allow_access({ packageName: pkg.name }, req.remote_user, function(err, allowed) {
|
||||
processing_pkgs--;
|
||||
|
||||
if (err) {
|
||||
|
@ -94,11 +90,11 @@ export default function (route, auth, storage): void {
|
|||
});
|
||||
});
|
||||
|
||||
stream.on('error', function () {
|
||||
stream.on('error', function() {
|
||||
res.socket.destroy();
|
||||
});
|
||||
|
||||
stream.on('end', function () {
|
||||
stream.on('end', function() {
|
||||
received_end = true;
|
||||
check_finish();
|
||||
});
|
|
@ -1,14 +1,11 @@
|
|||
// @flow
|
||||
|
||||
import { Response } from 'express';
|
||||
import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons';
|
||||
import {Response} from 'express';
|
||||
import _ from 'lodash';
|
||||
import buildDebug from 'debug';
|
||||
import { USERS, HTTP_STATUS } from '../../../lib/constants';
|
||||
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
|
||||
import { logger } from '../../../lib/logger';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
|
||||
const debug = buildDebug('verdaccio:star');
|
||||
export default function (storage: IStorageHandler): (req: $RequestExtend, res: Response, next: $NextFunctionVer) => void {
|
||||
import {$RequestExtend, $NextFunctionVer, IStorageHandler} from '@verdaccio/dev-types';
|
||||
|
||||
export default function(storage: IStorageHandler): (req: $RequestExtend, res: Response, next: $NextFunctionVer) => void {
|
||||
const validateInputs = (newUsers, localUsers, username, isStar): boolean => {
|
||||
const isExistlocalUsers = _.isNil(localUsers[username]) === false;
|
||||
if (isStar && isExistlocalUsers && localUsers[username]) {
|
||||
|
@ -23,8 +20,8 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
|
|||
|
||||
return (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
|
||||
const name = req.params.package;
|
||||
debug('starring a package for %o', name);
|
||||
const afterChangePackage = function (err?: Error) {
|
||||
logger.debug({name}, 'starring a package for @{name}');
|
||||
const afterChangePackage = function(err?: Error) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
@ -37,7 +34,7 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
|
|||
storage.getPackage({
|
||||
name,
|
||||
req,
|
||||
callback: function (err, info) {
|
||||
callback: function(err, info) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
@ -49,22 +46,16 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
|
|||
if (_.isNil(localStarUsers) === false && validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)) {
|
||||
return afterChangePackage();
|
||||
}
|
||||
const users = isStar
|
||||
? {
|
||||
...localStarUsers,
|
||||
[remoteUsername]: true,
|
||||
}
|
||||
: _.reduce(
|
||||
localStarUsers,
|
||||
(users, value, key) => {
|
||||
if (key !== remoteUsername) {
|
||||
users[key] = value;
|
||||
}
|
||||
return users;
|
||||
},
|
||||
{}
|
||||
);
|
||||
storage.changePackage(name, { ...info, users }, req.body._rev, function (err) {
|
||||
const users = isStar ? {
|
||||
...localStarUsers,
|
||||
[remoteUsername]: true,
|
||||
} : _.reduce(localStarUsers, (users, value, key) => {
|
||||
if (key !== remoteUsername) {
|
||||
users[key] = value;
|
||||
}
|
||||
return users;
|
||||
}, {});
|
||||
storage.changePackage(name, { ...info, users}, req.body._rev, function(err) {
|
||||
afterChangePackage(err);
|
||||
});
|
||||
},
|
32
packages/api/src/stars.ts
Normal file
32
packages/api/src/stars.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import _ from 'lodash';
|
||||
import { Response, Router } from 'express';
|
||||
|
||||
import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons';
|
||||
import { Package } from '@verdaccio/types';
|
||||
|
||||
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
|
||||
|
||||
type Packages = Package[];
|
||||
|
||||
export default function(route: Router, storage: IStorageHandler): void {
|
||||
route.get('/-/_view/starredByUser', (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
|
||||
const remoteUsername = req.remote_user.name;
|
||||
|
||||
storage.getLocalDatabase((err, localPackages: Packages) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
const filteredPackages: Packages = localPackages.filter((localPackage: Package) =>
|
||||
_.keys(localPackage[USERS]).includes(remoteUsername)
|
||||
);
|
||||
|
||||
res.status(HTTP_STATUS.OK);
|
||||
next({
|
||||
rows: filteredPackages.map((filteredPackage: Package) => ({
|
||||
value: filteredPackage.name,
|
||||
})),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,31 +1,30 @@
|
|||
import _ from 'lodash';
|
||||
import Cookies from 'cookies';
|
||||
import { Response, Router } from 'express';
|
||||
|
||||
import { createRemoteUser, createSessionToken, getApiToken, getAuthenticatedMessage, validatePassword, ErrorCode } from '@verdaccio/utils';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
|
||||
import { Config, RemoteUser } from '@verdaccio/types';
|
||||
import { Response, Router } from 'express';
|
||||
import { ErrorCode } from '../../../lib/utils';
|
||||
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '../../../lib/constants';
|
||||
import { createRemoteUser, createSessionToken, getApiToken, getAuthenticatedMessage, validatePassword } from '../../../lib/auth-utils';
|
||||
import { logger } from '../../../lib/logger';
|
||||
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types';
|
||||
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '@verdaccio/dev-commons';
|
||||
|
||||
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '../../../../types';
|
||||
|
||||
export default function (route: Router, auth: IAuth, config: Config): void {
|
||||
route.get('/-/user/:org_couchdb_user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
export default function(route: Router, auth: IAuth, config: Config): void {
|
||||
route.get('/-/user/:org_couchdb_user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
res.status(HTTP_STATUS.OK);
|
||||
next({
|
||||
ok: getAuthenticatedMessage(req.remote_user.name),
|
||||
});
|
||||
});
|
||||
|
||||
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
const { name, password } = req.body;
|
||||
const remoteName = req.remote_user.name;
|
||||
|
||||
if (_.isNil(remoteName) === false && _.isNil(name) === false && remoteName === name) {
|
||||
auth.authenticate(name, password, async function callbackAuthenticate(err, user): Promise<void> {
|
||||
if (err) {
|
||||
logger.error({ name, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
|
||||
logger.trace({ name, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD));
|
||||
}
|
||||
|
||||
|
@ -45,7 +44,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
|
|||
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT()));
|
||||
}
|
||||
|
||||
auth.add_user(name, password, async function (err, user): Promise<void> {
|
||||
auth.add_user(name, password, async function(err, user): Promise<void> {
|
||||
if (err) {
|
||||
if (err.status >= HTTP_STATUS.BAD_REQUEST && err.status < HTTP_STATUS.INTERNAL_ERROR) {
|
||||
// With npm registering is the same as logging in,
|
||||
|
@ -68,7 +67,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
|
|||
}
|
||||
});
|
||||
|
||||
route.delete('/-/user/token/*', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
route.delete('/-/user/token/*', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
res.status(HTTP_STATUS.OK);
|
||||
next({
|
||||
ok: API_MESSAGE.LOGGED_OUT,
|
||||
|
@ -77,7 +76,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
|
|||
|
||||
// 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 {
|
||||
route.post('/_session', Cookies.express(), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
res.cookies.set('AuthSession', String(Math.random()), createSessionToken());
|
||||
|
||||
next({
|
13
packages/api/src/utils.ts
Normal file
13
packages/api/src/utils.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import {Package} from '@verdaccio/types';
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* Check whether the package metadta has enough data to be published
|
||||
* @param pkg metadata
|
||||
*/
|
||||
|
||||
export function isPublishablePackage(pkg: Package): boolean {
|
||||
const keys: string[] = Object.keys(pkg);
|
||||
|
||||
return _.includes(keys, 'versions');
|
||||
}
|
83
packages/api/src/v1/profile.ts
Normal file
83
packages/api/src/v1/profile.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import _ from 'lodash';
|
||||
import { Response, Router } from 'express';
|
||||
|
||||
import { API_ERROR, APP_ERROR, HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
|
||||
import { ErrorCode, validatePassword } from '@verdaccio/utils';
|
||||
import { $NextFunctionVer, $RequestExtend, IAuth } from '@verdaccio/dev-types';
|
||||
|
||||
export interface Profile {
|
||||
tfa: boolean;
|
||||
name: string;
|
||||
email: string;
|
||||
email_verified: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
cidr_whitelist: string[] | null;
|
||||
fullname: string;
|
||||
}
|
||||
|
||||
export default function(route: Router, auth: IAuth): void {
|
||||
function buildProfile(name: string): Profile {
|
||||
return {
|
||||
tfa: false,
|
||||
name,
|
||||
email: '',
|
||||
email_verified: false,
|
||||
created: '',
|
||||
updated: '',
|
||||
cidr_whitelist: null,
|
||||
fullname: '',
|
||||
};
|
||||
}
|
||||
|
||||
route.get('/-/npm/v1/user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
if (_.isNil(req.remote_user.name) === false) {
|
||||
return next(buildProfile(req.remote_user.name));
|
||||
}
|
||||
|
||||
res.status(HTTP_STATUS.UNAUTHORIZED);
|
||||
return next({
|
||||
message: API_ERROR.MUST_BE_LOGGED,
|
||||
});
|
||||
});
|
||||
|
||||
route.post('/-/npm/v1/user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
if (_.isNil(req.remote_user.name)) {
|
||||
res.status(HTTP_STATUS.UNAUTHORIZED);
|
||||
return next({
|
||||
message: API_ERROR.MUST_BE_LOGGED,
|
||||
});
|
||||
}
|
||||
|
||||
const { password, tfa } = req.body;
|
||||
const { name } = req.remote_user;
|
||||
|
||||
if (_.isNil(password) === false) {
|
||||
if (validatePassword(password.new) === false) {
|
||||
/* eslint new-cap:off */
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT()));
|
||||
/* eslint new-cap:off */
|
||||
}
|
||||
|
||||
auth.changePassword(
|
||||
name,
|
||||
password.old,
|
||||
password.new,
|
||||
(err, isUpdated): $NextFunctionVer => {
|
||||
if (_.isNull(err) === false) {
|
||||
return next(ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message));
|
||||
}
|
||||
|
||||
if (isUpdated) {
|
||||
return next(buildProfile(req.remote_user.name));
|
||||
}
|
||||
return next(ErrorCode.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
);
|
||||
} else if (_.isNil(tfa) === false) {
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED));
|
||||
} else {
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));
|
||||
}
|
||||
});
|
||||
}
|
121
packages/api/src/v1/token.ts
Normal file
121
packages/api/src/v1/token.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import _ from 'lodash';
|
||||
import { HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
|
||||
import {ErrorCode, stringToMD5, mask, getApiToken } from '@verdaccio/utils';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import { Response, Router } from 'express';
|
||||
|
||||
import {$NextFunctionVer, $RequestExtend, IAuth, IStorageHandler} from '../../../types';
|
||||
import { Config, RemoteUser, Token } from '@verdaccio/types';
|
||||
|
||||
export type NormalizeToken = Token & {
|
||||
created: string;
|
||||
};
|
||||
|
||||
function normalizeToken(token: Token): NormalizeToken {
|
||||
return {
|
||||
...token,
|
||||
created: new Date(token.created).toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
// https://github.com/npm/npm-profile/blob/latest/lib/index.js
|
||||
export default function(route: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
|
||||
route.get('/-/npm/v1/tokens', async function(req: $RequestExtend, res: Response, next: $NextFunctionVer) {
|
||||
const { name } = req.remote_user;
|
||||
|
||||
if (_.isNil(name) === false) {
|
||||
try {
|
||||
const tokens = await storage.readTokens({user: name});
|
||||
const totalTokens = tokens.length;
|
||||
logger.debug({totalTokens}, 'token list retrieved: @{totalTokens}');
|
||||
|
||||
res.status(HTTP_STATUS.OK);
|
||||
return next({
|
||||
objects: tokens.map(normalizeToken),
|
||||
urls: {
|
||||
next: '', // TODO: pagination?
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({ error: error.msg }, 'token list has failed: @{error}');
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
|
||||
}
|
||||
}
|
||||
return next(ErrorCode.getUnauthorized());
|
||||
});
|
||||
|
||||
route.post('/-/npm/v1/tokens', function(req: $RequestExtend, res: Response, next: $NextFunctionVer) {
|
||||
const { password, readonly, cidr_whitelist } = req.body;
|
||||
const { name } = req.remote_user;
|
||||
|
||||
if (!_.isBoolean(readonly) || !_.isArray(cidr_whitelist)) {
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.BAD_DATA, SUPPORT_ERRORS.PARAMETERS_NOT_VALID));
|
||||
}
|
||||
|
||||
auth.authenticate(name, password, async (err, user: RemoteUser) => {
|
||||
if (err) {
|
||||
const errorCode = err.message ? HTTP_STATUS.UNAUTHORIZED : HTTP_STATUS.INTERNAL_ERROR;
|
||||
return next(ErrorCode.getCode(errorCode, err.message));
|
||||
}
|
||||
|
||||
req.remote_user = user;
|
||||
|
||||
if (!_.isFunction(storage.saveToken)) {
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT));
|
||||
}
|
||||
|
||||
try {
|
||||
const token = await getApiToken(auth, config, user, password);
|
||||
const key = stringToMD5(token);
|
||||
// TODO: use a utility here
|
||||
const maskedToken = mask(token, 5);
|
||||
const created = new Date().getTime();
|
||||
|
||||
/**
|
||||
* cidr_whitelist: is not being used, we pass it through
|
||||
* token: we do not store the real token (it is generated once and retrieved to the user), just a mask of it.
|
||||
*/
|
||||
const saveToken: Token = {
|
||||
user: name,
|
||||
token: maskedToken,
|
||||
key,
|
||||
cidr: cidr_whitelist,
|
||||
readonly,
|
||||
created,
|
||||
};
|
||||
|
||||
await storage.saveToken(saveToken);
|
||||
logger.debug({ key, name }, 'token @{key} was created for user @{name}');
|
||||
return next(normalizeToken({
|
||||
token,
|
||||
user: name,
|
||||
key: saveToken.key,
|
||||
cidr: cidr_whitelist,
|
||||
readonly,
|
||||
created: saveToken.created,
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.error({ error: error.msg }, 'token creation has failed: @{error}');
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
route.delete('/-/npm/v1/tokens/token/:tokenKey', async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {
|
||||
const { params: { tokenKey }} = req;
|
||||
const { name } = req.remote_user;
|
||||
|
||||
if (_.isNil(name) === false) {
|
||||
logger.debug({name}, '@{name} has requested remove a token');
|
||||
try {
|
||||
await storage.deleteToken(name, tokenKey);
|
||||
logger.info({ tokenKey, name }, 'token id @{tokenKey} was revoked for user @{name}');
|
||||
return next({});
|
||||
} catch(error) {
|
||||
logger.error({ error: error.msg }, 'token creation has failed: @{error}');
|
||||
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
|
||||
}
|
||||
}
|
||||
return next(ErrorCode.getUnauthorized());
|
||||
});
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Response, Router } from 'express';
|
||||
import { $RequestExtend, $NextFunctionVer } from '../../../../types';
|
||||
import { $RequestExtend, $NextFunctionVer } from '@verdaccio/dev-types';
|
||||
|
||||
export default function (route: Router): void {
|
||||
route.get('/whoami', (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
|
|
@ -1,15 +1,11 @@
|
|||
import {
|
||||
addVersion,
|
||||
uploadPackageTarball,
|
||||
removeTarball,
|
||||
unPublishPackage,
|
||||
publishPackage
|
||||
} from '../../../../src/api/endpoint/api/publish';
|
||||
import { HTTP_STATUS, API_ERROR } from '../../../../src/lib/constants';
|
||||
import { addVersion, uploadPackageTarball, removeTarball, unPublishPackage, publishPackage } from '../src/publish';
|
||||
import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons';
|
||||
|
||||
const REVISION_MOCK = '15-e53a77096b0ee33e';
|
||||
|
||||
require('../../../../src/lib/logger').setup([{ type: 'stdout', format: 'pretty', level: 'info' }]);
|
||||
require('@verdaccio/logger').setup([
|
||||
{ type: 'stdout', format: 'pretty', level: 'info' }
|
||||
]);
|
||||
|
||||
describe('Publish endpoints - add a tag', () => {
|
||||
let req;
|
||||
|
@ -21,18 +17,18 @@ describe('Publish endpoints - add a tag', () => {
|
|||
params: {
|
||||
version: '1.0.0',
|
||||
tag: 'tag',
|
||||
package: 'verdaccio'
|
||||
package: 'verdaccio',
|
||||
},
|
||||
body: ''
|
||||
body: '',
|
||||
};
|
||||
res = {
|
||||
status: jest.fn()
|
||||
status: jest.fn(),
|
||||
};
|
||||
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
test('should add a version', (done) => {
|
||||
test('should add a version', done => {
|
||||
const storage = {
|
||||
addVersion: (packageName, version, body, tag, cb) => {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
|
@ -41,7 +37,7 @@ describe('Publish endpoints - add a tag', () => {
|
|||
expect(tag).toEqual(req.params.tag);
|
||||
cb();
|
||||
done();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -51,15 +47,15 @@ describe('Publish endpoints - add a tag', () => {
|
|||
expect(next).toHaveBeenLastCalledWith({ ok: 'package published' });
|
||||
});
|
||||
|
||||
test('when failed to add a version', (done) => {
|
||||
test('when failed to add a version', done => {
|
||||
const storage = {
|
||||
addVersion: (packageName, version, body, tag, cb) => {
|
||||
const error = {
|
||||
message: 'failure'
|
||||
message: 'failure',
|
||||
};
|
||||
cb(error);
|
||||
done();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -81,10 +77,10 @@ describe('Publish endpoints - upload package tarball', () => {
|
|||
req = {
|
||||
params: {
|
||||
filename: 'verdaccio.gzip',
|
||||
package: 'verdaccio'
|
||||
package: 'verdaccio',
|
||||
},
|
||||
pipe: jest.fn(),
|
||||
on: jest.fn()
|
||||
on: jest.fn(),
|
||||
};
|
||||
res = { status: jest.fn(), report_error: jest.fn() };
|
||||
next = jest.fn();
|
||||
|
@ -94,14 +90,14 @@ describe('Publish endpoints - upload package tarball', () => {
|
|||
const stream = {
|
||||
done: jest.fn(),
|
||||
abort: jest.fn(),
|
||||
on: jest.fn(() => (status, cb) => cb())
|
||||
on: jest.fn(() => (status, cb) => cb()),
|
||||
};
|
||||
const storage = {
|
||||
addTarball(packageName, filename) {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
expect(filename).toEqual(req.params.filename);
|
||||
return stream;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -124,14 +120,14 @@ describe('Publish endpoints - delete tarball', () => {
|
|||
params: {
|
||||
filename: 'verdaccio.gzip',
|
||||
package: 'verdaccio',
|
||||
revision: REVISION_MOCK
|
||||
}
|
||||
revision: REVISION_MOCK,
|
||||
},
|
||||
};
|
||||
res = { status: jest.fn() };
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
test('should delete tarball successfully', (done) => {
|
||||
test('should delete tarball successfully', done => {
|
||||
const storage = {
|
||||
removeTarball(packageName, filename, revision, cb) {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
|
@ -139,7 +135,7 @@ describe('Publish endpoints - delete tarball', () => {
|
|||
expect(revision).toEqual(req.params.revision);
|
||||
cb();
|
||||
done();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -148,15 +144,15 @@ describe('Publish endpoints - delete tarball', () => {
|
|||
expect(next).toHaveBeenCalledWith({ ok: 'tarball removed' });
|
||||
});
|
||||
|
||||
test('failed while deleting the tarball', (done) => {
|
||||
test('failed while deleting the tarball', done => {
|
||||
const error = {
|
||||
message: 'deletion failed'
|
||||
message: 'deletion failed',
|
||||
};
|
||||
const storage = {
|
||||
removeTarball(packageName, filename, revision, cb) {
|
||||
cb(error);
|
||||
done();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -176,20 +172,20 @@ describe('Publish endpoints - un-publish package', () => {
|
|||
beforeEach(() => {
|
||||
req = {
|
||||
params: {
|
||||
package: 'verdaccio'
|
||||
}
|
||||
package: 'verdaccio',
|
||||
},
|
||||
};
|
||||
res = { status: jest.fn() };
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
test('should un-publish package successfully', (done) => {
|
||||
test('should un-publish package successfully', done => {
|
||||
const storage = {
|
||||
removePackage(packageName, cb) {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
cb();
|
||||
done();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -198,15 +194,15 @@ describe('Publish endpoints - un-publish package', () => {
|
|||
expect(next).toHaveBeenCalledWith({ ok: 'package removed' });
|
||||
});
|
||||
|
||||
test('un-publish failed', (done) => {
|
||||
test('un-publish failed', done => {
|
||||
const error = {
|
||||
message: 'un-publish failed'
|
||||
message: 'un-publish failed',
|
||||
};
|
||||
const storage = {
|
||||
removePackage(packageName, cb) {
|
||||
cb(error);
|
||||
done();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -226,11 +222,11 @@ describe('Publish endpoints - publish package', () => {
|
|||
beforeEach(() => {
|
||||
req = {
|
||||
body: {
|
||||
name: 'verdaccio'
|
||||
name: 'verdaccio',
|
||||
},
|
||||
params: {
|
||||
package: 'verdaccio'
|
||||
}
|
||||
package: 'verdaccio',
|
||||
},
|
||||
};
|
||||
res = { status: jest.fn() };
|
||||
next = jest.fn();
|
||||
|
@ -238,7 +234,7 @@ describe('Publish endpoints - publish package', () => {
|
|||
|
||||
test('should change the existing package', () => {
|
||||
const storage = {
|
||||
changePackage: jest.fn()
|
||||
changePackage: jest.fn(),
|
||||
};
|
||||
|
||||
req.params._rev = REVISION_MOCK;
|
||||
|
@ -250,7 +246,7 @@ describe('Publish endpoints - publish package', () => {
|
|||
|
||||
test('should publish a new a new package', () => {
|
||||
const storage = {
|
||||
addPackage: jest.fn()
|
||||
addPackage: jest.fn(),
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -262,7 +258,7 @@ describe('Publish endpoints - publish package', () => {
|
|||
const storage = {
|
||||
addPackage() {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -276,23 +272,23 @@ describe('Publish endpoints - publish package', () => {
|
|||
changePackage: jest.fn(),
|
||||
getPackage({ callback }) {
|
||||
callback(null, {
|
||||
users: {}
|
||||
users: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
req = {
|
||||
params: {
|
||||
package: 'verdaccio'
|
||||
package: 'verdaccio',
|
||||
},
|
||||
body: {
|
||||
_rev: REVISION_MOCK,
|
||||
users: {
|
||||
verdaccio: true
|
||||
}
|
||||
verdaccio: true,
|
||||
},
|
||||
},
|
||||
remote_user: {
|
||||
name: 'verdaccio'
|
||||
}
|
||||
name: 'verdaccio',
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
|
@ -12,9 +12,8 @@ import fs from 'fs';
|
|||
app.param('token', validate_name);
|
||||
*/
|
||||
describe('api endpoint app.param()', () => {
|
||||
const file = '../endpoint/index.ts';
|
||||
let m;
|
||||
const requirePath = path.normalize(path.join(__dirname + '/../../../../src/api/web/', file));
|
||||
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 = {};
|
9
packages/api/tsconfig.json
Normal file
9
packages/api/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*", "types/*.d.ts"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/auth/.babelrc
Normal file
3
packages/auth/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": [["@verdaccio"]]
|
||||
}
|
9
packages/auth/jest.config.js
Normal file
9
packages/auth/jest.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
transform: {
|
||||
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
|
||||
},
|
||||
verbose: true,
|
||||
collectCoverage: true,
|
||||
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
|
||||
};
|
39
packages/auth/package.json
Normal file
39
packages/auth/package.json
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "@verdaccio/auth",
|
||||
"version": "5.0.0-alpha.0",
|
||||
"description": "logger",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"homepage": "https://verdaccio.org",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "npm run build:js && npm run build:types"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@verdaccio/commons-api": "^9.0.0",
|
||||
"@verdaccio/dev-commons": "5.0.0-alpha.0",
|
||||
"@verdaccio/loaders": "5.0.0-alpha.0",
|
||||
"@verdaccio/utils": "5.0.0-alpha.0",
|
||||
"bunyan": "1.8.12",
|
||||
"express": "^4.17.1",
|
||||
"lodash": "^4.17.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bunyan": "1.8.6",
|
||||
"@verdaccio/dev-types": "5.0.0-alpha.0"
|
||||
},
|
||||
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
|
||||
}
|
|
@ -1,13 +1,10 @@
|
|||
import _ from 'lodash';
|
||||
import { VerdaccioError } from '@verdaccio/commons-api';
|
||||
import buildDebug from 'debug';
|
||||
import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types';
|
||||
import { NextFunction } from 'express';
|
||||
import loadPlugin from '../lib/plugin-loader';
|
||||
import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '../../types';
|
||||
import { API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER } from './constants';
|
||||
import { aesEncrypt, signPayload } from './crypto-utils';
|
||||
import { logger } from './logger';
|
||||
|
||||
import { VerdaccioError } from '@verdaccio/commons-api';
|
||||
import {API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER} from '@verdaccio/dev-commons';
|
||||
import { loadPlugin } from '@verdaccio/loaders';
|
||||
import { aesEncrypt, signPayload } from '@verdaccio/utils';
|
||||
import {
|
||||
getDefaultPlugins,
|
||||
getMiddlewareCredentials,
|
||||
|
@ -16,24 +13,29 @@ import {
|
|||
isAuthHeaderValid,
|
||||
getSecurity,
|
||||
isAESLegacy,
|
||||
convertPayloadToBase64,
|
||||
ErrorCode,
|
||||
parseAuthTokenHeader,
|
||||
parseBasicPayload,
|
||||
createRemoteUser,
|
||||
} from './auth-utils';
|
||||
import { convertPayloadToBase64, ErrorCode } from './utils';
|
||||
import { getMatchedPackagesSpec } from './config-utils';
|
||||
} from '@verdaccio/utils';
|
||||
|
||||
const debug = buildDebug('verdaccio:auth');
|
||||
import { getMatchedPackagesSpec } from '@verdaccio/utils';
|
||||
import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types';
|
||||
import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '@verdaccio/dev-types';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const LoggerApi = require('@verdaccio/logger');
|
||||
|
||||
class Auth implements IAuth {
|
||||
public config: Config;
|
||||
public logger: Logger;
|
||||
public secret: string; // pragma: allowlist secret
|
||||
public secret: string;
|
||||
public plugins: IPluginAuth<Config>[];
|
||||
|
||||
public constructor(config: Config) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
this.logger = LoggerApi.logger.child({ sub: 'auth' });
|
||||
this.secret = config.secret;
|
||||
this.plugins = this._loadPlugin(config);
|
||||
this._applyDefaultPlugins();
|
||||
|
@ -45,49 +47,56 @@ class Auth implements IAuth {
|
|||
logger: this.logger,
|
||||
};
|
||||
|
||||
return loadPlugin<IPluginAuth<Config>>(config, config.auth, pluginOptions, (plugin: IPluginAuth<Config>): boolean => {
|
||||
const { authenticate, allow_access, allow_publish } = plugin;
|
||||
// @ts-ignore
|
||||
return authenticate || allow_access || allow_publish;
|
||||
});
|
||||
return loadPlugin<IPluginAuth<Config>>(
|
||||
config,
|
||||
config.auth,
|
||||
pluginOptions,
|
||||
(plugin: IPluginAuth<Config>): boolean => {
|
||||
const { authenticate, allow_access, allow_publish } = plugin;
|
||||
|
||||
// @ts-ignore
|
||||
return authenticate || allow_access || allow_publish;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _applyDefaultPlugins(): void {
|
||||
this.plugins.push(getDefaultPlugins(this.logger));
|
||||
this.plugins.push(getDefaultPlugins());
|
||||
}
|
||||
|
||||
public changePassword(
|
||||
username: string,
|
||||
password: string, // pragma: allowlist secret
|
||||
newPassword: string, // pragma: allowlist secret
|
||||
cb: Callback
|
||||
): void {
|
||||
const validPlugins = _.filter(this.plugins, (plugin) => _.isFunction(plugin.changePassword));
|
||||
public changePassword(username: string, password: string, newPassword: string, cb: Callback): void {
|
||||
const validPlugins = _.filter(this.plugins, plugin => _.isFunction(plugin.changePassword));
|
||||
|
||||
if (_.isEmpty(validPlugins)) {
|
||||
return cb(ErrorCode.getInternalError(SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
|
||||
}
|
||||
if (_.isEmpty(validPlugins)) {
|
||||
return cb(ErrorCode.getInternalError(SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
|
||||
}
|
||||
|
||||
for (const plugin of validPlugins) {
|
||||
if (_.isNil(plugin) || _.isFunction(plugin.changePassword) === false) {
|
||||
debug('auth plugin does not implement changePassword, trying next one');
|
||||
continue;
|
||||
} else {
|
||||
debug('updating password for %o', username);
|
||||
plugin.changePassword!(username, password, newPassword, (err, profile): void => {
|
||||
if (err) {
|
||||
this.logger.error(
|
||||
{ username, err },
|
||||
`An error has been produced
|
||||
for (const plugin of validPlugins) {
|
||||
if (_.isNil(plugin) || _.isFunction(plugin.changePassword) === false) {
|
||||
this.logger.trace('auth plugin does not implement changePassword, trying next one');
|
||||
continue;
|
||||
} else {
|
||||
this.logger.trace({username}, 'updating password for @{username}');
|
||||
plugin.changePassword!(
|
||||
username,
|
||||
password,
|
||||
newPassword,
|
||||
(err, profile): void => {
|
||||
if (err) {
|
||||
this.logger.error(
|
||||
{username, err},
|
||||
`An error has been produced
|
||||
updating the password for @{username}. Error: @{err.message}`
|
||||
);
|
||||
return cb(err);
|
||||
}
|
||||
this.logger.info({ username }, 'updated password for @{username} was successful');
|
||||
return cb(null, profile);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.logger.trace({username}, 'updated password for @{username} was successful');
|
||||
return cb(null, profile);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public authenticate(username: string, password: string, cb: Callback): void {
|
||||
|
@ -95,13 +104,15 @@ class Auth implements IAuth {
|
|||
const self = this;
|
||||
(function next(): void {
|
||||
const plugin = plugins.shift() as IPluginAuth<Config>;
|
||||
|
||||
if (_.isFunction(plugin.authenticate) === false) {
|
||||
return next();
|
||||
}
|
||||
debug('authenticating %o', username);
|
||||
plugin.authenticate(username, password, function (err, groups): void {
|
||||
|
||||
self.logger.trace({ username }, 'authenticating @{username}');
|
||||
plugin.authenticate(username, password, function(err, groups): void {
|
||||
if (err) {
|
||||
self.logger.error({ username, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
|
||||
self.logger.trace({ username, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
|
@ -121,7 +132,8 @@ class Auth implements IAuth {
|
|||
if (!isGroupValid) {
|
||||
throw new TypeError(API_ERROR.BAD_FORMAT_USER_GROUP);
|
||||
}
|
||||
debug('authentication for user %o was successfully. Groups: %o', username, groups);
|
||||
|
||||
self.logger.trace({ username, groups }, 'authentication for user @{username} was successfully. Groups: @{groups}');
|
||||
return cb(err, createRemoteUser(username, groups));
|
||||
}
|
||||
next();
|
||||
|
@ -132,7 +144,8 @@ class Auth implements IAuth {
|
|||
public add_user(user: string, password: string, cb: Callback): void {
|
||||
const self = this;
|
||||
const plugins = this.plugins.slice(0);
|
||||
debug('add user %o', user);
|
||||
this.logger.trace({ user }, 'add user @{user}');
|
||||
|
||||
(function next(): void {
|
||||
const plugin = plugins.shift() as IPluginAuth<Config>;
|
||||
let method = 'adduser';
|
||||
|
@ -145,13 +158,13 @@ class Auth implements IAuth {
|
|||
next();
|
||||
} else {
|
||||
// p.add_user() execution
|
||||
plugin[method](user, password, function (err, ok): void {
|
||||
plugin[method](user, password, function(err, ok): void {
|
||||
if (err) {
|
||||
self.logger.error({ user, err: err.message }, 'the user @{user} could not being added. Error: @{err}');
|
||||
self.logger.trace({ user, err: err.message }, 'the user @{user} could not being added. Error: @{err}');
|
||||
return cb(err);
|
||||
}
|
||||
if (ok) {
|
||||
self.logger.info({ user }, 'the user @{user} has been added');
|
||||
self.logger.trace({ user }, 'the user @{user} has been added');
|
||||
return self.authenticate(user, password, cb);
|
||||
}
|
||||
next();
|
||||
|
@ -165,10 +178,10 @@ class Auth implements IAuth {
|
|||
*/
|
||||
public allow_access({ packageName, packageVersion }: AuthPluginPackage, user: RemoteUser, callback: Callback): void {
|
||||
const plugins = this.plugins.slice(0);
|
||||
const self = this;
|
||||
const pkgAllowAcces: AllowAccess = { name: packageName, version: packageVersion };
|
||||
const pkg = Object.assign({}, pkgAllowAcces, getMatchedPackagesSpec(packageName, this.config.packages)) as AllowAccess & PackageAccess;
|
||||
debug('allow access for %o', packageName);
|
||||
const self = this;
|
||||
this.logger.trace({ packageName }, 'allow access for @{packageName}');
|
||||
|
||||
(function next(): void {
|
||||
const plugin: IPluginAuth<Config> = plugins.shift() as IPluginAuth<Config>;
|
||||
|
@ -177,14 +190,14 @@ class Auth implements IAuth {
|
|||
return next();
|
||||
}
|
||||
|
||||
plugin.allow_access!(user, pkg, function (err, ok: boolean): void {
|
||||
plugin.allow_access!(user, pkg, function(err, ok: boolean): void {
|
||||
if (err) {
|
||||
self.logger.error({ packageName, err }, 'forbidden access for @{packageName}. Error: @{err.message}');
|
||||
self.logger.trace({ packageName, err }, 'forbidden access for @{packageName}. Error: @{err.message}');
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
self.logger.info({ packageName }, 'allowed access for @{packageName}');
|
||||
self.logger.trace({ packageName }, 'allowed access for @{packageName}');
|
||||
return callback(null, ok);
|
||||
}
|
||||
|
||||
|
@ -195,30 +208,35 @@ class Auth implements IAuth {
|
|||
|
||||
public allow_unpublish({ packageName, packageVersion }: AuthPluginPackage, user: RemoteUser, callback: Callback): void {
|
||||
const pkg = Object.assign({ name: packageName, version: packageVersion }, getMatchedPackagesSpec(packageName, this.config.packages));
|
||||
debug('allow unpublish for %o', packageName);
|
||||
this.logger.trace({ packageName }, 'allow unpublish for @{packageName}');
|
||||
|
||||
for (const plugin of this.plugins) {
|
||||
if (_.isNil(plugin) || _.isFunction(plugin.allow_unpublish) === false) {
|
||||
debug('allow unpublish for %o plugin does not implement allow_unpublish', packageName);
|
||||
this.logger.trace({ packageName }, 'allow unpublish for @{packageName} plugin does not implement allow_unpublish');
|
||||
continue;
|
||||
} else {
|
||||
plugin.allow_unpublish!(user, pkg, (err, ok: boolean): void => {
|
||||
if (err) {
|
||||
this.logger.error({ packageName, user: user?.name }, '@{user} forbidden publish for @{packageName}, it will fallback on unpublish permissions');
|
||||
return callback(err);
|
||||
}
|
||||
plugin.allow_unpublish!(
|
||||
user,
|
||||
pkg,
|
||||
(err, ok: boolean): void => {
|
||||
if (err) {
|
||||
this.logger.trace({ packageName }, 'forbidden publish for @{packageName}, it will fallback on unpublish permissions');
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (_.isNil(ok) === true) {
|
||||
debug('we bypass unpublish for %o, publish will handle the access', packageName);
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
return this.allow_publish(...arguments);
|
||||
}
|
||||
if (_.isNil(ok) === true) {
|
||||
this.logger.trace({ packageName }, 'we bypass unpublish for @{packageName}, publish will handle the access');
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
return this.allow_publish(...arguments);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
this.logger.info({ packageName, user: user?.name }, '@{user} allowed unpublish for @{packageName}');
|
||||
return callback(null, ok);
|
||||
if (ok) {
|
||||
this.logger.trace({ packageName }, 'allowed unpublish for @{packageName}');
|
||||
return callback(null, ok);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,29 +248,36 @@ class Auth implements IAuth {
|
|||
const plugins = this.plugins.slice(0);
|
||||
const self = this;
|
||||
const pkg = Object.assign({ name: packageName, version: packageVersion }, getMatchedPackagesSpec(packageName, this.config.packages));
|
||||
debug('allow publish for %o init | plugins: %o', packageName, plugins);
|
||||
this.logger.trace({ packageName, plugins: this.plugins.length }, 'allow publish for @{packageName} init | plugins: @{plugins}');
|
||||
|
||||
(function next(): void {
|
||||
const plugin = plugins.shift();
|
||||
|
||||
if (_.isNil(plugin) || _.isFunction(plugin.allow_publish) === false) {
|
||||
debug('allow publish for %o plugin does not implement allow_publish', packageName);
|
||||
self.logger.trace({ packageName }, 'allow publish for @{packageName} plugin does not implement allow_publish');
|
||||
return next();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
plugin.allow_publish(user, pkg, (err: VerdaccioError, ok: boolean): void => {
|
||||
if (_.isNil(err) === false && _.isError(err)) {
|
||||
self.logger.error({ packageName, user: user?.name }, '@{user} is forbidden publish for @{packageName}');
|
||||
return callback(err);
|
||||
}
|
||||
plugin.allow_publish(
|
||||
user,
|
||||
pkg,
|
||||
// @ts-ignore
|
||||
(err: VerdaccioError, ok: boolean): void => {
|
||||
if (_.isNil(err) === false && _.isError(err)) {
|
||||
self.logger.trace({ packageName }, 'forbidden publish for @{packageName}');
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
self.logger.info({ packageName, user: user?.name }, '@{user} is allowed publish for @{packageName}');
|
||||
return callback(null, ok);
|
||||
if (ok) {
|
||||
self.logger.trace({ packageName }, 'allowed publish for @{packageName}');
|
||||
return callback(null, ok);
|
||||
}
|
||||
|
||||
self.logger.trace({ packageName }, 'allow publish skip validation for @{packageName}');
|
||||
next(); // cb(null, false) causes next plugin to roll
|
||||
}
|
||||
debug('allow publish skip validation for %o', packageName);
|
||||
next(); // cb(null, false) causes next plugin to roll
|
||||
});
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
|
@ -268,7 +293,7 @@ class Auth implements IAuth {
|
|||
return (req: $RequestExtend, res: $ResponseExtend, _next: NextFunction): void => {
|
||||
req.pause();
|
||||
|
||||
const next = function (err: VerdaccioError | void): void {
|
||||
const next = function(err: VerdaccioError | void): void {
|
||||
req.resume();
|
||||
// uncomment this to reject users with bad auth headers
|
||||
// return _next.apply(null, arguments)
|
||||
|
@ -293,7 +318,7 @@ class Auth implements IAuth {
|
|||
}
|
||||
|
||||
if (!isAuthHeaderValid(authorization)) {
|
||||
debug('api middleware auth heather is not valid');
|
||||
this.logger.trace('api middleware auth heather is not valid');
|
||||
return next(ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER));
|
||||
}
|
||||
|
||||
|
@ -301,10 +326,10 @@ class Auth implements IAuth {
|
|||
const { secret } = this.config;
|
||||
|
||||
if (isAESLegacy(security)) {
|
||||
debug('api middleware using legacy auth token');
|
||||
this.logger.trace('api middleware using legacy auth token');
|
||||
this._handleAESMiddleware(req, security, secret, authorization, next);
|
||||
} else {
|
||||
debug('api middleware using JWT auth token');
|
||||
this.logger.trace('api middleware using JWT auth token');
|
||||
this._handleJWTAPIMiddleware(req, security, secret, authorization, next);
|
||||
}
|
||||
};
|
||||
|
@ -316,15 +341,19 @@ class Auth implements IAuth {
|
|||
// this should happen when client tries to login with an existing user
|
||||
const credentials = convertPayloadToBase64(token).toString();
|
||||
const { user, password } = parseBasicPayload(credentials) as AESPayload;
|
||||
this.authenticate(user, password, (err, user): void => {
|
||||
if (!err) {
|
||||
req.remote_user = user;
|
||||
next();
|
||||
} else {
|
||||
req.remote_user = createAnonymousRemoteUser();
|
||||
next(err);
|
||||
this.authenticate(
|
||||
user,
|
||||
password,
|
||||
(err, user): void => {
|
||||
if (!err) {
|
||||
req.remote_user = user;
|
||||
next();
|
||||
} else {
|
||||
req.remote_user = createAnonymousRemoteUser();
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
} else {
|
||||
// jwt handler
|
||||
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
|
||||
|
@ -343,15 +372,19 @@ class Auth implements IAuth {
|
|||
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
|
||||
if (credentials) {
|
||||
const { user, password } = credentials;
|
||||
this.authenticate(user, password, (err, user): void => {
|
||||
if (!err) {
|
||||
req.remote_user = user;
|
||||
next();
|
||||
} else {
|
||||
req.remote_user = createAnonymousRemoteUser();
|
||||
next(err);
|
||||
this.authenticate(
|
||||
user,
|
||||
password,
|
||||
(err, user): void => {
|
||||
if (!err) {
|
||||
req.remote_user = user;
|
||||
next();
|
||||
} else {
|
||||
req.remote_user = createAnonymousRemoteUser();
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
} else {
|
||||
// we force npm client to ask again with basic authentication
|
||||
return next(ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER));
|
||||
|
@ -438,4 +471,4 @@ class Auth implements IAuth {
|
|||
}
|
||||
}
|
||||
|
||||
export default Auth;
|
||||
export { Auth };
|
1
packages/auth/src/index.ts
Normal file
1
packages/auth/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { Auth } from './auth'
|
|
@ -1,27 +1,35 @@
|
|||
import _ from 'lodash';
|
||||
import Auth from '../../../../src/lib/auth';
|
||||
import { CHARACTER_ENCODING, TOKEN_BEARER } from '../../../../src/lib/constants';
|
||||
// $FlowFixMe
|
||||
import configExample from '../../partials/config';
|
||||
import AppConfig from '../../../../src/lib/config';
|
||||
import { setup } from '../../../../src/lib/logger';
|
||||
import { Auth } from '@verdaccio/auth';
|
||||
import {CHARACTER_ENCODING, TOKEN_BEARER} from '@verdaccio/dev-commons';
|
||||
|
||||
import { configExample } from '@verdaccio/mock';
|
||||
import {Config as AppConfig } from '@verdaccio/config';
|
||||
import {setup} from '@verdaccio/logger';
|
||||
|
||||
import { buildToken, convertPayloadToBase64, parseConfigFile } from '../../../../src/lib/utils';
|
||||
import {
|
||||
buildUserBuffer,
|
||||
getApiToken,
|
||||
getAuthenticatedMessage,
|
||||
getMiddlewareCredentials,
|
||||
getSecurity
|
||||
} from '../../../../src/lib/auth-utils';
|
||||
import { aesDecrypt, verifyPayload } from '../../../../src/lib/crypto-utils';
|
||||
import { parseConfigurationFile } from '../../__helper';
|
||||
getSecurity,
|
||||
aesDecrypt, verifyPayload,
|
||||
buildToken, convertPayloadToBase64, parseConfigFile
|
||||
} from '@verdaccio/utils';
|
||||
|
||||
import { IAuth } from '../../../../types';
|
||||
import { Config, Security, RemoteUser } from '@verdaccio/types';
|
||||
import { IAuth } from '@verdaccio/dev-types';
|
||||
import {Config, Security, RemoteUser} from '@verdaccio/types';
|
||||
import path from "path";
|
||||
|
||||
setup([]);
|
||||
|
||||
const parseConfigurationFile = (conf) => {
|
||||
const { name, ext } = path.parse(conf);
|
||||
const format = ext.startsWith('.') ? ext.substring(1) : 'yaml';
|
||||
|
||||
return path.join(__dirname, `./partials/config/${format}/security/${name}.${format}`);
|
||||
};
|
||||
|
||||
|
||||
describe('Auth utilities', () => {
|
||||
jest.setTimeout(20000);
|
||||
|
||||
|
@ -31,7 +39,7 @@ describe('Auth utilities', () => {
|
|||
|
||||
function getConfig(configFileName: string, secret: string) {
|
||||
const conf = parseConfigFile(parseConfigurationSecurityFile(configFileName));
|
||||
const secConf = _.merge(configExample(), conf);
|
||||
const secConf= _.merge(configExample(), conf);
|
||||
secConf.secret = secret;
|
||||
const config: Config = new AppConfig(secConf);
|
||||
|
||||
|
@ -44,8 +52,7 @@ describe('Auth utilities', () => {
|
|||
password: string,
|
||||
secret = '12345',
|
||||
methodToSpy: string,
|
||||
methodNotBeenCalled: string
|
||||
): Promise<string> {
|
||||
methodNotBeenCalled: string): Promise<string> {
|
||||
const config: Config = getConfig(configFileName, secret);
|
||||
const auth: IAuth = new Auth(config);
|
||||
// @ts-ignore
|
||||
|
@ -74,9 +81,7 @@ describe('Auth utilities', () => {
|
|||
};
|
||||
|
||||
const verifyAES = (token: string, user: string, password: string, secret: string) => {
|
||||
const payload = aesDecrypt(convertPayloadToBase64(token), secret).toString(
|
||||
CHARACTER_ENCODING.UTF8
|
||||
);
|
||||
const payload = aesDecrypt(convertPayloadToBase64(token), secret).toString(CHARACTER_ENCODING.UTF8);
|
||||
const content = payload.split(':');
|
||||
|
||||
expect(content[0]).toBe(user);
|
||||
|
@ -85,98 +90,56 @@ describe('Auth utilities', () => {
|
|||
|
||||
describe('getApiToken test', () => {
|
||||
test('should sign token with aes and security missing', async () => {
|
||||
const token = await signCredentials(
|
||||
'security-missing',
|
||||
'test',
|
||||
'test',
|
||||
'1234567',
|
||||
'aesEncrypt',
|
||||
'jwtEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-missing',
|
||||
'test', 'test', '1234567', 'aesEncrypt', 'jwtEncrypt');
|
||||
|
||||
verifyAES(token, 'test', 'test', '1234567');
|
||||
expect(_.isString(token)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should sign token with aes and security empty', async () => {
|
||||
const token = await signCredentials(
|
||||
'security-empty',
|
||||
'test',
|
||||
'test',
|
||||
'123456',
|
||||
'aesEncrypt',
|
||||
'jwtEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-empty',
|
||||
'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
|
||||
|
||||
verifyAES(token, 'test', 'test', '123456');
|
||||
expect(_.isString(token)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should sign token with aes', async () => {
|
||||
const token = await signCredentials(
|
||||
'security-basic',
|
||||
'test',
|
||||
'test',
|
||||
'123456',
|
||||
'aesEncrypt',
|
||||
'jwtEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-basic',
|
||||
'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
|
||||
|
||||
verifyAES(token, 'test', 'test', '123456');
|
||||
expect(_.isString(token)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should sign token with legacy and jwt disabled', async () => {
|
||||
const token = await signCredentials(
|
||||
'security-no-legacy',
|
||||
'test',
|
||||
'test',
|
||||
'x8T#ZCx=2t',
|
||||
'aesEncrypt',
|
||||
'jwtEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-no-legacy',
|
||||
'test', 'test', 'x8T#ZCx=2t', 'aesEncrypt', 'jwtEncrypt');
|
||||
|
||||
expect(_.isString(token)).toBeTruthy();
|
||||
verifyAES(token, 'test', 'test', 'x8T#ZCx=2t');
|
||||
});
|
||||
|
||||
test('should sign token with legacy enabled and jwt enabled', async () => {
|
||||
const token = await signCredentials(
|
||||
'security-jwt-legacy-enabled',
|
||||
'test',
|
||||
'test',
|
||||
'secret',
|
||||
'jwtEncrypt',
|
||||
'aesEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-jwt-legacy-enabled',
|
||||
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
|
||||
|
||||
verifyJWT(token, 'test', 'test', 'secret');
|
||||
expect(_.isString(token)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should sign token with jwt enabled', async () => {
|
||||
const token = await signCredentials(
|
||||
'security-jwt',
|
||||
'test',
|
||||
'test',
|
||||
'secret',
|
||||
'jwtEncrypt',
|
||||
'aesEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-jwt',
|
||||
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
|
||||
|
||||
expect(_.isString(token)).toBeTruthy();
|
||||
verifyJWT(token, 'test', 'test', 'secret');
|
||||
});
|
||||
|
||||
test('should sign with jwt whether legacy is disabled', async () => {
|
||||
const token = await signCredentials(
|
||||
'security-legacy-disabled',
|
||||
'test',
|
||||
'test',
|
||||
'secret',
|
||||
'jwtEncrypt',
|
||||
'aesEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-legacy-disabled',
|
||||
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
|
||||
|
||||
expect(_.isString(token)).toBeTruthy();
|
||||
verifyJWT(token, 'test', 'test', 'secret');
|
||||
|
@ -185,7 +148,7 @@ describe('Auth utilities', () => {
|
|||
|
||||
describe('getAuthenticatedMessage test', () => {
|
||||
test('should sign token with jwt enabled', () => {
|
||||
expect(getAuthenticatedMessage('test')).toBe("you are authenticated as 'test'");
|
||||
expect(getAuthenticatedMessage('test')).toBe('you are authenticated as \'test\'');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -195,14 +158,8 @@ describe('Auth utilities', () => {
|
|||
const secret = 'secret';
|
||||
const user = 'test';
|
||||
const pass = 'test';
|
||||
const token = await signCredentials(
|
||||
'security-legacy',
|
||||
user,
|
||||
pass,
|
||||
secret,
|
||||
'aesEncrypt',
|
||||
'jwtEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-legacy',
|
||||
user, pass, secret, 'aesEncrypt', 'jwtEncrypt');
|
||||
const config: Config = getConfig('security-legacy', secret);
|
||||
const security: Security = getSecurity(config);
|
||||
const credentials = getMiddlewareCredentials(security, secret, `Bearer ${token}`);
|
||||
|
@ -230,41 +187,21 @@ describe('Auth utilities', () => {
|
|||
|
||||
test.concurrent('should return empty credential wrong secret key', async () => {
|
||||
const secret = 'secret';
|
||||
const token = await signCredentials(
|
||||
'security-legacy',
|
||||
'test',
|
||||
'test',
|
||||
secret,
|
||||
'aesEncrypt',
|
||||
'jwtEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-legacy',
|
||||
'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
|
||||
const config: Config = getConfig('security-legacy', secret);
|
||||
const security: Security = getSecurity(config);
|
||||
const credentials = getMiddlewareCredentials(
|
||||
security,
|
||||
'BAD_SECRET',
|
||||
buildToken(TOKEN_BEARER, token)
|
||||
);
|
||||
const credentials = getMiddlewareCredentials(security, 'BAD_SECRET', buildToken(TOKEN_BEARER, token));
|
||||
expect(credentials).not.toBeDefined();
|
||||
});
|
||||
|
||||
test.concurrent('should return empty credential wrong scheme', async () => {
|
||||
const secret = 'secret';
|
||||
const token = await signCredentials(
|
||||
'security-legacy',
|
||||
'test',
|
||||
'test',
|
||||
secret,
|
||||
'aesEncrypt',
|
||||
'jwtEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-legacy',
|
||||
'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
|
||||
const config: Config = getConfig('security-legacy', secret);
|
||||
const security: Security = getSecurity(config);
|
||||
const credentials = getMiddlewareCredentials(
|
||||
security,
|
||||
secret,
|
||||
buildToken('BAD_SCHEME', token)
|
||||
);
|
||||
const credentials = getMiddlewareCredentials(security, secret, buildToken('BAD_SCHEME', token));
|
||||
expect(credentials).not.toBeDefined();
|
||||
});
|
||||
|
||||
|
@ -274,11 +211,7 @@ describe('Auth utilities', () => {
|
|||
const auth: IAuth = new Auth(config);
|
||||
const token = auth.aesEncrypt(Buffer.from(`corruptedBuffer`)).toString('base64');
|
||||
const security: Security = getSecurity(config);
|
||||
const credentials = getMiddlewareCredentials(
|
||||
security,
|
||||
secret,
|
||||
buildToken(TOKEN_BEARER, token)
|
||||
);
|
||||
const credentials = getMiddlewareCredentials(security, secret, buildToken(TOKEN_BEARER, token));
|
||||
expect(credentials).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
@ -287,11 +220,7 @@ describe('Auth utilities', () => {
|
|||
test('should return anonymous whether token is corrupted', () => {
|
||||
const config: Config = getConfig('security-jwt', '12345');
|
||||
const security: Security = getSecurity(config);
|
||||
const credentials = getMiddlewareCredentials(
|
||||
security,
|
||||
'12345',
|
||||
buildToken(TOKEN_BEARER, 'fakeToken')
|
||||
);
|
||||
const credentials = getMiddlewareCredentials(security, '12345', buildToken(TOKEN_BEARER, 'fakeToken'));
|
||||
|
||||
expect(credentials).toBeDefined();
|
||||
// @ts-ignore
|
||||
|
@ -305,11 +234,7 @@ describe('Auth utilities', () => {
|
|||
test('should return anonymous whether token and scheme are corrupted', () => {
|
||||
const config: Config = getConfig('security-jwt', '12345');
|
||||
const security: Security = getSecurity(config);
|
||||
const credentials = getMiddlewareCredentials(
|
||||
security,
|
||||
'12345',
|
||||
buildToken('FakeScheme', 'fakeToken')
|
||||
);
|
||||
const credentials = getMiddlewareCredentials(security, '12345', buildToken('FakeScheme', 'fakeToken'));
|
||||
|
||||
expect(credentials).not.toBeDefined();
|
||||
});
|
||||
|
@ -318,20 +243,10 @@ describe('Auth utilities', () => {
|
|||
const secret = 'secret';
|
||||
const user = 'test';
|
||||
const config: Config = getConfig('security-jwt', secret);
|
||||
const token = await signCredentials(
|
||||
'security-jwt',
|
||||
user,
|
||||
'secretTest',
|
||||
secret,
|
||||
'jwtEncrypt',
|
||||
'aesEncrypt'
|
||||
);
|
||||
const token = await signCredentials('security-jwt',
|
||||
user, 'secretTest', secret, 'jwtEncrypt', 'aesEncrypt');
|
||||
const security: Security = getSecurity(config);
|
||||
const credentials = getMiddlewareCredentials(
|
||||
security,
|
||||
secret,
|
||||
buildToken(TOKEN_BEARER, token)
|
||||
);
|
||||
const credentials = getMiddlewareCredentials(security, secret, buildToken(TOKEN_BEARER, token));
|
||||
expect(credentials).toBeDefined();
|
||||
// @ts-ignore
|
||||
expect(credentials.name).toEqual(user);
|
14
packages/auth/test/crypto-utils.spec.ts
Normal file
14
packages/auth/test/crypto-utils.spec.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {aesDecrypt, aesEncrypt, convertPayloadToBase64} from "@verdaccio/utils";
|
||||
|
||||
describe('test crypto utils', () => {
|
||||
describe('default encryption', () => {
|
||||
test('decrypt payload flow', () => {
|
||||
const payload = 'juan';
|
||||
const token = aesEncrypt(Buffer.from(payload), '12345').toString('base64');
|
||||
|
||||
const data = aesDecrypt(convertPayloadToBase64(token), '12345').toString('utf8');
|
||||
|
||||
expect(payload).toEqual(data);
|
||||
});
|
||||
});
|
||||
});
|
9
packages/auth/tsconfig.json
Normal file
9
packages/auth/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/cli/.babelrc
Normal file
3
packages/cli/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": [["@verdaccio"]]
|
||||
}
|
3
packages/cli/bin/verdaccio
Executable file
3
packages/cli/bin/verdaccio
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../build');
|
9
packages/cli/jest.config.js
Normal file
9
packages/cli/jest.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
transform: {
|
||||
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
|
||||
},
|
||||
verbose: true,
|
||||
collectCoverage: true,
|
||||
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
|
||||
};
|
40
packages/cli/package.json
Normal file
40
packages/cli/package.json
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "@verdaccio/cli",
|
||||
"version": "5.0.0-alpha.0",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
},
|
||||
"bin": {
|
||||
"verdaccio": "./bin/verdaccio",
|
||||
"verdaccio-cli": "./bin/verdaccio"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"homepage": "https://verdaccio.org",
|
||||
"description": "verdaccio CLI",
|
||||
"license": "MIT",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "npm run build:js && npm run build:types",
|
||||
"bundle": "cross-env BABEL_ENV=registry webpack --config scripts/bundle.js --profile --json > compilation-stats.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"envinfo": "7.4.0",
|
||||
"kleur": "3.0.3",
|
||||
"semver": "7.1.2"
|
||||
},
|
||||
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
|
||||
}
|
5
packages/cli/src/.eslintrc
Normal file
5
packages/cli/src/.eslintrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"rules": {
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
60
packages/cli/src/cli.ts
Normal file
60
packages/cli/src/cli.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/* eslint no-sync:0 */
|
||||
/* eslint no-empty:0 */
|
||||
|
||||
import commander from 'commander';
|
||||
import { bgYellow, bgRed } from 'kleur';
|
||||
|
||||
import { setup, logger } from '@verdaccio/logger';
|
||||
|
||||
import infoCommand from "./commands/info";
|
||||
import initProgram from "./commands/init";
|
||||
import {isVersionValid, MIN_NODE_VERSION} from "./utils";
|
||||
|
||||
const isRootUser = process.getuid && process.getuid() === 0;
|
||||
|
||||
if (isRootUser) {
|
||||
global.console.warn(bgYellow().red('*** WARNING: Verdaccio doesn\'t need superuser privileges. Don\'t run it under root! ***'));
|
||||
}
|
||||
|
||||
if (isVersionValid()) {
|
||||
global.console.error(bgRed(`Verdaccio requires at least Node.js ${MIN_NODE_VERSION} or higher, please upgrade your Node.js distribution`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.title = 'verdaccio';
|
||||
|
||||
// default setup
|
||||
setup(null, {logStart: false});
|
||||
|
||||
const pkgVersion = '5.0.0';
|
||||
const pkgName = 'verdaccio';
|
||||
|
||||
commander
|
||||
.option('-i, --info', 'prints debugging information about the local environment')
|
||||
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)')
|
||||
.option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)')
|
||||
.version(pkgVersion)
|
||||
.parse(process.argv);
|
||||
|
||||
const fallbackConfig = commander.args.length == 1 && !commander.config;
|
||||
const isHelp = commander.args.length !== 0;
|
||||
|
||||
if (commander.info) {
|
||||
infoCommand();
|
||||
} else if (fallbackConfig) {
|
||||
// handling "verdaccio [config]" case if "-c" is missing in command line
|
||||
commander.config = commander.args.pop();
|
||||
initProgram(commander, pkgVersion, pkgName);
|
||||
} else if (isHelp) {
|
||||
commander.help();
|
||||
} else {
|
||||
initProgram(commander, pkgVersion, pkgName);
|
||||
}
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
logger.fatal( {
|
||||
err: err,
|
||||
},
|
||||
'uncaught exception, please report (https://github.com/verdaccio/verdaccio/issues) this: \n@{err.stack}' );
|
||||
process.exit(255);
|
||||
});
|
18
packages/cli/src/commands/info.ts
Normal file
18
packages/cli/src/commands/info.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import envinfo from 'envinfo';
|
||||
|
||||
export default function infoCommand() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\nEnvironment Info:');
|
||||
(async () => {
|
||||
const data = await envinfo.run({
|
||||
System: ['OS', 'CPU'],
|
||||
Binaries: ['Node', 'Yarn', 'npm'],
|
||||
Virtualization: ['Docker'],
|
||||
Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'],
|
||||
npmGlobalPackages: ['verdaccio'],
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data);
|
||||
process.exit(0);
|
||||
})();
|
||||
}
|
42
packages/cli/src/commands/init.ts
Normal file
42
packages/cli/src/commands/init.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import path from "path";
|
||||
import _ from 'lodash';
|
||||
|
||||
import { parseConfigFile} from "@verdaccio/utils";
|
||||
import { findConfigFile } from "@verdaccio/config";
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import {startVerdaccio, listenDefaultCallback} from "@verdaccio/node-api";
|
||||
|
||||
export const DEFAULT_PROCESS_NAME: string = 'verdaccio';
|
||||
|
||||
export default function initProgram(commander, pkgVersion, pkgName) {
|
||||
const cliListener = commander.listen;
|
||||
let configPathLocation;
|
||||
let verdaccioConfiguration;
|
||||
try {
|
||||
configPathLocation = findConfigFile(commander.config);
|
||||
verdaccioConfiguration = parseConfigFile(configPathLocation);
|
||||
const { web, https, self_path } = verdaccioConfiguration;
|
||||
|
||||
process.title = web && web.title || DEFAULT_PROCESS_NAME;
|
||||
|
||||
// note: self_path is only being used by @verdaccio/storage , not really useful and migth be removed soon
|
||||
if (!self_path) {
|
||||
verdaccioConfiguration = _.assign({}, verdaccioConfiguration, {
|
||||
self_path: path.resolve(configPathLocation)
|
||||
});
|
||||
}
|
||||
|
||||
if (!https) {
|
||||
verdaccioConfiguration = _.assign({}, verdaccioConfiguration, {
|
||||
https: {enable: false}
|
||||
});
|
||||
}
|
||||
|
||||
logger.warn({file: configPathLocation}, 'config file - @{file}');
|
||||
|
||||
startVerdaccio(verdaccioConfiguration, cliListener, configPathLocation, pkgVersion, pkgName, listenDefaultCallback);
|
||||
} catch (err) {
|
||||
logger.fatal({file: configPathLocation, err: err}, 'cannot open config file @{file}: @{!err.message}');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
1
packages/cli/src/index.ts
Normal file
1
packages/cli/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
require('./cli');
|
5
packages/cli/src/utils.ts
Normal file
5
packages/cli/src/utils.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import semver from "semver";
|
||||
|
||||
export const MIN_NODE_VERSION = '6.9.0';
|
||||
|
||||
export const isVersionValid = () => semver.satisfies(process.version, `>=${MIN_NODE_VERSION}`) === false
|
3
packages/cli/test/cli-test.spec.ts
Normal file
3
packages/cli/test/cli-test.spec.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
describe('cli test', () => {
|
||||
test.todo('write some test for this module');
|
||||
});
|
9
packages/cli/tsconfig.json
Normal file
9
packages/cli/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/commons/.babelrc
Normal file
3
packages/commons/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": [["@verdaccio"]]
|
||||
}
|
25
packages/commons/package.json
Normal file
25
packages/commons/package.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "@verdaccio/dev-commons",
|
||||
"version": "5.0.0-alpha.0",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"homepage": "https://verdaccio.org",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "npm run build:js && npm run build:types"
|
||||
},
|
||||
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
|
||||
}
|
|
@ -1,9 +1,3 @@
|
|||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
export const DEFAULT_PORT = '4873';
|
||||
export const DEFAULT_PROTOCOL = 'http';
|
||||
export const DEFAULT_DOMAIN = 'localhost';
|
1
packages/commons/src/index.ts
Normal file
1
packages/commons/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './constants';
|
9
packages/commons/tsconfig.json
Normal file
9
packages/commons/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/config/.babelrc
Normal file
3
packages/config/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": [["@verdaccio"]]
|
||||
}
|
9
packages/config/jest.config.js
Normal file
9
packages/config/jest.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
transform: {
|
||||
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
|
||||
},
|
||||
verbose: true,
|
||||
collectCoverage: true,
|
||||
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
|
||||
};
|
32
packages/config/package.json
Normal file
32
packages/config/package.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "@verdaccio/config",
|
||||
"version": "5.0.0-alpha.0",
|
||||
"description": "logger",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://verdaccio.org",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "npm run build:js && npm run build:types"
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/utils": "5.0.0-alpha.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bunyan": "1.8.6"
|
||||
},
|
||||
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
|
||||
}
|
|
@ -1,22 +1,23 @@
|
|||
import fs from 'fs';
|
||||
import Path from 'path';
|
||||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
import Path from 'path';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import mkdirp from 'mkdirp';
|
||||
import { logger } from './logger';
|
||||
|
||||
import { folderExists, fileExists } from './utils';
|
||||
import { CHARACTER_ENCODING } from './constants';
|
||||
import { folderExists, fileExists } from '@verdaccio/utils';
|
||||
import { CHARACTER_ENCODING } from '@verdaccio/dev-commons';
|
||||
|
||||
const CONFIG_FILE = 'config.yaml';
|
||||
const XDG = 'xdg';
|
||||
const WIN = 'win';
|
||||
const WIN32 = 'win32';
|
||||
// eslint-disable-next-line
|
||||
const pkgJSON = require('../../package.json');
|
||||
const pkgJSON = require('../package.json');
|
||||
|
||||
export type SetupDirectory = {
|
||||
path: string;
|
||||
type: string;
|
||||
type: string
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -34,9 +35,7 @@ function findConfigFile(configPath: string): string {
|
|||
throw new Error('no configuration files can be processed');
|
||||
}
|
||||
|
||||
const primaryConf: any = _.find(configPaths, (configLocation: any) =>
|
||||
fileExists(configLocation.path)
|
||||
);
|
||||
const primaryConf: any = _.find(configPaths, (configLocation: any) => fileExists(configLocation.path));
|
||||
if (_.isNil(primaryConf) === false) {
|
||||
return primaryConf.path;
|
||||
}
|
||||
|
@ -54,8 +53,11 @@ function createConfigFile(configLocation: any): SetupDirectory {
|
|||
return configLocation;
|
||||
}
|
||||
|
||||
function readDefaultConfig(): string {
|
||||
return fs.readFileSync(require.resolve('../../conf/default.yaml'), 'utf-8');
|
||||
export function readDefaultConfig(): Buffer {
|
||||
const pathDefaultConf: string = path.resolve(__dirname, 'conf/default.yaml');
|
||||
|
||||
// @ts-ignore
|
||||
return fs.readFileSync(pathDefaultConf, CHARACTER_ENCODING.UTF8);
|
||||
}
|
||||
|
||||
function createConfigFolder(configLocation): void {
|
||||
|
@ -71,8 +73,7 @@ function updateStorageLinks(configLocation, defaultConfig): string {
|
|||
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored,
|
||||
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
|
||||
// $FlowFixMe
|
||||
let dataDir =
|
||||
process.env.XDG_DATA_HOME || Path.join(process.env.HOME as string, '.local', 'share');
|
||||
let dataDir = process.env.XDG_DATA_HOME || Path.join(process.env.HOME as string, '.local', 'share');
|
||||
if (folderExists(dataDir)) {
|
||||
dataDir = Path.resolve(Path.join(dataDir, pkgJSON.name, 'storage'));
|
||||
return defaultConfig.replace(/^storage: .\/storage$/m, `storage: ${dataDir}`);
|
||||
|
@ -81,17 +82,13 @@ function updateStorageLinks(configLocation, defaultConfig): string {
|
|||
}
|
||||
|
||||
function getConfigPaths(): SetupDirectory[] {
|
||||
const listPaths: SetupDirectory[] = [
|
||||
getXDGDirectory(),
|
||||
getWindowsDirectory(),
|
||||
getRelativeDefaultDirectory(),
|
||||
getOldDirectory()
|
||||
].reduce(function (acc, currentValue: any): SetupDirectory[] {
|
||||
if (_.isUndefined(currentValue) === false) {
|
||||
acc.push(currentValue);
|
||||
}
|
||||
return acc;
|
||||
}, [] as SetupDirectory[]);
|
||||
const listPaths: SetupDirectory[] = [getXDGDirectory(), getWindowsDirectory(), getRelativeDefaultDirectory(), getOldDirectory()].reduce(
|
||||
function(acc, currentValue: any): SetupDirectory[] {
|
||||
if (_.isUndefined(currentValue) === false) {
|
||||
acc.push(currentValue);
|
||||
}
|
||||
return acc;
|
||||
}, [] as SetupDirectory[]);
|
||||
|
||||
return listPaths;
|
||||
}
|
||||
|
@ -102,7 +99,7 @@ const getXDGDirectory = (): SetupDirectory | void => {
|
|||
if (XDGConfig && folderExists(XDGConfig)) {
|
||||
return {
|
||||
path: Path.join(XDGConfig, pkgJSON.name, CONFIG_FILE),
|
||||
type: XDG
|
||||
type: XDG,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -113,7 +110,7 @@ const getWindowsDirectory = (): SetupDirectory | void => {
|
|||
if (process.platform === WIN32 && process.env.APPDATA && folderExists(process.env.APPDATA)) {
|
||||
return {
|
||||
path: Path.resolve(Path.join(process.env.APPDATA, pkgJSON.name, CONFIG_FILE)),
|
||||
type: WIN
|
||||
type: WIN,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -121,15 +118,15 @@ const getWindowsDirectory = (): SetupDirectory | void => {
|
|||
const getRelativeDefaultDirectory = (): SetupDirectory => {
|
||||
return {
|
||||
path: Path.resolve(Path.join('.', pkgJSON.name, CONFIG_FILE)),
|
||||
type: 'def'
|
||||
type: 'def',
|
||||
};
|
||||
};
|
||||
|
||||
const getOldDirectory = (): SetupDirectory => {
|
||||
return {
|
||||
path: Path.resolve(Path.join('.', CONFIG_FILE)),
|
||||
type: 'old'
|
||||
type: 'old',
|
||||
};
|
||||
};
|
||||
|
||||
export default findConfigFile;
|
||||
export { findConfigFile };
|
|
@ -1,19 +1,23 @@
|
|||
import assert from 'assert';
|
||||
import _ from 'lodash';
|
||||
import assert from 'assert';
|
||||
|
||||
import { PackageList, Config as AppConfig, Security, Logger } from '@verdaccio/types';
|
||||
import { MatchedPackage, StartUpConfig } from '../../types';
|
||||
import { generateRandomHexString } from './crypto-utils';
|
||||
import {
|
||||
getMatchedPackagesSpec,
|
||||
normalisePackageAccess,
|
||||
sanityCheckUplinksProps,
|
||||
uplinkSanityCheck
|
||||
} from './config-utils';
|
||||
import { getUserAgent, isObject } from './utils';
|
||||
import { APP_ERROR } from './constants';
|
||||
uplinkSanityCheck,
|
||||
generateRandomHexString,
|
||||
getUserAgent,
|
||||
isObject
|
||||
} from '@verdaccio/utils';
|
||||
import { APP_ERROR } from '@verdaccio/dev-commons';
|
||||
|
||||
import { PackageList, Config as AppConfig, Security, Logger } from '@verdaccio/types';
|
||||
|
||||
import { MatchedPackage, StartUpConfig } from '@verdaccio/dev-types';
|
||||
|
||||
const LoggerApi = require('@verdaccio/logger');
|
||||
|
||||
const LoggerApi = require('./logger');
|
||||
const strategicConfigProps = ['uplinks', 'packages'];
|
||||
const allowedEnvConfig = ['http_proxy', 'https_proxy', 'no_proxy'];
|
||||
|
||||
|
@ -57,7 +61,7 @@ class Config implements AppConfig {
|
|||
assert(_.isObject(config), APP_ERROR.CONFIG_NOT_VALID);
|
||||
|
||||
// sanity check for strategic config properties
|
||||
strategicConfigProps.forEach(function (x): void {
|
||||
strategicConfigProps.forEach(function(x): void {
|
||||
if (self[x] == null) {
|
||||
self[x] = {};
|
||||
}
|
||||
|
@ -75,11 +79,13 @@ class Config implements AppConfig {
|
|||
this.packages = normalisePackageAccess(self.packages);
|
||||
|
||||
// loading these from ENV if aren't in config
|
||||
allowedEnvConfig.forEach((envConf): void => {
|
||||
if (!(envConf in self)) {
|
||||
self[envConf] = process.env[envConf] || process.env[envConf.toUpperCase()];
|
||||
allowedEnvConfig.forEach(
|
||||
(envConf): void => {
|
||||
if (!(envConf in self)) {
|
||||
self[envConf] = process.env[envConf] || process.env[envConf.toUpperCase()];
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// unique identifier of self server (or a cluster), used to avoid loops
|
||||
// @ts-ignore
|
||||
|
@ -110,4 +116,4 @@ class Config implements AppConfig {
|
|||
}
|
||||
}
|
||||
|
||||
export default Config;
|
||||
export { Config };
|
2
packages/config/src/index.ts
Normal file
2
packages/config/src/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './config'
|
||||
export * from './config-path'
|
|
@ -1,18 +1,20 @@
|
|||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Config from '../../../../src/lib/config';
|
||||
import { parseConfigFile } from '../../../../src/lib/utils';
|
||||
import { DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE } from '../../../../src/lib/constants';
|
||||
import { setup } from '../../../../src/lib/logger';
|
||||
import { Config, readDefaultConfig } from '@verdaccio/config';
|
||||
import { setup } from '@verdaccio/logger';
|
||||
import {DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE} from '@verdaccio/dev-commons';
|
||||
|
||||
import {parseConfigFile} from '@verdaccio/utils';
|
||||
|
||||
setup([]);
|
||||
|
||||
const resolveConf = (conf) => {
|
||||
const { name, ext } = path.parse(conf);
|
||||
|
||||
return path.join(__dirname, `../../../../conf/${name}${ext.startsWith('.') ? ext : '.yaml'}`);
|
||||
return path.join(__dirname, `../src/conf/${name}${ext.startsWith('.') ? ext : '.yaml'}`);
|
||||
};
|
||||
|
||||
const checkDefaultUplink = (config) => {
|
||||
expect(_.isObject(config.uplinks[DEFAULT_UPLINK])).toBeTruthy();
|
||||
expect(config.uplinks[DEFAULT_UPLINK].url).toMatch(DEFAULT_REGISTRY);
|
||||
|
@ -43,7 +45,7 @@ const checkDefaultConfPackages = (config) => {
|
|||
expect(config.packages['**'].publish).toBeDefined();
|
||||
expect(config.packages['**'].publish).toContainEqual(ROLES.$AUTH);
|
||||
expect(config.packages['**'].proxy).toBeDefined();
|
||||
expect(config.packages['**'].proxy).toContainEqual(DEFAULT_UPLINK);
|
||||
expect(config.packages['**'].proxy,).toContainEqual(DEFAULT_UPLINK);
|
||||
// uplinks
|
||||
expect(config.uplinks[DEFAULT_UPLINK]).toBeDefined();
|
||||
expect(config.uplinks[DEFAULT_UPLINK].url).toEqual(DEFAULT_REGISTRY);
|
||||
|
@ -67,7 +69,7 @@ const checkDefaultConfPackages = (config) => {
|
|||
};
|
||||
|
||||
describe('Config file', () => {
|
||||
beforeAll(function () {
|
||||
beforeAll(function() {
|
||||
/* eslint no-invalid-this: 0 */
|
||||
// @ts-ignore
|
||||
this.config = new Config(parseConfigFile(resolveConf('default')));
|
||||
|
@ -93,5 +95,9 @@ describe('Config file', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Config file', () => {});
|
||||
describe('Config file', () => {
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
9
packages/config/tsconfig.json
Normal file
9
packages/config/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/hooks/.babelrc
Normal file
3
packages/hooks/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": [["@verdaccio"]]
|
||||
}
|
9
packages/hooks/jest.config.js
Normal file
9
packages/hooks/jest.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
transform: {
|
||||
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
|
||||
},
|
||||
verbose: true,
|
||||
collectCoverage: true,
|
||||
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
|
||||
};
|
36
packages/hooks/package.json
Normal file
36
packages/hooks/package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@verdaccio/hooks",
|
||||
"version": "5.0.0-alpha.0",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://verdaccio.org",
|
||||
"dependencies": {
|
||||
"@verdaccio/commons-api": "^9.0.0",
|
||||
"@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": "^8.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration true",
|
||||
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "npm run build:js && npm run build:types"
|
||||
},
|
||||
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
|
||||
}
|
1
packages/hooks/src/index.ts
Normal file
1
packages/hooks/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export {handleNotify, notify, sendNotification } from './notify';
|
25
packages/hooks/src/notify-request.ts
Normal file
25
packages/hooks/src/notify-request.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import isNil from 'lodash/isNil';
|
||||
import request, { RequiredUriUrl } from 'request';
|
||||
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import { HTTP_STATUS } from '@verdaccio/commons-api';
|
||||
|
||||
export function notifyRequest(options: RequiredUriUrl, content): Promise<any | Error> {
|
||||
return new Promise(
|
||||
(resolve, reject): void => {
|
||||
request(options, function(err, response, body): void {
|
||||
if (err || response.statusCode >= HTTP_STATUS.BAD_REQUEST) {
|
||||
const errorMessage = isNil(err) ? response.body : err.message;
|
||||
logger.error({ errorMessage }, 'notify service has thrown an error: @{errorMessage}');
|
||||
reject(errorMessage);
|
||||
}
|
||||
logger.info({ content }, 'A notification has been shipped: @{content}');
|
||||
if (isNil(body) === false) {
|
||||
logger.debug({ body }, ' body: @{body}');
|
||||
resolve(body);
|
||||
}
|
||||
reject(Error('body is missing'));
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
import { parseConfigurationFile } from '../../__helper';
|
||||
import { parseConfigFile } from '../../../../src/lib/utils';
|
||||
import { notify } from '../../../../src/lib/notify';
|
||||
import {parseConfigFile} from '@verdaccio/utils';
|
||||
import { setup } from '@verdaccio/logger';
|
||||
|
||||
import { notifyRequest } from '../../../../src/lib/notify/notify-request';
|
||||
|
||||
import { setup } from '../../../../src/lib/logger';
|
||||
import {notify} from '../src';
|
||||
import {notifyRequest} from '../src/notify-request';
|
||||
import {parseConfigurationFile} from './__helper';
|
||||
|
||||
setup([]);
|
||||
|
||||
jest.mock('./../../../../src/lib/notify/notify-request', () => ({
|
||||
jest.mock('../src/notify-request', () => ({
|
||||
notifyRequest: jest.fn((options, content) => Promise.resolve([options, content]))
|
||||
}));
|
||||
|
||||
|
@ -16,32 +15,29 @@ const parseConfigurationNotifyFile = (name) => {
|
|||
return parseConfigurationFile(`notify/${name}`);
|
||||
};
|
||||
const singleNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.notify'));
|
||||
const singleHeaderNotificationConfig = parseConfigFile(
|
||||
parseConfigurationNotifyFile('single.header.notify')
|
||||
);
|
||||
const packagePatternNotificationConfig = parseConfigFile(
|
||||
parseConfigurationNotifyFile('single.packagePattern.notify')
|
||||
);
|
||||
const singleHeaderNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.header.notify'));
|
||||
const packagePatternNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.packagePattern.notify'));
|
||||
const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('multiple.notify'));
|
||||
|
||||
describe('Notifications:: Notify', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
// FUTURE: we should add some sort of health check of all props, (not implemented yet)
|
||||
|
||||
test('should not fails if config is not provided', async () => {
|
||||
test("should not fails if config is not provided", async () => {
|
||||
// @ts-ignore
|
||||
await notify({}, {});
|
||||
|
||||
expect(notifyRequest).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('should send notification', async () => {
|
||||
test("should send notification", async () => {
|
||||
const name = 'package';
|
||||
// @ts-ignore
|
||||
const response = await notify({ name }, singleNotificationConfig, { name: 'foo' }, 'bar');
|
||||
const response = await notify({name}, singleNotificationConfig, { name: 'foo'}, 'bar');
|
||||
const [options, content] = response;
|
||||
|
||||
expect(options.headers).toBeDefined();
|
||||
|
@ -52,36 +48,39 @@ describe('Notifications:: Notify', () => {
|
|||
expect(notifyRequest).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should send single header notification', async () => {
|
||||
test("should send single header notification", async () => {
|
||||
// @ts-ignore
|
||||
await notify({}, singleHeaderNotificationConfig, { name: 'foo' }, 'bar');
|
||||
await notify({}, singleHeaderNotificationConfig, { name: 'foo'}, 'bar');
|
||||
|
||||
expect(notifyRequest).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should send multiple notification', async () => {
|
||||
test("should send multiple notification", async () => {
|
||||
// @ts-ignore
|
||||
await notify({ name }, multiNotificationConfig, { name: 'foo' }, 'bar');
|
||||
await notify({name}, multiNotificationConfig, { name: 'foo'}, 'bar');
|
||||
|
||||
expect(notifyRequest).toHaveBeenCalled();
|
||||
expect(notifyRequest).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
describe('packagePatternFlags', () => {
|
||||
test('should send single notification with packagePatternFlags', async () => {
|
||||
test("should send single notification with packagePatternFlags", async () => {
|
||||
const name = 'package';
|
||||
// @ts-ignore
|
||||
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
|
||||
await notify({name}, packagePatternNotificationConfig, { name: 'foo'}, 'bar');
|
||||
|
||||
|
||||
expect(notifyRequest).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should not match on send single notification with packagePatternFlags', async () => {
|
||||
test("should not match on send single notification with packagePatternFlags", async () => {
|
||||
const name = 'no-mach-name';
|
||||
// @ts-ignore
|
||||
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
|
||||
await notify({name}, packagePatternNotificationConfig, { name: 'foo'}, 'bar');
|
||||
|
||||
expect(notifyRequest).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import { HTTP_STATUS, API_ERROR } from '../../../../src/lib/constants';
|
||||
import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons';
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
/**
|
||||
|
@ -8,16 +8,16 @@ const logger = {
|
|||
logger: {
|
||||
error: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
info: jest.fn()
|
||||
}
|
||||
info: jest.fn(),
|
||||
},
|
||||
};
|
||||
jest.doMock('../../../../src/lib/logger', () => logger);
|
||||
jest.doMock('@verdaccio/logger', () => logger);
|
||||
|
||||
/**
|
||||
* Test Data
|
||||
*/
|
||||
const options = {
|
||||
url: 'http://slack-service'
|
||||
url: 'http://slack-service',
|
||||
};
|
||||
const content = 'Verdaccio@x.x.x successfully published';
|
||||
|
||||
|
@ -29,19 +29,16 @@ describe('Notifications:: notifyRequest', () => {
|
|||
test('when notification service throws error', async () => {
|
||||
jest.doMock('request', () => (options, resolver) => {
|
||||
const response = {
|
||||
statusCode: HTTP_STATUS.BAD_REQUEST
|
||||
statusCode: HTTP_STATUS.BAD_REQUEST,
|
||||
};
|
||||
const error = {
|
||||
message: API_ERROR.BAD_DATA
|
||||
message: API_ERROR.BAD_DATA,
|
||||
};
|
||||
resolver(error, response);
|
||||
});
|
||||
|
||||
const notification = require('../../../../src/lib/notify/notify-request');
|
||||
const args = [
|
||||
{ errorMessage: 'bad data' },
|
||||
'notify service has thrown an error: @{errorMessage}'
|
||||
];
|
||||
const notification = require('../src/notify-request');
|
||||
const args = [{ errorMessage: 'bad data' }, 'notify service has thrown an error: @{errorMessage}'];
|
||||
|
||||
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
|
||||
expect(logger.logger.error).toHaveBeenCalledWith(...args);
|
||||
|
@ -51,17 +48,14 @@ describe('Notifications:: notifyRequest', () => {
|
|||
jest.doMock('request', () => (options, resolver) => {
|
||||
const response = {
|
||||
statusCode: HTTP_STATUS.BAD_REQUEST,
|
||||
body: API_ERROR.BAD_DATA
|
||||
body: API_ERROR.BAD_DATA,
|
||||
};
|
||||
|
||||
resolver(null, response);
|
||||
});
|
||||
|
||||
const notification = require('../../../../src/lib/notify/notify-request');
|
||||
const args = [
|
||||
{ errorMessage: 'bad data' },
|
||||
'notify service has thrown an error: @{errorMessage}'
|
||||
];
|
||||
const notification = require('../src/notify-request');
|
||||
const args = [{ errorMessage: 'bad data' }, 'notify service has thrown an error: @{errorMessage}'];
|
||||
|
||||
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
|
||||
expect(logger.logger.error).toHaveBeenCalledWith(...args);
|
||||
|
@ -71,19 +65,17 @@ describe('Notifications:: notifyRequest', () => {
|
|||
jest.doMock('request', () => (options, resolver) => {
|
||||
const response = {
|
||||
statusCode: HTTP_STATUS.OK,
|
||||
body: 'Successfully delivered'
|
||||
body: 'Successfully delivered',
|
||||
};
|
||||
|
||||
resolver(null, response, response.body);
|
||||
});
|
||||
|
||||
const notification = require('../../../../src/lib/notify/notify-request');
|
||||
const notification = require('../src/notify-request');
|
||||
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
|
||||
const debugArgs = [{ body: 'Successfully delivered' }, ' body: @{body}'];
|
||||
|
||||
await expect(notification.notifyRequest(options, content)).resolves.toEqual(
|
||||
'Successfully delivered'
|
||||
);
|
||||
await expect(notification.notifyRequest(options, content)).resolves.toEqual('Successfully delivered');
|
||||
expect(logger.logger.info).toHaveBeenCalledWith(...infoArgs);
|
||||
expect(logger.logger.debug).toHaveBeenCalledWith(...debugArgs);
|
||||
});
|
||||
|
@ -91,13 +83,13 @@ describe('Notifications:: notifyRequest', () => {
|
|||
test('when notification is successfully delivered but body is undefined/null', async () => {
|
||||
jest.doMock('request', () => (options, resolver) => {
|
||||
const response = {
|
||||
statusCode: HTTP_STATUS.OK
|
||||
statusCode: HTTP_STATUS.OK,
|
||||
};
|
||||
|
||||
resolver(null, response);
|
||||
});
|
||||
|
||||
const notification = require('../../../../src/lib/notify/notify-request');
|
||||
const notification = require('../src/notify-request');
|
||||
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
|
||||
|
||||
await expect(notification.notifyRequest(options, content)).rejects.toThrow('body is missing');
|
9
packages/hooks/tsconfig.json
Normal file
9
packages/hooks/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue