Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
4e851d1
feat(ENG-15089): deprecate the cloudsmith login username/password flow
BartoszBlizniak Oct 16, 2026
0a1a694
feat(ENG-15089): report disabled password login and keep JSON output …
BartoszBlizniak Oct 16, 2026
707bde6
docs(ENG-15089): point the login deprecation at the changelog for the…
BartoszBlizniak Oct 16, 2026
c4bb014
docs(ENG-15089): tidy the wording of the login messages
BartoszBlizniak Oct 16, 2026
2c8e02b
docs(ENG-15089): link the login messages to the API key docs
BartoszBlizniak Oct 16, 2026
49ac426
fix(ENG-15089): leave config file output unchanged
BartoszBlizniak Oct 16, 2026
10a3914
Merge branch 'master' into eng-15089-cli-deprecate-cloudsmith-login-u…
cloudsmith-iduffy Oct 16, 2026
4337462
Merge branch 'master' into eng-15089-cli-deprecate-cloudsmith-login-u…
BartoszBlizniak Oct 19, 2026
15eee57
fix: standardize login authentication messages on SAML
Copilot Oct 19, 2026
460e8d8
fix(ENG-15089): keep the failed-login hint text, use SAML in CHANGELOG
BartoszBlizniak Oct 19, 2026
6a5c6d0
Merge remote-tracking branch 'origin/master' into eng-15089-cli-depre…
BartoszBlizniak Oct 19, 2026
043fe1e
Merge remote-tracking branch 'origin/master' into eng-15089-cli-depre…
BartoszBlizniak Oct 19, 2026
057a15d
Merge branch 'master' into eng-15089-cli-deprecate-cloudsmith-login-u…
BartoszBlizniak Oct 19, 2026
430fc6d
docs(ENG-15089): announce the November 20, 2026 login removal date
BartoszBlizniak Oct 20, 2026
97bdc73
Merge branch 'master' into eng-15089-cli-deprecate-cloudsmith-login-u…
BartoszBlizniak Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `cloudsmith domains list` keeps version 1 of its JSON document. The API does not give `primary` and `created_at`, so these fields are now always `null`. The hosts are in the preference order of the API. `domain_type` can also be `cloudsmith_api`.
- Without a Workspace, `cloudsmith domains list` lists the domains that earlier runs cached. If no domains are cached, it gets the domains of a Workspace that the credential is a member of. Without a credential, the list is empty.
- `cloudsmith credential-helper install` now configures the Cloudsmith-owned hosts that the API lists for the Workspace. It uses its built-in default host, such as `docker.cloudsmith.io`, only when it cannot get the domains from the API.
- The hint shown for a `401 - Unauthorized` error when no API key or SAML session is set no longer suggests `cloudsmith token`. It now suggests `CLOUDSMITH_API_KEY` or `cloudsmith auth`.

### Deprecated

