3
0

Expose auth settings through REST API

This commit is contained in:
Denis Arh 2019-04-16 06:39:39 +02:00
parent f44648a3ea
commit 60f35bef47
7 changed files with 138 additions and 5 deletions

View File

@ -5,6 +5,13 @@
"entrypoint": "auth",
"authentication": [],
"apis": [
{
"name": "settings",
"method": "GET",
"title": "Returns auth settings",
"path": "/",
"parameters": {}
},
{
"name": "check",
"method": "GET",

View File

@ -7,6 +7,13 @@
"Authentication": [],
"Path": "/auth",
"APIs": [
{
"Name": "settings",
"Method": "GET",
"Title": "Returns auth settings",
"Path": "/",
"Parameters": {}
},
{
"Name": "check",
"Method": "GET",

View File

@ -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

View File

@ -1,5 +1,11 @@
package service
import (
"strings"
"github.com/markbates/goth"
)
type (
authSettings struct {
// Password reset path (<frontend password reset url> "?token=" + <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,
}
}

View File

@ -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
}

View File

@ -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)
})

View File

@ -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 {
}