@@ -2,20 +2,39 @@ package cli
2
2
3
3
import (
4
4
"fmt"
5
+ "io/ioutil"
5
6
"net/url"
7
+ "os/exec"
6
8
"os/user"
9
+ "runtime"
7
10
"strings"
8
11
9
12
"github.com/fatih/color"
10
13
"github.com/go-playground/validator/v10"
11
14
"github.com/manifoldco/promptui"
15
+ "github.com/pkg/browser"
12
16
"github.com/spf13/cobra"
13
17
"golang.org/x/xerrors"
14
18
15
19
"github.com/coder/coder/coderd"
16
20
"github.com/coder/coder/codersdk"
17
21
)
18
22
23
+ const (
24
+ goosWindows = "windows"
25
+ goosDarwin = "darwin"
26
+ )
27
+
28
+ func init () {
29
+ // Hide output from the browser library,
30
+ // otherwise we can get really verbose and non-actionable messages
31
+ // when in SSH or another type of headless session
32
+ // NOTE: This needs to be in `init` to prevent data races
33
+ // (multiple threads trying to set the global browser.Std* variables)
34
+ browser .Stderr = ioutil .Discard
35
+ browser .Stdout = ioutil .Discard
36
+ }
37
+
19
38
func login ()* cobra.Command {
20
39
return & cobra.Command {
21
40
Use :"login <url>" ,
@@ -116,8 +135,10 @@ func login() *cobra.Command {
116
135
if err != nil {
117
136
return xerrors .Errorf ("login with password: %w" ,err )
118
137
}
138
+
139
+ sessionToken := resp .SessionToken
119
140
config := createConfig (cmd )
120
- err = config .Session ().Write (resp . SessionToken )
141
+ err = config .Session ().Write (sessionToken )
121
142
if err != nil {
122
143
return xerrors .Errorf ("write session token: %w" ,err )
123
144
}
@@ -130,7 +151,82 @@ func login() *cobra.Command {
130
151
return nil
131
152
}
132
153
154
+ authURL := * serverURL
155
+ authURL .Path = serverURL .Path + "/cli-auth"
156
+ if err := openURL (authURL .String ());err != nil {
157
+ _ ,_ = fmt .Fprintf (cmd .OutOrStdout (),"Open the following in your browser:\n \n \t %s\n \n " ,authURL .String ())
158
+ }else {
159
+ _ ,_ = fmt .Fprintf (cmd .OutOrStdout (),"Your browser has been opened to visit:\n \n \t %s\n \n " ,authURL .String ())
160
+ }
161
+
162
+ sessionToken ,err := prompt (cmd ,& promptui.Prompt {
163
+ Label :"Paste your token here:" ,
164
+ Mask :'*' ,
165
+ Validate :func (token string )error {
166
+ client .SessionToken = token
167
+ _ ,err := client .User (cmd .Context (),"me" )
168
+ if err != nil {
169
+ return xerrors .New ("That's not a valid token!" )
170
+ }
171
+ return err
172
+ },
173
+ })
174
+ if err != nil {
175
+ return xerrors .Errorf ("paste token prompt: %w" ,err )
176
+ }
177
+
178
+ // Login to get user data - verify it is OK before persisting
179
+ client .SessionToken = sessionToken
180
+ resp ,err := client .User (cmd .Context (),"me" )
181
+ if err != nil {
182
+ return xerrors .Errorf ("get user: %w" ,err )
183
+ }
184
+
185
+ config := createConfig (cmd )
186
+ err = config .Session ().Write (sessionToken )
187
+ if err != nil {
188
+ return xerrors .Errorf ("write session token: %w" ,err )
189
+ }
190
+ err = config .URL ().Write (serverURL .String ())
191
+ if err != nil {
192
+ return xerrors .Errorf ("write server url: %w" ,err )
193
+ }
194
+
195
+ _ ,_ = fmt .Fprintf (cmd .OutOrStdout (),"%s Welcome to Coder, %s! You're authenticated.\n " ,color .HiBlackString (">" ),color .HiCyanString (resp .Username ))
133
196
return nil
134
197
},
135
198
}
136
199
}
200
+
201
+ // isWSL determines if coder-cli is running within Windows Subsystem for Linux
202
+ func isWSL () (bool ,error ) {
203
+ if runtime .GOOS == goosDarwin || runtime .GOOS == goosWindows {
204
+ return false ,nil
205
+ }
206
+ data ,err := ioutil .ReadFile ("/proc/version" )
207
+ if err != nil {
208
+ return false ,xerrors .Errorf ("read /proc/version: %w" ,err )
209
+ }
210
+ return strings .Contains (strings .ToLower (string (data )),"microsoft" ),nil
211
+ }
212
+
213
+ // openURL opens the provided URL via user's default browser
214
+ func openURL (urlToOpen string )error {
215
+ var cmd string
216
+ var args []string
217
+
218
+ wsl ,err := isWSL ()
219
+ if err != nil {
220
+ return xerrors .Errorf ("test running Windows Subsystem for Linux: %w" ,err )
221
+ }
222
+
223
+ if wsl {
224
+ cmd = "cmd.exe"
225
+ args = []string {"/c" ,"start" }
226
+ urlToOpen = strings .ReplaceAll (urlToOpen ,"&" ,"^&" )
227
+ args = append (args ,urlToOpen )
228
+ return exec .Command (cmd ,args ... ).Start ()
229
+ }
230
+
231
+ return browser .OpenURL (urlToOpen )
232
+ }