- `cloudsmith login` and its `cloudsmith token` alias are deprecated, and will stop working on November 20, 2026, when Cloudsmith disables username and password login. See the [Cloudsmith changelog](https://cloudsmith.com/changelog). Until then, both commands work as before, but they print a deprecation warning to stderr before they prompt for credentials. Once the server disables password login (`410 Gone`, or `404` after the endpoint is deleted), they fail with a message that explains how to get an API key instead. Other errors, such as outages or wrong credentials, are reported as before. To replace them, set `CLOUDSMITH_API_KEY` to an API key from the Cloudsmith web app (your user menu, then **Personal API Keys**), or run `cloudsmith auth --workspace <workspace>` to log in with SAML. See [Getting your API key](https://docs.cloudsmith.com/developer-tools/cli#getting-your-api-key).

### Removed

Expand Down
28 changes: 7 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The CLI currently supports the following commands (and sub-commands):
- `entitlements`|`ents`: List entitlements for a repository.
- `packages`: List packages for a repository. (Aliases `repos list`)
- `repos`: List repositories for a namespace (owner).
- `login`|`token`: Retrieve your API authentication token/key via login.
- `login`|`token`: [DEPRECATED] Retrieve your API authentication token/key via login.
- `logout`: Clear stored authentication credentials and SSO tokens (Keyring, API key from credential file and emit warning when `$CLOUDSMITH_API_KEY` is still set).
- `metadata`: Manage arbitrary JSON metadata (SBOM, BuildInfo, custom) attached to a package.
- `add`: Attach a new metadata entry to a package.
Expand Down Expand Up @@ -363,29 +363,15 @@ cloudsmith auth --owner example --no-browser

#### Getting Your API Key

You can retrieve your API key using the `cloudsmith login` command:
Get your API key from the Cloudsmith web app: click your user icon in the top right corner, then click **Personal API Keys**. See [API Key](https://docs.cloudsmith.com/accounts-and-teams/api-key) for more information. If you authenticated with `cloudsmith auth`, you can also use `cloudsmith tokens create` to create an API key.

```
cloudsmith login
Login: you@example.com
Password:
Repeat for confirmation:
```

*Note:* Please ensure you use your email for the 'Login' prompt and not your user slug/identifier.

The resulting output looks something like:

```
Retrieving API token for 'you@example.com' ... OK
Your API token is: 1234567890abcdef1234567890abcdef
```

Once you have your API key you can then put this into your `credentials.ini`, use it as an environment variable `export CLOUDSMITH_API_KEY=your_key_here` or pass it to the CLI using the `-k your_key_here` flag.
Give your API key to the CLI in one of these ways:

For convenience the CLI will ask you if you want to install the default configuration files, complete with your API key, if they don't already exist. Say 'y' or 'yes' to create the configuration files.
- Set the `CLOUDSMITH_API_KEY` environment variable: `export CLOUDSMITH_API_KEY=your_key_here`.
- Put the key into your `credentials.ini` file.
- Pass the key to the CLI with the `-k your_key_here` flag.

If the configuration files already exist, you'll have to manually put the API key into the configuration files, but the CLI will print out their locations.
*Note:* The `cloudsmith login` command and its `cloudsmith token` alias are deprecated. They retrieve your API key with your email address and password, and they will stop working on November 20, 2026, when Cloudsmith disables username and password login. See the [Cloudsmith changelog](https://cloudsmith.com/changelog). After that, both commands fail with a message that explains how to get an API key instead.


## Uploading Packages
Expand Down
82 changes: 76 additions & 6 deletions cloudsmith_cli/cli/commands/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,58 @@
return value


@main.command(aliases=["token"])
#: Docs section on getting an API key and logging in with SAML instead.
API_KEY_DOCS_URL = (
"https://docs.cloudsmith.com/developer-tools/cli#getting-your-api-key"
)

DEPRECATION_WARNING = (
"Warning: `cloudsmith {name}` is deprecated and will stop working on "
"November 20, 2026, when Cloudsmith disables username and password login. "
"See https://cloudsmith.com/changelog\n"
"Use an API key or SAML instead: set `CLOUDSMITH_API_KEY` to an API key from "
"the Cloudsmith web app (your user menu, then Personal API Keys), or run "
"`cloudsmith auth --workspace <workspace>` to log in with SAML.\n"
f"More: {API_KEY_DOCS_URL}"
)


REMOVED_MESSAGE = (
"Could not retrieve API key: username and password login is disabled.\n"
"Fix: set `CLOUDSMITH_API_KEY` to an API key from the Cloudsmith web app "
"(your user menu, then Personal API Keys), or run "
"`cloudsmith auth --workspace <workspace>` to log in with SAML.\n"
f"More: {API_KEY_DOCS_URL}"
)

#: Statuses the token endpoint returns once the server has disabled password
#: login (410) or deleted the route entirely (404).
REMOVED_STATUSES = (404, 410)


def summarise_removed_login(exc, detail, fields):
"""Replace the raw error with a clear notice once the endpoint is disabled."""
# pylint: disable=unused-argument
if exc.status in REMOVED_STATUSES:
return REMOVED_MESSAGE
return None


class DeprecatedLoginCommand(click.Command):
"""Command that prints the deprecation warning before any prompt."""

def parse_args(self, ctx, args):
# Click prompts for --login and --password while parsing, so the
# warning must be printed here rather than in the command body.
# Shell completion also parses (resiliently) and must stay quiet.
if not ctx.resilient_parsing:
click.secho(
DEPRECATION_WARNING.format(name=ctx.info_name), fg="yellow", err=True
)
return super().parse_args(ctx, args)


@main.command(cls=DeprecatedLoginCommand, aliases=["token"])
@click.option(
"-l",
"--login",
Expand All @@ -36,18 +87,32 @@
@decorators.initialise_api
@click.pass_context
def login(ctx, opts, login, password): # pylint: disable=redefined-outer-name
"""Retrieve your API authentication token/key via login."""
"""[DEPRECATED] Retrieve your API key with your email address and password.

This command will stop working on November 20, 2026, when Cloudsmith
disables username and password login. See https://cloudsmith.com/changelog

Set `CLOUDSMITH_API_KEY` to an API key from the Cloudsmith web app instead,
or run `cloudsmith auth --workspace <workspace>` to log in with SAML.

More: https://docs.cloudsmith.com/developer-tools/cli#getting-your-api-key
"""
use_stderr = utils.should_use_stderr(opts)
click.echo(
f"Retrieving API token for {click.style(login, bold=True)} ... ",
f"Retrieving API key for {click.style(login, bold=True)} ... ",
nl=False,
err=use_stderr,
)

context_msg = "Failed to retrieve the API token!"
context_msg = "Could not retrieve API key"
try:
with (
handle_api_exceptions(ctx, opts=opts, context_msg=context_msg),
handle_api_exceptions(
ctx,
opts=opts,
context_msg=context_msg,
summarise_error=summarise_removed_login,
),
maybe_spinner(opts),
):
api_key = get_user_token(login=login, password=password)
Expand All @@ -66,7 +131,12 @@

try:
with (
handle_api_exceptions(ctx, opts=opts, context_msg=context_msg),
handle_api_exceptions(
ctx,
opts=opts,
context_msg=context_msg,
summarise_error=summarise_removed_login,
),
maybe_spinner(opts),
):
api_key = get_user_token(
Expand All @@ -75,7 +145,7 @@
totp_token=totp_token,
two_factor_token=e.two_factor_token,
)
except cloudsmith_api.rest.ApiException:

Check warning on line 148 in cloudsmith_cli/cli/commands/login.py

View workflow job for this annotation

GitHub Actions / ty

ty (possibly-missing-submodule)

cloudsmith_cli/cli/commands/login.py:148:16: possibly-missing-submodule: Submodule `rest` might not have been imported help: Consider explicitly importing `cloudsmith_api.rest`
click.echo("\r\033[K", nl=False, err=use_stderr)
click.secho(
"Authentication failed: The entered TOTP token is not valid.",
Expand All @@ -84,7 +154,7 @@
)
ctx.exit(1)

except cloudsmith_api.rest.ApiException as e:

Check warning on line 157 in cloudsmith_cli/cli/commands/login.py

View workflow job for this annotation

GitHub Actions / ty

ty (possibly-missing-submodule)

cloudsmith_cli/cli/commands/login.py:157:12: possibly-missing-submodule: Submodule `rest` might not have been imported help: Consider explicitly importing `cloudsmith_api.rest`
click.echo("\r\033[K", nl=False, err=use_stderr)
click.secho(f"Authentication failed: {e!s}", fg="red", err=use_stderr)
ctx.exit(1)
Expand Down
16 changes: 10 additions & 6 deletions cloudsmith_cli/cli/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def get_401_error_hint(ctx, opts, exc):
credential = getattr(opts, "credential", None)

if credential and credential.auth_type == "bearer":
return "Since you have an SSO access token set, this probably means that it has expired. Try getting a new token with 'cloudsmith auth', then try again."
return "Since you have a SAML access token set, this probably means that it has expired. Try getting a new token with 'cloudsmith auth', then try again."

if credential:
return (
Expand All @@ -210,24 +210,28 @@ def get_401_error_hint(ctx, opts, exc):
"try again."
)

if ctx.info_name == "token":
# This is already the token command
if ctx.info_name in ("login", "token"):
# This is already the (deprecated) login command
return (
"The login failed - Either your email address and/or "
Comment thread
BartoszBlizniak marked this conversation as resolved.
"your password was incorrect. Please check them and "
"try again!"
)

return (
"You don't have an API key or access token set, but it seems this action "
"requires authentication - Try getting your API key via "
"'cloudsmith token', or access token via 'cloudsmith auth', then try again."
"This command requires authentication, and no API key or SAML session was "
"found. Set `CLOUDSMITH_API_KEY` to your API key, or run "
"`cloudsmith auth --workspace <workspace>` to log in with SAML."
)


def get_404_error_hint(ctx, opts, exc):
"""Get the hint for a 404/NotFound error."""
# pylint: disable=unused-argument
if ctx.info_name in ("login", "token"):
# The token endpoint itself is gone, not a user/org
return None

# pylint: disable=fixme
# TODO(ls): Expand this to be contextual (we could look at the
# arguments for the command).
Expand Down
Loading
Loading