@@ -2,6 +2,7 @@ package cli
2
2
3
3
import (
4
4
"encoding/json"
5
+ "fmt"
5
6
6
7
"golang.org/x/xerrors"
7
8
@@ -22,16 +23,147 @@ func (r *RootCmd) externalAuth() *serpent.Command {
22
23
},
23
24
Children : []* serpent.Command {
24
25
r .externalAuthAccessToken (),
26
+ r .externalAuthLink (),
25
27
},
26
28
}
27
29
}
28
30
31
+ func (r * RootCmd ) externalAuthLink () * serpent.Command {
32
+ var (
33
+ matchURL string
34
+ only string
35
+ formatter = cliui .NewOutputFormatter (
36
+ cliui .ChangeFormatterData (cliui .TextFormat (), func (data any ) (any , error ) {
37
+ auth , ok := data .(agentsdk.ExternalAuthResponse )
38
+ if ! ok {
39
+ return nil , xerrors .Errorf ("expected data to be of type codersdk.ExternalAuth, got %T" , data )
40
+ }
41
+
42
+ // If the url is set, only return that. It will be accompanied by an error message.
43
+ // This is helpful for scripts to take this output and give it to the user.
44
+ if auth .URL != "" {
45
+ return auth .URL , nil
46
+ }
47
+
48
+ switch only {
49
+ case "access_token" :
50
+ return auth .AccessToken , nil
51
+ case "refresh_token" :
52
+ return auth .RefreshToken , nil
53
+ default :
54
+ refresh := ""
55
+ if auth .RefreshToken != "" {
56
+ refresh = fmt .Sprintf ("\n refresh_token: %s" , auth .RefreshToken )
57
+ }
58
+ return fmt .Sprintf ("type: %s\n access_token: %s%s" , auth .Type , auth .AccessToken , refresh ), nil
59
+ }
60
+ },
61
+ ),
62
+ // Table expects a slice of data.
63
+ cliui .ChangeFormatterData (cliui .TableFormat ([]agentsdk.ExternalAuthResponse {}, []string {"type" , "access_token" }), func (data any ) (any , error ) {
64
+ auth , ok := data .(agentsdk.ExternalAuthResponse )
65
+ if ! ok {
66
+ return nil , xerrors .Errorf ("expected data to be of type codersdk.ExternalAuth, got %T" , data )
67
+ }
68
+ return []agentsdk.ExternalAuthResponse {auth }, nil
69
+ }),
70
+ cliui .JSONFormat (),
71
+ )
72
+ )
73
+
74
+ cmd := & serpent.Command {
75
+ Use : "link <provider>" ,
76
+ Short : "Print auth link for an external provider by ID or match regex." ,
77
+ Long : "Print auth link for an external provider by ID or match regex. " +
78
+ "Use the flags to tailor the output to your needs. " +
79
+ "If a valid access-token cannot be obtained, the URL to authenticate will be sent to stdout with exit code 1\n " + formatExamples (
80
+ example {
81
+ Description : "Only print the access token" ,
82
+ Command : "external-auth link <provider_id> --only access_token" ,
83
+ },
84
+ example {
85
+ Description : "Dump auth link as json" ,
86
+ Command : "external-auth link <provider_id> --o json" ,
87
+ },
88
+ ),
89
+ Middleware : serpent .Chain (
90
+ serpent .RequireRangeArgs (0 , 1 ),
91
+ ),
92
+ Options : serpent.OptionSet {
93
+ {
94
+ Name : "Match" ,
95
+ Flag : "match" ,
96
+ Description : "Match a provider with a url. If a provider has a regex that matches the url, the provider will be returned." ,
97
+ Value : serpent .StringOf (& matchURL ),
98
+ },
99
+ {
100
+ Name : "Only" ,
101
+ Flag : "only" ,
102
+ Description : "Only return the specified field from the external auth response. Only works with text response." ,
103
+ Value : serpent .EnumOf (& only , "access_token" , "refresh_token" ),
104
+ },
105
+ },
106
+
107
+ Handler : func (inv * serpent.Invocation ) error {
108
+ ctx := inv .Context ()
109
+ req := agentsdk.ExternalAuthRequest {
110
+ Match : matchURL ,
111
+ }
112
+
113
+ if len (inv .Args ) > 0 && matchURL != "" {
114
+ return xerrors .Errorf ("cannot specify both provider id and --match" )
115
+ }
116
+
117
+ if matchURL == "" {
118
+ if len (inv .Args ) == 0 {
119
+ return xerrors .Errorf ("missing provider argument" )
120
+ }
121
+ req .ID = inv .Args [0 ]
122
+ }
123
+
124
+ ctx , stop := inv .SignalNotifyContext (ctx , StopSignals ... )
125
+ defer stop ()
126
+
127
+ client , err := r .createAgentClient ()
128
+ if err != nil {
129
+ return xerrors .Errorf ("create agent client: %w" , err )
130
+ }
131
+
132
+ extAuth , err := client .ExternalAuth (ctx , req )
133
+ if err != nil {
134
+ return xerrors .Errorf ("get external auth token: %w" , err )
135
+ }
136
+
137
+ // We always write to the output because if the URL field is
138
+ // populated, we still want that information sent to the user.
139
+ out , err := formatter .Format (inv .Context (), extAuth )
140
+ if err != nil {
141
+ return err
142
+ }
143
+ _ , _ = fmt .Fprintln (inv .Stdout , out )
144
+
145
+ // If the URL field is set, we need to accompany the output with the
146
+ // authentication error.
147
+ if extAuth .URL != "" {
148
+ // The text & json outputs will have the url. The table one will not,
149
+ // but the error message will. So this is ok.
150
+ return xerrors .Errorf ("external auth requires login, visit %s" , extAuth .URL )
151
+ }
152
+
153
+ return nil
154
+ },
155
+ }
156
+ formatter .AttachOptions (& cmd .Options )
157
+
158
+ return cmd
159
+ }
160
+
29
161
func (r * RootCmd ) externalAuthAccessToken () * serpent.Command {
162
+
30
163
var extra string
31
- var full bool
32
164
return & serpent.Command {
33
165
Use : "access-token <provider>" ,
34
- Short : "Print auth for an external provider" ,
166
+ Short : "Use 'link <provider> --only access_token' instead. Will print auth for an external provider" ,
35
167
Long : "Print an access-token for an external auth provider. " +
36
168
"The access-token will be validated and sent to stdout with exit code 0. " +
37
169
"If a valid access-token cannot be obtained, the URL to authenticate will be sent to stdout with exit code 1\n " + formatExamples (
62
194
Flag : "extra" ,
63
195
Description : "Extract a field from the \" extra\" properties of the OAuth token." ,
64
196
Value : serpent .StringOf (& extra ),
65
- }, {
66
- Name : "Full" ,
67
- Description : "Print the full response from the external auth provider as json." ,
68
- Required : false ,
69
- Flag : "full" ,
70
- FlagShorthand : "" ,
71
- Default : "false" ,
72
- Value : serpent .BoolOf (& full ),
73
197
},
74
198
},
75
199
98
222
return cliui .Canceled
99
223
}
100
224
101
- if extra != "" && full {
102
- return xerrors .Errorf ("cannot specify both --extra and --full" )
103
- }
104
-
105
- if full {
106
- data , err := json .Marshal (extAuth )
107
- if err != nil {
108
- return xerrors .Errorf ("marshal auth: %w" , err )
109
- }
110
- _ , _ = inv .Stdout .Write (data )
111
- return nil
112
- }
113
-
114
225
if extra != "" {
115
226
if extAuth .TokenExtra == nil {
116
227
return xerrors .Errorf ("no extra properties found for token" )
0 commit comments