diff options
Diffstat (limited to 'pkg/handler')
-rw-r--r-- | pkg/handler/about/handler.go | 5 | ||||
-rw-r--r-- | pkg/handler/auth/login.go | 97 | ||||
-rw-r--r-- | pkg/handler/config/handler.go | 63 | ||||
-rw-r--r-- | pkg/handler/git/handler.go | 193 | ||||
-rw-r--r-- | pkg/handler/router.go | 23 | ||||
-rw-r--r-- | pkg/handler/static/handler.go | 4 |
6 files changed, 284 insertions, 101 deletions
diff --git a/pkg/handler/about/handler.go b/pkg/handler/about/handler.go index ac3d314..b3a1593 100644 --- a/pkg/handler/about/handler.go +++ b/pkg/handler/about/handler.go @@ -9,6 +9,7 @@ import ( "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" + "git.gabrielgio.me/cerrado/pkg/ext" "git.gabrielgio.me/cerrado/templates" ) @@ -26,7 +27,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 *ext.Request) error { f, err := os.Open(g.readmePath) if err != nil { return err @@ -50,6 +51,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 new file mode 100644 index 0000000..9cc13cc --- /dev/null +++ b/pkg/handler/auth/login.go @@ -0,0 +1,97 @@ +package auth + +import ( + "encoding/base64" + "net/http" + "time" + + "git.gabrielgio.me/cerrado/pkg/ext" + "git.gabrielgio.me/cerrado/templates" +) + +type ( + 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 *ext.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 *ext.Request) error { + referer := r.URL.Query().Get("referer") + + // if query value is empty tries to get from header + if referer == "" { + referer = r.Header.Get("Referer") + } + + if r.Method == "GET" { + ext.SetHTML(w) + + login := &templates.LoginPage{ + Referer: referer, + } + 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{ + Referer: referer, + 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, referer) + } + + } + + return nil +} diff --git a/pkg/handler/config/handler.go b/pkg/handler/config/handler.go deleted file mode 100644 index c43b54d..0000000 --- a/pkg/handler/config/handler.go +++ /dev/null @@ -1,63 +0,0 @@ -package config - -import ( - "bytes" - "encoding/json" - "net/http" - - "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/alecthomas/chroma/v2/lexers" - "github.com/alecthomas/chroma/v2/styles" - - "git.gabrielgio.me/cerrado/pkg/config" - "git.gabrielgio.me/cerrado/pkg/ext" - "git.gabrielgio.me/cerrado/templates" -) - -type ( - configurationRepository interface { - GetRootReadme() string - List() []*config.GitRepositoryConfiguration - } -) - -func ConfigFile(configRepo configurationRepository) ext.ErrorRequestHandler { - return func(w http.ResponseWriter, _ *http.Request) error { - - config := struct { - RootReadme string - Repositories []*config.GitRepositoryConfiguration - }{ - RootReadme: configRepo.GetRootReadme(), - Repositories: configRepo.List(), - } - - b, err := json.MarshalIndent(config, "", " ") - if err != nil { - return err - } - - lexer := lexers.Get("json") - style := styles.Get("monokailight") - formatter := html.New( - html.WithLineNumbers(true), - ) - iterator, err := lexer.Tokenise(nil, string(b)) - if err != nil { - return err - } - - var code bytes.Buffer - err = formatter.Format(&code, style, iterator) - if err != nil { - return err - } - - hello := &templates.ConfigPage{ - Body: code.Bytes(), - } - - templates.WritePageTemplate(w, hello) - return nil - } -} diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go index 40fae24..cb202a2 100644 --- a/pkg/handler/git/handler.go +++ b/pkg/handler/git/handler.go @@ -2,6 +2,7 @@ package git import ( "bytes" + "compress/gzip" "errors" "fmt" "io" @@ -9,10 +10,13 @@ import ( "net/http" "os" "path/filepath" + "sort" "strings" + "git.gabrielgio.me/cerrado/pkg/config" "git.gabrielgio.me/cerrado/pkg/ext" "git.gabrielgio.me/cerrado/pkg/service" + "git.gabrielgio.me/cerrado/pkg/u" "git.gabrielgio.me/cerrado/templates" "github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2/formatters/html" @@ -27,28 +31,38 @@ import ( type ( GitHandler struct { gitService *service.GitService - readmePath string + config configurationRepository } configurationRepository interface { GetRootReadme() string + GetSyntaxHighlight() string + GetOrderBy() config.OrderBy } ) func NewGitHandler(gitService *service.GitService, confRepo configurationRepository) *GitHandler { return &GitHandler{ gitService: gitService, - readmePath: confRepo.GetRootReadme(), + config: confRepo, } } -func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error { +func (g *GitHandler) List(w http.ResponseWriter, r *ext.Request) error { + // this is the only handler that needs to handle authentication itself. + // everything else relay on name path parameter + logged := ext.IsLoggedIn(r.Context()) + repos, err := g.gitService.ListRepositories() if err != nil { return err } - f, err := os.Open(g.readmePath) + if !logged { + repos = u.Filter(repos, isPublic) + } + + f, err := os.Open(g.config.GetRootReadme()) if err != nil { return err } @@ -69,14 +83,14 @@ func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error { bs = markdown.Render(doc, renderer) gitList := &templates.GitListPage{ - Respositories: repos, + Respositories: orderBy(repos, g.config.GetOrderBy()), About: bs, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } -func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Archive(w http.ResponseWriter, r *ext.Request) error { ext.SetGZip(w) name := r.PathValue("name") file := r.PathValue("file") @@ -84,7 +98,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 } @@ -102,7 +116,51 @@ func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error { return nil } -func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Multiplex(w http.ResponseWriter, r *ext.Request) error { + path := r.PathValue("rest") + name := r.PathValue("name") + + if r.URL.RawQuery == "service=git-receive-pack" { + ext.BadRequest(w, r, "no pushing allowed") + return nil + } + + if path == "info/refs" && r.URL.RawQuery == "service=git-upload-pack" && r.Method == "GET" { + w.Header().Set("content-type", "application/x-git-upload-pack-advertisement") + + err := g.gitService.WriteInfoRefs(r.Context(), name, w) + if err != nil { + slog.Error("Error WriteInfoRefs", "error", err) + } + } else if path == "git-upload-pack" && r.Method == "POST" { + w.Header().Set("content-type", "application/x-git-upload-pack-result") + w.Header().Set("Connection", "Keep-Alive") + w.Header().Set("Transfer-Encoding", "chunked") + w.WriteHeader(http.StatusOK) + + reader := r.Body + + if r.Header.Get("Content-Encoding") == "gzip" { + var err error + reader, err = gzip.NewReader(r.Body) + if err != nil { + return err + } + defer reader.Close() + } + + err := g.gitService.WriteUploadPack(r.Context(), name, reader, w) + if err != nil { + slog.Error("Error WriteUploadPack", "error", err) + } + } else if r.Method == "GET" { + return g.Summary(w, r) + } + + return nil +} + +func (g *GitHandler) Summary(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref, err := g.gitService.GetHead(name) @@ -120,11 +178,15 @@ func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error { return err } - commits, err := g.gitService.ListCommits(name, "", 10) + commits, _, err := g.gitService.ListCommits(name, "", "", 10) if err != nil { return err } + if len(tags) > 3 { + tags = tags[:3] + } + gitList := &templates.GitItemPage{ Name: name, Ref: ref.Name().Short(), @@ -134,11 +196,11 @@ 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 } -func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) About(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref, err := g.gitService.GetHead(name) @@ -154,7 +216,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 { @@ -178,11 +240,11 @@ 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 } -func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Refs(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") @@ -209,11 +271,11 @@ 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 } -func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Tree(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -238,11 +300,11 @@ 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 } -func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Blob(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -269,7 +331,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 } @@ -280,7 +342,8 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error { filename := filepath.Base(rest) lexer := GetLexers(filename) - style := styles.Get("xcode") + style := styles.Get(g.config.GetSyntaxHighlight()) + formatter := html.New( html.WithLineNumbers(true), html.WithLinkableLineNumbers(true, "L"), @@ -305,16 +368,17 @@ 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 } -func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Log(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") + from := r.URL.Query().Get("from") - commits, err := g.gitService.ListCommits(name, ref, 1000) + commits, next, err := g.gitService.ListCommits(name, ref, from, 100) if err != nil { return err } @@ -324,13 +388,36 @@ func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error { Ref: ref, GitItemBase: &templates.GitItemLogPage{ Commits: commits, + Next: next, + }, + } + templates.WritePageTemplate(w, gitList, r.Context()) + return nil +} + +func (g *GitHandler) Ref(w http.ResponseWriter, r *ext.Request) error { + ext.SetHTML(w) + name := r.PathValue("name") + ref := r.PathValue("ref") + + commit, tag, err := g.gitService.GetTag(ref, name) + if err != nil { + return err + } + + gitList := &templates.GitItemPage{ + Name: name, + Ref: ref, + GitItemBase: &templates.GitItemRefPage{ + Commit: commit, + Reference: tag, }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } -func (g *GitHandler) Commit(w http.ResponseWriter, r *http.Request) error { +func (g *GitHandler) Commit(w http.ResponseWriter, r *ext.Request) error { ext.SetHTML(w) name := r.PathValue("name") ref := r.PathValue("ref") @@ -345,15 +432,34 @@ func (g *GitHandler) Commit(w http.ResponseWriter, r *http.Request) error { return err } + lexer := lexers.Get("diff") + style := styles.Get(g.config.GetSyntaxHighlight()) + + formatter := html.New( + html.WithLineNumbers(true), + html.WithLinkableLineNumbers(true, "L"), + ) + + iterator, err := lexer.Tokenise(nil, diff) + if err != nil { + return err + } + + var code bytes.Buffer + err = formatter.Format(&code, style, iterator) + if err != nil { + return err + } + gitList := &templates.GitItemPage{ Name: name, Ref: ref, GitItemBase: &templates.GitItemCommitPage{ Commit: commit, - Diff: diff, + Diff: code.Bytes(), }, } - templates.WritePageTemplate(w, gitList) + templates.WritePageTemplate(w, gitList, r.Context()) return nil } @@ -362,6 +468,10 @@ func GetLexers(filename string) chroma.Lexer { return lexers.Get("sh") } + if strings.HasSuffix(filename, ".qtpl") { + return lexers.Get("html") + } + lexer := lexers.Get(filename) if lexer == nil { @@ -369,3 +479,30 @@ func GetLexers(filename string) chroma.Lexer { } return lexer } + +func isPublic(r *service.Repository) bool { + return r.Public +} + +func orderBy(repos []*service.Repository, order config.OrderBy) []*service.Repository { + switch order { + case config.AlphabeticalAsc: + sort.Slice(repos, func(i, j int) bool { + return repos[i].Name < repos[j].Name + }) + case config.AlphabeticalDesc: + sort.Slice(repos, func(i, j int) bool { + return repos[i].Name > repos[j].Name + }) + case config.LastCommitAsc: + sort.Slice(repos, func(i, j int) bool { + return repos[i].LastCommit.Commit().Committer.When.Before(repos[j].LastCommit.Commit().Committer.When) + }) + case config.LastCommitDesc: + sort.Slice(repos, func(i, j int) bool { + return repos[i].LastCommit.Commit().Committer.When.After(repos[j].LastCommit.Commit().Committer.When) + }) + } + + return repos +} diff --git a/pkg/handler/router.go b/pkg/handler/router.go index f464ac2..fea8827 100644 --- a/pkg/handler/router.go +++ b/pkg/handler/router.go @@ -6,7 +6,7 @@ import ( serverconfig "git.gabrielgio.me/cerrado/pkg/config" "git.gabrielgio.me/cerrado/pkg/ext" "git.gabrielgio.me/cerrado/pkg/handler/about" - "git.gabrielgio.me/cerrado/pkg/handler/config" + "git.gabrielgio.me/cerrado/pkg/handler/auth" "git.gabrielgio.me/cerrado/pkg/handler/git" "git.gabrielgio.me/cerrado/pkg/handler/static" "git.gabrielgio.me/cerrado/pkg/service" @@ -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) - configHandler = config.ConfigFile(configRepo) + gitHandler = git.NewGitHandler(gitService, configRepo) + aboutHandler = about.NewAboutHandler(configRepo) + loginHandler = auth.NewLoginHandler(authService) ) staticHandler, err := static.ServeStaticHandler() @@ -33,17 +34,27 @@ func MountHandler( mux := ext.NewRouter() mux.AddMiddleware(ext.Compress) mux.AddMiddleware(ext.Log) + mux.AddMiddleware(ext.VerifyRespository(configRepo)) + + if configRepo.IsAuthEnabled() { + mux.AddMiddleware(ext.Authenticate(authService)) + mux.HandleFunc("/login/{$}", loginHandler.Login) + mux.HandleFunc("/logout/{$}", loginHandler.Logout) + } else { + mux.AddMiddleware(ext.DisableAuthentication) + } mux.HandleFunc("/static/{file}", staticHandler) mux.HandleFunc("/{name}/about/{$}", gitHandler.About) - mux.HandleFunc("/{name}/", gitHandler.Summary) + mux.HandleFunc("/{name}", gitHandler.Multiplex) + mux.HandleFunc("/{name}/{rest...}", gitHandler.Multiplex) mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs) mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree) mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob) mux.HandleFunc("/{name}/log/{ref}/", gitHandler.Log) mux.HandleFunc("/{name}/commit/{ref}/", gitHandler.Commit) + mux.HandleFunc("/{name}/ref/{ref}/", gitHandler.Ref) mux.HandleFunc("/{name}/archive/{file}", gitHandler.Archive) - mux.HandleFunc("/config", configHandler) mux.HandleFunc("/about", aboutHandler.About) mux.HandleFunc("/", gitHandler.List) return mux.Handler(), nil diff --git a/pkg/handler/static/handler.go b/pkg/handler/static/handler.go index 361f690..cdb2ae6 100644 --- a/pkg/handler/static/handler.go +++ b/pkg/handler/static/handler.go @@ -16,7 +16,7 @@ func ServeStaticHandler() (ext.ErrorRequestHandler, error) { return nil, err } - return func(w http.ResponseWriter, r *http.Request) error { + return func(w http.ResponseWriter, r *ext.Request) error { var ( f = r.PathValue("file") e = filepath.Ext(f) @@ -24,7 +24,7 @@ func ServeStaticHandler() (ext.ErrorRequestHandler, error) { ) ext.SetMIME(w, m) w.Header().Add("Cache-Control", "max-age=31536000") - http.ServeFileFS(w, r, staticFs, f) + http.ServeFileFS(w, r.Request, staticFs, f) return nil }, nil } |