From 60f35bef4779b2bf92b175b28d9c6266e2381de7 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 16 Apr 2019 06:39:39 +0200 Subject: [PATCH] Expose auth settings through REST API --- api/system/spec.json | 7 ++++ api/system/spec/auth.json | 7 ++++ docs/system/README.md | 14 ++++++++ system/internal/service/auth_settings.go | 41 ++++++++++++++++++++++++ system/rest/auth.go | 20 ++++++++++-- system/rest/handlers/auth.go | 14 ++++++-- system/rest/request/auth.go | 40 +++++++++++++++++++++++ 7 files changed, 138 insertions(+), 5 deletions(-) diff --git a/api/system/spec.json b/api/system/spec.json index 4a928d3c6..85d029ba9 100644 --- a/api/system/spec.json +++ b/api/system/spec.json @@ -5,6 +5,13 @@ "entrypoint": "auth", "authentication": [], "apis": [ + { + "name": "settings", + "method": "GET", + "title": "Returns auth settings", + "path": "/", + "parameters": {} + }, { "name": "check", "method": "GET", diff --git a/api/system/spec/auth.json b/api/system/spec/auth.json index 6b29c51d2..f8ae9805a 100644 --- a/api/system/spec/auth.json +++ b/api/system/spec/auth.json @@ -7,6 +7,13 @@ "Authentication": [], "Path": "/auth", "APIs": [ + { + "Name": "settings", + "Method": "GET", + "Title": "Returns auth settings", + "Path": "/", + "Parameters": {} + }, { "Name": "check", "Method": "GET", diff --git a/docs/system/README.md b/docs/system/README.md index 37e5fe017..5952e1abb 100644 --- a/docs/system/README.md +++ b/docs/system/README.md @@ -93,9 +93,23 @@ | Method | Endpoint | Purpose | | ------ | -------- | ------- | +| `GET` | `/auth/` | Returns auth settings | | `GET` | `/auth/check` | Check JWT token | | `GET` | `/auth/logout` | Logout | +## Returns auth settings + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/auth/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | + ## Check JWT token #### Method diff --git a/system/internal/service/auth_settings.go b/system/internal/service/auth_settings.go index 7e7d662ac..031c4b864 100644 --- a/system/internal/service/auth_settings.go +++ b/system/internal/service/auth_settings.go @@ -1,5 +1,11 @@ package service +import ( + "strings" + + "github.com/markbates/goth" +) + type ( authSettings struct { // Password reset path ( "?token=" + ) @@ -53,3 +59,38 @@ func AuthSettings(kv authSettingsStore) authSettings { internalPasswordResetEnabled: kv.Bool("auth.internal.password-reset.enabled"), } } + +func (s authSettings) Format() map[string]interface{} { + type ( + externalProvider struct { + Label string `json:"label"` + Handle string `json:"handle"` + } + ) + + var ( + providers = []externalProvider{} + ) + + for p := range goth.GetProviders() { + label := p + if strings.Index(p, "openid-connect.") == 0 { + label = strings.SplitN(p, ".", 2)[1] + } + + providers = append(providers, externalProvider{ + Label: label, + Handle: p, + }) + } + + return map[string]interface{}{ + "internalEnabled": s.internalEnabled, + "internalPasswordResetEnabled": s.internalPasswordResetEnabled, + "internalSignUpEmailConfirmationRequired": s.internalSignUpEmailConfirmationRequired, + "internalSignUpEnabled": s.internalSignUpEnabled, + + "externalEnabled": s.externalEnabled, + "externalProviders": providers, + } +} diff --git a/system/rest/auth.go b/system/rest/auth.go index 98e8b32eb..b2aef4733 100644 --- a/system/rest/auth.go +++ b/system/rest/auth.go @@ -19,17 +19,23 @@ var _ = errors.Wrap type ( Auth struct { - jwt auth.TokenEncoder + jwt auth.TokenEncoder + authSettings authServiceSettingsProvider + } + + authServiceSettingsProvider interface { + Format() map[string]interface{} } checkResponse struct { - JWT string `json:"jwt"` User *outgoing.User `json:"user"` } ) func (Auth) New() *Auth { - return &Auth{} + return &Auth{ + authSettings: service.DefaultAuthSettings, + } } func (ctrl *Auth) Check(ctx context.Context, r *request.AuthCheck) (interface{}, error) { @@ -40,6 +46,10 @@ func (ctrl *Auth) Logout(ctx context.Context, r *request.AuthLogout) (interface{ return nil, errors.New("Not implemented: Auth.logout") } +func (ctrl *Auth) Settings(ctx context.Context, r *request.AuthSettings) (interface{}, error) { + return ctrl.authSettings.Format(), nil +} + // Handlers() func ignores "std" crust controllers // // Crush handlers are too abstract for our auth needs so we need (direct access to htt.ResponseWriter) @@ -74,5 +84,9 @@ func (ctrl *Auth) Handlers(jwtEncoder auth.TokenEncoder) *handlers.Auth { jwtEncoder.SetCookie(w, r, nil) } + h.Settings = func(w http.ResponseWriter, r *http.Request) { + resputil.JSON(w, ctrl.authSvc.FormatSettings()) + } + return h } diff --git a/system/rest/handlers/auth.go b/system/rest/handlers/auth.go index c73654448..fb48a90fb 100644 --- a/system/rest/handlers/auth.go +++ b/system/rest/handlers/auth.go @@ -28,18 +28,27 @@ import ( // Internal API interface type AuthAPI interface { + Settings(context.Context, *request.AuthSettings) (interface{}, error) Check(context.Context, *request.AuthCheck) (interface{}, error) Logout(context.Context, *request.AuthLogout) (interface{}, error) } // HTTP API interface type Auth struct { - Check func(http.ResponseWriter, *http.Request) - Logout func(http.ResponseWriter, *http.Request) + Settings func(http.ResponseWriter, *http.Request) + Check func(http.ResponseWriter, *http.Request) + Logout func(http.ResponseWriter, *http.Request) } func NewAuth(ah AuthAPI) *Auth { return &Auth{ + Settings: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewAuthSettings() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ah.Settings(r.Context(), params) + }) + }, Check: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() params := request.NewAuthCheck() @@ -60,6 +69,7 @@ func NewAuth(ah AuthAPI) *Auth { func (ah *Auth) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) + r.Get("/auth/", ah.Settings) r.Get("/auth/check", ah.Check) r.Get("/auth/logout", ah.Logout) }) diff --git a/system/rest/request/auth.go b/system/rest/request/auth.go index 7e3883d10..ee3cff221 100644 --- a/system/rest/request/auth.go +++ b/system/rest/request/auth.go @@ -30,6 +30,46 @@ import ( var _ = chi.URLParam var _ = multipart.FileHeader{} +// Auth settings request parameters +type AuthSettings struct { +} + +func NewAuthSettings() *AuthSettings { + return &AuthSettings{} +} + +func (auReq *AuthSettings) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(auReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + return err +} + +var _ RequestFiller = NewAuthSettings() + // Auth check request parameters type AuthCheck struct { }