Add persistent session storage with SQLite

This commit is contained in:
bronku 2025-03-28 14:44:46 +01:00
parent 747d9fe3c7
commit b7072a5865
10 changed files with 135 additions and 43 deletions

View file

@ -1,20 +1,25 @@
package iroon package iroon
import ( import (
"fmt"
"log" "log"
"net/http" "net/http"
"github.com/Bronku/iroon/internal/auth" "github.com/Bronku/iroon/internal/auth"
"github.com/Bronku/iroon/internal/logging" "github.com/Bronku/iroon/internal/logging"
"github.com/Bronku/iroon/internal/server" "github.com/Bronku/iroon/internal/server"
"github.com/Bronku/iroon/internal/store"
) )
func Run() { func Run() {
h := server.New() s := store.OpenStore("./foo.db")
defer s.Close()
h := server.New(s)
defer h.Close() defer h.Close()
var handler http.Handler = h var handler http.Handler = h
handler = logging.Middleware(handler) handler = logging.Middleware(handler)
handler = auth.New().Middleware(handler) handler = auth.New(s).Middleware(handler)
fmt.Println("starting server")
log.Fatal(http.ListenAndServe(":8080", handler)) log.Fatal(http.ListenAndServe(":8080", handler))
} }

View file

@ -3,22 +3,35 @@ package auth
import ( import (
_ "embed" _ "embed"
"fmt" "fmt"
"log"
"net/http" "net/http"
"time" "time"
"github.com/Bronku/iroon/internal/models"
"github.com/Bronku/iroon/internal/store"
) )
//go:embed login.html //go:embed templates/login.html
var loginPage string var loginPage string
//go:embed wrongPassword.html //go:embed templates/wrongPassword.html
var wrongPassword string var wrongPassword string
type Authenticator struct { type Authenticator struct {
sessions map[string]token sessions map[string]models.Token
s *store.Store
} }
func New() *Authenticator { func New(s *store.Store) *Authenticator {
return &Authenticator{sessions: make(map[string]token)} var out Authenticator
var err error
out.s = s
out.sessions, err = s.GetSessions()
fmt.Println(out.sessions)
if err != nil {
log.Fatal(err)
}
return &out
} }
func (a *Authenticator) login(w http.ResponseWriter, r *http.Request) { func (a *Authenticator) login(w http.ResponseWriter, r *http.Request) {
@ -40,18 +53,14 @@ func (a *Authenticator) login(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, wrongPassword) fmt.Fprint(w, wrongPassword)
return return
} }
key := generateKey() cookie, err := a.newSession(login)
a.sessions[key] = token{userName: login, created: time.Now(), lastAccess: time.Now()} // #todo: change to a some sort of internal server error
c := http.Cookie{ if err != nil {
Name: "token", w.Header().Set("content-type", "text/html")
Value: key, fmt.Fprint(w, "internal server error")
HttpOnly: true, return
// #todo (in prod) uncomment this line
//Secure: true,
SameSite: http.SameSiteStrictMode,
Path: "/",
} }
http.SetCookie(w, &c) http.SetCookie(w, &cookie)
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/", http.StatusFound)
} }
@ -67,12 +76,19 @@ func (a *Authenticator) Middleware(in http.Handler) http.Handler {
return return
} }
value, ok := a.sessions[c.Value] value, ok := a.sessions[c.Value]
if !ok || time.Since(value.lastAccess) > time.Hour*24 || time.Since(value.lastAccess) > time.Hour*240 { if !ok {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
if time.Since(value.Expiration) > 0 {
delete(a.sessions, c.Value)
err := a.s.CleanSessions()
if err != nil {
fmt.Println("error cleaning the sessions", err)
}
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, "/login", http.StatusSeeOther)
return return
} }
value.lastAccess = time.Now()
a.sessions[c.Value] = value
in.ServeHTTP(w, r) in.ServeHTTP(w, r)
}) })
} }

View file

@ -4,13 +4,31 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"log" "log"
"net/http"
"time" "time"
"github.com/Bronku/iroon/internal/models"
) )
type token struct { func (a *Authenticator) newSession(user string) (http.Cookie, error) {
userName string key := generateKey()
created time.Time var session models.Token
lastAccess time.Time var cookie http.Cookie
session.User = user
session.Expiration = time.Now().Add(time.Hour * 24)
a.sessions[key] = session
err := a.s.AddSession(key, user, session.Expiration)
if err != nil {
return cookie, err
}
cookie.Name = "token"
cookie.Value = key
cookie.HttpOnly = true
cookie.SameSite = http.SameSiteStrictMode
cookie.Path = "/"
//cookie.Secure = true
return cookie, nil
} }
func generateKey() string { func generateKey() string {

8
internal/models/auth.go Normal file
View file

@ -0,0 +1,8 @@
package models
import "time"
type Token struct {
User string
Expiration time.Time
}

View file

@ -14,9 +14,6 @@ type Server struct {
} }
func (h *Server) Close() { func (h *Server) Close() {
if h.s != nil {
h.s.Close()
}
} }
func (h *Server) loadHandler() { func (h *Server) loadHandler() {
@ -32,11 +29,11 @@ func (h *Server) loadHandler() {
h.Handler = mux h.Handler = mux
} }
func New() *Server { func New(store *store.Store) *Server {
var server Server var server Server
server.loadTemplates() server.loadTemplates()
server.s = store.OpenStore("./foo.db") server.s = store
server.loadHandler() server.loadHandler()
return &server return &server

52
internal/store/auth.go Normal file
View file

@ -0,0 +1,52 @@
package store
import (
"time"
"github.com/Bronku/iroon/internal/models"
)
func (s *Store) AddSession(token, userName string, expiration time.Time) error {
query := "insert into session (token, user, expiration) values(?, ?, ?)"
_, err := s.db.Exec(query, token, userName, expiration.Format("2006-01-02 15:04"))
return err
}
func (s *Store) CleanSessions() error {
now := time.Now().Format("2006-01-02 15:04")
query := "delete from session where expiration < ?;"
_, err := s.db.Exec(query, now)
return err
}
func (s *Store) GetSessions() (map[string]models.Token, error) {
out := make(map[string]models.Token)
err := s.CleanSessions()
if err != nil {
return out, err
}
query := "select token, user, expiration from session;"
rows, err := s.db.Query(query)
if err != nil {
return out, err
}
defer rows.Close()
for rows.Next() {
var current models.Token
var token string
var expiration string
err = rows.Scan(&token, &current.User, &expiration)
if err != nil {
continue
}
current.Expiration, err = time.Parse("2006-01-02 15:04", expiration)
if err != nil {
continue
}
if token == "" {
continue
}
out[token] = current
}
return out, nil
}

View file

@ -0,0 +1,9 @@
drop table session;
create table session (
token text pirmary key,
user text,
expiration text
);
pragma user_version = 6;

13
main.go
View file

@ -2,19 +2,6 @@ package main
import "github.com/Bronku/iroon/cmd/iroon" import "github.com/Bronku/iroon/cmd/iroon"
type test struct {
data []int
}
func (t *test) xd() []int {
return t.data
}
func (t *test) setData(input []int) {
t.data = make([]int, len(input))
copy(t.data, input)
}
func main() { func main() {
iroon.Run() iroon.Run()
} }