bridge_auth_addtoken.go

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