Add named login profiles and directory-scoped tenant selection - #372
Add named login profiles and directory-scoped tenant selection#372scott-ray-wilson wants to merge 1 commit into
Conversation
|
💬 Discussion in Slack: #pr-review-cli-372-add-named-login-profiles-and-directory-scoped-tenant-selection Posted by Review Police — reviews, comments, new commits, and CI failures will stream into this channel. |
|
| Filename | Overview |
|---|---|
| packages/util/profile.go | Implements profile migration, selection, directory binding, organization resolution, and persistence; the previously reported concurrent whole-config update remains outstanding. |
| packages/util/credentials.go | Loads credentials by profile name, prevents explicit cross-domain token use, applies organization overrides, and intentionally leaves session renewal disabled. |
| packages/cmd/user.go | Reworks domain updates to target one named profile and aborts before repointing when credential cleanup fails. |
| packages/cmd/profile.go | Adds the profile command surface for creation, selection, terminal pinning, directory binding, organization defaults, and deletion. |
| packages/util/logout.go | Adds profile-aware local cleanup and session revocation while retaining server sessions shared by other profiles. |
| packages/cmd/root.go | Adds global profile and organization overrides and integrates them into command-wide resolution. |
| packages/api/api.go | Adds the authenticated API operation used to revoke a server-side user session. |
Reviews (5): Last reviewed commit: "feat(cli): named login profiles for mult..." | Re-trigger Greptile
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 6 · PR risk: 0/10 |
5c2f55a to
cff3364
Compare
|
@greptile review |
cff3364 to
84e2fcb
Compare
|
Verification script referenced in the description. Runs in a throwaway INFISICAL_BIN=./infisical \
VERIFY_DOMAIN=https://app.infisical.com \
VERIFY_EMAIL=you@example.com VERIFY_PASSWORD='...' \
VERIFY_ORG_A=<org-id> VERIFY_ORG_B=<org-id> \
./verify.shLast run against a local instance on the current head: 21 passed, 0 failed.
|
84e2fcb to
0cf3c39
Compare
|
@veria-ai review |
|
@greptile review |
0cf3c39 to
d6eb7bc
Compare
|
@greptile review |
Switching organizations meant logging out and back in. The CLI stored one
session per account, keyed by email in the keyring, and a session token is
scoped to a single organization, so a second login destroyed the first.
Working across tenants in parallel meant exporting tokens into env vars or
.env files.
A profile is now one login: an account on one instance, plus the organization
it uses by default. Each profile has its own keyring entry, so sessions
coexist, and selecting a profile selects the account, instance and
organization together.
The organization is a field of the profile rather than part of its identity.
--org and INFISICAL_ORG retarget a single command by name, slug or id, and the
organization-scoped token is cached per organization in the keyring, so the
switch costs one exchange and nothing thereafter. Changing the profile's
default is `profile set-org` (also reachable as `org switch`).
Which profile a command uses is decided by --profile, then INFISICAL_PROFILE,
then a bound directory, then the machine default. An explicit override wins over
a bound directory, and says so, so that a binding which did not apply is
explained rather than silently ignored. Those last three each get
their own verb, so all of them are discoverable from `profile --help`:
profile use <name> the default for this machine
profile pin <name> this terminal only, via eval
profile bind [name] [path] a directory and everything under it
Sub-organizations are handled throughout: they appear nested in `org list`,
`--org` resolves them by name, slug or id, and a profile scoped to one reports
it as "Acme / Research" rather than as the root organization it would otherwise
be indistinguishable from. Organizations that require MFA prompt during
`profile new` and `profile set-org`, which perform their own exchange; `--org`
on an ordinary command cannot prompt, so it fails with a message pointing at
the command that can.
Commands added:
profile list | current | new | use | pin | unpin | bind | unbind
| set-org | delete
org list | switch
logout
Session handling. Sessions continue to expire at JWT_AUTH_LIFETIME, with expiry
sending the user back through login, unchanged from today. Renewal via the
stored refresh token stays unimplemented on purpose: the server rotates the
refresh token on every refresh and treats a stale one as theft by revoking the
session, which several CLI processes sharing one vault entry cannot coordinate
safely. The token is also no longer written to the vault, since nothing read it
and storing it only widens what a stolen vault yields. `logout` revokes
server-side, and so do `profile delete` and `reset`. Because the server keys
sessions by user, IP and user agent, several profiles for one account on one
machine share a session, so a session another profile still uses is left intact
and only local credentials are removed.
Integration with existing commands: `init` uses the profile's organization
instead of asking again and offers to bind the directory; `user switch`
operates on profiles; `vault set` clears them. An explicit --domain now beats a
profile's saved domain instead of being silently overridden, `user update
domain` only repoints profiles that were on the instance being changed rather
than every profile sharing an email, and `reset` removes every stored session
instead of orphaning all but the active one.
Hardening from review: profile names are shell-quoted where pin prints an
export, since a derived name comes from a server-supplied email and would
otherwise run as a command under eval; organization selectors match by id, then
slug, then name, with ambiguity rejected, so an organization named after
another's id cannot be selected in its place; logout authenticates revocation
with any live token rather than only the profile's own, which previously let a
cached organization token survive locally deleted credentials; a profile's
session is refused rather than sent when an explicit --domain names a different
instance; `user update domain` selects a profile rather than an account, so profiles
sharing an email and instance for different organizations are not moved
together, and the moved profile's session is cleared, before the new instance is
recorded, since a session that outlived the change would be sent there; server-supplied names are stripped
of control characters before reaching a terminal; and the legacy login pointer
is published only for email-named profiles, so an older binary cannot load one
profile's token while aimed at another's instance.
Migration is lazy and requires no re-login. Legacy loggedInUserEmail and
loggedInUsers entries become profiles named after the account email, which is
also the legacy keyring key, so existing sessions keep working untouched, and
those fields stay in sync with the active profile for older binaries and
scripts that read them. Single-profile users see no change in behavior.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
d6eb7bc to
77d6522
Compare
|
@greptile review |
Problem
Switching organizations means logging out and back in. The CLI keeps one session per account, keyed by email in the keyring, and a session token is scoped to a single organization, so a second login destroys the first. Working across tenants in parallel means exporting tokens into env vars or
.envfiles. Community ask: Infisical/infisical#2191.Model
A profile is one login: an account on one instance, plus the organization it uses by default. Each profile gets its own keyring entry, so sessions coexist.
The organization is a field of the profile, not part of its identity.
--org/INFISICAL_ORGretarget a single command by name, slug or id, backed by an org-keyed token cache, so the switch costs one exchange and nothing after.profile set-org(akaorg switch) changes the profile's default.Selection per invocation:
--profile>INFISICAL_PROFILE> bound directory > machine default. Those last three each get a verb, so all are discoverable fromprofile --help:profile use <name>profile pin <name>(viaeval)profile unpinprofile bind [name] [path]profile unbindAn explicit override beats a bound directory and says so, so a binding that did not apply is explained rather than silently ignored.
Commands
Rationale for the less obvious calls
org switchand profiles felt like unrelated features. They were: org lived inside the profile's identity, so changing it meant mutating or forking a profile. Making it a field is the kubectl context/namespace split.login --profile x,profile new,org switchfrom a pinned shell all leave it alone. The default is what every unpinned terminal resolves to, so moving it reaches across terminals.--useopts in explicitly.JWT_AUTH_LIFETIME; renewal stays unimplemented. Unchanged frommain, where the refresh path is commented out. The server rotates the refresh token on every refresh and treats a stale one as theft by revoking the session, with a 10s grace window, which several CLI processes sharing one vault entry cannot coordinate. The token is also no longer persisted, since nothing read it.initstops asking for an organization. It takes the profile's. The old prompt also silently re-scoped your session as a side effect.profile newandprofile set-orgperform their own exchange and prompt.--orgon an ordinary command runs inside credential resolution and cannot prompt, so it fails with a message pointing at the command that can.org list, resolvable by--org, and a profile scoped to one reportsAcme / Researchrather than the root org it would otherwise be indistinguishable from.Compatibility
Migration is lazy and needs no re-login: legacy
loggedInUserEmail/loggedInUsersbecome profiles named after the account email, which is already the keyring key, so existing sessions keep working. Those fields stay synced, and amainbinary still works against a config this branch wrote (verified, including with several profiles present).Behavior changes for release notes:
initno longer prompts for an organization (--orgorprofile set-orgto cross);resetnow revokes sessions server-side instead of only deleting local files;vault setclears the profile list along with the login it already cleared.Verification
Automated script covering 20 behaviors (login and migration, targeted-login isolation, org-as-a-dimension including token-repointing,
profile newwith--use/--pin, the full precedence chain, pin's refusal to report false success, logout revocation including the shared-session case). Runs in a throwawayHOME, needs an account with two orgs:verify.shScript is attached in a comment below.
Done manually against a local instance: sub-org resolution by name and slug with a seeded child org, email-MFA prompts driven through a pty for
profile set-organdprofile new, single-profile output diffed byte-for-byte against amainbuild, downgrade tomainon a branch-written config, two terminals pinned to different profiles, and per-profile instance routing.Worth a reviewer's own pass:
--domainprofile set-org, and the--orgfailure message🤖 Generated with Claude Code