@@ -4,19 +4,23 @@ import (
4
4
"bufio"
5
5
"crypto/rand"
6
6
"crypto/sha256"
7
+ "crypto/tls"
7
8
"encoding/base64"
8
9
"fmt"
9
10
"net/http"
10
11
"net/url"
11
12
"os"
12
13
"os/exec"
14
+ "path"
13
15
"runtime"
16
+ "strconv"
14
17
"strings"
15
18
"time"
16
19
17
20
"context"
18
21
19
22
"github.com/danielgtaylor/restish/cli"
23
+ "github.com/spf13/viper"
20
24
"golang.org/x/oauth2"
21
25
)
22
26
@@ -178,7 +182,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
178
182
179
183
// AuthorizationCodeTokenSource with PKCE as described in:
180
184
// https://www.oauth.com/oauth2-servers/pkce/
181
- // This works by running a local HTTP server on port 8484 and then having the
185
+ // This works by running a local HTTP or HTTPS server on port 8484 and then having the
182
186
// user log in through a web browser, which redirects to the local server with
183
187
// an authorization code. That code is then used to make another HTTP request
184
188
// to fetch an auth token (and refresh token). That token is then in turn
@@ -190,6 +194,7 @@ type AuthorizationCodeTokenSource struct {
190
194
TokenURL string
191
195
EndpointParams * url.Values
192
196
Scopes []string
197
+ UseHTTPS bool
193
198
}
194
199
195
200
// Token generates a new token using an authorization code.
@@ -213,12 +218,22 @@ func (ac *AuthorizationCodeTokenSource) Token() (*oauth2.Token, error) {
213
218
panic (err )
214
219
}
215
220
221
+ redirectURL := url.URL {
222
+ Host : "localhost:8484" ,
223
+ Path : "/" ,
224
+ }
225
+ if ac .UseHTTPS {
226
+ redirectURL .Scheme = "https"
227
+ } else {
228
+ redirectURL .Scheme = "http"
229
+ }
230
+
216
231
aq := authorizeURL .Query ()
217
232
aq .Set ("response_type" , "code" )
218
233
aq .Set ("code_challenge" , challenge )
219
234
aq .Set ("code_challenge_method" , "S256" )
220
235
aq .Set ("client_id" , ac .ClientID )
221
- aq .Set ("redirect_uri" , "http://localhost:8484/" )
236
+ aq .Set ("redirect_uri" , redirectURL . String () )
222
237
aq .Set ("scope" , strings .Join (ac .Scopes , " " ))
223
238
if ac .EndpointParams != nil {
224
239
for k , v := range * ac .EndpointParams {
@@ -234,16 +249,38 @@ func (ac *AuthorizationCodeTokenSource) Token() (*oauth2.Token, error) {
234
249
}
235
250
236
251
s := & http.Server {
237
- Addr : "localhost:8484" ,
252
+ Addr : redirectURL . Host ,
238
253
Handler : handler ,
239
254
ReadTimeout : 5 * time .Second ,
240
255
WriteTimeout : 5 * time .Second ,
241
256
MaxHeaderBytes : 1024 ,
242
257
}
243
258
259
+ if ac .UseHTTPS {
260
+ configDirectory := viper .GetString ("config-directory" )
261
+ certName := path .Join (configDirectory , "localhost.crt" )
262
+ keyfileName := path .Join (configDirectory , "localhost.key" )
263
+
264
+ cert , err := tls .LoadX509KeyPair (certName , keyfileName )
265
+ if err != nil {
266
+ panic (err )
267
+ }
268
+
269
+ s .TLSConfig = & tls.Config {
270
+ Certificates : []tls.Certificate {cert },
271
+ }
272
+ }
273
+
244
274
go func () {
245
275
// Run in a goroutine until the server is closed or we get an error.
246
- if err := s .ListenAndServe (); err != http .ErrServerClosed {
276
+ var err error
277
+ if ac .UseHTTPS {
278
+ err = s .ListenAndServeTLS ("" , "" )
279
+ } else {
280
+ err = s .ListenAndServe ()
281
+ }
282
+
283
+ if err != http .ErrServerClosed {
247
284
panic (err )
248
285
}
249
286
}()
@@ -279,7 +316,7 @@ func (ac *AuthorizationCodeTokenSource) Token() (*oauth2.Token, error) {
279
316
payload .Set ("client_id" , ac .ClientID )
280
317
payload .Set ("code_verifier" , verifier )
281
318
payload .Set ("code" , code )
282
- payload .Set ("redirect_uri" , "http://localhost:8484/" )
319
+ payload .Set ("redirect_uri" , redirectURL . String () )
283
320
if ac .ClientSecret != "" {
284
321
payload .Set ("client_secret" , ac .ClientSecret )
285
322
}
@@ -299,6 +336,7 @@ func (h *AuthorizationCodeHandler) Parameters() []cli.AuthParam {
299
336
{Name : "authorize_url" , Required : true , Help : "OAuth 2.0 authorization URL, e.g. https://api.example.com/oauth/authorize" },
300
337
{Name : "token_url" , Required : true , Help : "OAuth 2.0 token URL, e.g. https://api.example.com/oauth/token" },
301
338
{Name : "scopes" , Help : "Optional scopes to request in the token" },
339
+ {Name : "use_https" , Help : "Use HTTPS for authentication page" },
302
340
}
303
341
}
304
342
@@ -307,21 +345,31 @@ func (h *AuthorizationCodeHandler) OnRequest(request *http.Request, key string,
307
345
if request .Header .Get ("Authorization" ) == "" {
308
346
endpointParams := url.Values {}
309
347
for k , v := range params {
310
- if k == "client_id" || k == "client_secret" || k == "scopes" || k == "authorize_url" || k == "token_url" {
348
+ if k == "client_id" || k == "client_secret" || k == "scopes" || k == "authorize_url" || k == "token_url" || k == "use_https" {
311
349
// Not a custom param...
312
350
continue
313
351
}
314
352
315
353
endpointParams .Add (k , v )
316
354
}
317
355
356
+ var useHTTPS bool
357
+ if v := params ["use_https" ]; v != "" {
358
+ var err error
359
+ useHTTPS , err = strconv .ParseBool (v )
360
+ if err != nil {
361
+ return err
362
+ }
363
+ }
364
+
318
365
source := & AuthorizationCodeTokenSource {
319
366
ClientID : params ["client_id" ],
320
367
ClientSecret : params ["client_secret" ],
321
368
AuthorizeURL : params ["authorize_url" ],
322
369
TokenURL : params ["token_url" ],
323
370
EndpointParams : & endpointParams ,
324
371
Scopes : strings .Split (params ["scopes" ], "," ),
372
+ UseHTTPS : useHTTPS ,
325
373
}
326
374
327
375
// Try to get a cached refresh token from the current profile and use
0 commit comments