Skip to content

Commit

Permalink
console: Remove Echo usage
Browse files Browse the repository at this point in the history
  • Loading branch information
pgalic96 authored and adriansmares committed Nov 26, 2021
1 parent 1122619 commit c39dc93
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 138 deletions.
9 changes: 0 additions & 9 deletions config/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -8315,15 +8315,6 @@
"file": "remote.go"
}
},
"error:pkg/web/middleware:http_recovered": {
"translations": {
"en": "Internal Server Error"
},
"description": {
"package": "pkg/web/middleware",
"file": "recover.go"
}
},
"error:pkg/web/oauthclient:exchange": {
"translations": {
"en": "token exchange refused"
Expand Down
91 changes: 50 additions & 41 deletions pkg/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ package console
import (
"context"
"fmt"
"net/http"
"net/url"

echo "github.com/labstack/echo/v4"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
"go.thethings.network/lorawan-stack/v3/pkg/component"
web_errors "go.thethings.network/lorawan-stack/v3/pkg/errors/web"
"go.thethings.network/lorawan-stack/v3/pkg/web"
"go.thethings.network/lorawan-stack/v3/pkg/web/middleware"
"go.thethings.network/lorawan-stack/v3/pkg/web/oauthclient"
"go.thethings.network/lorawan-stack/v3/pkg/webmiddleware"
"go.thethings.network/lorawan-stack/v3/pkg/webui"
)

Expand Down Expand Up @@ -126,46 +127,54 @@ func generateConsoleCSPString(config *Config, nonce string) string {
return webui.GenerateCSPString(cspMap)
}

func cspHeader(console *Console) webmiddleware.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r, nonce := webui.WithNonce(r)
cspString := generateConsoleCSPString(console.configFromContext(r.Context()), nonce)
w.Header().Set("Content-Security-Policy", cspString)
next.ServeHTTP(w, r)
})
}
}

func templateMiddleware(console *Console) webmiddleware.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
config := console.configFromContext(r.Context())
r = webui.WithTemplateData(r, config.UI.TemplateData)
frontendConfig := config.UI.FrontendConfig
frontendConfig.Language = config.UI.TemplateData.Language
r = webui.WithAppConfig(r, struct {
FrontendConfig
}{
FrontendConfig: frontendConfig,
})
next.ServeHTTP(w, r)
})
}
}

// RegisterRoutes implements web.Registerer. It registers the Console to the web server.
func (console *Console) RegisterRoutes(server *web.Server) {
group := server.Group(
console.config.Mount,
func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
nonce := webui.GenerateNonce()
c.Set("csp_nonce", nonce)
cspString := generateConsoleCSPString(console.configFromContext(c.Request().Context()), nonce)
c.Response().Header().Set("Content-Security-Policy", cspString)
return next(c)
}
},
func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
config := console.configFromContext(c.Request().Context())
c.Set("template_data", config.UI.TemplateData)
frontendConfig := config.UI.FrontendConfig
frontendConfig.Language = config.UI.TemplateData.Language
c.Set("app_config", struct {
FrontendConfig
}{
FrontendConfig: frontendConfig,
})
return next(c)
}
},
web_errors.ErrorMiddleware(map[string]web_errors.ErrorRenderer{
"text/html": webui.Template,
}),
middleware.CSRF("_console_csrf", console.config.Mount, console.GetBaseConfig(console.Context()).HTTP.Cookie.HashKey),
router := server.Router().NewRoute().PathPrefix(console.config.Mount).Subrouter()
router.Use(
mux.MiddlewareFunc(cspHeader(console)),
mux.MiddlewareFunc(templateMiddleware(console)),
mux.MiddlewareFunc(
webmiddleware.CSRF(
console.GetBaseConfig(console.Context()).HTTP.Cookie.HashKey,
csrf.CookieName("_console_csrf"),
csrf.FieldName("_console_csrf"),
csrf.Path(console.config.Mount),
),
),
)
api := router.NewRoute().PathPrefix("/api/auth/").Subrouter()
api.Path("/token").HandlerFunc(console.oc.HandleToken).Methods(http.MethodGet)
api.Path("/logout").HandlerFunc(console.oc.HandleLogout).Methods(http.MethodPost)

api := group.Group("/api/auth")
api.GET("/token", console.oc.HandleToken)
api.POST("/logout", console.oc.HandleLogout)

group.GET("/oauth/callback", console.oc.HandleCallback)

group.GET("/login/ttn-stack", console.oc.HandleLogin)

