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)
 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}