Commit e748c2a2 authored by Steve Abrams's avatar Steve Abrams

Support NPM package API with deploy tokens

Add support to use deploy tokens for NPM API endpoints
parent 4e5e26a0
---
title: Enable deploy token authentication for the NPM registry
merge_request: 31264
author:
type: added
......@@ -100,14 +100,15 @@ configure GitLab as a remote registry.
If a project is private or you want to upload an NPM package to GitLab,
credentials will need to be provided for authentication. [Personal access tokens](../../profile/personal_access_tokens.md)
and [deploy tokens](../../project/deploy_tokens/index.md)
are preferred, but support is available for [OAuth tokens](../../../api/oauth2.md#resource-owner-password-credentials-flow).
CAUTION: **2FA is only supported with personal access tokens:**
If you have 2FA enabled, you need to use a [personal access token](../../profile/personal_access_tokens.md) with OAuth headers with the scope set to `api`. Standard OAuth tokens won't be able to authenticate to the GitLab NPM Registry.
CAUTION: **Two-factor authentication (2FA) is only supported with personal access tokens:**
If you have 2FA enabled, you need to use a [personal access token](../../profile/personal_access_tokens.md) with OAuth headers with the scope set to `api` or a [deploy token](../../project/deploy_tokens/index.md) with `read_package_registry` or `write_package_registry` scopes. Standard OAuth tokens won't be able to authenticate to the GitLab NPM Registry.
### Authenticating with a personal access token
### Authenticating with a personal access token or deploy token
To authenticate with a [personal access token](../../profile/personal_access_tokens.md),
To authenticate with a [personal access token](../../profile/personal_access_tokens.md) or [deploy token](../../project/deploy_tokens/index.md),
set your NPM configuration:
```shell
......@@ -125,7 +126,7 @@ npm config set '//gitlab.com/api/v4/projects/<your_project_id>/packages/npm/:_au
```
Replace `<your_project_id>` with your project ID which can be found on the home page
of your project and `<your_token>` with your personal access token.
of your project and `<your_token>` with your personal access token or deploy token.
If you have a self-managed GitLab installation, replace `gitlab.com` with your
domain name.
......@@ -160,7 +161,7 @@ Then, you could run `npm publish` either locally or via GitLab CI/CD:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/9104) in GitLab Premium 12.5.
If you’re using NPM with GitLab CI/CD, a CI job token can be used instead of a personal access token.
If you’re using NPM with GitLab CI/CD, a CI job token can be used instead of a personal access token or deploy token.
The token will inherit the permissions of the user that generates the pipeline.
Add a corresponding section to your `.npmrc` file:
......@@ -286,7 +287,7 @@ page.
## Publishing a package with CI/CD
To work with NPM commands within [GitLab CI/CD](./../../../ci/README.md), you can use
`CI_JOB_TOKEN` in place of the personal access token in your commands.
`CI_JOB_TOKEN` in place of the personal access token or deploy token in your commands.
A simple example `.gitlab-ci.yml` file for publishing NPM packages:
......@@ -323,7 +324,7 @@ info Visit https://classic.yarnpkg.com/en/docs/cli/install for documentation abo
```
In this case, try adding this to your `.npmrc` file (and replace `<your_token>`
with your personal access token):
with your personal access token or deploy token):
```text
//gitlab.com/api/v4/projects/:_authToken=<your_token>
......
......@@ -106,7 +106,7 @@ module API
params do
requires :package_name, type: String, desc: 'Package name'
end
route_setting :authentication, job_token_allowed: true
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get 'packages/npm/*package_name', format: false, requirements: NPM_ENDPOINT_REQUIREMENTS do
package_name = params[:package_name]
......@@ -137,7 +137,7 @@ module API
requires :package_name, type: String, desc: 'Package name'
requires :file_name, type: String, desc: 'Package file name'
end
route_setting :authentication, job_token_allowed: true
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get ':id/packages/npm/*package_name/-/*file_name', format: false do
authorize_read_package!(user_project)
......@@ -159,7 +159,7 @@ module API
requires :package_name, type: String, desc: 'Package name'
requires :versions, type: Hash, desc: 'Package version info'
end
route_setting :authentication, job_token_allowed: true
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!(user_project)
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe API::NpmPackages do
include EE::PackagesManagerApiSpecHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
......@@ -10,6 +12,8 @@ describe API::NpmPackages do
let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:job) { create(:ci_build, user: user) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
before do
project.add_developer(user)
......@@ -34,6 +38,12 @@ describe API::NpmPackages do
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'returns the package info with deploy token' do
get_package_with_deploy_token(package)
expect_a_valid_package_response
end
end
describe 'GET /api/v4/packages/npm/*package_name' do
......@@ -132,8 +142,8 @@ describe API::NpmPackages do
expect(response).to have_gitlab_http_status(:forbidden)
end
def get_package(package, params = {})
get api("/packages/npm/#{package.name}"), params: params
def get_package(package, params = {}, headers = {})
get api("/packages/npm/#{package.name}"), params: params, headers: headers
end
def get_package_with_token(package, params = {})
......@@ -143,6 +153,10 @@ describe API::NpmPackages do
def get_package_with_job_token(package, params = {})
get_package(package, params.merge(job_token: job.token))
end
def get_package_with_deploy_token(package, params = {})
get_package(package, {}, build_token_auth_header(deploy_token.token))
end
end
describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
......
......@@ -107,9 +107,12 @@ module Gitlab
def deploy_token_from_request
return unless route_authentication_setting[:deploy_token_allowed]
token = current_request.env[DEPLOY_TOKEN_HEADER].presence
token = current_request.env[DEPLOY_TOKEN_HEADER].presence || parsed_oauth_token
DeployToken.active.find_by_token(token)
deploy_token = DeployToken.active.find_by_token(token)
@current_authenticated_deploy_token = deploy_token # rubocop:disable Gitlab/ModuleWithInstanceVariables
deploy_token
end
def find_runner_from_token
......@@ -122,6 +125,9 @@ module Gitlab
end
def validate_access_token!(scopes: [])
# return early if we've already authenticated via a deploy token
return if @current_authenticated_deploy_token.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
return unless access_token
case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
......
......@@ -220,6 +220,24 @@ describe Gitlab::Auth::AuthFinders do
it { is_expected.to be_nil }
end
end
context 'with oauth headers' do
before do
set_header('HTTP_AUTHORIZATION', "Bearer #{deploy_token.token}")
end
it { is_expected.to eq deploy_token }
it_behaves_like 'an unauthenticated route'
context 'with invalid token' do
before do
set_header('HTTP_AUTHORIZATION', "Bearer invalid_token")
end
it { is_expected.to be_nil }
end
end
end
describe '#find_user_from_access_token' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment