2026-04-13 13:40:18 +05:30
package cloud
import (
"github.com/safedep/dry/cloud"
"github.com/safedep/dry/log"
2026-05-24 12:22:19 +05:30
"github.com/safedep/dry/usefulerror"
"github.com/safedep/pmg/errcodes"
2026-05-24 12:46:06 +05:30
"github.com/safedep/pmg/internal/ui"
2026-04-13 13:40:18 +05:30
"github.com/spf13/cobra"
)
2026-07-15 21:53:55 +05:30
var (
loginFromEnv bool
loginInsecureFileStore bool
)
2026-05-13 21:19:43 +05:30
2026-04-13 13:40:18 +05:30
func newLoginCommand () * cobra . Command {
2026-05-13 21:19:43 +05:30
cmd := & cobra . Command {
2026-04-13 13:40:18 +05:30
Use : "login" ,
Short : "Store SafeDep Cloud credentials securely" ,
RunE : runLogin ,
}
2026-05-13 21:19:43 +05:30
cmd . Flags (). BoolVar ( & loginFromEnv , "from-env" , false ,
"Read credentials from SAFEDEP_API_KEY and SAFEDEP_TENANT_ID environment variables" )
2026-07-15 21:53:55 +05:30
cmd . Flags (). BoolVar ( & loginInsecureFileStore , "insecure-file-store" , false ,
"Store credentials in a plaintext file when no OS keychain is available (headless Linux, containers)" )
2026-05-13 21:19:43 +05:30
return cmd
2026-04-13 13:40:18 +05:30
}
func runLogin ( cmd * cobra . Command , args [] string ) error {
2026-05-13 21:19:43 +05:30
var tenantID , apiKey string
2026-04-13 13:40:18 +05:30
2026-05-13 21:19:43 +05:30
if loginFromEnv {
resolver , err := cloud . NewEnvCredentialResolver ()
if err != nil {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
2026-05-13 21:19:43 +05:30
Wrap ( err ).
2026-05-24 12:22:19 +05:30
WithCode ( errcodes . Lifecycle ).
2026-05-13 21:19:43 +05:30
WithHumanError ( "Failed to create environment credential resolver" ))
}
2026-04-13 13:40:18 +05:30
2026-05-13 21:19:43 +05:30
creds , err := resolver . Resolve ()
if err != nil {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
2026-05-13 21:19:43 +05:30
Wrap ( err ).
2026-05-24 12:22:19 +05:30
WithCode ( errcodes . InvalidArgument ).
2026-05-13 21:19:43 +05:30
WithHumanError ( "Failed to resolve credentials from environment" ).
WithHelp ( "Set SAFEDEP_API_KEY and SAFEDEP_TENANT_ID environment variables" ))
}
2026-04-13 13:40:18 +05:30
2026-05-13 21:19:43 +05:30
apiKey , err = creds . GetAPIKey ()
if err != nil || apiKey == "" {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
WithCode ( errcodes . InvalidArgument ).
2026-05-13 21:19:43 +05:30
WithHumanError ( "SAFEDEP_API_KEY environment variable is not set" ))
}
tenantID , err = creds . GetTenantDomain ()
if err != nil || tenantID == "" {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
WithCode ( errcodes . InvalidArgument ).
2026-05-13 21:19:43 +05:30
WithHumanError ( "SAFEDEP_TENANT_ID environment variable is not set" ))
}
} else {
var err error
tenantID , err = ui . PromptInput ( "Tenant ID: " )
if err != nil {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
2026-05-13 21:19:43 +05:30
Wrap ( err ).
2026-05-24 12:22:19 +05:30
WithCode ( errcodes . Lifecycle ).
2026-05-13 21:19:43 +05:30
WithHumanError ( "Failed to read Tenant ID" ))
}
if tenantID == "" {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
WithCode ( errcodes . InvalidArgument ).
2026-05-13 21:19:43 +05:30
WithHumanError ( "Tenant ID cannot be empty" ))
}
apiKey , err = ui . PromptSecret ( "API Key: " )
if err != nil {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
2026-05-13 21:19:43 +05:30
Wrap ( err ).
2026-05-24 12:22:19 +05:30
WithCode ( errcodes . Lifecycle ).
2026-05-13 21:19:43 +05:30
WithHumanError ( "Failed to read API Key" ))
}
if apiKey == "" {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
WithCode ( errcodes . InvalidArgument ).
2026-05-13 21:19:43 +05:30
WithHumanError ( "API Key cannot be empty" ))
}
2026-04-13 13:40:18 +05:30
}
2026-07-15 21:53:55 +05:30
var opts [] cloud . KeychainOption
if loginInsecureFileStore {
opts = append ( opts , cloud . WithInsecureFileFallback ())
}
store , err := cloud . NewKeychainCredentialStore ( opts ... )
2026-04-13 13:40:18 +05:30
if err != nil {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
2026-04-13 13:40:18 +05:30
Wrap ( err ).
2026-05-24 12:22:19 +05:30
WithCode ( errcodes . Lifecycle ).
2026-04-13 13:40:18 +05:30
WithHumanError ( "Failed to initialize credential store" ).
2026-07-15 21:53:55 +05:30
WithHelp ( "No OS keychain is available. Re-run with --insecure-file-store to use plaintext file storage, or set SAFEDEP_API_KEY and SAFEDEP_TENANT_ID environment variables" ))
2026-04-13 13:40:18 +05:30
}
defer func () {
if err := store . Close (); err != nil {
log . Warnf ( "failed to close credential store: %v" , err )
}
}()
if err := store . SaveAPIKeyCredential ( apiKey , tenantID ); err != nil {
2026-05-24 12:22:19 +05:30
ui . ErrorExit ( usefulerror . NewUsefulError ().
2026-04-13 13:40:18 +05:30
Wrap ( err ).
2026-05-24 12:22:19 +05:30
WithCode ( errcodes . Lifecycle ).
2026-04-13 13:40:18 +05:30
WithHumanError ( "Failed to save credentials" ).
WithHelp ( "Your system may not support secure credential storage" ))
}
2026-07-15 21:53:55 +05:30
if loginInsecureFileStore {
ui . Successf ( "Credentials saved" )
} else {
ui . Successf ( "Credentials saved securely" )
}
2026-04-13 13:40:18 +05:30
return nil
}