rename auth service

This commit is contained in:
rmanach 2025-01-03 14:17:37 +01:00
parent 874378cf2d
commit 18661f82f8
5 changed files with 90 additions and 84 deletions

View File

@ -30,8 +30,5 @@ func getHome(w http.ResponseWriter, _ *http.Request) {
return return
} }
if _, err := fmt.Fprint(w, buf); err != nil { fmt.Fprint(w, buf)
log.Err(err).Msg("unable to write to response")
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
}
} }

View File

@ -5,8 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"os"
"sync"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -14,16 +12,6 @@ import (
"librapi/templates" "librapi/templates"
) )
var (
adminUsername = sync.OnceValue[string](func() string {
return os.Getenv("API_ADMIN_USERNAME")
})
adminPassword = sync.OnceValue[string](func() string {
return os.Getenv("API_ADMIN_PASSWORD")
})
)
var ( var (
ErrInvalidUsername = errors.New("username must not be empty") ErrInvalidUsername = errors.New("username must not be empty")
ErrInvalidPassword = errors.New("password must not be empty") ErrInvalidPassword = errors.New("password must not be empty")
@ -63,17 +51,13 @@ func (lf *LoginForm) IsSuccess() bool {
return lf.Method == http.MethodPost && lf.Error != nil && !lf.HasErrors() return lf.Method == http.MethodPost && lf.Error != nil && !lf.HasErrors()
} }
func (lf *LoginForm) ValidCredentials() bool { func Handler(a services.IAuthenticate) func(http.ResponseWriter, *http.Request) {
return lf.Username.Value == adminUsername() && lf.Password.Value == adminPassword()
}
func Handler(s *services.SessionStore) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
getLogin(w, r, s) getLogin(w, r, a)
case http.MethodPost: case http.MethodPost:
postLogin(w, r, s) postLogin(w, r, a)
default: default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed) http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
} }
@ -98,8 +82,20 @@ func extractLoginForm(r *http.Request) LoginForm {
return lf return lf
} }
func postLogin(w http.ResponseWriter, r *http.Request, s *services.SessionStore) { func postLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate) {
loginForm := templates.GetLoginForm() loginForm := templates.GetLoginForm()
loginSuccess := templates.GetLoginSuccess()
if a.IsLogged(r) {
buf := bytes.NewBufferString("")
if err := loginSuccess.Execute(buf, nil); err != nil {
log.Err(err).Msg("unable to generate template")
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
return
}
fmt.Fprint(w, buf)
return
}
lf := extractLoginForm(r) lf := extractLoginForm(r)
if lf.HasErrors() { if lf.HasErrors() {
@ -114,8 +110,11 @@ func postLogin(w http.ResponseWriter, r *http.Request, s *services.SessionStore)
return return
} }
if ok := lf.ValidCredentials(); !ok { session, err := a.Authenticate(lf.Username.Value, lf.Password.Value)
if err != nil {
if errors.Is(err, services.ErrUnauthorized) {
log.Warn().Str("username", lf.Username.Value).Msg("bad credentials") log.Warn().Str("username", lf.Username.Value).Msg("bad credentials")
lf.Error = ErrInvalidCredentials lf.Error = ErrInvalidCredentials
buf := bytes.NewBufferString("") buf := bytes.NewBufferString("")
@ -130,9 +129,6 @@ func postLogin(w http.ResponseWriter, r *http.Request, s *services.SessionStore)
return return
} }
session, err := s.NewSession()
if err != nil {
log.Err(err).Msg("unable to create a new session")
http.Error(w, "unexpected error occurred", http.StatusInternalServerError) http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
return return
} }
@ -140,7 +136,6 @@ func postLogin(w http.ResponseWriter, r *http.Request, s *services.SessionStore)
cookie := session.GenerateCookie() cookie := session.GenerateCookie()
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
loginSuccess := templates.GetLoginSuccess()
buf := bytes.NewBufferString("") buf := bytes.NewBufferString("")
if err := loginSuccess.Execute(buf, nil); err != nil { if err := loginSuccess.Execute(buf, nil); err != nil {
log.Err(err).Msg("unable to generate template") log.Err(err).Msg("unable to generate template")
@ -151,12 +146,20 @@ func postLogin(w http.ResponseWriter, r *http.Request, s *services.SessionStore)
fmt.Fprint(w, buf) fmt.Fprint(w, buf)
} }
func getLogin(w http.ResponseWriter, r *http.Request, s *services.SessionStore) { func getLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate) {
loginForm := templates.GetLoginForm() loginForm := templates.GetLoginForm()
if s.IsLogged(r) { if a.IsLogged(r) {
loginSuccess := templates.GetLoginSuccess() loginSuccess := templates.GetLoginSuccess()
fmt.Fprint(w, loginSuccess.Tree.Root.String())
buf := bytes.NewBufferString("")
if err := loginSuccess.Execute(buf, nil); err != nil {
log.Err(err).Msg("unable to generate template")
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
return
}
fmt.Fprint(w, buf)
return return
} }
@ -167,8 +170,5 @@ func getLogin(w http.ResponseWriter, r *http.Request, s *services.SessionStore)
return return
} }
if _, err := fmt.Fprint(w, buf); err != nil { fmt.Fprint(w, buf)
log.Err(err).Msg("unable to write to response")
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
}
} }

