github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_login.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main
    21  
    22  import (
    23  	"bufio"
    24  	"fmt"
    25  	"strings"
    26  
    27  	"github.com/jessevdk/go-flags"
    28  
    29  	"github.com/snapcore/snapd/client"
    30  	"github.com/snapcore/snapd/i18n"
    31  )
    32  
    33  type cmdLogin struct {
    34  	clientMixin
    35  	Positional struct {
    36  		Email string
    37  	} `positional-args:"yes"`
    38  }
    39  
    40  var shortLoginHelp = i18n.G("Authenticate to snapd and the store")
    41  
    42  var longLoginHelp = i18n.G(`
    43  The login command authenticates the user to snapd and the snap store, and saves
    44  credentials into the ~/.snap/auth.json file. Further communication with snapd
    45  will then be made using those credentials.
    46  
    47  It's not necessary to log in to interact with snapd. Doing so, however, enables
    48  purchasing of snaps using 'snap buy', as well as some some developer-oriented
    49  features as detailed in the help for the find, install and refresh commands.
    50  
    51  An account can be set up at https://login.ubuntu.com
    52  `)
    53  
    54  func init() {
    55  	addCommand("login",
    56  		shortLoginHelp,
    57  		longLoginHelp,
    58  		func() flags.Commander {
    59  			return &cmdLogin{}
    60  		}, nil, []argDesc{{
    61  			// TRANSLATORS: This is a noun, and it needs to begin with < and end with >
    62  			name: i18n.G("<email>"),
    63  			// TRANSLATORS: This should not start with a lowercase letter (unless it's "login.ubuntu.com")
    64  			desc: i18n.G("The login.ubuntu.com email to login as"),
    65  		}})
    66  }
    67  
    68  func requestLoginWith2faRetry(cli *client.Client, email, password string) error {
    69  	var otp []byte
    70  	var err error
    71  
    72  	var msgs = [3]string{
    73  		i18n.G("Two-factor code: "),
    74  		i18n.G("Bad code. Try again: "),
    75  		i18n.G("Wrong again. Once more: "),
    76  	}
    77  
    78  	reader := bufio.NewReader(nil)
    79  
    80  	for i := 0; ; i++ {
    81  		// first try is without otp
    82  		_, err = cli.Login(email, password, string(otp))
    83  		if i >= len(msgs) || !client.IsTwoFactorError(err) {
    84  			return err
    85  		}
    86  
    87  		reader.Reset(Stdin)
    88  		fmt.Fprint(Stdout, msgs[i])
    89  		// the browser shows it as well (and Sergio wants to see it ;)
    90  		otp, _, err = reader.ReadLine()
    91  		if err != nil {
    92  			return err
    93  		}
    94  	}
    95  }
    96  
    97  func requestLogin(cli *client.Client, email string) error {
    98  	fmt.Fprintf(Stdout, i18n.G("Password of %q: "), email)
    99  	password, err := ReadPassword(0)
   100  	fmt.Fprint(Stdout, "\n")
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	// strings.TrimSpace needed because we get \r from the pty in the tests
   106  	return requestLoginWith2faRetry(cli, email, strings.TrimSpace(string(password)))
   107  }
   108  
   109  func (x *cmdLogin) Execute(args []string) error {
   110  	if len(args) > 0 {
   111  		return ErrExtraArgs
   112  	}
   113  
   114  	//TRANSLATORS: after the "... at" follows a URL in the next line
   115  	fmt.Fprint(Stdout, i18n.G("Personal information is handled as per our privacy notice at\n"))
   116  	fmt.Fprint(Stdout, "https://www.ubuntu.com/legal/dataprivacy/snap-store\n\n")
   117  
   118  	email := x.Positional.Email
   119  	if email == "" {
   120  		fmt.Fprint(Stdout, i18n.G("Email address: "))
   121  		in, _, err := bufio.NewReader(Stdin).ReadLine()
   122  		if err != nil {
   123  			return err
   124  		}
   125  		email = string(in)
   126  	}
   127  
   128  	err := requestLogin(x.client, email)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	fmt.Fprintln(Stdout, i18n.G("Login successful"))
   133  
   134  	return nil
   135  }