github.com/dhax/go-base@v0.0.0-20231004214136-8be7e5c1972b/auth/pwdless/logintoken.go (about) 1 package pwdless 2 3 import ( 4 "crypto/rand" 5 "errors" 6 "sync" 7 "time" 8 9 "github.com/spf13/viper" 10 ) 11 12 var ( 13 errTokenNotFound = errors.New("login token not found") 14 ) 15 16 // LoginToken is an in-memory saved token referencing an account ID and an expiry date. 17 type LoginToken struct { 18 Token string 19 AccountID int 20 Expiry time.Time 21 } 22 23 // LoginTokenAuth implements passwordless login authentication flow using temporary in-memory stored tokens. 24 type LoginTokenAuth struct { 25 token map[string]LoginToken 26 mux sync.RWMutex 27 loginURL string 28 loginTokenLength int 29 loginTokenExpiry time.Duration 30 } 31 32 // NewLoginTokenAuth configures and returns a LoginToken authentication instance. 33 func NewLoginTokenAuth() (*LoginTokenAuth, error) { 34 a := &LoginTokenAuth{ 35 token: make(map[string]LoginToken), 36 loginURL: viper.GetString("auth_login_url"), 37 loginTokenLength: viper.GetInt("auth_login_token_length"), 38 loginTokenExpiry: viper.GetDuration("auth_login_token_expiry"), 39 } 40 return a, nil 41 } 42 43 // CreateToken creates an in-memory login token referencing account ID. It returns a token containing a random tokenstring and expiry date. 44 func (a *LoginTokenAuth) CreateToken(id int) LoginToken { 45 lt := LoginToken{ 46 Token: randStringBytes(a.loginTokenLength), 47 AccountID: id, 48 Expiry: time.Now().Add(a.loginTokenExpiry), 49 } 50 a.add(lt) 51 a.purgeExpired() 52 return lt 53 } 54 55 // GetAccountID looks up the token by tokenstring and returns the account ID or error if token not found or expired. 56 func (a *LoginTokenAuth) GetAccountID(token string) (int, error) { 57 lt, exists := a.get(token) 58 if !exists || time.Now().After(lt.Expiry) { 59 return 0, errTokenNotFound 60 } 61 a.delete(lt.Token) 62 return lt.AccountID, nil 63 } 64 65 func (a *LoginTokenAuth) get(token string) (LoginToken, bool) { 66 a.mux.RLock() 67 lt, ok := a.token[token] 68 a.mux.RUnlock() 69 return lt, ok 70 } 71 72 func (a *LoginTokenAuth) add(lt LoginToken) { 73 a.mux.Lock() 74 a.token[lt.Token] = lt 75 a.mux.Unlock() 76 } 77 78 func (a *LoginTokenAuth) delete(token string) { 79 a.mux.Lock() 80 delete(a.token, token) 81 a.mux.Unlock() 82 } 83 84 func (a *LoginTokenAuth) purgeExpired() { 85 for t, v := range a.token { 86 if time.Now().After(v.Expiry) { 87 a.delete(t) 88 } 89 } 90 } 91 92 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 93 94 func randStringBytes(n int) string { 95 buf := make([]byte, n) 96 if _, err := rand.Read(buf); err != nil { 97 panic(err) 98 } 99 100 for k, v := range buf { 101 buf[k] = letterBytes[v%byte(len(letterBytes))] 102 } 103 return string(buf) 104 }