bridge_auth_addtoken.go

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