Fixed broken oauth2 login for operator (#1217)

This PR includes many fixes and refactors for oauth2 authentication and
login endpoints, ie:

- Invalid login returns `403` instead of `500` error
- Removed the session token from console/operator `user credentials
  login`, `oauth flow login` and `change-password` api responses
- Removed session token from localStorage
- Added styles for oauth_callback page and display more descriptive
  errors for debugging
- Success logins returns `204` instead of `200`
- Removed unused swagger apis and code from both, operator and console
  projects
- Operator `Oauth2` login flow was not validating anything, now it does

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2021-11-11 14:46:14 -08:00
committed by GitHub
parent 0086aa8f64
commit 34dc51a579
33 changed files with 439 additions and 1462 deletions

View File

@@ -123,16 +123,17 @@ func TestMain(m *testing.M) {
}
if response != nil {
bodyBytes, _ := ioutil.ReadAll(response.Body)
loginResponse := models.LoginResponse{}
err = json.Unmarshal(bodyBytes, &loginResponse)
if err != nil {
log.Println(err)
for _, cookie := range response.Cookies() {
if cookie.Name == "token" {
token = cookie.Value
break
}
}
}
token = loginResponse.SessionID
if token == "" {
log.Println("authentication token not found in cookies response")
return
}
code := m.Run()

View File

@@ -272,38 +272,6 @@ func init() {
}
}
}
},
"post": {
"security": [],
"tags": [
"UserAPI"
],
"summary": "Login to Console",
"operationId": "Login",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/loginRequest"
}
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/login/oauth2/auth": {
@@ -325,11 +293,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -359,11 +324,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -2215,14 +2177,6 @@ func init() {
}
}
},
"loginResponse": {
"type": "object",
"properties": {
"sessionId": {
"type": "string"
}
}
},
"maxAllocatableMemResponse": {
"type": "object",
"properties": {
@@ -3456,38 +3410,6 @@ func init() {
}
}
}
},
"post": {
"security": [],
"tags": [
"UserAPI"
],
"summary": "Login to Console",
"operationId": "Login",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/loginRequest"
}
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/login/oauth2/auth": {
@@ -3509,11 +3431,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -3543,11 +3462,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -6112,14 +6028,6 @@ func init() {
}
}
},
"loginResponse": {
"type": "object",
"properties": {
"sessionId": {
"type": "string"
}
}
},
"maxAllocatableMemResponse": {
"type": "object",
"properties": {

View File

@@ -123,9 +123,6 @@ func NewOperatorAPI(spec *loads.Document) *OperatorAPI {
OperatorAPIListTenantsHandler: operator_api.ListTenantsHandlerFunc(func(params operator_api.ListTenantsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.ListTenants has not yet been implemented")
}),
UserAPILoginHandler: user_api.LoginHandlerFunc(func(params user_api.LoginParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.Login has not yet been implemented")
}),
UserAPILoginDetailHandler: user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginDetail has not yet been implemented")
}),
@@ -269,8 +266,6 @@ type OperatorAPI struct {
OperatorAPIListPVCsForTenantHandler operator_api.ListPVCsForTenantHandler
// OperatorAPIListTenantsHandler sets the operation handler for the list tenants operation
OperatorAPIListTenantsHandler operator_api.ListTenantsHandler
// UserAPILoginHandler sets the operation handler for the login operation
UserAPILoginHandler user_api.LoginHandler
// UserAPILoginDetailHandler sets the operation handler for the login detail operation
UserAPILoginDetailHandler user_api.LoginDetailHandler
// UserAPILoginOauth2AuthHandler sets the operation handler for the login oauth2 auth operation
@@ -448,9 +443,6 @@ func (o *OperatorAPI) Validate() error {
if o.OperatorAPIListTenantsHandler == nil {
unregistered = append(unregistered, "operator_api.ListTenantsHandler")
}
if o.UserAPILoginHandler == nil {
unregistered = append(unregistered, "user_api.LoginHandler")
}
if o.UserAPILoginDetailHandler == nil {
unregistered = append(unregistered, "user_api.LoginDetailHandler")
}
@@ -683,10 +675,6 @@ func (o *OperatorAPI) initHandlerCache() {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/namespaces/{namespace}/tenants"] = operator_api.NewListTenants(o.context, o.OperatorAPIListTenantsHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login"] = user_api.NewLogin(o.context, o.UserAPILoginHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}

View File

@@ -1,73 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// LoginHandlerFunc turns a function with the right signature into a login handler
type LoginHandlerFunc func(LoginParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginHandlerFunc) Handle(params LoginParams) middleware.Responder {
return fn(params)
}
// LoginHandler interface for that can handle valid login params
type LoginHandler interface {
Handle(LoginParams) middleware.Responder
}
// NewLogin creates a new http.Handler for the login operation
func NewLogin(ctx *middleware.Context, handler LoginHandler) *Login {
return &Login{Context: ctx, Handler: handler}
}
/* Login swagger:route POST /login UserAPI login
Login to Console
*/
type Login struct {
Context *middleware.Context
Handler LoginHandler
}
func (o *Login) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewLoginParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -30,48 +30,28 @@ import (
"github.com/minio/console/models"
)
// LoginOauth2AuthCreatedCode is the HTTP code returned for type LoginOauth2AuthCreated
const LoginOauth2AuthCreatedCode int = 201
// LoginOauth2AuthNoContentCode is the HTTP code returned for type LoginOauth2AuthNoContent
const LoginOauth2AuthNoContentCode int = 204
/*LoginOauth2AuthCreated A successful login.
/*LoginOauth2AuthNoContent A successful login.
swagger:response loginOauth2AuthCreated
swagger:response loginOauth2AuthNoContent
*/
type LoginOauth2AuthCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
type LoginOauth2AuthNoContent struct {
}
// NewLoginOauth2AuthCreated creates LoginOauth2AuthCreated with default headers values
func NewLoginOauth2AuthCreated() *LoginOauth2AuthCreated {
// NewLoginOauth2AuthNoContent creates LoginOauth2AuthNoContent with default headers values
func NewLoginOauth2AuthNoContent() *LoginOauth2AuthNoContent {
return &LoginOauth2AuthCreated{}
}
// WithPayload adds the payload to the login oauth2 auth created response
func (o *LoginOauth2AuthCreated) WithPayload(payload *models.LoginResponse) *LoginOauth2AuthCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login oauth2 auth created response
func (o *LoginOauth2AuthCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
return &LoginOauth2AuthNoContent{}
}
// WriteResponse to the client
func (o *LoginOauth2AuthCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
func (o *LoginOauth2AuthNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
/*LoginOauth2AuthDefault Generic error response.

View File

@@ -30,48 +30,28 @@ import (
"github.com/minio/console/models"
)
// LoginOperatorCreatedCode is the HTTP code returned for type LoginOperatorCreated
const LoginOperatorCreatedCode int = 201
// LoginOperatorNoContentCode is the HTTP code returned for type LoginOperatorNoContent
const LoginOperatorNoContentCode int = 204
/*LoginOperatorCreated A successful login.
/*LoginOperatorNoContent A successful login.
swagger:response loginOperatorCreated
swagger:response loginOperatorNoContent
*/
type LoginOperatorCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
type LoginOperatorNoContent struct {
}
// NewLoginOperatorCreated creates LoginOperatorCreated with default headers values
func NewLoginOperatorCreated() *LoginOperatorCreated {
// NewLoginOperatorNoContent creates LoginOperatorNoContent with default headers values
func NewLoginOperatorNoContent() *LoginOperatorNoContent {
return &LoginOperatorCreated{}
}
// WithPayload adds the payload to the login operator created response
func (o *LoginOperatorCreated) WithPayload(payload *models.LoginResponse) *LoginOperatorCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login operator created response
func (o *LoginOperatorCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
return &LoginOperatorNoContent{}
}
// WriteResponse to the client
func (o *LoginOperatorCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
func (o *LoginOperatorNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
/*LoginOperatorDefault Generic error response.

View File

@@ -1,102 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewLoginParams creates a new LoginParams object
//
// There are no default values defined in the spec.
func NewLoginParams() LoginParams {
return LoginParams{}
}
// LoginParams contains all the bound params for the login operation
// typically these are obtained from a http.Request
//
// swagger:parameters Login
type LoginParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.LoginRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewLoginParams() beforehand.
func (o *LoginParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.LoginRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -1,133 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// LoginCreatedCode is the HTTP code returned for type LoginCreated
const LoginCreatedCode int = 201
/*LoginCreated A successful login.
swagger:response loginCreated
*/
type LoginCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
}
// NewLoginCreated creates LoginCreated with default headers values
func NewLoginCreated() *LoginCreated {
return &LoginCreated{}
}
// WithPayload adds the payload to the login created response
func (o *LoginCreated) WithPayload(payload *models.LoginResponse) *LoginCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login created response
func (o *LoginCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*LoginDefault Generic error response.
swagger:response loginDefault
*/
type LoginDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewLoginDefault creates LoginDefault with default headers values
func NewLoginDefault(code int) *LoginDefault {
if code <= 0 {
code = 500
}
return &LoginDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the login default response
func (o *LoginDefault) WithStatusCode(code int) *LoginDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the login default response
func (o *LoginDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the login default response
func (o *LoginDefault) WithPayload(payload *models.Error) *LoginDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login default response
func (o *LoginDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,104 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// LoginURL generates an URL for the login operation
type LoginURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginURL) WithBasePath(bp string) *LoginURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *LoginURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *LoginURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *LoginURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *LoginURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on LoginURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on LoginURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *LoginURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -17,7 +17,11 @@
package operatorapi
import (
"context"
"net/http"
"time"
xoauth2 "golang.org/x/oauth2"
"github.com/minio/minio-go/v7/pkg/credentials"
@@ -33,7 +37,7 @@ import (
)
func registerLoginHandlers(api *operations.OperatorAPI) {
// get login strategy
// GET login strategy
api.UserAPILoginDetailHandler = user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse()
if err != nil {
@@ -41,31 +45,7 @@ func registerLoginHandlers(api *operations.OperatorAPI) {
}
return user_api.NewLoginDetailOK().WithPayload(loginDetails)
})
// post login
api.UserAPILoginHandler = user_api.LoginHandlerFunc(func(params user_api.LoginParams) middleware.Responder {
loginResponse, err := getLoginResponse(params.Body)
if err != nil {
return user_api.NewLoginDefault(int(err.Code)).WithPayload(err)
}
// Custom response writer to set the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginCreated().WithPayload(loginResponse).WriteResponse(w, p)
})
})
api.UserAPILoginOauth2AuthHandler = user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse()
if err != nil {
return user_api.NewLoginOauth2AuthDefault(int(err.Code)).WithPayload(err)
}
// Custom response writer to set the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginOauth2AuthCreated().WithPayload(loginResponse).WriteResponse(w, p)
})
})
// POST login using k8s service account token
api.UserAPILoginOperatorHandler = user_api.LoginOperatorHandlerFunc(func(params user_api.LoginOperatorParams) middleware.Responder {
loginResponse, err := getLoginOperatorResponse(params.Body)
if err != nil {
@@ -75,7 +55,20 @@ func registerLoginHandlers(api *operations.OperatorAPI) {
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginOperatorCreated().WithPayload(loginResponse).WriteResponse(w, p)
user_api.NewLoginOperatorNoContent().WriteResponse(w, p)
})
})
// POST login using external IDP
api.UserAPILoginOauth2AuthHandler = user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params.Body)
if err != nil {
return user_api.NewLoginOauth2AuthDefault(int(err.Code)).WithPayload(err)
}
// Custom response writer to set the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginOauth2AuthNoContent().WriteResponse(w, p)
})
})
}
@@ -97,36 +90,6 @@ func login(credentials restapi.ConsoleCredentialsI) (*string, error) {
return &token, nil
}
// getConsoleCredentials will return consoleCredentials interface including the associated policy of the current account
func getConsoleCredentials(accessKey, secretKey string) (*restapi.ConsoleCredentials, error) {
creds, err := newConsoleCredentials(secretKey)
if err != nil {
return nil, err
}
return &restapi.ConsoleCredentials{
ConsoleCredentials: creds,
AccountAccessKey: accessKey,
}, nil
}
// getLoginResponse performs login() and serializes it to the handler's output
func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, *models.Error) {
// prepare console credentials
consolCreds, err := getConsoleCredentials(*lr.AccessKey, *lr.SecretKey)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
}
sessionID, err := login(consolCreds)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *sessionID,
}
return loginResponse, nil
}
// getLoginDetailsResponse returns information regarding the Console authentication mechanism.
func getLoginDetailsResponse() (*models.LoginDetails, *models.Error) {
loginStrategy := models.LoginDetailsLoginStrategyServiceDashAccount
@@ -151,22 +114,48 @@ func getLoginDetailsResponse() (*models.LoginDetails, *models.Error) {
return loginDetails, nil
}
func getLoginOauth2AuthResponse() (*models.LoginResponse, *models.Error) {
// verifyUserAgainstIDP will verify user identity against the configured IDP and return MinIO credentials
func verifyUserAgainstIDP(ctx context.Context, provider auth.IdentityProviderI, code, state string) (*xoauth2.Token, error) {
oauth2Token, err := provider.VerifyIdentityForOperator(ctx, code, state)
if err != nil {
return nil, err
}
return oauth2Token, nil
}
creds, err := newConsoleCredentials(getK8sSAToken())
if err != nil {
return nil, prepareError(err)
func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
if oauth2.IsIDPEnabled() {
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(nil, restapi.GetConsoleHTTPClient())
if err != nil {
return nil, prepareError(err)
}
// initialize new identity provider
identityProvider := auth.IdentityProvider{Client: oauth2Client}
// Validate user against IDP
_, err = verifyUserAgainstIDP(ctx, identityProvider, *lr.Code, *lr.State)
if err != nil {
return nil, prepareError(err)
}
// If we pass here that means the IDP correctly authenticate the user with the operator resource
// we proceed to use the service account token configured in the operator-console pod
creds, err := newConsoleCredentials(getK8sSAToken())
if err != nil {
return nil, prepareError(err)
}
token, err := login(restapi.ConsoleCredentials{ConsoleCredentials: creds})
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *token,
}
return loginResponse, nil
}
consoleCredentials := restapi.ConsoleCredentials{ConsoleCredentials: creds}
token, err := login(consoleCredentials)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *token,
}
return loginResponse, nil
return nil, prepareError(errorGeneric)
}
func newConsoleCredentials(secretKey string) (*credentials.Credentials, error) {

View File

@@ -20,8 +20,8 @@ import (
"context"
"github.com/minio/console/pkg/auth/idp/oauth2"
"github.com/minio/minio-go/v7/pkg/credentials"
xoauth2 "golang.org/x/oauth2"
)
// IdentityProviderI interface with all functions to be implemented
@@ -29,6 +29,7 @@ import (
// that are used within this project.
type IdentityProviderI interface {
VerifyIdentity(ctx context.Context, code, state string) (*credentials.Credentials, error)
VerifyIdentityForOperator(ctx context.Context, code, state string) (*xoauth2.Token, error)
GenerateLoginURL() string
}
@@ -45,6 +46,11 @@ func (c IdentityProvider) VerifyIdentity(ctx context.Context, code, state string
return c.Client.VerifyIdentity(ctx, code, state)
}
// VerifyIdentityForOperator will verify the user identity against the idp using the authorization code flow
func (c IdentityProvider) VerifyIdentityForOperator(ctx context.Context, code, state string) (*xoauth2.Token, error) {
return c.Client.VerifyIdentityForOperator(ctx, code, state)
}
// GenerateLoginURL returns a new URL used by the user to login against the idp
func (c IdentityProvider) GenerateLoginURL() string {
return c.Client.GenerateLoginURL()

View File

@@ -181,7 +181,8 @@ type User struct {
Username string `json:"username"`
}
// VerifyIdentity will contact the configured IDP and validate the user identity based on the authorization code
// VerifyIdentity will contact the configured IDP to the user identity based on the authorization code and state
// if the user is valid, then it will contact MinIO to get valid sts credentials based on the identity provided by the IDP
func (client *Provider) VerifyIdentity(ctx context.Context, code, state string) (*credentials.Credentials, error) {
// verify the provided state is valid (prevents CSRF attacks)
if err := validateOauth2State(state); err != nil {
@@ -232,6 +233,23 @@ func (client *Provider) VerifyIdentity(ctx context.Context, code, state string)
return sts, nil
}
// VerifyIdentityForOperator will contact the configured IDP and validate the user identity based on the authorization code and state
func (client *Provider) VerifyIdentityForOperator(ctx context.Context, code, state string) (*xoauth2.Token, error) {
// verify the provided state is valid (prevents CSRF attacks)
if err := validateOauth2State(state); err != nil {
return nil, err
}
customCtx := context.WithValue(ctx, oauth2.HTTPClient, client.provHTTPClient)
oauth2Token, err := client.oauth2Config.Exchange(customCtx, code)
if err != nil {
return nil, err
}
if !oauth2Token.Valid() {
return nil, errors.New("invalid token")
}
return oauth2Token, nil
}
// validateOauth2State validates the provided state was originated using the same
// instance (or one configured using the same secrets) of Console, this is basically used to prevent CSRF attacks
// https://security.stackexchange.com/questions/20187/oauth2-cross-site-request-forgery-and-state-parameter

View File

@@ -28,7 +28,7 @@ export class API {
.then((res) => res.body)
.catch((err) => {
// if we get unauthorized, kick out the user
if (err.status === 401) {
if (err.status === 401 && localStorage.getItem("userLoggedIn")) {
if (window.location.pathname !== "/") {
localStorage.setItem("redirect-path", window.location.pathname);
}

View File

@@ -20,9 +20,14 @@ import { SvgIcon, SvgIconProps } from "@mui/material";
const PrometheusIcon = (props: SvgIconProps) => {
return (
<SvgIcon {...props}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 27.999">
<path id="prometheus-icon" d="M14,.667a14,14,0,1,0,14,14A14,14,0,0,0,14,.667Zm0,26.2c-2.2,0-3.983-1.469-3.983-3.282h7.967C17.983,25.4,16.2,26.869,14,26.869ZM20.579,22.5H7.42V20.114H20.58V22.5h0Zm-.047-3.614H7.458c-.044-.05-.088-.1-.13-.15a9.536,9.536,0,0,1-1.972-3.36c-.005-.029,1.633.335,2.8.6,0,0,.6.138,1.472.3a5.463,5.463,0,0,1-1.338-3.513c0-2.806,2.153-5.259,1.376-7.241.756.061,1.564,1.6,1.619,3.993a8.574,8.574,0,0,0,1.14-4.381c0-1.287.848-2.783,1.7-2.834-.756,1.246.2,2.315,1.042,4.966.317,1,.277,2.671.522,3.734.081-2.207.461-5.427,1.861-6.539-.618,1.4.091,3.152.576,3.994a7.568,7.568,0,0,1,1.257,4.335,5.416,5.416,0,0,1-1.3,3.5c.924-.173,1.563-.33,1.563-.33l3-.586A8.512,8.512,0,0,1,20.532,18.886Z" transform="translate(0 -0.667)" fill="#07193e"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 27.999">
<path
id="prometheus-icon"
d="M14,.667a14,14,0,1,0,14,14A14,14,0,0,0,14,.667Zm0,26.2c-2.2,0-3.983-1.469-3.983-3.282h7.967C17.983,25.4,16.2,26.869,14,26.869ZM20.579,22.5H7.42V20.114H20.58V22.5h0Zm-.047-3.614H7.458c-.044-.05-.088-.1-.13-.15a9.536,9.536,0,0,1-1.972-3.36c-.005-.029,1.633.335,2.8.6,0,0,.6.138,1.472.3a5.463,5.463,0,0,1-1.338-3.513c0-2.806,2.153-5.259,1.376-7.241.756.061,1.564,1.6,1.619,3.993a8.574,8.574,0,0,0,1.14-4.381c0-1.287.848-2.783,1.7-2.834-.756,1.246.2,2.315,1.042,4.966.317,1,.277,2.671.522,3.734.081-2.207.461-5.427,1.861-6.539-.618,1.4.091,3.152.576,3.994a7.568,7.568,0,0,1,1.257,4.335,5.416,5.416,0,0,1-1.3,3.5c.924-.173,1.563-.33,1.563-.33l3-.586A8.512,8.512,0,0,1,20.532,18.886Z"
transform="translate(0 -0.667)"
fill="#07193e"
/>
</svg>
</SvgIcon>
);
};

View File

@@ -92,7 +92,7 @@ const ChangePassword = ({
api
.invoke("POST", "/api/v1/account/change-password", request)
.then((res) => {
.then(() => {
setLoading(false);
setNewPassword("");
setReNewPassword("");

View File

@@ -31,7 +31,6 @@ import { Usage } from "./types";
import { setErrorSnackMessage } from "../../../actions";
import { ErrorResponseHandler } from "../../../common/types";
interface IDashboardSimple {
classes: any;
displayErrorMessage: typeof setErrorSnackMessage;
@@ -89,7 +88,6 @@ const Dashboard = ({ classes, displayErrorMessage }: IDashboardSimple) => {
)}
</Fragment>
)}
</Fragment>
);
};

View File

@@ -14,31 +14,191 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { FC, useEffect, useState } from "react"; // eslint-disable-line @typescript-eslint/no-unused-vars
import { RouteComponentProps } from "react-router";
import storage from "local-storage-fallback";
import React, { useEffect, useState } from "react"; // eslint-disable-line @typescript-eslint/no-unused-vars
import api from "../../common/api";
import { setErrorSnackMessage } from "../../actions";
import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import history from "../../history";
import { Paper } from "@mui/material";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
const LoginCallback: FC<RouteComponentProps> = ({ location }) => {
const styles = (theme: Theme) =>
createStyles({
"@global": {
body: {
backgroundColor: "#FAFAFA",
},
},
paper: {
borderRadius: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
width: 800,
height: 424,
margin: "auto",
position: "absolute",
top: "50%",
left: "50%",
marginLeft: -400,
marginTop: -212,
"&.MuiPaper-root": {
borderRadius: 8,
},
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: "100%", // Fix IE 11 issue.
},
submit: {
margin: "30px 0px 16px",
height: 40,
boxShadow: "none",
padding: "16px 30px",
},
errorBlock: {
backgroundColor: "#C72C48",
width: 800,
height: 64,
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "absolute",
left: "50%",
top: "50%",
marginLeft: -400,
marginTop: -290,
color: "#fff",
fontWeight: 700,
fontSize: 14,
borderRadius: 8,
padding: 10,
boxSizing: "border-box",
},
mainContainer: {
position: "relative",
height: 424,
},
theOcean: {
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,
background:
"transparent linear-gradient(to bottom, #073052 0%,#05122b 100%); 0% 0% no-repeat padding-box;",
},
oceanBg: {
backgroundImage: "url(/images/BG_Illustration.svg)",
backgroundRepeat: "no-repeat",
backgroundPosition: "bottom left",
height: "100%",
width: 324,
},
theLogin: {
padding: "40px 45px 20px 45px",
},
loadingLoginStrategy: {
textAlign: "center",
},
headerTitle: {
marginBottom: 10,
},
submitContainer: {
textAlign: "right",
},
disclaimer: {
fontSize: 12,
marginTop: 30,
},
jwtInput: {
marginTop: 45,
},
linearPredef: {
height: 10,
},
errorIconStyle: {
marginRight: 3,
},
loaderAlignment: {
display: "flex",
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
},
retryButton: {
alignSelf: "flex-end",
},
extraDetailsContainer: {
fontStyle: "italic",
color: "#9C9C9C",
transition: "all .2s ease-in-out",
padding: "0 5px",
marginTop: 5,
overflow: "auto",
},
errorLabel: {
color: "#000",
fontSize: 18,
fontWeight: 500,
marginLeft: 5,
},
simpleError: {
marginTop: 5,
padding: "2px 5px",
fontSize: 16,
color: "#000",
},
messageIcon: {
color: "#C72C48",
display: "flex",
"& svg": {
width: 32,
height: 32,
},
},
errorTitle: {
display: "flex",
alignItems: "center",
},
});
interface ILoginCallBackProps {
setErrorSnackMessage: typeof setErrorSnackMessage;
classes: any;
}
const LoginCallback = ({
classes,
setErrorSnackMessage,
}: ILoginCallBackProps) => {
const [error, setError] = useState<string>("");
const [errorDescription, setErrorDescription] = useState<string>("");
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => {
const code = (location.search.match(/code=([^&]+)/) || [])[1];
const state = (location.search.match(/state=([^&]+)/) || [])[1];
const error = (location.search.match(/error=([^&]+)/) || [])[1];
const errorDescription = (location.search.match(
/error_description=([^&]+)/
) || [])[1];
if (error !== undefined || errorDescription !== undefined) {
setError(error);
setErrorDescription(errorDescription);
} else {
api
.invoke("POST", "/api/v1/login/oauth2/auth", { code, state })
.then((res: any) => {
if (res && res.sessionId) {
// store the jwt token
storage.setItem("token", res.sessionId);
if (loading) {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const code = urlParams.get("code");
const state = urlParams.get("state");
const error = urlParams.get("error");
const errorDescription = urlParams.get("errorDescription");
if (error || errorDescription) {
setError(error || "");
setErrorDescription(errorDescription || "");
setLoading(false);
} else {
api
.invoke("POST", "/api/v1/login/oauth2/auth", { code, state })
.then(() => {
// We push to history the new URL.
let targetPath = "/";
if (
@@ -48,22 +208,55 @@ const LoginCallback: FC<RouteComponentProps> = ({ location }) => {
targetPath = `${localStorage.getItem("redirect-path")}`;
localStorage.setItem("redirect-path", "");
}
window.location.href = targetPath;
}
})
.catch((res: any) => {
window.location.href = "/login";
});
// eslint-disable-next-line react-hooks/exhaustive-deps
setLoading(false);
history.push(targetPath);
})
.catch((error) => {
setError(error.errorMessage);
setErrorDescription(error.detailedError);
setLoading(false);
});
}
}
}, [location.search]);
}, [loading]);
return error !== "" || errorDescription !== "" ? (
<div>
<h2>IDP Error:</h2>
<p>{error}</p>
<p>{errorDescription}</p>
</div>
<React.Fragment>
<Paper className={classes.paper}>
<Grid container className={classes.mainContainer}>
<Grid item xs={7} className={classes.theOcean}>
<div className={classes.oceanBg} />
</Grid>
<Grid item xs={5} className={classes.theLogin}>
<div className={classes.errorTitle}>
<span className={classes.messageIcon}>
<ErrorOutlineIcon />
</span>
<span className={classes.errorLabel}>Error from IDP</span>
</div>
<div className={classes.simpleError}>{error}</div>
<Typography
variant="body1"
gutterBottom
component="div"
className={classes.extraDetailsContainer}
>
{errorDescription}
</Typography>
<Button
component={"a"}
href="/login"
type="submit"
variant="contained"
color="primary"
className={classes.submit}
>
Back to Login
</Button>
</Grid>
</Grid>
</Paper>
</React.Fragment>
) : null;
};
export default LoginCallback;
export default withStyles(styles)(LoginCallback);

View File

@@ -70,11 +70,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -2324,11 +2321,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -2358,45 +2352,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/login/operator": {
"post": {
"security": [],
"tags": [
"UserAPI"
],
"summary": "Login to Operator Console.",
"operationId": "LoginOperator",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/loginOperatorRequest"
}
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -2412,7 +2369,7 @@ func init() {
"tags": [
"UserAPI"
],
"summary": "Logout from Operator.",
"summary": "Logout from Console.",
"operationId": "Logout",
"responses": {
"200": {
@@ -4482,17 +4439,6 @@ func init() {
}
}
},
"loginOperatorRequest": {
"type": "object",
"required": [
"jwt"
],
"properties": {
"jwt": {
"type": "string"
}
}
},
"loginRequest": {
"type": "object",
"required": [
@@ -5771,11 +5717,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -8025,11 +7968,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -8059,45 +7999,8 @@ func init() {
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/login/operator": {
"post": {
"security": [],
"tags": [
"UserAPI"
],
"summary": "Login to Operator Console.",
"operationId": "LoginOperator",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/loginOperatorRequest"
}
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
"204": {
"description": "A successful login."
},
"default": {
"description": "Generic error response.",
@@ -8113,7 +8016,7 @@ func init() {
"tags": [
"UserAPI"
],
"summary": "Logout from Operator.",
"summary": "Logout from Console.",
"operationId": "Logout",
"responses": {
"200": {
@@ -10303,17 +10206,6 @@ func init() {
}
}
},
"loginOperatorRequest": {
"type": "object",
"required": [
"jwt"
],
"properties": {
"jwt": {
"type": "string"
}
}
},
"loginRequest": {
"type": "object",
"required": [

View File

@@ -14,6 +14,7 @@ var (
// ErrorGeneric is a generic error message
ErrorGeneric = errors.New("an error occurred, please try again")
errInvalidCredentials = errors.New("invalid Login")
errForbidden = errors.New("403 Forbidden")
errorGenericInvalidSession = errors.New("invalid session")
// ErrorGenericNotFound Generic error for not found
ErrorGenericNotFound = errors.New("not found")
@@ -54,6 +55,9 @@ func prepareError(err ...error) *models.Error {
frame := getFrame(2)
fileParts := strings.Split(frame.File, "/")
LogError("original error -> (%s:%d: %v)", fileParts[len(fileParts)-1], frame.Line, err[0])
if err[0].Error() == errForbidden.Error() {
errorCode = 403
}
if err[0] == ErrorGenericNotFound {
errorCode = 404
errorMessage = ErrorGenericNotFound.Error()

View File

@@ -263,9 +263,6 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
UserAPILoginOauth2AuthHandler: user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginOauth2Auth has not yet been implemented")
}),
UserAPILoginOperatorHandler: user_api.LoginOperatorHandlerFunc(func(params user_api.LoginOperatorParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginOperator has not yet been implemented")
}),
UserAPILogoutHandler: user_api.LogoutHandlerFunc(func(params user_api.LogoutParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.Logout has not yet been implemented")
}),
@@ -549,8 +546,6 @@ type ConsoleAPI struct {
UserAPILoginDetailHandler user_api.LoginDetailHandler
// UserAPILoginOauth2AuthHandler sets the operation handler for the login oauth2 auth operation
UserAPILoginOauth2AuthHandler user_api.LoginOauth2AuthHandler
// UserAPILoginOperatorHandler sets the operation handler for the login operator operation
UserAPILoginOperatorHandler user_api.LoginOperatorHandler
// UserAPILogoutHandler sets the operation handler for the logout operation
UserAPILogoutHandler user_api.LogoutHandler
// UserAPIMakeBucketHandler sets the operation handler for the make bucket operation
@@ -900,9 +895,6 @@ func (o *ConsoleAPI) Validate() error {
if o.UserAPILoginOauth2AuthHandler == nil {
unregistered = append(unregistered, "user_api.LoginOauth2AuthHandler")
}
if o.UserAPILoginOperatorHandler == nil {
unregistered = append(unregistered, "user_api.LoginOperatorHandler")
}
if o.UserAPILogoutHandler == nil {
unregistered = append(unregistered, "user_api.LogoutHandler")
}
@@ -1368,10 +1360,6 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login/operator"] = user_api.NewLoginOperator(o.context, o.UserAPILoginOperatorHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/logout"] = user_api.NewLogout(o.context, o.UserAPILogoutHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)

View File

@@ -30,48 +30,28 @@ import (
"github.com/minio/console/models"
)
// AccountChangePasswordCreatedCode is the HTTP code returned for type AccountChangePasswordCreated
const AccountChangePasswordCreatedCode int = 201
// AccountChangePasswordNoContentCode is the HTTP code returned for type AccountChangePasswordNoContent
const AccountChangePasswordNoContentCode int = 204
/*AccountChangePasswordCreated A successful login.
/*AccountChangePasswordNoContent A successful login.
swagger:response accountChangePasswordCreated
swagger:response accountChangePasswordNoContent
*/
type AccountChangePasswordCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
type AccountChangePasswordNoContent struct {
}
// NewAccountChangePasswordCreated creates AccountChangePasswordCreated with default headers values
func NewAccountChangePasswordCreated() *AccountChangePasswordCreated {
// NewAccountChangePasswordNoContent creates AccountChangePasswordNoContent with default headers values
func NewAccountChangePasswordNoContent() *AccountChangePasswordNoContent {
return &AccountChangePasswordCreated{}
}
// WithPayload adds the payload to the account change password created response
func (o *AccountChangePasswordCreated) WithPayload(payload *models.LoginResponse) *AccountChangePasswordCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the account change password created response
func (o *AccountChangePasswordCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
return &AccountChangePasswordNoContent{}
}
// WriteResponse to the client
func (o *AccountChangePasswordCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
func (o *AccountChangePasswordNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
/*AccountChangePasswordDefault Generic error response.

View File

@@ -30,48 +30,28 @@ import (
"github.com/minio/console/models"
)
// LoginOauth2AuthCreatedCode is the HTTP code returned for type LoginOauth2AuthCreated
const LoginOauth2AuthCreatedCode int = 201
// LoginOauth2AuthNoContentCode is the HTTP code returned for type LoginOauth2AuthNoContent
const LoginOauth2AuthNoContentCode int = 204
/*LoginOauth2AuthCreated A successful login.
/*LoginOauth2AuthNoContent A successful login.
swagger:response loginOauth2AuthCreated
swagger:response loginOauth2AuthNoContent
*/
type LoginOauth2AuthCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
type LoginOauth2AuthNoContent struct {
}
// NewLoginOauth2AuthCreated creates LoginOauth2AuthCreated with default headers values
func NewLoginOauth2AuthCreated() *LoginOauth2AuthCreated {
// NewLoginOauth2AuthNoContent creates LoginOauth2AuthNoContent with default headers values
func NewLoginOauth2AuthNoContent() *LoginOauth2AuthNoContent {
return &LoginOauth2AuthCreated{}
}
// WithPayload adds the payload to the login oauth2 auth created response
func (o *LoginOauth2AuthCreated) WithPayload(payload *models.LoginResponse) *LoginOauth2AuthCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login oauth2 auth created response
func (o *LoginOauth2AuthCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
return &LoginOauth2AuthNoContent{}
}
// WriteResponse to the client
func (o *LoginOauth2AuthCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
func (o *LoginOauth2AuthNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
/*LoginOauth2AuthDefault Generic error response.

View File

@@ -1,73 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// LoginOperatorHandlerFunc turns a function with the right signature into a login operator handler
type LoginOperatorHandlerFunc func(LoginOperatorParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginOperatorHandlerFunc) Handle(params LoginOperatorParams) middleware.Responder {
return fn(params)
}
// LoginOperatorHandler interface for that can handle valid login operator params
type LoginOperatorHandler interface {
Handle(LoginOperatorParams) middleware.Responder
}
// NewLoginOperator creates a new http.Handler for the login operator operation
func NewLoginOperator(ctx *middleware.Context, handler LoginOperatorHandler) *LoginOperator {
return &LoginOperator{Context: ctx, Handler: handler}
}
/* LoginOperator swagger:route POST /login/operator UserAPI loginOperator
Login to Operator Console.
*/
type LoginOperator struct {
Context *middleware.Context
Handler LoginOperatorHandler
}
func (o *LoginOperator) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewLoginOperatorParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,102 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewLoginOperatorParams creates a new LoginOperatorParams object
//
// There are no default values defined in the spec.
func NewLoginOperatorParams() LoginOperatorParams {
return LoginOperatorParams{}
}
// LoginOperatorParams contains all the bound params for the login operator operation
// typically these are obtained from a http.Request
//
// swagger:parameters LoginOperator
type LoginOperatorParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.LoginOperatorRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewLoginOperatorParams() beforehand.
func (o *LoginOperatorParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.LoginOperatorRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -1,133 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// LoginOperatorCreatedCode is the HTTP code returned for type LoginOperatorCreated
const LoginOperatorCreatedCode int = 201
/*LoginOperatorCreated A successful login.
swagger:response loginOperatorCreated
*/
type LoginOperatorCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
}
// NewLoginOperatorCreated creates LoginOperatorCreated with default headers values
func NewLoginOperatorCreated() *LoginOperatorCreated {
return &LoginOperatorCreated{}
}
// WithPayload adds the payload to the login operator created response
func (o *LoginOperatorCreated) WithPayload(payload *models.LoginResponse) *LoginOperatorCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login operator created response
func (o *LoginOperatorCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginOperatorCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*LoginOperatorDefault Generic error response.
swagger:response loginOperatorDefault
*/
type LoginOperatorDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewLoginOperatorDefault creates LoginOperatorDefault with default headers values
func NewLoginOperatorDefault(code int) *LoginOperatorDefault {
if code <= 0 {
code = 500
}
return &LoginOperatorDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the login operator default response
func (o *LoginOperatorDefault) WithStatusCode(code int) *LoginOperatorDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the login operator default response
func (o *LoginOperatorDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the login operator default response
func (o *LoginOperatorDefault) WithPayload(payload *models.Error) *LoginOperatorDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login operator default response
func (o *LoginOperatorDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginOperatorDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,104 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// LoginOperatorURL generates an URL for the login operator operation
type LoginOperatorURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginOperatorURL) WithBasePath(bp string) *LoginOperatorURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginOperatorURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *LoginOperatorURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login/operator"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *LoginOperatorURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *LoginOperatorURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *LoginOperatorURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on LoginOperatorURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on LoginOperatorURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *LoginOperatorURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -30,48 +30,28 @@ import (
"github.com/minio/console/models"
)
// LoginCreatedCode is the HTTP code returned for type LoginCreated
const LoginCreatedCode int = 201
// LoginNoContentCode is the HTTP code returned for type LoginNoContent
const LoginNoContentCode int = 204
/*LoginCreated A successful login.
/*LoginNoContent A successful login.
swagger:response loginCreated
swagger:response loginNoContent
*/
type LoginCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
type LoginNoContent struct {
}
// NewLoginCreated creates LoginCreated with default headers values
func NewLoginCreated() *LoginCreated {
// NewLoginNoContent creates LoginNoContent with default headers values
func NewLoginNoContent() *LoginNoContent {
return &LoginCreated{}
}
// WithPayload adds the payload to the login created response
func (o *LoginCreated) WithPayload(payload *models.LoginResponse) *LoginCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login created response
func (o *LoginCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
return &LoginNoContent{}
}
// WriteResponse to the client
func (o *LoginCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
func (o *LoginNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
/*LoginDefault Generic error response.

View File

@@ -50,7 +50,7 @@ func NewLogout(ctx *middleware.Context, handler LogoutHandler) *Logout {
/* Logout swagger:route POST /logout UserAPI logout
Logout from Operator.
Logout from Console.
*/
type Logout struct {

View File

@@ -41,7 +41,7 @@ func registerAccountHandlers(api *operations.ConsoleAPI) {
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := NewSessionCookieForConsole(changePasswordResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginCreated().WithPayload(changePasswordResponse).WriteResponse(w, p)
user_api.NewLoginNoContent().WriteResponse(w, p)
})
})
// Checks if user can perform an action

View File

@@ -36,7 +36,7 @@ import (
)
func registerLoginHandlers(api *operations.ConsoleAPI) {
// get login strategy
// GET login strategy
api.UserAPILoginDetailHandler = user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse()
if err != nil {
@@ -44,7 +44,7 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
}
return user_api.NewLoginDetailOK().WithPayload(loginDetails)
})
// post login
// POST login using user credentials
api.UserAPILoginHandler = user_api.LoginHandlerFunc(func(params user_api.LoginParams) middleware.Responder {
loginResponse, err := getLoginResponse(params.Body)
if err != nil {
@@ -54,9 +54,10 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginCreated().WithPayload(loginResponse).WriteResponse(w, p)
user_api.NewLoginNoContent().WriteResponse(w, p)
})
})
// POST login using external IDP
api.UserAPILoginOauth2AuthHandler = user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params.Body)
if err != nil {
@@ -66,19 +67,7 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginOauth2AuthCreated().WithPayload(loginResponse).WriteResponse(w, p)
})
})
api.UserAPILoginOperatorHandler = user_api.LoginOperatorHandlerFunc(func(params user_api.LoginOperatorParams) middleware.Responder {
loginResponse, err := getLoginOperatorResponse(params.Body)
if err != nil {
return user_api.NewLoginOperatorDefault(int(err.Code)).WithPayload(err)
}
// Custom response writer to set the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginOperatorCreated().WithPayload(loginResponse).WriteResponse(w, p)
user_api.NewLoginOauth2AuthNoContent().WriteResponse(w, p)
})
})
}
@@ -189,7 +178,7 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
// Validate user against IDP
userCredentials, err := verifyUserAgainstIDP(ctx, identityProvider, *lr.Code, *lr.State)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
return nil, prepareError(err)
}
// initialize admin client
// login user against console and generate session token
@@ -198,7 +187,7 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
AccountAccessKey: "",
})
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
return nil, prepareError(err)
}
// serialize output
loginResponse := &models.LoginResponse{
@@ -208,21 +197,3 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
}
return nil, prepareError(ErrorGeneric)
}
// getLoginOperatorResponse validate the provided service account token against k8s api
func getLoginOperatorResponse(lmr *models.LoginOperatorRequest) (*models.LoginResponse, *models.Error) {
creds, err := NewConsoleCredentials("", *lmr.Jwt, "")
if err != nil {
return nil, prepareError(err)
}
consoleCreds := ConsoleCredentials{ConsoleCredentials: creds}
token, err := login(consoleCreds)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *token,
}
return loginResponse, nil
}

View File

@@ -22,6 +22,8 @@ import (
"reflect"
"testing"
xoauth2 "golang.org/x/oauth2"
"github.com/minio/madmin-go"
iampolicy "github.com/minio/pkg/iam/policy"
@@ -78,12 +80,17 @@ func TestLogin(t *testing.T) {
type IdentityProviderMock struct{}
var idpVerifyIdentityMock func(ctx context.Context, code, state string) (*credentials.Credentials, error)
var idpVerifyIdentityForOperatorMock func(ctx context.Context, code, state string) (*xoauth2.Token, error)
var idpGenerateLoginURLMock func() string
func (ac IdentityProviderMock) VerifyIdentity(ctx context.Context, code, state string) (*credentials.Credentials, error) {
return idpVerifyIdentityMock(ctx, code, state)
}
func (ac IdentityProviderMock) VerifyIdentityForOperator(ctx context.Context, code, state string) (*xoauth2.Token, error) {
return idpVerifyIdentityForOperatorMock(ctx, code, state)
}
func (ac IdentityProviderMock) GenerateLoginURL() string {
return idpGenerateLoginURLMock()
}

View File

@@ -48,10 +48,8 @@ paths:
schema:
$ref: "#/definitions/loginRequest"
responses:
201:
204:
description: A successful login.
schema:
$ref: "#/definitions/loginResponse"
default:
description: Generic error response.
schema:
@@ -60,29 +58,6 @@ paths:
security: []
tags:
- UserAPI
/login/operator:
post:
summary: Login to Operator Console.
operationId: LoginOperator
parameters:
- name: body
in: body
required: true
schema:
$ref: "#/definitions/loginOperatorRequest"
responses:
201:
description: A successful login.
schema:
$ref: "#/definitions/loginResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
security: []
tags:
- UserAPI
/login/oauth2/auth:
post:
summary: Identity Provider oauth2 callback endpoint.
@@ -94,10 +69,8 @@ paths:
schema:
$ref: "#/definitions/loginOauth2AuthRequest"
responses:
201:
204:
description: A successful login.
schema:
$ref: "#/definitions/loginResponse"
default:
description: Generic error response.
schema:
@@ -147,10 +120,8 @@ paths:
schema:
$ref: "#/definitions/accountChangePasswordRequest"
responses:
201:
204:
description: A successful login.
schema:
$ref: "#/definitions/loginResponse"
default:
description: Generic error response.
schema:
@@ -3045,13 +3016,6 @@ definitions:
type: string
code:
type: string
loginOperatorRequest:
type: object
required:
- jwt
properties:
jwt:
type: string
loginRequest:
type: object
required:

View File

@@ -38,28 +38,6 @@ paths:
security: [ ]
tags:
- UserAPI
post:
summary: Login to Console
operationId: Login
parameters:
- name: body
in: body
required: true
schema:
$ref: "#/definitions/loginRequest"
responses:
201:
description: A successful login.
schema:
$ref: "#/definitions/loginResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
# Exclude this API from the authentication requirement
security: [ ]
tags:
- UserAPI
/login/operator:
post:
summary: Login to Operator Console.
@@ -71,10 +49,8 @@ paths:
schema:
$ref: "#/definitions/loginOperatorRequest"
responses:
201:
204:
description: A successful login.
schema:
$ref: "#/definitions/loginResponse"
default:
description: Generic error response.
schema:
@@ -94,10 +70,8 @@ paths:
schema:
$ref: "#/definitions/loginOauth2AuthRequest"
responses:
201:
204:
description: A successful login.
schema:
$ref: "#/definitions/loginResponse"
default:
description: Generic error response.
schema:
@@ -984,11 +958,6 @@ definitions:
type: string
secretKey:
type: string
loginResponse:
type: object
properties:
sessionId:
type: string
loginOauth2AuthRequest:
type: object
required: