Skip to content

Commit 48ef6b3

Browse files
authored
Merge pull request #30 from jaxxstorm/oauth_exchange
feat: add `create token` command for OAuth token generation
2 parents e45ca5f + 0703431 commit 48ef6b3

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ tscli <noun> <verb> [flags]
143143
| List tailnet keys | :white_check_mark: | `tscli list keys` |
144144
| Create auth-key / OAuth client | :white_check_mark: | `tscli create key --type authkey --oauthclient …` |
145145
| Get key | :white_check_mark: | `tscli get key --key <id>` |
146+
| Create a token | :white_check_mark: | `create token --client-id <oauth-client-id> --client-secret <oauth-client-secret>` |
146147
| Delete / revoke key | :x: ||
147148
| **Policy File** | | |
148149
| Get policy file | :white_check_mark: | `tscli get policy [--json]` |

cmd/tscli/create/cli.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/jaxxstorm/tscli/cmd/tscli/create/invite"
66
"github.com/jaxxstorm/tscli/cmd/tscli/create/key"
77
"github.com/jaxxstorm/tscli/cmd/tscli/create/webhook"
8+
"github.com/jaxxstorm/tscli/cmd/tscli/create/token"
89
"github.com/spf13/cobra"
910
)
1011

@@ -19,6 +20,7 @@ func Command() *cobra.Command {
1920
command.AddCommand(key.Command())
2021
command.AddCommand(webhook.Command())
2122
command.AddCommand(invite.Command())
23+
command.AddCommand(token.Command())
2224

2325
return command
2426
}

cmd/tscli/create/token/cli.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// cmd/tscli/create/token/cli.go
2+
//
3+
// Exchange an OAuth client-id/secret for an access-token.
4+
//
5+
// tscli create token --client-id XXX --client-secret YYY
6+
//
7+
package token
8+
9+
import (
10+
"bytes"
11+
"errors"
12+
"fmt"
13+
"io"
14+
"net/http"
15+
"net/http/httputil"
16+
"net/url"
17+
18+
"github.com/jaxxstorm/tscli/pkg/output"
19+
"github.com/spf13/cobra"
20+
"github.com/spf13/viper"
21+
)
22+
23+
const endpoint = "https://api.tailscale.com/api/v2/oauth/token"
24+
25+
func Command() *cobra.Command {
26+
var (
27+
clientID string
28+
clientSecret string
29+
)
30+
31+
cmd := &cobra.Command{
32+
Use: "token",
33+
Short: "Create an OAuth access-token from a client_id / client_secret pair",
34+
Long: "POSTs to /api/v2/oauth/token. No API-key auth is required.",
35+
Example: ` tscli create token \
36+
--client-id "$OAUTH_CLIENT_ID" \
37+
--client-secret "$OAUTH_CLIENT_SECRET"`,
38+
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
39+
if clientID == "" || clientSecret == "" {
40+
return errors.New("--client-id and --client-secret are both required")
41+
}
42+
return nil
43+
},
44+
45+
RunE: func(cmd *cobra.Command, _ []string) error {
46+
/* ------------------------------------------------------ */
47+
form := url.Values{}
48+
form.Set("client_id", clientID)
49+
form.Set("client_secret", clientSecret)
50+
51+
req, _ := http.NewRequest(http.MethodPost, endpoint,
52+
bytes.NewBufferString(form.Encode()))
53+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
54+
req.Header.Set("User-Agent", "tscli")
55+
56+
if viper.GetBool("debug") {
57+
if dump, err := httputil.DumpRequestOut(req, true); err == nil {
58+
fmt.Fprintf(cmd.ErrOrStderr(), "\n--- REQUEST ---\n%s\n", dump)
59+
}
60+
}
61+
62+
resp, err := http.DefaultClient.Do(req)
63+
if err != nil {
64+
return err
65+
}
66+
defer resp.Body.Close()
67+
68+
if viper.GetBool("debug") {
69+
if dump, err := httputil.DumpResponse(resp, true); err == nil {
70+
fmt.Fprintf(cmd.ErrOrStderr(), "\n--- RESPONSE ---\n%s\n", dump)
71+
}
72+
}
73+
74+
body, _ := io.ReadAll(resp.Body)
75+
76+
/* ------------------------------------------------------ */
77+
if resp.StatusCode/100 != 2 { // non-2xx
78+
_ = output.Print(viper.GetString("format"), body)
79+
return fmt.Errorf("tailscale API returned %s", resp.Status)
80+
}
81+
82+
return output.Print(viper.GetString("format"), body)
83+
},
84+
}
85+
86+
cmd.Flags().StringVar(&clientID, "client-id", "", "OAuth client ID (required)")
87+
cmd.Flags().StringVar(&clientSecret, "client-secret", "", "OAuth client secret (required)")
88+
_ = cmd.MarkFlagRequired("client-id")
89+
_ = cmd.MarkFlagRequired("client-secret")
90+
91+
return cmd
92+
}

0 commit comments

Comments
 (0)