use TTL with LastSeen instead of hard expiry time

This commit is contained in:
Iris Lightshard 2022-08-02 20:46:34 -06:00
parent 48dbb967f3
commit 92f0f035a9
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
5 changed files with 36 additions and 38 deletions

View file

@ -17,7 +17,7 @@ type User struct {
} }
type UserStore interface { type UserStore interface {
InitiateSession(user string, password string) (string, error) InitiateSession(user string, password string, ttl int) (string, error)
ValidateUser(user string, sessionId string) (bool, error) ValidateUser(user string, sessionId string) (bool, error)
EndSession(user string) error EndSession(user string) error
AddUser(user string, password string) error AddUser(user string, password string) error
@ -27,13 +27,13 @@ type UserStore interface {
GetLastTimeSeen(user string) (time.Time, error) GetLastTimeSeen(user string) (time.Time, error)
SetData(user string, key string, value interface{}) error SetData(user string, key string, value interface{}) error
GetData(user string, key string) (interface{}, error) GetData(user string, key string) (interface{}, error)
GrantToken(user, password, scope string, minutes int) (string, error) GrantToken(user, password string, ttl int) (string, error)
ValidateToken(token string) (bool, error) ValidateToken(token string) (bool, error)
ValidateTokenWithScopes(token string, scopes map[string]string) (bool, error) ValidateTokenWithScopes(token string, scopes map[string]string) (bool, error)
} }
func Login(user string, password string, userStore UserStore, w http.ResponseWriter, t int) error { func Login(user string, password string, userStore UserStore, w http.ResponseWriter, t int) error {
session, loginErr := userStore.InitiateSession(user, password) session, loginErr := userStore.InitiateSession(user, password, t)
if loginErr == nil { if loginErr == nil {
cookie.StoreToken("user", user, w, t) cookie.StoreToken("user", user, w, t)
cookie.StoreToken("session", session, w, t) cookie.StoreToken("session", session, w, t)

View file

@ -18,11 +18,11 @@ func GenToken(length int) string {
return string(b) return string(b)
} }
func StoreToken(field string, token string, w http.ResponseWriter, hrs int) { func StoreToken(field string, token string, w http.ResponseWriter, ttl int) {
cookie := http.Cookie{ cookie := http.Cookie{
Name: field, Name: field,
Value: token, Value: token,
Expires: time.Now().Add(time.Duration(hrs) * time.Hour), Expires: time.Now().Add(time.Duration(ttl) * time.Minute),
} }
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)

View file

@ -9,6 +9,7 @@ import (
"nilfm.cc/git/quartzgun/cookie" "nilfm.cc/git/quartzgun/cookie"
"os" "os"
"strings" "strings"
"strconv"
"time" "time"
) )
@ -36,7 +37,7 @@ func CreateIndentalUserDB(filePath string) *IndentalUserDB {
} }
} }
func (self *IndentalUserDB) InitiateSession(user string, password string) (string, error) { func (self *IndentalUserDB) InitiateSession(user string, password string, ttl int) (string, error) {
if _, exists := self.Users[user]; !exists { if _, exists := self.Users[user]; !exists {
return "", errors.New("User not in DB") return "", errors.New("User not in DB")
} }
@ -47,42 +48,45 @@ func (self *IndentalUserDB) InitiateSession(user string, password string) (strin
self.Users[user].Session = sessionId self.Users[user].Session = sessionId
self.Users[user].LoginTime = time.Now() self.Users[user].LoginTime = time.Now()
self.Users[user].LastSeen = time.Now() self.Users[user].LastSeen = time.Now()
self.SetData(user, "token_expiry", strconv.Itoa(ttl))
writeDB(self.Basis, self.Users) writeDB(self.Basis, self.Users)
return sessionId, nil return sessionId, nil
} }
func (self *IndentalUserDB) GrantToken(user, password, scope string, minutes int) (string, error) { func (self *IndentalUserDB) GrantToken(user, password string, ttl int) (string, error) {
if _, exists := self.Users[user]; !exists { if _, exists := self.Users[user]; !exists {
return "", errors.New("User not in DB") return "", errors.New("User not in DB")
} }
if bcrypt.CompareHashAndPassword([]byte(self.Users[user].Pass), []byte(password)) != nil { if bcrypt.CompareHashAndPassword([]byte(self.Users[user].Pass), []byte(password)) != nil {
return "", errors.New("Incorrect password") return "", errors.New("Incorrect password")
} }
s, err := self.GetData(user, "scope")
if err == nil && s == scope {
sessionId := cookie.GenToken(64) sessionId := cookie.GenToken(64)
self.Users[user].Session = sessionId self.Users[user].Session = sessionId
self.Users[user].LoginTime = time.Now() self.Users[user].LoginTime = time.Now()
self.Users[user].LastSeen = time.Now() self.Users[user].LastSeen = time.Now()
self.SetData(user, "token_expiry", time.Now().Add(time.Minute*time.Duration(minutes)).Format(timeFmt)) self.SetData(user, "token_expiry", strconv.Itoa(ttl))
writeDB(self.Basis, self.Users) writeDB(self.Basis, self.Users)
return base64.StdEncoding.EncodeToString([]byte(user + "\n" + sessionId)), nil return base64.StdEncoding.EncodeToString([]byte(user + "\n" + sessionId)), nil
} }
return "", errors.New("Incorrect scope for this user")
}
func (self *IndentalUserDB) ValidateUser(user string, sessionId string) (bool, error) { func (self *IndentalUserDB) ValidateUser(user string, sessionId string) (bool, error) {
if _, exists := self.Users[user]; !exists { if _, exists := self.Users[user]; !exists {
return false, errors.New("User not in DB") return false, errors.New("User not in DB")
} }
validated := self.Users[user].Session == sessionId validated := self.Users[user].Session == sessionId
expiry, err3 := self.GetData(user, "token_expiry")
expiryInt, err4 := strconv.ParseInt(expiry.(string), 10, 64)
expiryTime := self.Users[user].LastSeen.Add(time.Minute * time.Duration(expiryInt))
if validated { if validated {
if err3 == nil && err4 == nil && time.Now().After(expiryTime) {
self.EndSession(user)
return true, errors.New("Cookie or token expired")
} else {
self.Users[user].LastSeen = time.Now() self.Users[user].LastSeen = time.Now()
writeDB(self.Basis, self.Users) writeDB(self.Basis, self.Users)
} }
}
return validated, nil return validated, nil
} }
@ -92,16 +96,9 @@ func (self *IndentalUserDB) ValidateToken(token string) (bool, error) {
if err == nil { if err == nil {
parts := strings.Split(string(data), "\n") parts := strings.Split(string(data), "\n")
if len(parts) == 2 { if len(parts) == 2 {
expiry, err3 := self.GetData(parts[0], "token_expiry")
expiryTime, err4 := time.Parse(timeFmt, expiry.(string))
if err3 == nil && err4 == nil && time.Now().After(expiryTime) {
self.EndSession(parts[0])
return false, errors.New("token has expired")
} else {
return self.ValidateUser(parts[0], parts[1]) return self.ValidateUser(parts[0], parts[1])
} }
} }
}
return false, errors.New("Token was not in a valid format: b64(USER\nSESSION)") return false, errors.New("Token was not in a valid format: b64(USER\nSESSION)")
} }
@ -212,7 +209,7 @@ func (self *IndentalUserDB) GetData(user string, key string) (interface{}, error
} }
data, exists := self.Users[user].Data[key] data, exists := self.Users[user].Data[key]
if !exists { if !exists {
return nil, errors.New("No data key for user") return nil, errors.New("Key not found in user data")
} }
return data, nil return data, nil

