1// Copyright 2015 Keybase, Inc. All rights reserved. Use of 2// this source code is governed by the included BSD license. 3 4package libkb 5 6import ( 7 "encoding/base64" 8 "encoding/hex" 9 "errors" 10 "fmt" 11 "time" 12 13 keybase1 "github.com/keybase/client/go/protocol/keybase1" 14) 15 16const LoginSessionMemoryTimeout time.Duration = time.Minute * 5 17 18var ErrLoginSessionNotLoaded = errors.New("LoginSession not loaded") 19var ErrLoginSessionCleared = errors.New("LoginSession already cleared") 20 21type LoginSession struct { 22 sessionFor string // set by constructor 23 salt []byte // retrieved from server, or set by WithSalt constructor 24 loginSessionB64 string 25 loginSession []byte // decoded from above parameter 26 loaded bool // load state 27 cleared bool // clear state 28 createTime time.Time // load time 29 Contextified 30} 31 32func NewLoginSession(g *GlobalContext, emailOrUsername string) *LoginSession { 33 return &LoginSession{ 34 sessionFor: emailOrUsername, 35 Contextified: NewContextified(g), 36 } 37} 38 39// Upon signup, a login session is created with a generated salt. 40func NewLoginSessionWithSalt(g *GlobalContext, emailOrUsername string, salt []byte) *LoginSession { 41 ls := NewLoginSession(g, emailOrUsername) 42 ls.salt = salt 43 // XXX are these right? is this just so the salt can be retrieved? 44 ls.loaded = true 45 ls.cleared = true 46 return ls 47} 48 49func (s *LoginSession) Status() *keybase1.SessionStatus { 50 return &keybase1.SessionStatus{ 51 SessionFor: s.sessionFor, 52 Loaded: s.loaded, 53 Cleared: s.cleared, 54 Expired: !s.NotExpired(), 55 SaltOnly: s.loaded && s.loginSession == nil && s.salt != nil, 56 } 57} 58 59func (s *LoginSession) Session() ([]byte, error) { 60 if s == nil { 61 return nil, ErrLoginSessionNotLoaded 62 } 63 if !s.loaded { 64 return nil, ErrLoginSessionNotLoaded 65 } 66 if s.cleared { 67 return nil, ErrLoginSessionCleared 68 } 69 return s.loginSession, nil 70} 71 72func (s *LoginSession) SessionEncoded() (string, error) { 73 if s == nil { 74 return "", ErrLoginSessionNotLoaded 75 } 76 if !s.loaded { 77 return "", ErrLoginSessionNotLoaded 78 } 79 if s.cleared { 80 return "", ErrLoginSessionCleared 81 } 82 return s.loginSessionB64, nil 83} 84 85func (s *LoginSession) ExistsFor(emailOrUsername string) bool { 86 if s == nil { 87 return false 88 } 89 if s.sessionFor != emailOrUsername { 90 return false 91 } 92 if s.cleared { 93 return false 94 } 95 if s.loginSession == nil { 96 return false 97 } 98 return true 99} 100 101func (s *LoginSession) NotExpired() bool { 102 now := s.G().Clock().Now() 103 104 if now.Sub(s.createTime) < LoginSessionMemoryTimeout { 105 return true 106 } 107 s.G().Log.Debug("login_session expired") 108 return false 109} 110 111// Clear removes the loginSession value from s. It does not 112// clear the salt. Unclear how this is useful. 113func (s *LoginSession) Clear() error { 114 if s == nil { 115 return nil 116 } 117 if !s.loaded { 118 return ErrLoginSessionNotLoaded 119 } 120 s.loginSession = nil 121 s.loginSessionB64 = "" 122 s.cleared = true 123 return nil 124} 125 126func (s *LoginSession) Salt() ([]byte, error) { 127 if s == nil { 128 return nil, ErrLoginSessionNotLoaded 129 } 130 if !s.loaded { 131 return nil, ErrLoginSessionNotLoaded 132 } 133 return s.salt, nil 134} 135 136func (s *LoginSession) Dump() { 137 if s == nil { 138 fmt.Printf("LoginSession Dump: nil\n") 139 return 140 } 141 fmt.Printf("sessionFor: %q\n", s.sessionFor) 142 fmt.Printf("loaded: %v\n", s.loaded) 143 fmt.Printf("cleared: %v\n", s.cleared) 144 fmt.Printf("salt: %x\n", s.salt) 145 fmt.Printf("loginSessionB64: %s\n", s.loginSessionB64) 146 fmt.Printf("\n") 147} 148 149func (s *LoginSession) Load(m MetaContext) error { 150 if s == nil { 151 return fmt.Errorf("LoginSession is nil") 152 } 153 if s.loaded && !s.cleared { 154 return fmt.Errorf("LoginSession already loaded for %s", s.sessionFor) 155 } 156 157 res, err := m.G().API.Get(m, APIArg{ 158 Endpoint: "getsalt", 159 SessionType: APISessionTypeNONE, 160 Args: HTTPArgs{ 161 "email_or_username": S{Val: s.sessionFor}, 162 "pdpka_login": B{Val: true}, 163 }, 164 }) 165 if err != nil { 166 return err 167 } 168 169 shex, err := res.Body.AtKey("salt").GetString() 170 if err != nil { 171 return err 172 } 173 174 salt, err := hex.DecodeString(shex) 175 if err != nil { 176 return err 177 } 178 179 b64, err := res.Body.AtKey("login_session").GetString() 180 if err != nil { 181 return err 182 } 183 184 ls, err := base64.StdEncoding.DecodeString(b64) 185 if err != nil { 186 return err 187 } 188 189 s.salt = salt 190 s.loginSessionB64 = b64 191 s.loginSession = ls 192 s.loaded = true 193 s.cleared = false 194 s.createTime = s.G().Clock().Now() 195 196 return nil 197} 198 199func LookupSaltForUID(m MetaContext, uid keybase1.UID) (salt []byte, err error) { 200 defer m.Trace(fmt.Sprintf("GetSaltForUID(%s)", uid), &err)() 201 res, err := m.G().API.Get(m, APIArg{ 202 Endpoint: "getsalt", 203 SessionType: APISessionTypeNONE, 204 Args: HTTPArgs{ 205 "uid": S{Val: uid.String()}, 206 }, 207 }) 208 if err != nil { 209 return nil, err 210 } 211 var shex string 212 shex, err = res.Body.AtKey("salt").GetString() 213 if err != nil { 214 return nil, err 215 } 216 salt, err = hex.DecodeString(shex) 217 if err != nil { 218 return nil, err 219 } 220 return salt, err 221} 222