github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/login_session.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"errors"
    10  	"fmt"
    11  	"time"
    12  
    13  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    14  )
    15  
    16  const LoginSessionMemoryTimeout time.Duration = time.Minute * 5
    17  
    18  var ErrLoginSessionNotLoaded = errors.New("LoginSession not loaded")
    19  var ErrLoginSessionCleared = errors.New("LoginSession already cleared")
    20  
    21  type 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  
    32  func 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.
    40  func 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  
    49  func (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  
    59  func (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  
    72  func (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  
    85  func (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  
   101  func (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.
   113  func (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  
   126  func (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  
   136  func (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  
   149  func (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  
   199  func 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  }