group.GET("/*", webui.Template.Handler)
router.Path("/login/ttn-stack").HandlerFunc(console.oc.HandleLogin).Methods(http.MethodGet)
router.Path("/oauth/callback").HandlerFunc(console.oc.HandleCallback).Methods(http.MethodGet)
router.NewRoute().HandlerFunc(webui.Template.Render)
}
82 changes: 51 additions & 31 deletions pkg/web/oauthclient/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import (
stderrors "errors"
"net/http"

echo "github.com/labstack/echo/v4"
"github.com/gorilla/schema"
"go.thethings.network/lorawan-stack/v3/pkg/errors"
"go.thethings.network/lorawan-stack/v3/pkg/webhandlers"
"golang.org/x/oauth2"
)

Expand All @@ -34,10 +35,10 @@ var (
)

type oauthAuthorizeResponse struct {
Error string `form:"error" query:"error"`
ErrorDescription string `form:"error_description" query:"error_description"`
State string `form:"state" query:"state"`
Code string `form:"code" query:"code"`
Error string `schema:"error"`
ErrorDescription string `schema:"error_description"`
State string `schema:"state"`
Code string `schema:"code"`
}

func (res *oauthAuthorizeResponse) ValidateContext(c context.Context) error {
Expand All @@ -55,75 +56,94 @@ func (res *oauthAuthorizeResponse) ValidateContext(c context.Context) error {

// HandleCallback is a handler that takes the auth code and exchanges it for the
// access token.
func (oc *OAuthClient) HandleCallback(c echo.Context) error {
func (oc *OAuthClient) HandleCallback(w http.ResponseWriter, r *http.Request) {
var response oauthAuthorizeResponse
if err := c.Bind(&response); err != nil {
return err
if err := r.ParseForm(); err != nil {
webhandlers.Error(w, r, err)
return
}
if err := response.ValidateContext(c.Request().Context()); err != nil {
return err
if err := schema.NewDecoder().Decode(&response, r.Form); err != nil {
webhandlers.Error(w, r, err)
return
}
if err := response.ValidateContext(r.Context()); err != nil {
webhandlers.Error(w, r, err)
return
}

stateCookie, err := oc.getStateCookie(c)
value, acErr := oc.getAuthCookie(c)
stateCookie, err := oc.getStateCookie(w, r)
value, acErr := oc.getAuthCookie(w, r)
if err != nil {
// Running the callback without state cookie often occurs when re-running
// the callback after successful token exchange (e.g. using the browser's
// back button after logging in). If there is a valid auth cookie, we just
// redirect back to the client mount instead of showing an error.
if acErr != nil {
return errNoStateCookie.New()
webhandlers.Error(w, r, acErr)
return
}
if value.AccessToken != "" {
config := oc.configFromContext(c.Request().Context())
return c.Redirect(http.StatusFound, config.RootURL)
config := oc.configFromContext(r.Context())
http.Redirect(w, r, config.RootURL, http.StatusFound)
return
}
return err
webhandlers.Error(w, r, err)
return
}
if stateCookie.Secret != response.State {
return errInvalidState.New()
webhandlers.Error(w, r, errInvalidState.New())
return
}

// Exchange token.
ctx, err := oc.withHTTPClient(c.Request().Context())
ctx, err := oc.withHTTPClient(r.Context())
if err != nil {
return err
webhandlers.Error(w, r, err)
return
}
conf, err := oc.oauth(c)
conf, err := oc.oauth(w, r)
if err != nil {
return err
webhandlers.Error(w, r, err)
return
}
token, err := conf.Exchange(ctx, response.Code)
if err != nil {
var retrieveError *oauth2.RetrieveError
if stderrors.As(err, &retrieveError) {
var ttnErr errors.Error
if decErr := ttnErr.UnmarshalJSON(retrieveError.Body); decErr == nil {
return errExchange.WithCause(&ttnErr)
webhandlers.Error(w, r, errExchange.WithCause(&ttnErr))
return
}
}
return errExchange.WithCause(err)
webhandlers.Error(w, r, errExchange.WithCause(err))
return
}

oc.removeStateCookie(c)
oc.removeStateCookie(w, r)

err = oc.setAuthCookie(c, authCookie{
err = oc.setAuthCookie(w, r, authCookie{
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
Expiry: token.Expiry,
})
if err != nil {
return err
webhandlers.Error(w, r, err)
return
}

return oc.callback(c, token, stateCookie.Next)
if err := oc.callback(w, r, token, stateCookie.Next); err != nil {
webhandlers.Error(w, r, errExchange.WithCause(err))
return
}
}

func (oc *OAuthClient) defaultCallback(c echo.Context, _ *oauth2.Token, next string) error {
config := oc.configFromContext(c.Request().Context())
return c.Redirect(http.StatusFound, config.RootURL+next)
func (oc *OAuthClient) defaultCallback(w http.ResponseWriter, r *http.Request, _ *oauth2.Token, next string) error {
config := oc.configFromContext(r.Context())
http.Redirect(w, r, config.RootURL+next, http.StatusFound)
return nil
}

func (oc *OAuthClient) defaultAuthCodeURLOptions(echo.Context) ([]oauth2.AuthCodeOption, error) {
func (oc *OAuthClient) defaultAuthCodeURLOptions(w http.ResponseWriter, r *http.Request) ([]oauth2.AuthCodeOption, error) {
return nil, nil
}
12 changes: 6 additions & 6 deletions pkg/web/oauthclient/cookie_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ type authCookie struct {
Expiry time.Time
}

func (oc *OAuthClient) getAuthCookie(c echo.Context) (authCookie, error) {
func (oc *OAuthClient) getAuthCookie(w http.ResponseWriter, r *http.Request) (authCookie, error) {
value := authCookie{}
ok, err := oc.AuthCookie().Get(c.Response(), c.Request(), &value)
ok, err := oc.AuthCookie().Get(w, r, &value)
if err != nil {
return authCookie{}, err
}
Expand All @@ -55,10 +55,10 @@ func (oc *OAuthClient) getAuthCookie(c echo.Context) (authCookie, error) {
return value, nil
}

func (oc *OAuthClient) setAuthCookie(c echo.Context, value authCookie) error {
return oc.AuthCookie().Set(c.Response(), c.Request(), value)
func (oc *OAuthClient) setAuthCookie(w http.ResponseWriter, r *http.Request, value authCookie) error {
return oc.AuthCookie().Set(w, r, value)
}

func (oc *OAuthClient) removeAuthCookie(c echo.Context) {
oc.AuthCookie().Remove(c.Response(), c.Request())
func (oc *OAuthClient) removeAuthCookie(w http.ResponseWriter, r *http.Request) {
oc.AuthCookie().Remove(w, r)
}
17 changes: 8 additions & 9 deletions pkg/web/oauthclient/cookie_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"net/http"
"time"

echo "github.com/labstack/echo/v4"
"go.thethings.network/lorawan-stack/v3/pkg/random"
"go.thethings.network/lorawan-stack/v3/pkg/web/cookie"
)
Expand Down Expand Up @@ -56,24 +55,24 @@ func newState(next string) state {
}
}

func (oc *OAuthClient) getStateCookie(c echo.Context) (state, error) {
func (oc *OAuthClient) getStateCookie(w http.ResponseWriter, r *http.Request) (state, error) {
s := state{}
ok, err := oc.StateCookie().Get(c.Response(), c.Request(), &s)
ok, err := oc.StateCookie().Get(w, r, &s)
if err != nil {
return s, echo.NewHTTPError(http.StatusBadRequest, "Invalid state cookie")
return s, err
}

if !ok {
return s, echo.NewHTTPError(http.StatusBadRequest, "No state cookie")
return s, err
}

return s, nil
}

func (oc *OAuthClient) setStateCookie(c echo.Context, value state) error {
return oc.StateCookie().Set(c.Response(), c.Request(), value)
func (oc *OAuthClient) setStateCookie(w http.ResponseWriter, r *http.Request, value state) error {
return oc.StateCookie().Set(w, r, value)
}

func (oc *OAuthClient) removeStateCookie(c echo.Context) {
oc.StateCookie().Remove(c.Response(), c.Request())
func (oc *OAuthClient) removeStateCookie(w http.ResponseWriter, r *http.Request) {
oc.StateCookie().Remove(w, r)
}
24 changes: 13 additions & 11 deletions pkg/web/oauthclient/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,34 @@ import (
"net/http"
"strings"

echo "github.com/labstack/echo/v4"
"go.thethings.network/lorawan-stack/v3/pkg/webhandlers"
)

// HandleLogin is the handler for redirecting the user to the authorization
// endpoint.
func (oc *OAuthClient) HandleLogin(c echo.Context) error {
next := c.QueryParam(oc.nextKey)

func (oc *OAuthClient) HandleLogin(w http.ResponseWriter, r *http.Request) {
next := r.URL.Query().Get(oc.nextKey)
// Only allow relative paths.
if !strings.HasPrefix(next, "/") && !strings.HasPrefix(next, "#") && !strings.HasPrefix(next, "?") {
next = ""
}

// Set state cookie.
state := newState(next)
if err := oc.setStateCookie(c, state); err != nil {
return err
if err := oc.setStateCookie(w, r, state); err != nil {
webhandlers.Error(w, r, err)
return
}

conf, err := oc.oauth(c)
conf, err := oc.oauth(w, r)
if err != nil {
return err
webhandlers.Error(w, r, err)
return
}
opts, err := oc.authCodeURLOpts(c)
opts, err := oc.authCodeURLOpts(w, r)
if err != nil {
return err
webhandlers.Error(w, r, err)
return
}
return c.Redirect(http.StatusFound, conf.AuthCodeURL(state.Secret, opts...))
http.Redirect(w, r, conf.AuthCodeURL(state.Secret, opts...), http.StatusFound)
}
Loading

0 comments on commit c39dc93

Please sign in to comment.