Skip to content

Commit

Permalink
Fix UI and backend paths with subpath (#1799) (#2133)
Browse files Browse the repository at this point in the history
  • Loading branch information
qwerty287 authored Aug 7, 2023
1 parent 4b0db4e commit 239b00c
Show file tree
Hide file tree
Showing 30 changed files with 168 additions and 98 deletions.
4 changes: 2 additions & 2 deletions cmd/server/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ var flags = []cli.Flag{
Usage: "server fully qualified url for forge's Webhooks (<scheme>://<host>)",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_ROOT_URL"},
Name: "root-url",
EnvVars: []string{"WOODPECKER_ROOT_PATH", "WOODPECKER_ROOT_URL"},
Name: "root-path",
Usage: "server url root (used for statics loading when having a url path prefix)",
},
&cli.StringFlag{
Expand Down
6 changes: 5 additions & 1 deletion cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,11 @@ func setupEvilGlobals(c *cli.Context, v store.Store, f forge.Forge) {
server.Config.Server.StatusContext = c.String("status-context")
server.Config.Server.StatusContextFormat = c.String("status-context-format")
server.Config.Server.SessionExpires = c.Duration("session-expires")
server.Config.Server.RootURL = strings.TrimSuffix(c.String("root-url"), "/")
rootPath := strings.TrimSuffix(c.String("root-path"), "/")
if rootPath != "" && !strings.HasPrefix(rootPath, "/") {
rootPath = "/" + rootPath
}
server.Config.Server.RootPath = rootPath
server.Config.Server.CustomCSSFile = strings.TrimSpace(c.String("custom-css-file"))
server.Config.Server.CustomJsFile = strings.TrimSpace(c.String("custom-js-file"))
server.Config.Pipeline.Networks = c.StringSlice("network")
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/30-administration/00-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,4 @@ A [Prometheus endpoint](./90-prometheus.md) is exposed.

See the [proxy guide](./70-proxy.md) if you want to see a setup behind Apache, Nginx, Caddy or ngrok.

In the case you need to use Woodpecker with a URL path prefix (like: https://example.org/woodpecker/), you can use the option [`WOODPECKER_ROOT_URL`](./10-server-config.md#woodpecker_root_url).
In the case you need to use Woodpecker with a URL path prefix (like: https://example.org/woodpecker/), you can use the option [`WOODPECKER_ROOT_PATH`](./10-server-config.md#woodpecker_root_path).
4 changes: 2 additions & 2 deletions docs/docs/30-administration/10-server-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,12 +525,12 @@ Specify a configuration service endpoint, see [Configuration Extension](./100-ex
Specify how many seconds before timeout when fetching the Woodpecker configuration from a Forge

### `WOODPECKER_ROOT_URL`
### `WOODPECKER_ROOT_PATH`
> Default: ``
Server URL path prefix (used for statics loading when having a url path prefix), should start with `/`

Example: `WOODPECKER_ROOT_URL=/woodpecker`
Example: `WOODPECKER_ROOT_PATH=/woodpecker`

---

Expand Down
28 changes: 12 additions & 16 deletions server/api/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,10 @@ import (
)

func HandleLogin(c *gin.Context) {
var (
w = c.Writer
r = c.Request
)
if err := r.FormValue("error"); err != "" {
http.Redirect(w, r, "/login/error?code="+err, 303)
if err := c.Request.FormValue("error"); err != "" {
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login/error?code="+err)
} else {
http.Redirect(w, r, "/authorize", 303)
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/authorize")
}
}

Expand All @@ -56,7 +52,7 @@ func HandleAuth(c *gin.Context) {
tmpuser, err := _forge.Login(c, c.Writer, c.Request)
if err != nil {
log.Error().Msgf("cannot authenticate user. %s", err)
c.Redirect(http.StatusSeeOther, "/login?error=oauth_error")
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=oauth_error")
return
}
// this will happen when the user is redirected by the forge as
Expand All @@ -77,7 +73,7 @@ func HandleAuth(c *gin.Context) {
// if self-registration is disabled we should return a not authorized error
if !config.Open && !config.IsAdmin(tmpuser) {
log.Error().Msgf("cannot register %s. registration closed", tmpuser.Login)
c.Redirect(http.StatusSeeOther, "/login?error=access_denied")
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=access_denied")
return
}

Expand All @@ -87,7 +83,7 @@ func HandleAuth(c *gin.Context) {
teams, terr := _forge.Teams(c, tmpuser)
if terr != nil || !config.IsMember(teams) {
log.Error().Err(terr).Msgf("cannot verify team membership for %s.", u.Login)
c.Redirect(303, "/login?error=access_denied")
c.Redirect(303, server.Config.Server.RootPath+"/login?error=access_denied")
return
}
}
Expand All @@ -108,7 +104,7 @@ func HandleAuth(c *gin.Context) {
// insert the user into the database
if err := _store.CreateUser(u); err != nil {
log.Error().Msgf("cannot insert %s. %s", u.Login, err)
c.Redirect(http.StatusSeeOther, "/login?error=internal_error")
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=internal_error")
return
}

Expand Down Expand Up @@ -137,22 +133,22 @@ func HandleAuth(c *gin.Context) {
teams, terr := _forge.Teams(c, u)
if terr != nil || !config.IsMember(teams) {
log.Error().Err(terr).Msgf("cannot verify team membership for %s.", u.Login)
c.Redirect(http.StatusSeeOther, "/login?error=access_denied")
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=access_denied")
return
}
}

if err := _store.UpdateUser(u); err != nil {
log.Error().Msgf("cannot update %s. %s", u.Login, err)
c.Redirect(http.StatusSeeOther, "/login?error=internal_error")
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=internal_error")
return
}

exp := time.Now().Add(server.Config.Server.SessionExpires).Unix()
tokenString, err := token.New(token.SessToken, u.Login).SignExpires(u.Hash, exp)
if err != nil {
log.Error().Msgf("cannot create token for %s. %s", u.Login, err)
c.Redirect(http.StatusSeeOther, "/login?error=internal_error")
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=internal_error")
return
}

Expand Down Expand Up @@ -187,13 +183,13 @@ func HandleAuth(c *gin.Context) {

httputil.SetCookie(c.Writer, c.Request, "user_sess", tokenString)

c.Redirect(http.StatusSeeOther, "/")
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/")
}

func GetLogout(c *gin.Context) {
httputil.DelCookie(c.Writer, c.Request, "user_sess")
httputil.DelCookie(c.Writer, c.Request, "user_last")
c.Redirect(http.StatusSeeOther, "/")
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/")
}

func GetLoginToken(c *gin.Context) {
Expand Down
2 changes: 1 addition & 1 deletion server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ var Config = struct {
StatusContext string
StatusContextFormat string
SessionExpires time.Duration
RootURL string
RootPath string
CustomCSSFile string
CustomJsFile string
Migrations struct {
Expand Down
2 changes: 1 addition & 1 deletion server/forge/bitbucket/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (c *config) URL() string {
// Login authenticates an account with Bitbucket using the oauth2 protocol. The
// Bitbucket account details are returned when the user is successfully authenticated.
func (c *config) Login(ctx context.Context, w http.ResponseWriter, req *http.Request) (*model.User, error) {
config := c.newConfig(server.Config.Server.Host)
config := c.newConfig(server.Config.Server.Host + server.Config.Server.RootPath)

// get the OAuth errors
if err := req.FormValue("error"); err != "" {
Expand Down
2 changes: 1 addition & 1 deletion server/forge/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (c *Gitea) oauth2Config(ctx context.Context) (*oauth2.Config, context.Conte
AuthURL: fmt.Sprintf(authorizeTokenURL, c.url),
TokenURL: fmt.Sprintf(accessTokenURL, c.url),
},
RedirectURL: fmt.Sprintf("%s/authorize", server.Config.Server.OAuthHost),
RedirectURL: fmt.Sprintf("%s%s/authorize", server.Config.Server.OAuthHost, server.Config.Server.RootPath),
},

context.WithValue(ctx, oauth2.HTTPClient, &http.Client{Transport: &http.Transport{
Expand Down
4 changes: 2 additions & 2 deletions server/forge/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,9 @@ func (c *client) newConfig(req *http.Request) *oauth2.Config {

intendedURL := req.URL.Query()["url"]
if len(intendedURL) > 0 {
redirect = fmt.Sprintf("%s/authorize?url=%s", server.Config.Server.OAuthHost, intendedURL[0])
redirect = fmt.Sprintf("%s%s/authorize?url=%s", server.Config.Server.OAuthHost, server.Config.Server.RootPath, intendedURL[0])
} else {
redirect = fmt.Sprintf("%s/authorize", server.Config.Server.OAuthHost)
redirect = fmt.Sprintf("%s%s/authorize", server.Config.Server.OAuthHost, server.Config.Server.RootPath)
}

return &oauth2.Config{
Expand Down
2 changes: 1 addition & 1 deletion server/forge/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (g *GitLab) oauth2Config(ctx context.Context) (*oauth2.Config, context.Cont
TokenURL: fmt.Sprintf("%s/oauth/token", g.url),
},
Scopes: []string{defaultScope},
RedirectURL: fmt.Sprintf("%s/authorize", server.Config.Server.OAuthHost),
RedirectURL: fmt.Sprintf("%s%s/authorize", server.Config.Server.OAuthHost, server.Config.Server.RootPath),
},

context.WithValue(ctx, oauth2.HTTPClient, &http.Client{Transport: &http.Transport{
Expand Down
2 changes: 1 addition & 1 deletion server/router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
)

func apiRoutes(e *gin.Engine) {
func apiRoutes(e *gin.RouterGroup) {
apiBase := e.Group("/api")
{
user := apiBase.Group("/user")
Expand Down
31 changes: 19 additions & 12 deletions server/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import (
"github.com/rs/zerolog/log"
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"

"github.com/woodpecker-ci/woodpecker/cmd/server/docs"
"github.com/woodpecker-ci/woodpecker/server"

"github.com/woodpecker-ci/woodpecker/server/api"
"github.com/woodpecker-ci/woodpecker/server/api/metrics"
"github.com/woodpecker-ci/woodpecker/server/router/middleware/header"
Expand Down Expand Up @@ -53,31 +53,38 @@ func Load(noRouteHandler http.HandlerFunc, middleware ...gin.HandlerFunc) http.H

e.NoRoute(gin.WrapF(noRouteHandler))

e.GET("/web-config.js", web.Config)

e.GET("/logout", api.GetLogout)
e.GET("/login", api.HandleLogin)
auth := e.Group("/authorize")
base := e.Group(server.Config.Server.RootPath)
{
auth.GET("", api.HandleAuth)
auth.POST("", api.HandleAuth)
auth.POST("/token", api.GetLoginToken)
base.GET("/web-config.js", web.Config)

base.GET("/logout", api.GetLogout)
base.GET("/login", api.HandleLogin)
auth := base.Group("/authorize")
{
auth.GET("", api.HandleAuth)
auth.POST("", api.HandleAuth)
auth.POST("/token", api.GetLoginToken)
}

base.GET("/metrics", metrics.PromHandler())
base.GET("/version", api.Version)
base.GET("/healthz", api.Health)
}

e.GET("/metrics", metrics.PromHandler())
e.GET("/version", api.Version)
e.GET("/healthz", api.Health)

apiRoutes(e)
apiRoutes(base)
setupSwaggerConfigAndRoutes(e)

return e
}

func setupSwaggerConfigAndRoutes(e *gin.Engine) {
docs.SwaggerInfo.Host = getHost(server.Config.Server.Host)
docs.SwaggerInfo.BasePath = server.Config.Server.RootURL + "/api"
e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
docs.SwaggerInfo.BasePath = server.Config.Server.RootPath + "/api"
e.GET(server.Config.Server.RootPath+"/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
}

func getHost(s string) string {
Expand Down
14 changes: 7 additions & 7 deletions server/web/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ func Config(c *gin.Context) {
}

configData := map[string]interface{}{
"user": user,
"csrf": csrf,
"docs": server.Config.Server.Docs,
"version": version.String(),
"forge": server.Config.Services.Forge.Name(),
"root_url": server.Config.Server.RootURL,
"user": user,
"csrf": csrf,
"docs": server.Config.Server.Docs,
"version": version.String(),
"forge": server.Config.Services.Forge.Name(),
"root_path": server.Config.Server.RootPath,
}

// default func map with json parser.
Expand Down Expand Up @@ -74,5 +74,5 @@ window.WOODPECKER_CSRF = "{{ .csrf }}";
window.WOODPECKER_VERSION = "{{ .version }}";
window.WOODPECKER_DOCS = "{{ .docs }}";
window.WOODPECKER_FORGE = "{{ .forge }}";
window.WOODPECKER_ROOT_URL = "{{ .root_url }}";
window.WOODPECKER_ROOT_PATH = "{{ .root_path }}";
`
Loading

0 comments on commit 239b00c

Please sign in to comment.