@@ -5,8 +5,6 @@ package auth
55import (
66 "context"
77 "fmt"
8- "net"
9- "net/http"
108 "os"
119
1210 "github.com/localstack/lstk/internal/api"
@@ -15,106 +13,69 @@ import (
1513)
1614
1715const webAppURL = "https://app.localstack.cloud"
18- const loginCallbackURL = "127.0.0.1:45678"
1916
2017type LoginProvider interface {
2118 Login (ctx context.Context ) (string , error )
2219}
2320
24- type browserLogin struct {
21+ type loginProvider struct {
2522 platformClient api.PlatformAPI
2623 sink output.Sink
2724}
2825
29- func newBrowserLogin (sink output.Sink , platformClient api.PlatformAPI ) * browserLogin {
30- return & browserLogin {
26+ func newLoginProvider (sink output.Sink , platformClient api.PlatformAPI ) * loginProvider {
27+ return & loginProvider {
3128 platformClient : platformClient ,
3229 sink : sink ,
3330 }
3431}
3532
36- func startCallbackServer () ( * http. Server , chan string , chan error , error ) {
37- listener , err := net . Listen ( "tcp" , loginCallbackURL )
33+ func ( l * loginProvider ) Login ( ctx context. Context ) ( string , error ) {
34+ authReq , err := l . platformClient . CreateAuthRequest ( ctx )
3835 if err != nil {
39- return nil , nil , nil , fmt .Errorf ("failed to start callback server : %w" , err )
36+ return "" , fmt .Errorf ("failed to create auth request : %w" , err )
4037 }
4138
42- tokenCh := make (chan string , 1 )
43- errCh := make (chan error , 1 )
39+ authURL := fmt .Sprintf ("%s/auth/request/%s" , getWebAppURL (), authReq .ID )
40+ output .EmitLog (l .sink , fmt .Sprintf ("Visit: %s" , authURL ))
41+ output .EmitLog (l .sink , fmt .Sprintf ("Verification code: %s" , authReq .Code ))
4442
45- mux := http .NewServeMux ()
46- mux .HandleFunc ("/auth/success" , func (w http.ResponseWriter , r * http.Request ) {
47- token := r .URL .Query ().Get ("token" )
48- if token == "" {
49- errCh <- fmt .Errorf ("no token in callback" )
50- http .Error (w , "No token received" , http .StatusBadRequest )
51- return
52- }
53- w .WriteHeader (http .StatusOK )
54- tokenCh <- token
43+ // Ask whether to open the browser; ENTER or Y accepts (default yes), N skips
44+ browserCh := make (chan output.InputResponse , 1 )
45+ output .EmitUserInputRequest (l .sink , output.UserInputRequestEvent {
46+ Prompt : "Open browser now?" ,
47+ Options : []output.InputOption {{Key : "y" , Label : "Y" }, {Key : "n" , Label : "n" }},
48+ ResponseCh : browserCh ,
5549 })
5650
57- server := & http. Server { Handler : mux }
58- go func () {
59- if err := server . Serve ( listener ); err != nil && err != http . ErrServerClosed {
60- errCh <- fmt . Errorf ( "callback server error: %w " , err )
51+ select {
52+ case resp := <- browserCh :
53+ if resp . Cancelled {
54+ return " " , context . Canceled
6155 }
62- }()
63-
64- return server , tokenCh , errCh , nil
65- }
66-
67- func (b * browserLogin ) Login (ctx context.Context ) (string , error ) {
68- server , tokenCh , errCh , err := startCallbackServer ()
69- if err != nil {
70- return "" , err
71- }
72- defer func () {
73- if err := server .Shutdown (ctx ); err != nil {
74- output .EmitWarning (b .sink , fmt .Sprintf ("failed to shutdown server: %v" , err ))
56+ if resp .SelectedKey != "n" {
57+ if err := browser .OpenURL (authURL ); err != nil {
58+ output .EmitLog (l .sink , fmt .Sprintf ("Warning: Failed to open browser: %v" , err ))
59+ }
7560 }
76- }()
77-
78- // Device flow as fallback
79- authReq , err := b .platformClient .CreateAuthRequest (ctx )
80- if err != nil {
81- return "" , fmt .Errorf ("failed to create auth request: %w" , err )
82- }
83-
84- deviceURL := fmt .Sprintf ("%s/auth/request/%s" , getWebAppURL (), authReq .ID )
85-
86- // Try to open browser
87- loginURL := fmt .Sprintf ("%s/redirect?name=CLI" , getWebAppURL ())
88- browserOpened := browser .OpenURL (loginURL ) == nil
89-
90- // Display device flow instructions
91- if browserOpened {
92- output .EmitLog (b .sink , fmt .Sprintf ("Browser didn't open? Open %s to authorize device." , deviceURL ))
93- } else {
94- output .EmitLog (b .sink , fmt .Sprintf ("Open %s to authorize device." , deviceURL ))
61+ case <- ctx .Done ():
62+ return "" , ctx .Err ()
9563 }
96- output .EmitLog (b .sink , fmt .Sprintf ("Verification code: %s" , authReq .Code ))
9764
98- // Emit user input request event
99- responseCh := make (chan output.InputResponse , 1 )
100- output .EmitUserInputRequest (b .sink , output.UserInputRequestEvent {
101- Prompt : "Waiting for authentication... " ,
65+ // Wait for the user to complete authentication in the browser
66+ enterCh := make (chan output.InputResponse , 1 )
67+ output .EmitUserInputRequest (l .sink , output.UserInputRequestEvent {
68+ Prompt : "Waiting for authentication" ,
10269 Options : []output.InputOption {{Key : "enter" , Label : "Press ENTER when complete" }},
103- ResponseCh : responseCh ,
70+ ResponseCh : enterCh ,
10471 })
10572
106- // Wait for either browser callback, user response, or context cancellation
10773 select {
108- case token := <- tokenCh :
109- return token , nil
110- case err := <- errCh :
111- return "" , err
112- case resp := <- responseCh :
74+ case resp := <- enterCh :
11375 if resp .Cancelled {
11476 return "" , context .Canceled
11577 }
116- // User pressed ENTER, try device flow
117- return b .completeDeviceFlow (ctx , authReq )
78+ return l .completeAuth (ctx , authReq )
11879 case <- ctx .Done ():
11980 return "" , ctx .Err ()
12081 }
@@ -128,24 +89,24 @@ func getWebAppURL() string {
12889 return webAppURL
12990}
13091
131- func (b * browserLogin ) completeDeviceFlow (ctx context.Context , authReq * api.AuthRequest ) (string , error ) {
132- output .EmitLog (b .sink , "Checking if auth request is confirmed..." )
133- confirmed , err := b .platformClient .CheckAuthRequestConfirmed (ctx , authReq .ID , authReq .ExchangeToken )
92+ func (l * loginProvider ) completeAuth (ctx context.Context , authReq * api.AuthRequest ) (string , error ) {
93+ output .EmitLog (l .sink , "Checking if auth request is confirmed..." )
94+ confirmed , err := l .platformClient .CheckAuthRequestConfirmed (ctx , authReq .ID , authReq .ExchangeToken )
13495 if err != nil {
13596 return "" , fmt .Errorf ("failed to check auth request: %w" , err )
13697 }
13798 if ! confirmed {
138- return "" , fmt .Errorf ("auth request not confirmed - please enter the code in the browser first " )
99+ return "" , fmt .Errorf ("auth request not confirmed - please complete the authorization in your browser" )
139100 }
140- output .EmitLog (b .sink , "Auth request confirmed, exchanging for token..." )
101+ output .EmitLog (l .sink , "Auth request confirmed, exchanging for token..." )
141102
142- bearerToken , err := b .platformClient .ExchangeAuthRequest (ctx , authReq .ID , authReq .ExchangeToken )
103+ bearerToken , err := l .platformClient .ExchangeAuthRequest (ctx , authReq .ID , authReq .ExchangeToken )
143104 if err != nil {
144105 return "" , fmt .Errorf ("failed to exchange auth request: %w" , err )
145106 }
146107
147- output .EmitLog (b .sink , "Fetching license token..." )
148- licenseToken , err := b .platformClient .GetLicenseToken (ctx , bearerToken )
108+ output .EmitLog (l .sink , "Fetching license token..." )
109+ licenseToken , err := l .platformClient .GetLicenseToken (ctx , bearerToken )
149110 if err != nil {
150111 return "" , fmt .Errorf ("failed to get license token: %w" , err )
151112 }
0 commit comments