Add persistent session storage with SQLite
This commit is contained in:
parent
747d9fe3c7
commit
b7072a5865
10 changed files with 135 additions and 43 deletions
|
|
@ -1,20 +1,25 @@
|
|||
package iroon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/Bronku/iroon/internal/auth"
|
||||
"github.com/Bronku/iroon/internal/logging"
|
||||
"github.com/Bronku/iroon/internal/server"
|
||||
"github.com/Bronku/iroon/internal/store"
|
||||
)
|
||||
|
||||
func Run() {
|
||||
h := server.New()
|
||||
s := store.OpenStore("./foo.db")
|
||||
defer s.Close()
|
||||
h := server.New(s)
|
||||
defer h.Close()
|
||||
|
||||
var handler http.Handler = h
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,35 @@ package auth
|
|||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"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
|
||||
|
||||
//go:embed wrongPassword.html
|
||||
//go:embed templates/wrongPassword.html
|
||||
var wrongPassword string
|
||||
|
||||
type Authenticator struct {
|
||||
sessions map[string]token
|
||||
sessions map[string]models.Token
|
||||
s *store.Store
|
||||
}
|
||||
|
||||
func New() *Authenticator {
|
||||
return &Authenticator{sessions: make(map[string]token)}
|
||||
func New(s *store.Store) *Authenticator {
|
||||
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) {
|
||||
|
|
@ -40,18 +53,14 @@ func (a *Authenticator) login(w http.ResponseWriter, r *http.Request) {
|
|||
fmt.Fprint(w, wrongPassword)
|
||||
return
|
||||
}
|
||||
key := generateKey()
|
||||
a.sessions[key] = token{userName: login, created: time.Now(), lastAccess: time.Now()}
|
||||
c := http.Cookie{
|
||||
Name: "token",
|
||||
Value: key,
|
||||
HttpOnly: true,
|
||||
// #todo (in prod) uncomment this line
|
||||
//Secure: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Path: "/",
|
||||
cookie, err := a.newSession(login)
|
||||
// #todo: change to a some sort of internal server error
|
||||
if err != nil {
|
||||
w.Header().Set("content-type", "text/html")
|
||||
fmt.Fprint(w, "internal server error")
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &c)
|
||||
http.SetCookie(w, &cookie)
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
|
|
@ -67,12 +76,19 @@ func (a *Authenticator) Middleware(in http.Handler) http.Handler {
|
|||
return
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
value.lastAccess = time.Now()
|
||||
a.sessions[c.Value] = value
|
||||
in.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,31 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Bronku/iroon/internal/models"
|
||||
)
|
||||
|
||||
type token struct {
|
||||
userName string
|
||||
created time.Time
|
||||
lastAccess time.Time
|
||||
func (a *Authenticator) newSession(user string) (http.Cookie, error) {
|
||||
key := generateKey()
|
||||
var session models.Token
|
||||
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 {
|
||||
|
|
|
|||
8
internal/models/auth.go
Normal file
8
internal/models/auth.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Token struct {
|
||||
User string
|
||||
Expiration time.Time
|
||||
}
|
||||
|
|
@ -14,9 +14,6 @@ type Server struct {
|
|||
}
|
||||
|
||||
func (h *Server) Close() {
|
||||
if h.s != nil {
|
||||
h.s.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Server) loadHandler() {
|
||||
|
|
@ -32,11 +29,11 @@ func (h *Server) loadHandler() {
|
|||
h.Handler = mux
|
||||
}
|
||||
|
||||
func New() *Server {
|
||||
func New(store *store.Store) *Server {
|
||||
var server Server
|
||||
|
||||
server.loadTemplates()
|
||||
server.s = store.OpenStore("./foo.db")
|
||||
server.s = store
|
||||
server.loadHandler()
|
||||
|
||||
return &server
|
||||
|
|
|
|||
52
internal/store/auth.go
Normal file
52
internal/store/auth.go
Normal 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, ¤t.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
|
||||
}
|
||||
9
internal/store/migrations/6.sql
Normal file
9
internal/store/migrations/6.sql
Normal 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
13
main.go
|
|
@ -2,19 +2,6 @@ package main
|
|||
|
||||
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() {
|
||||
iroon.Run()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue