Réorganisation des packages + renommage du projet en gitea-kan

This commit is contained in:
2019-11-28 12:13:01 +01:00
parent 18f2dbf592
commit 98f4288c8a
18 changed files with 21 additions and 29 deletions

72
internal/config/config.go Normal file
View File

@ -0,0 +1,72 @@
package config
import (
"io"
"os"
ini "gopkg.in/ini.v1"
)
type Config struct {
Debug bool
HTTP HTTPConfig
Gitea GiteaConfig
}
type HTTPConfig struct {
Address string
PublicDir string
}
type GiteaConfig struct {
ClientID string
ClientSecret string
RedirectURL string
LogoutURL string
AuthURL string
TokenURL string
Scopes []string
APIBaseURL string
}
// NewFromFile retrieves the configuration from the given file
func NewFromFile(filepath string) (*Config, error) {
config := NewDefault()
cfg, err := ini.Load(filepath)
if err != nil {
return nil, err
}
cfg.ValueMapper = os.ExpandEnv
if err := cfg.MapTo(config); err != nil {
return nil, err
}
return config, nil
}
func NewDefault() *Config {
return &Config{
Debug: false,
HTTP: HTTPConfig{
Address: ":3000",
PublicDir: "${GITEA_APP_PUBDIR}",
},
Gitea: GiteaConfig{},
}
}
func Dump(config *Config, w io.Writer) error {
cfg := ini.Empty()
if err := cfg.ReflectFrom(config); err != nil {
return err
}
if _, err := cfg.WriteTo(w); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,9 @@
package config
import "gitlab.com/wpetit/goweb/service"
func ServiceProvider(config *Config) service.Provider {
return func(ctn *service.Container) (interface{}, error) {
return config, nil
}
}

View File

@ -0,0 +1,33 @@
package config
import (
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/service"
)
const ServiceName service.Name = "config"
// From retrieves the config service in the given container
func From(container *service.Container) (*Config, error) {
service, err := container.Service(ServiceName)
if err != nil {
return nil, errors.Wrapf(err, "error while retrieving '%s' service", ServiceName)
}
srv, ok := service.(*Config)
if !ok {
return nil, errors.Errorf("retrieved service is not a valid '%s' service", ServiceName)
}
return srv, nil
}
// Must retrieves the config service in the given container or panic otherwise
func Must(container *service.Container) *Config {
srv, err := From(container)
if err != nil {
panic(err)
}
return srv
}

View File

@ -0,0 +1,65 @@
package middleware
import (
"net/http"
"github.com/pborman/uuid"
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service"
"gitlab.com/wpetit/goweb/service/session"
"golang.org/x/oauth2"
)
const (
SessionOAuth2AccessToken = "accessToken"
SessionOAuth2State = "oauth2State"
)
func Authenticate(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
sess, err := session.Must(ctn).Get(w, r)
if err != nil {
panic(errors.Wrap(err, "could not retrieve session"))
}
accessToken, ok := sess.Get(SessionOAuth2AccessToken).(string)
if !ok || accessToken == "" {
state := uuid.New()
sess.Set(SessionOAuth2State, state)
if err := sess.Save(w, r); err != nil {
panic(errors.Wrap(err, "could not save session"))
}
giteaOAuth2Config := GiteaOAuth2Config(ctn)
url := giteaOAuth2Config.AuthCodeURL(state)
http.Redirect(w, r, url, http.StatusSeeOther)
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func GiteaOAuth2Config(ctn *service.Container) *oauth2.Config {
conf := config.Must(ctn)
return &oauth2.Config{
RedirectURL: conf.Gitea.RedirectURL,
ClientID: conf.Gitea.ClientID,
ClientSecret: conf.Gitea.ClientSecret,
Scopes: conf.Gitea.Scopes,
Endpoint: oauth2.Endpoint{
AuthURL: conf.Gitea.AuthURL,
TokenURL: conf.Gitea.TokenURL,
AuthStyle: oauth2.AuthStyleInParams,
},
}
}

31
internal/route/helper.go Normal file
View File

@ -0,0 +1,31 @@
package route
import (
"crypto/rand"
"net/http"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/template"
"gitlab.com/wpetit/goweb/template/html"
)
func extendTemplateData(w http.ResponseWriter, r *http.Request, data template.Data) template.Data {
ctn := container.Must(r.Context())
data, err := template.Extend(data,
html.WithFlashes(w, r, ctn),
)
if err != nil {
panic(errors.Wrap(err, "error while extending template data"))
}
return data
}
func generateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}

23
internal/route/logout.go Normal file
View File

@ -0,0 +1,23 @@
package route
import (
"net/http"
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session"
)
func handleLogout(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
conf := config.Must(ctn)
sess, err := session.Must(ctn).Get(w, r)
if err != nil {
panic(errors.Wrap(err, "could not retrieve session"))
}
if err := sess.Delete(w, r); err != nil {
panic(errors.Wrap(err, "could not delete session"))
}
http.Redirect(w, r, conf.Gitea.LogoutURL, http.StatusSeeOther)
}

46
internal/route/oauth2.go Normal file
View File

@ -0,0 +1,46 @@
package route
import (
"net/http"
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session"
"golang.org/x/oauth2"
)
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
sess, err := session.Must(ctn).Get(w, r)
if err != nil {
panic(errors.Wrap(err, "could not retrieve session"))
}
expectedState := sess.Get(middleware.SessionOAuth2State)
state := r.FormValue("state")
if state != expectedState {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
giteaOAuth2Config := middleware.GiteaOAuth2Config(ctn)
code := r.FormValue("code")
token, err := giteaOAuth2Config.Exchange(oauth2.NoContext, code)
if err != nil {
panic(errors.Wrap(err, "could not exchange oauth2 token"))
}
sess.Set(middleware.SessionOAuth2AccessToken, token.AccessToken)
sess.Set(middleware.SessionOAuth2State, "")
if err := sess.Save(w, r); err != nil {
panic(errors.Wrap(err, "could not save session"))
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}

44
internal/route/proxy.go Normal file
View File

@ -0,0 +1,44 @@
package route
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"github.com/davecgh/go-spew/spew"
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session"
)
func proxyAPIRequest(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
conf := config.Must(ctn)
apiBaseURL, err := url.Parse(conf.Gitea.APIBaseURL)
if err != nil {
panic(errors.Wrap(err, "could not parse api base url"))
}
sess, err := session.Must(ctn).Get(w, r)
if err != nil {
panic(errors.Wrap(err, "could not retrieve session"))
}
accessToken := sess.Get(middleware.SessionOAuth2AccessToken)
proxy := httputil.NewSingleHostReverseProxy(apiBaseURL)
proxy.Director = func(r *http.Request) {
r.Host = apiBaseURL.Host
r.URL.Scheme = apiBaseURL.Scheme
r.URL.Host = apiBaseURL.Host
r.Header.Add("Authorization", fmt.Sprintf("token %s", accessToken))
spew.Dump(r)
}
proxy.ServeHTTP(w, r)
}

33
internal/route/route.go Normal file
View File

@ -0,0 +1,33 @@
package route
import (
"net/http"
"path"
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware"
"github.com/go-chi/chi"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/static"
)
func Mount(r *chi.Mux, config *config.Config) {
r.Group(func(r chi.Router) {
r.Get("/callback", handleOAuth2Callback)
// Authenticated routes
r.Group(func(r chi.Router) {
r.Use(middleware.Authenticate)
r.Get("/logout", handleLogout)
r.Get("/gitea/api/*", http.StripPrefix("/gitea", http.HandlerFunc(proxyAPIRequest)).ServeHTTP)
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", html5PushStateHandler))
})
})
}
func html5PushStateHandler(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
conf := config.Must(ctn)
indexFile := path.Join(conf.HTTP.PublicDir, "index.html")
http.ServeFile(w, r, indexFile)
}