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 }