github.com/koron/hk@v0.0.0-20150303213137-b8aeaa3ab34c/auth.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "strings" 8 "time" 9 10 "github.com/heroku/hk/Godeps/_workspace/src/github.com/bgentry/heroku-go" 11 "github.com/heroku/hk/Godeps/_workspace/src/github.com/bgentry/speakeasy" 12 "github.com/heroku/hk/hkclient" 13 "github.com/heroku/hk/term" 14 ) 15 16 var cmdAuthorize = &Command{ 17 Run: runAuthorize, 18 Usage: "authorize", 19 Category: "hk", 20 Short: "procure a temporary privileged token" + extra, 21 Long: ` 22 Have heroku-agent procure and store a temporary privileged token 23 that will bypass any requirement for a second authentication factor. 24 25 Example: 26 27 $ hk authorize 28 Enter email: user@test.com 29 Enter two-factor auth code: 30 Authorization successful. 31 `, 32 } 33 34 func runAuthorize(cmd *Command, args []string) { 35 if len(args) != 0 { 36 cmd.PrintUsage() 37 os.Exit(2) 38 } 39 40 if os.Getenv("HEROKU_AGENT_SOCK") == "" { 41 printFatal("Authorize must be used with heroku-agent; please set " + 42 "HEROKU_AGENT_SOCK") 43 } 44 45 var twoFactorCode string 46 fmt.Printf("Enter two-factor auth code: ") 47 if _, err := fmt.Scanln(&twoFactorCode); err != nil { 48 printFatal("reading two-factor auth code: " + err.Error()) 49 } 50 51 client.AdditionalHeaders.Set("Heroku-Two-Factor-Code", twoFactorCode) 52 53 // No-op: GET /apps with max=0. heroku-agent will detect that a two-factor 54 // code was included and attempt to procure a temporary token. This token 55 // will then be re-used automatically on subsequent requests. 56 _, err := client.AppList(&heroku.ListRange{Field: "name", Max: 0}) 57 must(err) 58 59 fmt.Println("Authorization successful.") 60 } 61 62 var cmdCreds = &Command{ 63 Run: runCreds, 64 Usage: "creds", 65 Category: "hk", 66 Short: "show credentials" + extra, 67 Long: `Creds shows credentials that will be used for API calls.`, 68 } 69 70 func runCreds(cmd *Command, args []string) { 71 var err error 72 73 nrc, err = hkclient.LoadNetRc() 74 if err != nil { 75 printFatal(err.Error()) 76 } 77 78 u, err := url.Parse(apiURL) 79 if err != nil { 80 printFatal("could not parse API url: " + err.Error()) 81 } 82 83 user, pass, err := nrc.GetCreds(u) 84 if err != nil { 85 printFatal("could not get credentials: " + err.Error()) 86 } 87 88 fmt.Println(user, pass) 89 } 90 91 var cmdLogin = &Command{ 92 Run: runLogin, 93 Usage: "login", 94 Category: "hk", 95 Short: "log in to your Heroku account" + extra, 96 Long: ` 97 Log in with your Heroku credentials. Input is accepted by typing 98 on the terminal. On unix machines, you can also pipe a password 99 on standard input. 100 101 Example: 102 103 $ hk login 104 Enter email: user@test.com 105 Enter password: 106 Login successful. 107 `, 108 } 109 110 func runLogin(cmd *Command, args []string) { 111 if len(args) != 0 { 112 cmd.PrintUsage() 113 os.Exit(2) 114 } 115 116 oldEmail := client.Username 117 var email string 118 if oldEmail == "" { 119 fmt.Printf("Enter email: ") 120 } else { 121 fmt.Printf("Enter email [%s]: ", oldEmail) 122 } 123 _, err := fmt.Scanln(&email) 124 switch { 125 case err != nil && err.Error() != "unexpected newline": 126 printFatal(err.Error()) 127 case email == "" && oldEmail == "": 128 printFatal("email is required.") 129 case email == "": 130 email = oldEmail 131 } 132 133 // NOTE: gopass doesn't support multi-byte chars on Windows 134 password, err := readPassword("Enter password: ") 135 switch { 136 case err == nil: 137 case err.Error() == "unexpected newline": 138 printFatal("password is required.") 139 default: 140 printFatal(err.Error()) 141 } 142 143 hostname, token, err := attemptLogin(email, password, "") 144 if err != nil { 145 if herror, ok := err.(heroku.Error); ok && herror.Id == "two_factor" { 146 // 2FA requested, attempt 2FA login 147 var twoFactorCode string 148 fmt.Printf("Enter two-factor auth code: ") 149 if _, err := fmt.Scanln(&twoFactorCode); err != nil { 150 printFatal("reading two-factor auth code: " + err.Error()) 151 } 152 hostname, token, err = attemptLogin(email, password, twoFactorCode) 153 must(err) 154 } else { 155 must(err) 156 } 157 } 158 159 nrc, err = hkclient.LoadNetRc() 160 if err != nil { 161 printFatal("loading netrc: " + err.Error()) 162 } 163 164 err = nrc.SaveCreds(hostname, email, token) 165 if err != nil { 166 printFatal("saving new token: " + err.Error()) 167 } 168 fmt.Println("Logged in.") 169 } 170 171 func readPassword(prompt string) (password string, err error) { 172 if acceptPasswordFromStdin && !term.IsTerminal(os.Stdin) { 173 _, err = fmt.Scanln(&password) 174 return 175 } 176 // NOTE: speakeasy may not support multi-byte chars on Windows 177 return speakeasy.Ask("Enter password: ") 178 } 179 180 func attemptLogin(username, password, twoFactorCode string) (hostname, token string, err error) { 181 description := "hk login from " + time.Now().UTC().Format(time.RFC3339) 182 expires := 2592000 // 30 days 183 opts := heroku.OAuthAuthorizationCreateOpts{ 184 Description: &description, 185 ExpiresIn: &expires, 186 } 187 188 req, err := client.NewRequest("POST", "/oauth/authorizations", &opts) 189 if err != nil { 190 return "", "", fmt.Errorf("unknown error when creating login request: %s", err.Error()) 191 } 192 req.SetBasicAuth(username, password) 193 194 if twoFactorCode != "" { 195 req.Header.Set("Heroku-Two-Factor-Code", twoFactorCode) 196 } 197 198 var auth heroku.OAuthAuthorization 199 if err = client.DoReq(req, &auth); err != nil { 200 return 201 } 202 if auth.AccessToken == nil { 203 return "", "", fmt.Errorf("access token missing from Heroku API login response") 204 } 205 return strings.Split(req.Host, ":")[0], auth.AccessToken.Token, nil 206 } 207 208 var cmdLogout = &Command{ 209 Run: runLogout, 210 Usage: "logout", 211 Category: "hk", 212 Short: "log out of your Heroku account" + extra, 213 Long: ` 214 Log out of your Heroku account and remove credentials from 215 this machine. 216 217 Example: 218 219 $ hk logout 220 Logged out. 221 `, 222 } 223 224 func runLogout(cmd *Command, args []string) { 225 if len(args) != 0 { 226 cmd.PrintUsage() 227 os.Exit(2) 228 } 229 u, err := url.Parse(client.URL) 230 if err != nil { 231 printFatal("couldn't parse client URL: " + err.Error()) 232 } 233 234 nrc, err = hkclient.LoadNetRc() 235 if err != nil { 236 printError(err.Error()) 237 } 238 239 err = removeCreds(strings.Split(u.Host, ":")[0]) 240 if err != nil { 241 printFatal("saving new netrc: " + err.Error()) 242 } 243 fmt.Println("Logged out.") 244 }