Commit 77d6522
feat(cli): named login profiles for multi-organization work
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>1 parent e1e829c commit 77d6522
21 files changed
Lines changed: 3310 additions & 261 deletions
File tree
- packages
- api
- cmd
- config
- models
- telemetry
- util
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| 80 | + | |
80 | 81 | | |
81 | 82 | | |
82 | 83 | | |
| |||
160 | 161 | | |
161 | 162 | | |
162 | 163 | | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
163 | 183 | | |
164 | 184 | | |
165 | 185 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
64 | 67 | | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
70 | 74 | | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
104 | 78 | | |
105 | | - | |
106 | 79 | | |
107 | | - | |
108 | | - | |
109 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
110 | 89 | | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
115 | 94 | | |
116 | | - | |
117 | | - | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
118 | 121 | | |
119 | 122 | | |
120 | 123 | | |
| |||
140 | 143 | | |
141 | 144 | | |
142 | 145 | | |
| 146 | + | |
| 147 | + | |
143 | 148 | | |
144 | 149 | | |
145 | 150 | | |
146 | 151 | | |
147 | 152 | | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
148 | 188 | | |
149 | 189 | | |
150 | 190 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
134 | 134 | | |
135 | 135 | | |
136 | 136 | | |
137 | | - | |
138 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
139 | 140 | | |
140 | 141 | | |
141 | 142 | | |
142 | 143 | | |
143 | 144 | | |
144 | | - | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
145 | 148 | | |
146 | 149 | | |
147 | 150 | | |
| |||
227 | 230 | | |
228 | 231 | | |
229 | 232 | | |
230 | | - | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
231 | 267 | | |
232 | 268 | | |
233 | 269 | | |
| |||
236 | 272 | | |
237 | 273 | | |
238 | 274 | | |
239 | | - | |
240 | | - | |
241 | | - | |
242 | | - | |
243 | | - | |
244 | 275 | | |
245 | 276 | | |
246 | 277 | | |
| |||
267 | 298 | | |
268 | 299 | | |
269 | 300 | | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
270 | 312 | | |
271 | 313 | | |
272 | 314 | | |
| |||
0 commit comments