Skip to content

feat(portals): call the Auth0 CLI from the tool and bundle it (Beta) - #1739

Open
jacobovidal wants to merge 11 commits into
feat/up-embedded-sourcefrom
feat/up-embedded-script
Open

jacobovidal wants to merge 11 commits into
feat/up-embedded-sourcefrom
feat/up-embedded-script

Conversation

@jacobovidal

@jacobovidal jacobovidal commented Oct 26, 2026 •

Copy link
Copy Markdown

🔧 Changes

Second of three stacked PRs (on #1738). It changes only the TypeScript tool and adds its single-file build. Nothing in the CLI uses the file yet.

  • auth0 api transport. The tool calls the Management API by running auth0 api instead of talking to a local proxy, so the CLI keeps the login and the token. Request bodies go to the command on stdin, because they carry client secrets and process arguments are visible to other processes. The first request runs alone and then up to four run together, so several processes never refresh an expired token at once.
  • How the tool starts. It uses the proxy variables if they are set, then AUTH0_CLI_BIN and AUTH0_CLI_TENANT_DOMAIN if the CLI passes them, and otherwise auth0 on the PATH and the active tenant. The --tenant flag wins.
  • Missing scopes. Every command now says how to get a missing scope, for both ways the CLI can be signed in: auth0 login --scopes for a user login, or adding the scope to the application's Management API grant for a client-credentials login. Only setup did this before, and it printed the login command twice.
  • Parallel reads in the permission check and the audit. The CLI takes about a second and a half per call, so reading one thing after another was slow. The permission check also sends its seven create probes together; they send an empty body and are expected to be rejected, as before. Real writes during setup stay one at a time.
  • Paging fix. The audit failed against a real tenant (Additional properties not allowed: per_page,page): the endpoint that lists an application's connections pages by checkpoint and rejects page numbers. It now has its own helper. The test double was too forgiving and accepted page numbers there; it now behaves like the real endpoint.
  • Single-file build. npm run bundle builds one ES module with esbuild (new dev dependency, exact version, in _source only). The first lines record a hash of the sources, the lockfile and the build script; a test fails when the committed file is older than its sources. .gitattributes marks the file as generated so GitHub folds it.
  • The unused auth0 npm package is removed. It was the source of two moderate audit findings and nothing imported it. The lockfile uses public registry URLs, and a test keeps it that way.
  • A test that a request without a body still closes stdin: auth0 api waits for stdin to end on every method except GET, so a transport that left it open would hang.

Dependabot watches only gomod and bundler in this repository, so the TypeScript dependencies get no automated updates. Adding an npm ecosystem is a .github change that I left for a maintainer to decide.

📚 References

Stacked on #1738. Followed by #1740.

🔬 Testing

cd internal/cli/data/universal-portals/_source
npm test                  # 229 tests
npm run bundle            # rebuilding changes nothing in git

Beyond the unit tests, the built file was run by hand against a development tenant with the installed CLI (v1.32.0): an audit of two existing portals, a consumer setup and a second run that reused everything, a business setup that found its user by email, and an audit of each new portal (no errors, no warnings). Everything the runs created was deleted afterwards and checked.

That run found the paging bug above, which no unit test had caught. Flows that have not run against a real tenant yet: the interactive prompts (organization and role creation, the user picker) and audit --fix.

📝 Checklist

  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

🤖 Generated with Claude Code

jacobovidal and others added 8 commits October 26, 2026 16:01
Add a Management API client that runs `auth0 api` instead of talking
to a local proxy. The CLI owns the login and the token, so the script
never holds a credential. Request bodies go to the command on stdin:
they carry client secrets, and process arguments are visible to every
process on the machine.

Failures are mapped to the errors the rest of the code already
handles: the API's status, code and message become an ApiError, a
missing scope becomes the 403 the permission check reads, and a
session that cannot be used asks the user to log in. The first
request runs alone, then up to four run together, so several
processes never refresh an expired token at the same time.

The client is not used yet. A later change selects it.

Co-Authored-By: Claude <noreply@anthropic.com>
Decide how the script reaches the Management API when it starts. If
the plugin framework passed a proxy, that proxy is used as before. If
the Auth0 CLI started the script and passed its path and the tenant,
requests go through `auth0 api`. Run by hand with nothing set, the
script uses `auth0` on the PATH and the CLI's active tenant.

The tenant comes from the --tenant flag first, then the environment,
then the CLI's active tenant. One function builds the API client for
every command.

Tests can no longer reach the real CLI by accident: the test helper
gives every run a runner that refuses to start a process, and a test
fails if the empty-environment case ever calls it.

Co-Authored-By: Claude <noreply@anthropic.com>
Only setup checked scopes, and it printed the login command twice.
Audit and every other call showed a bare "Insufficient scope" error.

A missing scope is now explained the same way everywhere, and the
advice covers both ways the Auth0 CLI can be signed in. A user login
can ask for more scopes with `auth0 login --scopes`. A client-credentials
(machine to machine) login cannot, so the scope has to be added to that
application's Management API grant. When any one of several scopes is
enough, the first one is suggested.

Co-Authored-By: Claude <noreply@anthropic.com>
Each call through the Auth0 CLI takes about a second and a half, mostly
the CLI starting up, so reading one thing after another made the
permission check and the audit slow.

The permission probes are now sent together, and their answers are
read in the order of the probes, so the report and the first error do
not depend on which answer came back first. The audit reads the
application with its grants and connections, the resource servers, the
forms, the custom domain and the organization together, once the
portal itself is read. Writes stay one at a time.

Co-Authored-By: Claude <noreply@anthropic.com>
The Auth0 CLI will embed the setup and audit tool as one file that
`node` can run. `npm run bundle` builds that file with esbuild: one ES
module with the dependencies included, no sourcemap, and the same
bytes every time it runs.

The first lines of the file record a hash of everything it is built
from: the sources, the lockfile and the build script. A test can
recompute the hash, so a bundle that is older than its sources is
caught. The hash is of text with LF line endings so a Windows checkout
gives the same value, and its definition is pinned by a test.

The unused `auth0` package is removed. It was the source of the two
moderate audit findings, and nothing imported it. esbuild is added as
a development dependency at an exact version, and the lockfile keeps
public registry URLs.

Co-Authored-By: Claude <noreply@anthropic.com>
Add the file the Auth0 CLI will embed, built with `npm run bundle`
from the sources in the _source folder. It is generated, so it is not
meant to be reviewed line by line: `.gitattributes` marks it as
generated so GitHub folds it, and a test fails when the sources change
without the bundle being rebuilt.

Co-Authored-By: Claude <noreply@anthropic.com>
The audit failed against a real tenant with "Additional properties not
allowed: per_page,page". The endpoint that lists an application's
connections pages by checkpoint (a count, a token to start from, and a
`next` token in the response) and rejects page numbers, but every list
was read with page numbers.

That endpoint now has its own helper that follows the `next` token. The
test double was too forgiving: it accepted page numbers on this
endpoint, so no test noticed. It now behaves like the real endpoint,
and a test reads an application with more connections than one page.

The bundle is rebuilt in the same commit, because a test fails when the
bundle is older than its sources.

Co-Authored-By: Claude <noreply@anthropic.com>
`auth0 api` reads stdin until it ends for every method except GET,
even when there is no body, and waits forever if it never ends. The
transport closes stdin for every request, but nothing tested that.
A test now runs a stand-in command that reads stdin to the end and
fails after a few seconds if the request does not finish. The stand-in
stops by itself, so a failing run does not keep the test process alive.

Co-Authored-By: Claude <noreply@anthropic.com>
jacobovidal and others added 3 commits October 26, 2026 18:49
The scope message was matched on all of the CLI's output first, so an
API error whose message quoted "lacks scope" was reported as a missing
permission. The API error now comes first, its body is read to the end
of the output (the CLI prints long bodies over several lines), and the
status must not be part of a longer number.

Co-Authored-By: Claude <noreply@anthropic.com>
The login test checked for text the message always contains, the
half-set proxy test looked for the token name with a trailing comma, and
no test proved that the source files are hashed in sorted order. They
now check the CLI's own reason, the bare variable name, and a tree whose
sorted order differs from the order a directory walk finds it.

Co-Authored-By: Claude <noreply@anthropic.com>
The header of api.ts still said every request goes through the loopback
proxy, which is no longer true now that the auth0 api command is used
as well. The bundle is rebuilt because it carries the comment.

Co-Authored-By: Claude <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant