Skip to content

feat: support --organization-slug flag for user/browser SSO login#143

Open
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin/1772816828-organization-slug-user-login
Open

feat: support --organization-slug flag for user/browser SSO login#143
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin/1772816828-organization-slug-user-login

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Mar 6, 2026

feat: support --organization-slug for user/browser SSO login

Summary

Extends the existing --organization-slug flag (previously only for machine identity login) to also work with user login methods, including interactive browser/SSO login. This allows users to scope their login session to a specific sub-organization without manually selecting it during the interactive flow.

Approach: After any user login completes (browser SSO or CLI email/password), if --organization-slug is provided, the CLI:

  1. Calls GET /v1/organization/accessible-with-sub-orgs to fetch all accessible orgs and sub-orgs
  2. Resolves the slug to an organization ID by searching root orgs and their sub-organizations
  3. Prompts the user to confirm the matched organization name and slug before proceeding
  4. Calls selectOrganization to re-scope the JWT token to the matched organization

This is a purely additive change — existing login behavior is unchanged when the flag is not provided.

Files changed:

  • packages/api/model.go — New response types for the accessible-with-sub-orgs endpoint
  • packages/api/api.go — New CallGetAccessibleOrganizationsWithSubOrgs API function
  • packages/cmd/login.gorescopeTokenToOrgBySlug() function + integration into user login flow + updated flag description

Updates since initial revision

Addressed Greptile review feedback across two rounds, plus a security concern raised by an engineer:

Round 1 (8240907):

  • MFA safety check: rescopeTokenToOrgBySlug now inspects MfaEnabled on the SelectOrganizationResponse. If the target org requires MFA, the CLI returns a clear error message instead of silently storing a pre-MFA token.
  • Flag conflict validation: Added early validation that errors out if both --organization-id and --organization-slug are provided, preventing silent override of --organization-id by the slug rescoping logic. This only applies to direct email/password login (browser/interactive login doesn't use --organization-id).

Round 2 (003476b):

  • Error handling in conflict check: The error return from util.GetCmdFlagOrEnv in the conflict validation was silently discarded with _. Now properly handled with util.HandleError(orgIdErr), consistent with the rest of the function.

Round 3 (a108a6b) — Security hardening:

  • User confirmation prompt before re-scoping: Addresses a phishing risk where an attacker could trick a user into running a command with a malicious --organization-slug, silently scoping their session to an attacker-controlled org. The CLI now displays the matched organization name and slug and requires explicit "Yes"/"No" confirmation via promptui.Select before re-scoping the token. If the user selects "No", the login is aborted with a clear message.

Review & Testing Checklist for Human

  • Verify backend endpoint exists and returns expected shape: This PR calls GET /v1/organization/accessible-with-sub-orgs. Confirm this endpoint exists and returns { organizations: [{ id, name, slug, subOrganizations: [{ id, name, slug }] }] }. If the response shape differs, the slug lookup will silently fail to match — this is the highest-risk item since the CLI changes were made without backend verification.
  • Verify the initial token (pre-org-selection) can call the accessible-with-sub-orgs endpoint: After browser SSO login completes but before org selection, the token may have limited scope. Confirm that calling accessible-with-sub-orgs with this token works, otherwise the rescoping flow will fail.
  • Non-interactive/CI context behavior: The new confirmation prompt uses promptui.Select, which requires interactive stdin. Verify that using --organization-slug in a non-interactive context (e.g., piped input, CI scripts with user login) either fails gracefully or consider whether a --yes/--force flag is needed to skip the prompt for automation use cases.
  • Test end-to-end (browser SSO): Run infisical login --organization-slug <sub-org-slug> with browser SSO, confirm the prompt shows the correct org name, select "Yes", and verify the stored token is scoped to the sub-org (e.g., by running infisical secrets get against a project in that sub-org).
  • Test error cases: (a) Non-existent slug → expect "not found or not accessible" error. (b) MFA-enforced org → expect clear MFA requirement error. (c) Direct login with both --organization-id and --organization-slug → expect conflict error. (d) User selects "No" at confirmation prompt → expect clear cancellation message.
  • Verify no breaking changes: Run infisical login without --organization-slug and confirm the interactive org selection flow still works. Also verify machine identity login with --organization-slug is unaffected (machine identity should continue to work without prompts since it uses a different code path).

Notes

  • No backend changes are included — this relies entirely on existing backend endpoints.
  • The --organization-slug flag was already registered on the login command (for machine identity use). This PR reuses it for user login without adding a new flag.
  • No automated or manual testing was performed. The login flow requires real SSO authentication and access to organizations/sub-organizations, making it difficult to test without a production-like environment.
  • Documentation for the CLI lives in the main Infisical/infisical repo under /docs; a docs update should follow separately.
  • Security note: The confirmation prompt prevents phishing attacks where an attacker tricks a user into scoping their session to a malicious organization. The user always sees the full organization name and slug before confirming.
  • Non-interactive use: Users running infisical login --organization-slug <slug> in CI/automation scripts will encounter an interactive prompt. If this becomes a common pattern, consider adding a --yes or --skip-prompt flag to bypass confirmation for non-interactive environments.

Link to Devin session: https://app.devin.ai/sessions/783a23defe5244d09b8b3ceb771e49c6
Requested by: ashwin

Add support for scoping interactive SSO login to a specific
sub-organization using the --organization-slug flag, similar to how
machine identity login already works.

Changes:
- Add API types and function for accessible-with-sub-orgs endpoint
- Add rescopeTokenToOrgBySlug function to resolve org slug to ID
- After user login (browser or CLI), re-scope token if --organization-slug is set
- Update flag description to reflect support for both user and machine identity login

Co-Authored-By: ashwin <ashwin@infisical.com>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR extends the existing --organization-slug flag to work with user login methods (browser/SSO and interactive CLI), in addition to machine identity login. After a successful user login, if the flag is provided, a new rescopeTokenToOrgBySlug function resolves the slug against the /v1/organization/accessible-with-sub-orgs endpoint (searching both root orgs and sub-orgs) and exchanges the initial token for one scoped to the matched organization. The change is purely additive — existing login behavior is unaffected when the flag is omitted.

Key changes:

  • packages/api/model.go: Three new response types (SubOrganization, OrganizationWithSubOrgs, GetAccessibleOrganizationsWithSubOrgsResponse) to model the sub-orgs endpoint response.
  • packages/api/api.go: New CallGetAccessibleOrganizationsWithSubOrgs API function following the existing client pattern.
  • packages/cmd/login.go: rescopeTokenToOrgBySlug() helper + integration into the user login flow, with a conflict guard preventing simultaneous use of --organization-id and --organization-slug, and an MFA guard that surfaces a clear error instead of silently returning a pre-MFA token.
  • One minor issue: the error return from util.GetCmdFlagOrEnv in the conflict-check block is silently discarded (_), which could cause the guard to silently pass when the flag/env cannot be read.

Confidence Score: 4/5

  • This PR is safe to merge; the only outstanding issue is a minor silently-discarded error in the conflict validation path.
  • The core logic is clean and follows existing patterns. The MFA guard and conflict validation were both addressed in a previous iteration. The one remaining nit — ignoring the error from GetCmdFlagOrEnv in the conflict check — is low-risk in practice but inconsistent with how the same call is handled elsewhere in the same function.
  • packages/cmd/login.go — specifically the silently-discarded error at line 238 in the conflict check block.

Important Files Changed

Filename Overview
packages/cmd/login.go Adds --organization-slug support for user/browser login via rescopeTokenToOrgBySlug(); one minor issue: the error from GetCmdFlagOrEnv is silently discarded during conflict validation.
packages/api/api.go Adds CallGetAccessibleOrganizationsWithSubOrgs — clean, follows the same pattern as other API call functions in the file.
packages/api/model.go Adds SubOrganization, OrganizationWithSubOrgs, and GetAccessibleOrganizationsWithSubOrgsResponse types; well-structured and correctly mirrors expected API response shape.

Last reviewed commit: 8240907

Comment on lines +236 to +241
newToken, rescopeErr := rescopeTokenToOrgBySlug(userCredentialsToBeStored.JTWToken, organizationSlug)
if rescopeErr != nil {
util.HandleError(rescopeErr, "Unable to scope login to the specified organization")
}
userCredentialsToBeStored.JTWToken = newToken
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MFA bypass risk on re-scoped org token

rescopeTokenToOrgBySlug calls CallSelectOrganization and returns selectedOrgRes.Token, but ignores the MfaEnabled/MfaMethod fields from SelectOrganizationResponse. If the target organization (or sub-organization) enforces MFA, the returned token may be an incomplete/pre-MFA token that grants less access than expected — or one that silently bypasses MFA challenge handling that is performed elsewhere in the interactive login flow (e.g., in cliDefaultLogin).

The interactive CLI path (cliDefaultLogin) already handles MFA via a polling loop. If rescopeTokenToOrgBySlug returns a token that still requires MFA verification, subsequent API calls will silently fail with 401/403 rather than prompting the user for a code.

Consider inspecting the SelectOrganizationResponse for MFA fields and triggering the appropriate MFA challenge before storing the token:

selectedOrgRes, err := api.CallSelectOrganization(httpClient, api.SelectOrganizationRequest{OrganizationId: matchedOrgId})
if err != nil {
    return "", fmt.Errorf("unable to select organization: %w", err)
}
if selectedOrgRes.MfaEnabled {
    return "", fmt.Errorf("organization '%s' requires MFA; re-scoping via --organization-slug is not supported for MFA-protected orgs", organizationSlug)
}
return selectedOrgRes.Token, nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 8240907. rescopeTokenToOrgBySlug now checks selectedOrgRes.MfaEnabled and returns a clear error if the target org requires MFA:

if selectedOrgRes.MfaEnabled {
    return "", fmt.Errorf("organization '%s' requires MFA verification; please log in without --organization-slug and complete the MFA challenge during interactive org selection", organizationSlug)
}

This ensures we never silently store a pre-MFA token.

Copy link

@ashwin-infisical ashwin-infisical Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptile-apps can you re-run the review, taking into account all updated changes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work addressing the previous feedback! The MFA safety check and error handling improvements are now in place. I've reviewed the complete PR and the code looks solid. Here's my assessment:

✅ Previous Issues Resolved

MFA Safety (8240907): The rescoping function now properly checks selectedOrgRes.MfaEnabled and returns a clear error when the target org requires MFA. This prevents storing pre-MFA tokens.

Error Handling (003476b): The flag conflict validation now properly handles errors with util.HandleError(orgIdErr) instead of discarding them.

✅ Code Quality

API Integration: The new CallGetAccessibleOrganizationsWithSubOrgs function follows existing patterns and has proper error handling.

Model Definitions: Clean, well-structured types for sub-organizations.

Search Logic: The nested loop to find matching slugs (root orgs → sub-orgs) correctly breaks out of both loops when a match is found.

Flag Conflict Validation: Properly prevents users from specifying both --organization-id and --organization-slug during direct login, with clear error messaging.

📝 Notes

Scope: The rescoping logic is correctly placed only in the user login path (if loginMethod == "user"). Machine identity login already has separate org-slug handling.

Timing: Rescoping happens after login but before storing credentials, which is the correct order - you need the initial token to query accessible orgs, and you want to store the rescoped token.

Backend Dependency: As noted in your PR description, this relies on the backend having the /v1/organization/accessible-with-sub-orgs endpoint. If it doesn't exist on older instances, users will get a clear error from the API call.

🚀 Ready to Merge

The implementation looks solid and all previous concerns have been addressed. The testing checklist in your PR description covers the important edge cases (backend endpoint existence, token permissions, MFA enforcement, flag conflicts).

One optional future consideration: if users report confusion when the backend endpoint doesn't exist (e.g., on older self-hosted instances), you could add a more helpful error message that suggests upgrading the Infisical instance. But that's not blocking for this PR.

Comment on lines 888 to 939
return &loginResponse, nil
}

// rescopeTokenToOrgBySlug resolves an organization slug to its ID using the accessible-with-sub-orgs
// endpoint and then calls selectOrganization to get a new token scoped to that org.
func rescopeTokenToOrgBySlug(currentToken string, organizationSlug string) (string, error) {
httpClient, err := util.GetRestyClientWithCustomHeaders()
if err != nil {
return "", fmt.Errorf("unable to get resty client with custom headers: %w", err)
}
httpClient.SetAuthToken(currentToken)

// Fetch all accessible organizations including sub-orgs
orgsResponse, err := api.CallGetAccessibleOrganizationsWithSubOrgs(httpClient)
if err != nil {
return "", fmt.Errorf("unable to fetch accessible organizations: %w", err)
}

// Search for the matching organization by slug (both root orgs and sub-orgs)
var matchedOrgId string
for _, org := range orgsResponse.Organizations {
if org.Slug == organizationSlug {
matchedOrgId = org.ID
break
}
for _, subOrg := range org.SubOrganizations {
if subOrg.Slug == organizationSlug {
matchedOrgId = subOrg.ID
break
}
}
if matchedOrgId != "" {
break
}
}

if matchedOrgId == "" {
return "", fmt.Errorf("organization with slug '%s' not found or not accessible", organizationSlug)
}

// Call selectOrganization to get a new token scoped to the matched org
selectedOrgRes, err := api.CallSelectOrganization(httpClient, api.SelectOrganizationRequest{OrganizationId: matchedOrgId})
if err != nil {
return "", fmt.Errorf("unable to select organization: %w", err)
}

return selectedOrgRes.Token, nil
}

// Manages the browser login flow.
// Returns a UserCredentials object on success and an error on failure
func browserCliLogin() (models.UserCredentials, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feature discoverability — no documentation found

No documentation was found in the /docs folder (the folder does not exist in this repository). Could you clarify how users will discover the new --organization-slug behavior for user/browser SSO login? For example, is there a corresponding update to the Infisical docs site, a changelog entry, or a help-text reference users can follow?

The existing flag help text in init() does now mention both user and machine identity login, which is good — but external documentation for the browser SSO flow in particular would help users find and trust this feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 8240907. Added early validation that errors out when both --organization-id (or INFISICAL_ORGANIZATION_ID env) and --organization-slug are provided:

if organizationSlug != "" && isDirectUserLoginFlagsAndEnvsSet {
    orgIdFlag, _ := util.GetCmdFlagOrEnv(cmd, "organization-id", []string{"INFISICAL_ORGANIZATION_ID"})
    if orgIdFlag != "" {
        util.PrintErrorMessageAndExit("Cannot use both --organization-id and --organization-slug at the same time. Please use only one to specify the target organization.")
    }
}

This prevents the silent override behavior. Note this conflict can only occur in direct user login mode (email/password via flags), since browser/interactive login doesn't use --organization-id.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptile-apps please re-review with the latest changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest changes look good! Both rounds of feedback have been properly addressed:

✅ Round 1 feedback (commit 8240907):

  • MFA safety check: Correctly implemented at line 946-948. If the target org requires MFA, a clear error message is returned instead of storing a pre-MFA token.
  • Flag conflict validation: Properly validates at lines 238-246 that both --organization-id and --organization-slug cannot be used together.

✅ Round 2 feedback (commit 003476b):

  • Error handling fix: The error from util.GetCmdFlagOrEnv is now properly handled with util.HandleError(orgIdErr) at line 240-242, instead of being discarded.

Code quality observations:

The rescopeTokenToOrgBySlug function (lines 905-950) is well-structured:

  • Clean separation of concerns (fetch orgs → find match → select org → check MFA)
  • Proper error wrapping with context
  • Correct nested loop logic for searching both root orgs and sub-orgs
  • Clear, actionable error messages

The integration point (lines 230-253) is logical:

  • Conflict validation only runs for direct login mode (correct, since browser/interactive don't use --organization-id)
  • Rescoping happens after login but before storing credentials (correct order)
  • Errors are properly handled

No issues found. The implementation is solid and ready for the manual testing checklist outlined in the PR description.

…n-id/--organization-slug flags

- Check MfaEnabled on SelectOrganizationResponse and return a clear error
  if the target org requires MFA verification
- Error out early if both --organization-id and --organization-slug are
  provided to avoid silent override behavior

Co-Authored-By: ashwin <ashwin@infisical.com>
@devin-ai-integration
Copy link
Contributor Author

Thanks for the thorough review! All three concerns have been addressed:

  1. MFA bypass risk - Fixed. rescopeTokenToOrgBySlug now checks MfaEnabled on the SelectOrganizationResponse and returns a clear error if MFA is required, preventing silent storage of a pre-MFA token.

  2. Silent --organization-id / --organization-slug conflict - Fixed. Added early validation that errors out with a clear message if both flags are provided simultaneously.

  3. Documentation - The CLI docs live in the separate Infisical/infisical repo under /docs. The flag help text in init() has been updated to reflect the new behavior for both user and machine identity login. A docs update can follow as a separate PR to the main repo.

@greptile-apps please re-review with the latest changes.


// Validate that --organization-id and --organization-slug are not both set
if organizationSlug != "" && isDirectUserLoginFlagsAndEnvsSet {
orgIdFlag, _ := util.GetCmdFlagOrEnv(cmd, "organization-id", []string{"INFISICAL_ORGANIZATION_ID"})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error silently discarded on conflict check

The error return from util.GetCmdFlagOrEnv is discarded with _. If that call fails (e.g., because the flag is not registered or the env variable cannot be read), orgIdFlag will be an empty string and the conflict validation at line 239 will silently pass — meaning a user who has INFISICAL_ORGANIZATION_ID set and also provides --organization-slug will NOT see the expected error. Compare to the identical call at line 221–224 which correctly handles the error.

Suggested change
orgIdFlag, _ := util.GetCmdFlagOrEnv(cmd, "organization-id", []string{"INFISICAL_ORGANIZATION_ID"})
orgIdFlag, err := util.GetCmdFlagOrEnv(cmd, "organization-id", []string{"INFISICAL_ORGANIZATION_ID"})
if err != nil {
util.HandleError(err)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 003476b. The error is now properly handled instead of discarded:

orgIdFlag, orgIdErr := util.GetCmdFlagOrEnv(cmd, "organization-id", []string{"INFISICAL_ORGANIZATION_ID"})
if orgIdErr != nil {
    util.HandleError(orgIdErr)
}

This is now consistent with how the same call is handled at line 221-224.

@greptile-apps please re-review with the latest changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix confirmed

The error handling is now correct and consistent with the pattern at lines 221-224. The validation will properly fail if util.GetCmdFlagOrEnv encounters an error (e.g., flag not registered or environment variable issues), preventing the security concern where conflicts could be silently ignored.

Good catch and clean fix!

devin-ai-integration bot and others added 2 commits March 6, 2026 17:31
Instead of silently discarding the error with _, properly handle it
to ensure the --organization-id/--organization-slug conflict check
works correctly even if the flag/env cannot be read.

Co-Authored-By: ashwin <ashwin@infisical.com>
…ation

Addresses security concern where an attacker could trick a user into
running a command with a malicious --organization-slug. Now the CLI
shows the matched organization name and slug, and requires the user
to explicitly confirm before re-scoping the token.

Prompt example:
  You are about to scope your login to organization "Acme Sub-Org"
  (slug: acme-sub-org). Do you want to continue?
  > Yes
    No

Co-Authored-By: ashwin <ashwin@infisical.com>
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