View file

@ -29,6 +29,8 @@ func Protected(next http.Handler, method string, userStore auth.UserStore, login
req.Method = method req.Method = method
next.ServeHTTP(w, req) next.ServeHTTP(w, req)
return return
} else if err != nil && err.Error() == "Cookie or token expired"{
auth.Logout(user, userStore, w)
} }
} }
} }
@ -60,14 +62,14 @@ func Bunt(next string, userStore auth.UserStore, denied string) http.Handler {
return http.HandlerFunc(handlerFunc) return http.HandlerFunc(handlerFunc)
} }
func Authorize(next string, userStore auth.UserStore, denied string) http.Handler { func Authorize(next string, userStore auth.UserStore, denied string, ttl int) http.Handler {
handlerFunc := func(w http.ResponseWriter, req *http.Request) { handlerFunc := func(w http.ResponseWriter, req *http.Request) {
err := auth.Login( err := auth.Login(
req.FormValue("user"), req.FormValue("user"),
req.FormValue("password"), req.FormValue("password"),
userStore, userStore,
w, w,
24*7*52) ttl)
if err == nil { if err == nil {
req.Method = http.MethodGet req.Method = http.MethodGet
fmt.Printf("logged in as %s\n", req.FormValue("user")) fmt.Printf("logged in as %s\n", req.FormValue("user"))
@ -82,17 +84,16 @@ func Authorize(next string, userStore auth.UserStore, denied string) http.Handle
return http.HandlerFunc(handlerFunc) return http.HandlerFunc(handlerFunc)
} }
func Provision(userStore auth.UserStore, minutes int) http.Handler { func Provision(userStore auth.UserStore, ttl int) http.Handler {
handlerFunc := func(w http.ResponseWriter, req *http.Request) { handlerFunc := func(w http.ResponseWriter, req *http.Request) {
user, password, ok := req.BasicAuth() user, password, ok := req.BasicAuth()
scope := req.FormValue("scope") if ok {
if ok && scope != "" { token, err := userStore.GrantToken(user, password, ttl)
token, err := userStore.GrantToken(user, password, scope, minutes)
if err == nil { if err == nil {
token := TokenPayload{ token := TokenPayload{
access_token: token, access_token: token,
token_type: "bearer", token_type: "bearer",
expires_in: minutes, expires_in: ttl,
} }
util.AddContextValue(req, "token", token) util.AddContextValue(req, "token", token)
renderer.JSON("token").ServeHTTP(w, req) renderer.JSON("token").ServeHTTP(w, req)

View file

@ -34,7 +34,7 @@ func ApiSomething(next http.Handler) http.Handler {
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
udb := indentalUserDB.CreateIndentalUserDB("testData/userDB.ndtl") udb := indentalUserDB.CreateIndentalUserDB("testData/userDB.ndtl")
udb.AddUser("nilix", "questing") udb.AddUser("nilix", "questing")
sesh, _ := udb.InitiateSession("nilix", "questing") sesh, _ := udb.InitiateSession("nilix", "questing", 60)
fmt.Printf("%s // %s\n", sesh, sesh) fmt.Printf("%s // %s\n", sesh, sesh)
rtr := &router.Router{ rtr := &router.Router{
@ -47,7 +47,7 @@ func TestMain(m *testing.M) {
rtr.Get("/login", renderer.Template( rtr.Get("/login", renderer.Template(
"testData/templates/login.html")) "testData/templates/login.html"))
rtr.Post("/login", middleware.Authorize("/", udb, "/login?tryagain=1")) rtr.Post("/login", middleware.Authorize("/", udb, "/login?tryagain=1", 120))
rtr.Get("/", middleware.Protected( rtr.Get("/", middleware.Protected(
renderer.Template( renderer.Template(