github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/client/login.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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 client
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"os"
    27  	"path/filepath"
    28  
    29  	"github.com/snapcore/snapd/osutil"
    30  )
    31  
    32  // User holds logged in user information.
    33  type User struct {
    34  	ID       int    `json:"id,omitempty"`
    35  	Username string `json:"username,omitempty"`
    36  	Email    string `json:"email,omitempty"`
    37  
    38  	Macaroon   string   `json:"macaroon,omitempty"`
    39  	Discharges []string `json:"discharges,omitempty"`
    40  }
    41  
    42  type loginData struct {
    43  	Email    string `json:"email,omitempty"`
    44  	Password string `json:"password,omitempty"`
    45  	Otp      string `json:"otp,omitempty"`
    46  }
    47  
    48  // Login logs user in.
    49  func (client *Client) Login(email, password, otp string) (*User, error) {
    50  	postData := loginData{
    51  		Email:    email,
    52  		Password: password,
    53  		Otp:      otp,
    54  	}
    55  	var body bytes.Buffer
    56  	if err := json.NewEncoder(&body).Encode(postData); err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	var user User
    61  	if _, err := client.doSync("POST", "/v2/login", nil, nil, &body, &user); err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	if err := writeAuthData(user); err != nil {
    66  		return nil, fmt.Errorf("cannot persist login information: %v", err)
    67  	}
    68  	return &user, nil
    69  }
    70  
    71  // Logout logs the user out.
    72  func (client *Client) Logout() error {
    73  	_, err := client.doSync("POST", "/v2/logout", nil, nil, nil, nil)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	return removeAuthData()
    78  }
    79  
    80  // LoggedInUser returns the logged in User or nil
    81  func (client *Client) LoggedInUser() *User {
    82  	u, err := readAuthData()
    83  	if err != nil {
    84  		return nil
    85  	}
    86  	return u
    87  }
    88  
    89  const authFileEnvKey = "SNAPD_AUTH_DATA_FILENAME"
    90  
    91  func storeAuthDataFilename(homeDir string) string {
    92  	if fn := os.Getenv(authFileEnvKey); fn != "" {
    93  		return fn
    94  	}
    95  
    96  	if homeDir == "" {
    97  		real, err := osutil.UserMaybeSudoUser()
    98  		if err != nil {
    99  			panic(err)
   100  		}
   101  		homeDir = real.HomeDir
   102  	}
   103  
   104  	return filepath.Join(homeDir, ".snap", "auth.json")
   105  }
   106  
   107  // writeAuthData saves authentication details for later reuse through ReadAuthData
   108  func writeAuthData(user User) error {
   109  	real, err := osutil.UserMaybeSudoUser()
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	uid, gid, err := osutil.UidGid(real)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	targetFile := storeAuthDataFilename(real.HomeDir)
   120  
   121  	if err := osutil.MkdirAllChown(filepath.Dir(targetFile), 0700, uid, gid); err != nil {
   122  		return err
   123  	}
   124  
   125  	outStr, err := json.Marshal(user)
   126  	if err != nil {
   127  		return nil
   128  	}
   129  
   130  	return osutil.AtomicWriteFileChown(targetFile, []byte(outStr), 0600, 0, uid, gid)
   131  }
   132  
   133  // readAuthData reads previously written authentication details
   134  func readAuthData() (*User, error) {
   135  	sourceFile := storeAuthDataFilename("")
   136  	f, err := os.Open(sourceFile)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	defer f.Close()
   141  
   142  	var user User
   143  	dec := json.NewDecoder(f)
   144  	if err := dec.Decode(&user); err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	return &user, nil
   149  }
   150  
   151  // removeAuthData removes any previously written authentication details.
   152  func removeAuthData() error {
   153  	filename := storeAuthDataFilename("")
   154  	return os.Remove(filename)
   155  }