From 1e45ae2ea3497958b2ea6a20137955cfc3bbc964 Mon Sep 17 00:00:00 2001 From: "Gabriel A. Giovanini" Date: Wed, 11 Dec 2024 17:05:12 +0100 Subject: feat: Add UI/Handler login process It adds the whole workflow to store and handle login on both UI and handler level. With that the login information should be available at any point given the context. --- pkg/config/config.go | 16 +++++++--- pkg/ext/auth.go | 45 ++++++++++++++++++++++++++ pkg/ext/router.go | 18 +++++++---- pkg/handler/about/handler.go | 4 +-- pkg/handler/auth/login.go | 75 +++++++++++++++++++++++++++++++++++++++++--- pkg/handler/git/handler.go | 24 +++++++------- pkg/handler/router.go | 5 ++- pkg/service/auth.go | 8 ++++- pkg/service/git.go | 4 +-- 9 files changed, 166 insertions(+), 33 deletions(-) create mode 100644 pkg/ext/auth.go (limited to 'pkg') diff --git a/pkg/config/config.go b/pkg/config/config.go index 812a06e..da6e0e7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -55,8 +55,8 @@ type ( ConfigurationRepository struct { rootReadme string listenAddr string - passphrase string - aesKey string + passphrase []byte + aesKey []byte syntaxHighlight string repositories []*GitRepositoryConfiguration } @@ -74,9 +74,9 @@ func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, e } repo := &ConfigurationRepository{ - aesKey: config.AESKey, + aesKey: []byte(config.AESKey), listenAddr: config.ListenAddr, - passphrase: config.Passphrase, + passphrase: []byte(config.Passphrase), repositories: config.Repositories, rootReadme: config.RootReadme, syntaxHighlight: config.SyntaxHighlight, @@ -105,6 +105,14 @@ func (c *ConfigurationRepository) GetListenAddr() string { return c.listenAddr } +func (c *ConfigurationRepository) GetPassphrase() []byte { + return c.passphrase +} + +func (c *ConfigurationRepository) GetBase64AesKey() []byte { + return c.aesKey +} + // GetByName returns configuration of repository for a given name. // It returns nil if there is not match for it. func (c *ConfigurationRepository) GetByName(name string) *GitRepositoryConfiguration { diff --git a/pkg/ext/auth.go b/pkg/ext/auth.go new file mode 100644 index 0000000..bb6c0a2 --- /dev/null +++ b/pkg/ext/auth.go @@ -0,0 +1,45 @@ +package ext + +import ( + "context" + "encoding/base64" + "log/slog" + "net/http" +) + +type authService interface { + ValidateToken(token []byte) (bool, error) +} + +func Authenticate(auth authService) func(next http.HandlerFunc) http.HandlerFunc { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("auth") + if err != nil { + slog.Error("Error loading cookie", "error", err) + next(w, r) + return + } + + value, err := base64.StdEncoding.DecodeString(cookie.Value) + if err != nil { + slog.Error("Error decoding", "error", err) + next(w, r) + return + } + + valid, err := auth.ValidateToken(value) + if err != nil { + slog.Error("Error validating token", "error", err, "cookie", cookie.Value) + next(w, r) + return + } + + ctx := r.Context() + ctx = context.WithValue(ctx, "logged", true) + + slog.Info("Validated token", "valid?", valid) + next(w, r.WithContext(ctx)) + } + } +} diff --git a/pkg/ext/router.go b/pkg/ext/router.go index 96da1c9..956254d 100644 --- a/pkg/ext/router.go +++ b/pkg/ext/router.go @@ -23,6 +23,7 @@ func NewRouter() *Router { router: http.NewServeMux(), } } + func (r *Router) Handler() http.Handler { return r.router } @@ -35,9 +36,9 @@ func wrapError(next ErrorRequestHandler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := next(w, r); err != nil { if errors.Is(err, service.ErrRepositoryNotFound) { - NotFound(w) + NotFound(w, r) } else { - InternalServerError(w, err) + InternalServerError(r, w, err) } } } @@ -57,16 +58,21 @@ func (r *Router) HandleFunc(path string, handler ErrorRequestHandler) { r.router.HandleFunc(path, r.run(handler)) } -func NotFound(w http.ResponseWriter) { +func NotFound(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) templates.WritePageTemplate(w, &templates.ErrorPage{ Message: "Not Found", - }) + }, r.Context()) +} + +func Redirect(w http.ResponseWriter, location string) { + w.Header().Add("location", location) + w.WriteHeader(http.StatusTemporaryRedirect) } -func InternalServerError(w http.ResponseWriter, err error) { +func InternalServerError(r *http.Request, w http.ResponseWriter, err error) { w.WriteHeader(http.StatusInternalServerError) templates.WritePageTemplate(w, &templates.ErrorPage{ Message: fmt.Sprintf("Internal Server Error:\n%s", err.Error()), - }) + }, r.Context()) } diff --git a/pkg/handler/about/handler.go b/pkg/handler/about/handler.go index ac3d314..ee084cd 100644 --- a/pkg/handler/about/handler.go +++ b/pkg/handler/about/handler.go @@ -26,7 +26,7 @@ func NewAboutHandler(configRepo configurationRepository) *AboutHandler { return &AboutHandler{configRepo.GetRootReadme()} } -func (g *AboutHandler) About(w http.ResponseWriter, _ *http.Request) error { +func (g *AboutHandler) About(w http.ResponseWriter, r *http.Request) error { f, err := os.Open(g.readmePath) if err != nil { return err @@ -50,6 +50,6 @@ func (g *AboutHandler) About(w http.ResponseWriter, _ *http.Request) error { gitList := &templates.AboutPage{ Body: bs, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } diff --git a/pkg/handler/auth/login.go b/pkg/handler/auth/login.go index 7e77a67..7014548 100644 --- a/pkg/handler/auth/login.go +++ b/pkg/handler/auth/login.go @@ -1,20 +1,87 @@ package auth import ( + "encoding/base64" "net/http" + "time" "git.gabrielgio.me/cerrado/pkg/ext" "git.gabrielgio.me/cerrado/templates" ) type ( - LoginHandler struct{} + LoginHandler struct { + auth authService + } + + authService interface { + CheckAuth(username, password string) bool + IssueToken() ([]byte, error) + } ) +func NewLoginHandler(auth authService) *LoginHandler { + return &LoginHandler{ + auth: auth, + } +} + +func (g *LoginHandler) Logout(w http.ResponseWriter, r *http.Request) error { + cookie := &http.Cookie{ + Name: "auth", + Value: "", + Path: "/", + Expires: time.Unix(0, 0), + } + + referer := r.Header.Get("Referer") + if referer == "" { + referer = "/" + } + + http.SetCookie(w, cookie) + ext.Redirect(w, referer) + return nil +} + func (g *LoginHandler) Login(w http.ResponseWriter, r *http.Request) error { - ext.SetHTML(w) + if r.Method == "GET" { + ext.SetHTML(w) + + login := &templates.LoginPage{} + templates.WritePageTemplate(w, login, r.Context()) + } else if r.Method == "POST" { + + username := r.FormValue("username") + password := r.FormValue("password") + + if !g.auth.CheckAuth(username, password) { + login := &templates.LoginPage{ + ErrorMessage: "Invalid login", + } + templates.WritePageTemplate(w, login, r.Context()) + } else { + + bytes, err := g.auth.IssueToken() + if err != nil { + return err + } + + cookie := &http.Cookie{ + Name: "auth", + Value: base64.StdEncoding.EncodeToString(bytes), + Path: "/", + MaxAge: 3600, + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + } + + http.SetCookie(w, cookie) + ext.Redirect(w, "/") + } + + } - login := &templates.LoginPage{} - templates.WritePageTemplate(w, login) return nil } diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go index 5739c8e..4276159 100644 --- a/pkg/handler/git/handler.go +++ b/pkg/handler/git/handler.go @@ -43,7 +43,7 @@ func NewGitHandler(gitService *service.GitService, confRepo configurationReposit } } -func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error { +func (g *GitHandler) List(w http.ResponseWriter, r *http.Request) error { repos, err := g.gitService.ListRepositories() if err != nil { return err @@ -73,7 +73,7 @@ func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error { Respositories: repos, About: bs, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -85,7 +85,7 @@ func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error { // TODO: remove it once we can support more than gzip if !strings.HasSuffix(file, ".tar.gz") { - ext.NotFound(w) + ext.NotFound(w, r) return nil } @@ -135,7 +135,7 @@ func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error { Commits: commits, }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -155,7 +155,7 @@ func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error { GitItemBase: &templates.GitItemAboutPage{ About: []byte("About file not configured properly"), }, - }) + }, r.Context()) return nil } if err != nil { @@ -179,7 +179,7 @@ func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error { About: bs, }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -210,7 +210,7 @@ func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error { Branches: branches, }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -239,7 +239,7 @@ func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error { Tree: tree, }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -270,7 +270,7 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error { Content: []byte("Binary file"), }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -307,7 +307,7 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error { Content: code.Bytes(), }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -328,7 +328,7 @@ func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error { Commits: commits, }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -355,7 +355,7 @@ func (g *GitHandler) Commit(w http.ResponseWriter, r *http.Request) error { Diff: diff, }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } diff --git a/pkg/handler/router.go b/pkg/handler/router.go index 32bd78a..ee4081b 100644 --- a/pkg/handler/router.go +++ b/pkg/handler/router.go @@ -17,12 +17,13 @@ import ( // its sub package don't leak in other places. func MountHandler( gitService *service.GitService, + authService *service.AuthService, configRepo *serverconfig.ConfigurationRepository, ) (http.Handler, error) { var ( gitHandler = git.NewGitHandler(gitService, configRepo) aboutHandler = about.NewAboutHandler(configRepo) - loginHandler = &auth.LoginHandler{} + loginHandler = auth.NewLoginHandler(authService) ) staticHandler, err := static.ServeStaticHandler() @@ -32,10 +33,12 @@ func MountHandler( mux := ext.NewRouter() mux.AddMiddleware(ext.Compress) + mux.AddMiddleware(ext.Authenticate(authService)) mux.AddMiddleware(ext.Log) mux.HandleFunc("/static/{file}", staticHandler) mux.HandleFunc("/login/{$}", loginHandler.Login) + mux.HandleFunc("/logout/{$}", loginHandler.Logout) mux.HandleFunc("/{name}/about/{$}", gitHandler.About) mux.HandleFunc("/{name}/", gitHandler.Summary) mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs) diff --git a/pkg/service/auth.go b/pkg/service/auth.go index 1fbf4b6..0dbd960 100644 --- a/pkg/service/auth.go +++ b/pkg/service/auth.go @@ -23,7 +23,13 @@ type ( } ) -var tokenSeed = []byte("cerrado") +var tokenSeed = []byte("this is a token for cerrado") + +func NewAuthService(repostiory authRepository) *AuthService { + return &AuthService{ + authRepository: repostiory, + } +} func (a *AuthService) CheckAuth(username, password string) bool { passphrase := a.authRepository.GetPassphrase() diff --git a/pkg/service/git.go b/pkg/service/git.go index f03ba42..6c3912f 100644 --- a/pkg/service/git.go +++ b/pkg/service/git.go @@ -30,9 +30,7 @@ type ( } ) -var ( - ErrRepositoryNotFound = errors.New("Repository not found") -) +var ErrRepositoryNotFound = errors.New("Repository not found") // TODO: make it configurable const timeFormat = "2006.01.02 15:04:05" -- cgit v1.2.3