Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6f51a54

Browse files
committed
make new auth link command with better option set
1 parent 7c4d8f8 commit 6f51a54

File tree

1 file changed

+134
-23
lines changed

1 file changed

+134
-23
lines changed

cli/externalauth.go

Lines changed: 134 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"encoding/json"
5+
"fmt"
56

67
"golang.org/x/xerrors"
78

@@ -22,16 +23,147 @@ func (r *RootCmd) externalAuth() *serpent.Command {
2223
},
2324
Children: []*serpent.Command{
2425
r.externalAuthAccessToken(),
26+
r.externalAuthLink(),
2527
},
2628
}
2729
}
2830

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("\nrefresh_token: %s", auth.RefreshToken)
57+
}
58+
return fmt.Sprintf("type: %s\naccess_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+
29161
func (r *RootCmd) externalAuthAccessToken() *serpent.Command {
162+
30163
var extra string
31-
var full bool
32164
return &serpent.Command{
33165
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",
35167
Long: "Print an access-token for an external auth provider. " +
36168
"The access-token will be validated and sent to stdout with exit code 0. " +
37169
"If a valid access-token cannot be obtained, the URL to authenticate will be sent to stdout with exit code 1\n" + formatExamples(
@@ -62,14 +194,6 @@ fi
62194
Flag: "extra",
63195
Description: "Extract a field from the \"extra\" properties of the OAuth token.",
64196
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),
73197
},
74198
},
75199

@@ -98,19 +222,6 @@ fi
98222
return cliui.Canceled
99223
}
100224

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-
114225
if extra != "" {
115226
if extAuth.TokenExtra == nil {
116227
return xerrors.Errorf("no extra properties found for token")

0 commit comments

Comments
 (0)