rename auth service
This commit is contained in:
parent
874378cf2d
commit
18661f82f8
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
main.go
8
main.go
@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 (s *SessionStore) NewSession() (*Session, error) {
|
func (a *Authentication) Authenticate(username, password string) (*Session, error) {
|
||||||
|
adminUsername, adminPassword := GetEnv().GetCredentials()
|
||||||
|
if username != adminUsername || password != adminPassword {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user