1package commands
2
3import (
4 "bufio"
5 "fmt"
6 "os"
7 "strings"
8
9 "github.com/mattn/go-isatty"
10 "github.com/pkg/errors"
11 "github.com/spf13/cobra"
12
13 "github.com/MichaelMure/git-bug/bridge"
14 "github.com/MichaelMure/git-bug/bridge/core"
15 "github.com/MichaelMure/git-bug/bridge/core/auth"
16 "github.com/MichaelMure/git-bug/cache"
17)
18
19type bridgeAuthAddTokenOptions struct {
20 target string
21 login string
22 user string
23}
24
25func newBridgeAuthAddTokenCommand() *cobra.Command {
26 env := newEnv()
27 options := bridgeAuthAddTokenOptions{}
28
29 cmd := &cobra.Command{
30 Use: "add-token [TOKEN]",
31 Short: "Store a new token",
32 PreRunE: loadBackendEnsureUser(env),
33 RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
34 return runBridgeAuthAddToken(env, options, args)
35 }),
36 Args: cobra.MaximumNArgs(1),
37 }
38
39 flags := cmd.Flags()
40 flags.SortFlags = false
41
42 flags.StringVarP(&options.target, "target", "t", "",
43 fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
44 flags.StringVarP(&options.login,
45 "login", "l", "", "The login in the remote bug-tracker")
46 flags.StringVarP(&options.user,
47 "user", "u", "", "The user to add the token to. Default is the current user")
48
49 return cmd
50}
51
52func runBridgeAuthAddToken(env *Env, opts bridgeAuthAddTokenOptions, args []string) error {
53 // Note: as bridgeAuthAddTokenLogin is not checked against the remote bug-tracker,
54 // it's possible to register a credential with an incorrect login (including bad case).
55 // The consequence is that it will not get picked later by the bridge. I find that
56 // checking it would require a cumbersome UX (need to provide a base URL for some bridges, ...)
57 // so it's probably not worth it, unless we refactor that entirely.
58
59 if opts.target == "" {
60 return fmt.Errorf("flag --target is required")
61 }
62 if opts.login == "" {
63 return fmt.Errorf("flag --login is required")
64 }
65
66 if !core.TargetExist(opts.target) {
67 return fmt.Errorf("unknown target")
68 }
69
70 var value string
71
72 if len(args) == 1 {
73 value = args[0]
74 } else {
75 // Read from Stdin
76 if isatty.IsTerminal(os.Stdin.Fd()) {
77 env.err.Println("Enter the token:")
78 }
79 reader := bufio.NewReader(os.Stdin)
80 raw, err := reader.ReadString('\n')
81 if err != nil {
82 return fmt.Errorf("reading from stdin: %v", err)
83 }
84 value = strings.TrimSuffix(raw, "\n")
85 }
86
87 var user *cache.IdentityCache
88 var err error
89
90 if opts.user == "" {
91 user, err = env.backend.GetUserIdentity()
92 } else {
93 user, err = env.backend.ResolveIdentityPrefix(opts.user)
94 }
95 if err != nil {
96 return err
97 }
98
99 metaKey, _ := bridge.LoginMetaKey(opts.target)
100 login, ok := user.ImmutableMetadata()[metaKey]
101
102 switch {
103 case ok && login == opts.login:
104 // nothing to do
105 case ok && login != opts.login:
106 return fmt.Errorf("this user is already tagged with a different %s login", opts.target)
107 default:
108 user.SetMetadata(metaKey, opts.login)
109 err = user.Commit()
110 if err != nil {
111 return err
112 }
113 }
114
115 token := auth.NewToken(opts.target, value)
116 token.SetMetadata(auth.MetaKeyLogin, opts.login)
117
118 if err := token.Validate(); err != nil {
119 return errors.Wrap(err, "invalid token")
120 }
121
122 err = auth.Store(env.repo, token)
123 if err != nil {
124 return err
125 }
126
127 env.out.Printf("token %s added\n", token.ID())
128 return nil
129}