View File

@ -100,13 +100,13 @@ func (bf *BookForm) IsSuccess() bool {
return bf.Method == http.MethodPost && bf.Error == "" && !bf.HasErrors() return bf.Method == http.MethodPost && bf.Error == "" && !bf.HasErrors()
} }
func Handler(s *services.SessionStore) func(http.ResponseWriter, *http.Request) { func Handler(a services.IAuthenticate) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
getUploadFile(w, r) getUploadFile(w, r)
case http.MethodPost: case http.MethodPost:
postUploadFile(w, r, s) postUploadFile(w, r, a)
default: default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed) http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
} }
@ -164,10 +164,10 @@ func extractBookForm(r *http.Request) BookForm {
return bf return bf
} }
func postUploadFile(w http.ResponseWriter, r *http.Request, s *services.SessionStore) { func postUploadFile(w http.ResponseWriter, r *http.Request, a services.IAuthenticate) {
uploadForm := templates.GetUploadForm() uploadForm := templates.GetUploadForm()
if !s.IsLogged(r) { if !a.IsLogged(r) {
buf := bytes.NewBufferString("") buf := bytes.NewBufferString("")
if err := uploadForm.Execute(buf, &BookForm{Error: services.ErrUnauthorized.Error()}); err != nil { if err := uploadForm.Execute(buf, &BookForm{Error: services.ErrUnauthorized.Error()}); err != nil {
log.Err(err).Msg("unable to generate template") log.Err(err).Msg("unable to generate template")
@ -179,8 +179,8 @@ func postUploadFile(w http.ResponseWriter, r *http.Request, s *services.SessionS
return return
} }
buf := bytes.NewBufferString("")
bf := extractBookForm(r) bf := extractBookForm(r)
buf := bytes.NewBufferString("")
if err := uploadForm.Execute(buf, &bf); err != nil { if err := uploadForm.Execute(buf, &bf); err != nil {
log.Err(err).Msg("unable to generate template") log.Err(err).Msg("unable to generate template")
http.Error(w, "unexpected error occurred", http.StatusInternalServerError) http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
@ -241,8 +241,5 @@ func getUploadFile(w http.ResponseWriter, _ *http.Request) {
return return
} }
if _, err := fmt.Fprint(w, buf); err != nil { fmt.Fprint(w, buf)
log.Err(err).Msg("unable to write to response")
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
}
} }

View File

@ -26,17 +26,17 @@ func main() {
ctx, fnCancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt) ctx, fnCancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
defer fnCancel() defer fnCancel()
sessionStore := services.NewSessionStore(ctx) auth := services.NewAuthentication(ctx)
srv := server.NewServer( srv := server.NewServer(
ctx, ctx,
services.GetEnv().GetPort(), services.GetEnv().GetPort(),
server.NewHandler("/", home.Handler()), server.NewHandler("/", home.Handler()),
server.NewHandler("/upload", upload.Handler(sessionStore)), server.NewHandler("/upload", upload.Handler(auth)),
server.NewHandler("/login", login.Handler(sessionStore)), server.NewHandler("/login", login.Handler(auth)),
) )
srv.Serve() srv.Serve()
<-srv.Done() <-srv.Done()
<-sessionStore.Done() <-auth.Done()
} }

View File

@ -45,7 +45,14 @@ func (s *Session) GenerateCookie() *http.Cookie {
} }
} }
type SessionStore struct { type IAuthenticate interface {
IsLogged(r *http.Request) bool
Authenticate(username, password string) (*Session, error)
}
var _ IAuthenticate = (*Authentication)(nil)
type Authentication struct {
l sync.RWMutex l sync.RWMutex
ctx context.Context ctx context.Context
@ -54,10 +61,10 @@ type SessionStore struct {
sessions map[string]*Session sessions map[string]*Session
} }
func NewSessionStore(ctx context.Context) *SessionStore { func NewAuthentication(ctx context.Context) *Authentication {
ctxChild, fnCancel := context.WithCancel(ctx) ctxChild, fnCancel := context.WithCancel(ctx)
s := &SessionStore{ s := &Authentication{
ctx: ctxChild, ctx: ctxChild,
fnCancel: fnCancel, fnCancel: fnCancel,
sessions: map[string]*Session{}, sessions: map[string]*Session{},
@ -67,13 +74,13 @@ func NewSessionStore(ctx context.Context) *SessionStore {
return s return s
} }
func (s *SessionStore) purge() { func (a *Authentication) purge() {
s.l.Lock() a.l.Lock()
defer s.l.Unlock() defer a.l.Unlock()
now := time.Now() now := time.Now()
toDelete := []*Session{} toDelete := []*Session{}
for _, session := range s.sessions { for _, session := range a.sessions {
if now.After(session.expirationTime) { if now.After(session.expirationTime) {
toDelete = append(toDelete, session) toDelete = append(toDelete, session)
} }
@ -81,18 +88,18 @@ func (s *SessionStore) purge() {
for _, session := range toDelete { for _, session := range toDelete {
log.Debug().Str("sessionId", session.sessionID).Msg("purge expired session") log.Debug().Str("sessionId", session.sessionID).Msg("purge expired session")
delete(s.sessions, session.sessionID) delete(a.sessions, session.sessionID)
} }
} }
func (s *SessionStore) purgeWorker() { func (a *Authentication) purgeWorker() {
ticker := time.NewTicker(10 * time.Second) //nolint ticker := time.NewTicker(10 * time.Second) //nolint
go func() { go func() {
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
s.purge() a.purge()
case <-s.ctx.Done(): case <-a.ctx.Done():
log.Info().Msg("purge worker stopped") log.Info().Msg("purge worker stopped")
ticker.Stop() ticker.Stop()
return return
@ -101,45 +108,50 @@ func (s *SessionStore) purgeWorker() {
}() }()
} }
func (s *SessionStore) Stop() { func (a *Authentication) Stop() {
s.fnCancel() a.fnCancel()
} }
func (s *SessionStore) Done() <-chan struct{} { func (a *Authentication) Done() <-chan struct{} {
return s.ctx.Done() return a.ctx.Done()
}
func (a *Authentication) Authenticate(username, password string) (*Session, error) {
adminUsername, adminPassword := GetEnv().GetCredentials()
if username != adminUsername || password != adminPassword {
return nil, ErrUnauthorized
} }
func (s *SessionStore) NewSession() (*Session, error) {
sessionID, err := generateSessionID() sessionID, err := generateSessionID()
if err != nil { if err != nil {
log.Err(err).Msg("unable to generate sessionId") log.Err(err).Msg("unable to generate sessionId")
return nil, err return nil, err
} }
s.l.Lock() a.l.Lock()
defer s.l.Unlock() defer a.l.Unlock()
if _, ok := s.sessions[sessionID]; ok { if _, ok := a.sessions[sessionID]; ok {
log.Error().Str("sessionId", sessionID).Msg("sessionId collision") log.Error().Str("sessionId", sessionID).Msg("sessionId collision")
return nil, ErrSessionIDCollision return nil, ErrSessionIDCollision
} }
now := time.Now().Add(GetEnv().GetSessionExpirationDuration()) now := time.Now().Add(GetEnv().GetSessionExpirationDuration())
session := Session{expirationTime: now, sessionID: sessionID} session := Session{expirationTime: now, sessionID: sessionID}
s.sessions[sessionID] = &session a.sessions[sessionID] = &session
return &session, nil return &session, nil
} }
func (s *SessionStore) IsLogged(r *http.Request) bool { func (a *Authentication) IsLogged(r *http.Request) bool {
cookie, err := r.Cookie("session_id") cookie, err := r.Cookie("session_id")
if err != nil { if err != nil {
return false return false
} }
s.l.RLock() a.l.RLock()
defer s.l.RUnlock() defer a.l.RUnlock()
_, ok := s.sessions[cookie.Value] _, ok := a.sessions[cookie.Value]
return ok return ok
} }