From bba9377b9f7158cf25ea9b301bcb26dba5008194 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 25 Jan 2024 09:55:45 -0600 Subject: [PATCH 1/7] initial work on token revocation --- controller/controller.go | 1 + controller/resetToken.go | 49 +++ rest_client_zrok/account/account_client.go | 40 +++ .../account/reset_token_parameters.go | 146 +++++++++ .../account/reset_token_responses.go | 303 ++++++++++++++++++ rest_server_zrok/embedded_spec.go | 78 +++++ .../operations/account/reset_token.go | 133 ++++++++ .../account/reset_token_parameters.go | 74 +++++ .../account/reset_token_responses.go | 107 +++++++ .../account/reset_token_urlbuilder.go | 87 +++++ rest_server_zrok/operations/zrok_api.go | 12 + sdk/python/sdk/zrok/.swagger-codegen/VERSION | 2 +- sdk/python/sdk/zrok/requirements.txt | 1 - sdk/python/sdk/zrok/zrok_api/__init__.py | 2 + .../sdk/zrok/zrok_api/api/account_api.py | 93 ++++++ sdk/python/sdk/zrok/zrok_api/configuration.py | 9 +- .../sdk/zrok/zrok_api/models/__init__.py | 2 + .../zrok_api/models/inline_response200.py | 110 +++++++ .../zrok/zrok_api/models/reset_token_body.py | 110 +++++++ sdk/python/sdk/zrok/zrok_api/rest.py | 4 +- specs/zrok.yml | 24 ++ ui/src/api/account.js | 21 ++ .../console/detail/account/AccountDetail.js | 4 + ui/src/console/detail/account/ActionsTab.js | 34 ++ .../detail/account/actions/ResetToken.js | 29 ++ 25 files changed, 1468 insertions(+), 7 deletions(-) create mode 100644 controller/resetToken.go create mode 100644 rest_client_zrok/account/reset_token_parameters.go create mode 100644 rest_client_zrok/account/reset_token_responses.go create mode 100644 rest_server_zrok/operations/account/reset_token.go create mode 100644 rest_server_zrok/operations/account/reset_token_parameters.go create mode 100644 rest_server_zrok/operations/account/reset_token_responses.go create mode 100644 rest_server_zrok/operations/account/reset_token_urlbuilder.go create mode 100644 sdk/python/sdk/zrok/zrok_api/models/inline_response200.py create mode 100644 sdk/python/sdk/zrok/zrok_api/models/reset_token_body.py create mode 100644 ui/src/console/detail/account/ActionsTab.js create mode 100644 ui/src/console/detail/account/actions/ResetToken.js diff --git a/controller/controller.go b/controller/controller.go index 0663f995..c30d8625 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -49,6 +49,7 @@ func Run(inCfg *config.Config) error { api.AccountRegisterHandler = newRegisterHandler(cfg) api.AccountResetPasswordHandler = newResetPasswordHandler(cfg) api.AccountResetPasswordRequestHandler = newResetPasswordRequestHandler() + api.AccountResetTokenHandler = newResetTokenHandler() api.AccountVerifyHandler = newVerifyHandler() api.AdminCreateFrontendHandler = newCreateFrontendHandler() api.AdminCreateIdentityHandler = newCreateIdentityHandler() diff --git a/controller/resetToken.go b/controller/resetToken.go new file mode 100644 index 00000000..b0290ae4 --- /dev/null +++ b/controller/resetToken.go @@ -0,0 +1,49 @@ +package controller + +import ( + "github.com/go-openapi/runtime/middleware" + "github.com/openziti/zrok/rest_server_zrok/operations/account" + "github.com/sirupsen/logrus" +) + +type resetTokenHandler struct{} + +func newResetTokenHandler() *resetTokenHandler { + return &resetTokenHandler{} +} + +func (handler *resetTokenHandler) Handle(params account.ResetTokenParams) middleware.Responder { + if params.Body.EmailAddress == "" { + logrus.Error("missing email") + return account.NewResetTokenNotFound() + } + logrus.Infof("received token reset request for email '%v'", params.Body.EmailAddress) + + tx, err := str.Begin() + if err != nil { + logrus.Errorf("error starting transaction for '%v': %v", params.Body.EmailAddress, err) + return account.NewResetTokenInternalServerError() + } + defer tx.Rollback() + + a, err := str.FindAccountWithEmail(params.Body.EmailAddress, tx) + if err != nil { + logrus.Errorf("error finding account for '%v': %v", params.Body.EmailAddress, err) + return account.NewResetTokenNotFound() + } + if a.Deleted { + logrus.Errorf("account '%v' for '%v' deleted", a.Email, a.Token) + return account.NewResetTokenNotFound() + } + + // Need to create new token and invalidate all other resources + + if err := tx.Commit(); err != nil { + logrus.Errorf("error committing '%v' (%v): %v", params.Body.EmailAddress, a.Email, err) + return account.NewResetTokenInternalServerError() + } + + logrus.Infof("reset token for '%v'", a.Email) + + return account.NewResetTokenOK() +} diff --git a/rest_client_zrok/account/account_client.go b/rest_client_zrok/account/account_client.go index 3df2ebbc..991869eb 100644 --- a/rest_client_zrok/account/account_client.go +++ b/rest_client_zrok/account/account_client.go @@ -40,6 +40,8 @@ type ClientService interface { ResetPasswordRequest(params *ResetPasswordRequestParams, opts ...ClientOption) (*ResetPasswordRequestCreated, error) + ResetToken(params *ResetTokenParams, opts ...ClientOption) (*ResetTokenOK, error) + Verify(params *VerifyParams, opts ...ClientOption) (*VerifyOK, error) SetTransport(transport runtime.ClientTransport) @@ -235,6 +237,44 @@ func (a *Client) ResetPasswordRequest(params *ResetPasswordRequestParams, opts . panic(msg) } +/* +ResetToken reset token API +*/ +func (a *Client) ResetToken(params *ResetTokenParams, opts ...ClientOption) (*ResetTokenOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewResetTokenParams() + } + op := &runtime.ClientOperation{ + ID: "resetToken", + Method: "POST", + PathPattern: "/resetToken", + ProducesMediaTypes: []string{"application/zrok.v1+json"}, + ConsumesMediaTypes: []string{"application/zrok.v1+json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &ResetTokenReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*ResetTokenOK) + if ok { + return success, nil + } + // unexpected success response + // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue + msg := fmt.Sprintf("unexpected success response for resetToken: API contract not enforced by server. Client expected to get an error, but got: %T", result) + panic(msg) +} + /* Verify verify API */ diff --git a/rest_client_zrok/account/reset_token_parameters.go b/rest_client_zrok/account/reset_token_parameters.go new file mode 100644 index 00000000..a8eae203 --- /dev/null +++ b/rest_client_zrok/account/reset_token_parameters.go @@ -0,0 +1,146 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package account + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewResetTokenParams creates a new ResetTokenParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewResetTokenParams() *ResetTokenParams { + return &ResetTokenParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewResetTokenParamsWithTimeout creates a new ResetTokenParams object +// with the ability to set a timeout on a request. +func NewResetTokenParamsWithTimeout(timeout time.Duration) *ResetTokenParams { + return &ResetTokenParams{ + timeout: timeout, + } +} + +// NewResetTokenParamsWithContext creates a new ResetTokenParams object +// with the ability to set a context for a request. +func NewResetTokenParamsWithContext(ctx context.Context) *ResetTokenParams { + return &ResetTokenParams{ + Context: ctx, + } +} + +// NewResetTokenParamsWithHTTPClient creates a new ResetTokenParams object +// with the ability to set a custom HTTPClient for a request. +func NewResetTokenParamsWithHTTPClient(client *http.Client) *ResetTokenParams { + return &ResetTokenParams{ + HTTPClient: client, + } +} + +/* +ResetTokenParams contains all the parameters to send to the API endpoint + + for the reset token operation. + + Typically these are written to a http.Request. +*/ +type ResetTokenParams struct { + + // Body. + Body ResetTokenBody + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the reset token params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ResetTokenParams) WithDefaults() *ResetTokenParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the reset token params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ResetTokenParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the reset token params +func (o *ResetTokenParams) WithTimeout(timeout time.Duration) *ResetTokenParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the reset token params +func (o *ResetTokenParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the reset token params +func (o *ResetTokenParams) WithContext(ctx context.Context) *ResetTokenParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the reset token params +func (o *ResetTokenParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the reset token params +func (o *ResetTokenParams) WithHTTPClient(client *http.Client) *ResetTokenParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the reset token params +func (o *ResetTokenParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the reset token params +func (o *ResetTokenParams) WithBody(body ResetTokenBody) *ResetTokenParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the reset token params +func (o *ResetTokenParams) SetBody(body ResetTokenBody) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *ResetTokenParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/rest_client_zrok/account/reset_token_responses.go b/rest_client_zrok/account/reset_token_responses.go new file mode 100644 index 00000000..ed973e6a --- /dev/null +++ b/rest_client_zrok/account/reset_token_responses.go @@ -0,0 +1,303 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package account + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResetTokenReader is a Reader for the ResetToken structure. +type ResetTokenReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *ResetTokenReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewResetTokenOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 404: + result := NewResetTokenNotFound() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 500: + result := NewResetTokenInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + return nil, runtime.NewAPIError("[POST /resetToken] resetToken", response, response.Code()) + } +} + +// NewResetTokenOK creates a ResetTokenOK with default headers values +func NewResetTokenOK() *ResetTokenOK { + return &ResetTokenOK{} +} + +/* +ResetTokenOK describes a response with status code 200, with default header values. + +token reset +*/ +type ResetTokenOK struct { + Payload *ResetTokenOKBody +} + +// IsSuccess returns true when this reset token o k response has a 2xx status code +func (o *ResetTokenOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this reset token o k response has a 3xx status code +func (o *ResetTokenOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this reset token o k response has a 4xx status code +func (o *ResetTokenOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this reset token o k response has a 5xx status code +func (o *ResetTokenOK) IsServerError() bool { + return false +} + +// IsCode returns true when this reset token o k response a status code equal to that given +func (o *ResetTokenOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the reset token o k response +func (o *ResetTokenOK) Code() int { + return 200 +} + +func (o *ResetTokenOK) Error() string { + return fmt.Sprintf("[POST /resetToken][%d] resetTokenOK %+v", 200, o.Payload) +} + +func (o *ResetTokenOK) String() string { + return fmt.Sprintf("[POST /resetToken][%d] resetTokenOK %+v", 200, o.Payload) +} + +func (o *ResetTokenOK) GetPayload() *ResetTokenOKBody { + return o.Payload +} + +func (o *ResetTokenOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(ResetTokenOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewResetTokenNotFound creates a ResetTokenNotFound with default headers values +func NewResetTokenNotFound() *ResetTokenNotFound { + return &ResetTokenNotFound{} +} + +/* +ResetTokenNotFound describes a response with status code 404, with default header values. + +account not found +*/ +type ResetTokenNotFound struct { +} + +// IsSuccess returns true when this reset token not found response has a 2xx status code +func (o *ResetTokenNotFound) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this reset token not found response has a 3xx status code +func (o *ResetTokenNotFound) IsRedirect() bool { + return false +} + +// IsClientError returns true when this reset token not found response has a 4xx status code +func (o *ResetTokenNotFound) IsClientError() bool { + return true +} + +// IsServerError returns true when this reset token not found response has a 5xx status code +func (o *ResetTokenNotFound) IsServerError() bool { + return false +} + +// IsCode returns true when this reset token not found response a status code equal to that given +func (o *ResetTokenNotFound) IsCode(code int) bool { + return code == 404 +} + +// Code gets the status code for the reset token not found response +func (o *ResetTokenNotFound) Code() int { + return 404 +} + +func (o *ResetTokenNotFound) Error() string { + return fmt.Sprintf("[POST /resetToken][%d] resetTokenNotFound ", 404) +} + +func (o *ResetTokenNotFound) String() string { + return fmt.Sprintf("[POST /resetToken][%d] resetTokenNotFound ", 404) +} + +func (o *ResetTokenNotFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewResetTokenInternalServerError creates a ResetTokenInternalServerError with default headers values +func NewResetTokenInternalServerError() *ResetTokenInternalServerError { + return &ResetTokenInternalServerError{} +} + +/* +ResetTokenInternalServerError describes a response with status code 500, with default header values. + +internal server error +*/ +type ResetTokenInternalServerError struct { +} + +// IsSuccess returns true when this reset token internal server error response has a 2xx status code +func (o *ResetTokenInternalServerError) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this reset token internal server error response has a 3xx status code +func (o *ResetTokenInternalServerError) IsRedirect() bool { + return false +} + +// IsClientError returns true when this reset token internal server error response has a 4xx status code +func (o *ResetTokenInternalServerError) IsClientError() bool { + return false +} + +// IsServerError returns true when this reset token internal server error response has a 5xx status code +func (o *ResetTokenInternalServerError) IsServerError() bool { + return true +} + +// IsCode returns true when this reset token internal server error response a status code equal to that given +func (o *ResetTokenInternalServerError) IsCode(code int) bool { + return code == 500 +} + +// Code gets the status code for the reset token internal server error response +func (o *ResetTokenInternalServerError) Code() int { + return 500 +} + +func (o *ResetTokenInternalServerError) Error() string { + return fmt.Sprintf("[POST /resetToken][%d] resetTokenInternalServerError ", 500) +} + +func (o *ResetTokenInternalServerError) String() string { + return fmt.Sprintf("[POST /resetToken][%d] resetTokenInternalServerError ", 500) +} + +func (o *ResetTokenInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +/* +ResetTokenBody reset token body +swagger:model ResetTokenBody +*/ +type ResetTokenBody struct { + + // email address + EmailAddress string `json:"emailAddress,omitempty"` +} + +// Validate validates this reset token body +func (o *ResetTokenBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this reset token body based on context it is used +func (o *ResetTokenBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ResetTokenBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ResetTokenBody) UnmarshalBinary(b []byte) error { + var res ResetTokenBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ResetTokenOKBody reset token o k body +swagger:model ResetTokenOKBody +*/ +type ResetTokenOKBody struct { + + // token + Token string `json:"token,omitempty"` +} + +// Validate validates this reset token o k body +func (o *ResetTokenOKBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this reset token o k body based on context it is used +func (o *ResetTokenOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ResetTokenOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ResetTokenOKBody) UnmarshalBinary(b []byte) error { + var res ResetTokenOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/rest_server_zrok/embedded_spec.go b/rest_server_zrok/embedded_spec.go index 08c43fd2..d01cc2f4 100644 --- a/rest_server_zrok/embedded_spec.go +++ b/rest_server_zrok/embedded_spec.go @@ -832,6 +832,45 @@ func init() { } } }, + "/resetToken": { + "post": { + "tags": [ + "account" + ], + "operationId": "resetToken", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "properties": { + "emailAddress": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "token reset", + "schema": { + "properties": { + "token": { + "type": "string" + } + } + } + }, + "404": { + "description": "account not found" + }, + "500": { + "description": "internal server error" + } + } + } + }, "/share": { "post": { "security": [ @@ -2455,6 +2494,45 @@ func init() { } } }, + "/resetToken": { + "post": { + "tags": [ + "account" + ], + "operationId": "resetToken", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "properties": { + "emailAddress": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "token reset", + "schema": { + "properties": { + "token": { + "type": "string" + } + } + } + }, + "404": { + "description": "account not found" + }, + "500": { + "description": "internal server error" + } + } + } + }, "/share": { "post": { "security": [ diff --git a/rest_server_zrok/operations/account/reset_token.go b/rest_server_zrok/operations/account/reset_token.go new file mode 100644 index 00000000..25f7dc8f --- /dev/null +++ b/rest_server_zrok/operations/account/reset_token.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package account + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "context" + "net/http" + + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResetTokenHandlerFunc turns a function with the right signature into a reset token handler +type ResetTokenHandlerFunc func(ResetTokenParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn ResetTokenHandlerFunc) Handle(params ResetTokenParams) middleware.Responder { + return fn(params) +} + +// ResetTokenHandler interface for that can handle valid reset token params +type ResetTokenHandler interface { + Handle(ResetTokenParams) middleware.Responder +} + +// NewResetToken creates a new http.Handler for the reset token operation +func NewResetToken(ctx *middleware.Context, handler ResetTokenHandler) *ResetToken { + return &ResetToken{Context: ctx, Handler: handler} +} + +/* + ResetToken swagger:route POST /resetToken account resetToken + +ResetToken reset token API +*/ +type ResetToken struct { + Context *middleware.Context + Handler ResetTokenHandler +} + +func (o *ResetToken) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewResetTokenParams() + 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) + +} + +// ResetTokenBody reset token body +// +// swagger:model ResetTokenBody +type ResetTokenBody struct { + + // email address + EmailAddress string `json:"emailAddress,omitempty"` +} + +// Validate validates this reset token body +func (o *ResetTokenBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this reset token body based on context it is used +func (o *ResetTokenBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ResetTokenBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ResetTokenBody) UnmarshalBinary(b []byte) error { + var res ResetTokenBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +// ResetTokenOKBody reset token o k body +// +// swagger:model ResetTokenOKBody +type ResetTokenOKBody struct { + + // token + Token string `json:"token,omitempty"` +} + +// Validate validates this reset token o k body +func (o *ResetTokenOKBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this reset token o k body based on context it is used +func (o *ResetTokenOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ResetTokenOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ResetTokenOKBody) UnmarshalBinary(b []byte) error { + var res ResetTokenOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/rest_server_zrok/operations/account/reset_token_parameters.go b/rest_server_zrok/operations/account/reset_token_parameters.go new file mode 100644 index 00000000..76decd3a --- /dev/null +++ b/rest_server_zrok/operations/account/reset_token_parameters.go @@ -0,0 +1,74 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package account + +// 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/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" +) + +// NewResetTokenParams creates a new ResetTokenParams object +// +// There are no default values defined in the spec. +func NewResetTokenParams() ResetTokenParams { + + return ResetTokenParams{} +} + +// ResetTokenParams contains all the bound params for the reset token operation +// typically these are obtained from a http.Request +// +// swagger:parameters resetToken +type ResetTokenParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: body + */ + Body ResetTokenBody +} + +// 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 NewResetTokenParams() beforehand. +func (o *ResetTokenParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body ResetTokenBody + if err := route.Consumer.Consume(r.Body, &body); err != nil { + 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(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = body + } + } + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/rest_server_zrok/operations/account/reset_token_responses.go b/rest_server_zrok/operations/account/reset_token_responses.go new file mode 100644 index 00000000..f67e0e0e --- /dev/null +++ b/rest_server_zrok/operations/account/reset_token_responses.go @@ -0,0 +1,107 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package account + +// 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" +) + +// ResetTokenOKCode is the HTTP code returned for type ResetTokenOK +const ResetTokenOKCode int = 200 + +/* +ResetTokenOK token reset + +swagger:response resetTokenOK +*/ +type ResetTokenOK struct { + + /* + In: Body + */ + Payload *ResetTokenOKBody `json:"body,omitempty"` +} + +// NewResetTokenOK creates ResetTokenOK with default headers values +func NewResetTokenOK() *ResetTokenOK { + + return &ResetTokenOK{} +} + +// WithPayload adds the payload to the reset token o k response +func (o *ResetTokenOK) WithPayload(payload *ResetTokenOKBody) *ResetTokenOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the reset token o k response +func (o *ResetTokenOK) SetPayload(payload *ResetTokenOKBody) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ResetTokenOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ResetTokenNotFoundCode is the HTTP code returned for type ResetTokenNotFound +const ResetTokenNotFoundCode int = 404 + +/* +ResetTokenNotFound account not found + +swagger:response resetTokenNotFound +*/ +type ResetTokenNotFound struct { +} + +// NewResetTokenNotFound creates ResetTokenNotFound with default headers values +func NewResetTokenNotFound() *ResetTokenNotFound { + + return &ResetTokenNotFound{} +} + +// WriteResponse to the client +func (o *ResetTokenNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(404) +} + +// ResetTokenInternalServerErrorCode is the HTTP code returned for type ResetTokenInternalServerError +const ResetTokenInternalServerErrorCode int = 500 + +/* +ResetTokenInternalServerError internal server error + +swagger:response resetTokenInternalServerError +*/ +type ResetTokenInternalServerError struct { +} + +// NewResetTokenInternalServerError creates ResetTokenInternalServerError with default headers values +func NewResetTokenInternalServerError() *ResetTokenInternalServerError { + + return &ResetTokenInternalServerError{} +} + +// WriteResponse to the client +func (o *ResetTokenInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(500) +} diff --git a/rest_server_zrok/operations/account/reset_token_urlbuilder.go b/rest_server_zrok/operations/account/reset_token_urlbuilder.go new file mode 100644 index 00000000..aacabb41 --- /dev/null +++ b/rest_server_zrok/operations/account/reset_token_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package account + +// 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" +) + +// ResetTokenURL generates an URL for the reset token operation +type ResetTokenURL 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 *ResetTokenURL) WithBasePath(bp string) *ResetTokenURL { + 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 *ResetTokenURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *ResetTokenURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/resetToken" + + _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 *ResetTokenURL) 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 *ResetTokenURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *ResetTokenURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on ResetTokenURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on ResetTokenURL") + } + + 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 *ResetTokenURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/rest_server_zrok/operations/zrok_api.go b/rest_server_zrok/operations/zrok_api.go index 9b508804..d64baa8b 100644 --- a/rest_server_zrok/operations/zrok_api.go +++ b/rest_server_zrok/operations/zrok_api.go @@ -115,6 +115,9 @@ func NewZrokAPI(spec *loads.Document) *ZrokAPI { AccountResetPasswordRequestHandler: account.ResetPasswordRequestHandlerFunc(func(params account.ResetPasswordRequestParams) middleware.Responder { return middleware.NotImplemented("operation account.ResetPasswordRequest has not yet been implemented") }), + AccountResetTokenHandler: account.ResetTokenHandlerFunc(func(params account.ResetTokenParams) middleware.Responder { + return middleware.NotImplemented("operation account.ResetToken has not yet been implemented") + }), ShareShareHandler: share.ShareHandlerFunc(func(params share.ShareParams, principal *rest_model_zrok.Principal) middleware.Responder { return middleware.NotImplemented("operation share.Share has not yet been implemented") }), @@ -230,6 +233,8 @@ type ZrokAPI struct { AccountResetPasswordHandler account.ResetPasswordHandler // AccountResetPasswordRequestHandler sets the operation handler for the reset password request operation AccountResetPasswordRequestHandler account.ResetPasswordRequestHandler + // AccountResetTokenHandler sets the operation handler for the reset token operation + AccountResetTokenHandler account.ResetTokenHandler // ShareShareHandler sets the operation handler for the share operation ShareShareHandler share.ShareHandler // ShareUnaccessHandler sets the operation handler for the unaccess operation @@ -391,6 +396,9 @@ func (o *ZrokAPI) Validate() error { if o.AccountResetPasswordRequestHandler == nil { unregistered = append(unregistered, "account.ResetPasswordRequestHandler") } + if o.AccountResetTokenHandler == nil { + unregistered = append(unregistered, "account.ResetTokenHandler") + } if o.ShareShareHandler == nil { unregistered = append(unregistered, "share.ShareHandler") } @@ -602,6 +610,10 @@ func (o *ZrokAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } + o.handlers["POST"]["/resetToken"] = account.NewResetToken(o.context, o.AccountResetTokenHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } o.handlers["POST"]["/share"] = share.NewShare(o.context, o.ShareShareHandler) if o.handlers["DELETE"] == nil { o.handlers["DELETE"] = make(map[string]http.Handler) diff --git a/sdk/python/sdk/zrok/.swagger-codegen/VERSION b/sdk/python/sdk/zrok/.swagger-codegen/VERSION index b262b4de..97e616f0 100644 --- a/sdk/python/sdk/zrok/.swagger-codegen/VERSION +++ b/sdk/python/sdk/zrok/.swagger-codegen/VERSION @@ -1 +1 @@ -3.0.51 \ No newline at end of file +3.0.52 \ No newline at end of file diff --git a/sdk/python/sdk/zrok/requirements.txt b/sdk/python/sdk/zrok/requirements.txt index b57b1448..bafdc075 100644 --- a/sdk/python/sdk/zrok/requirements.txt +++ b/sdk/python/sdk/zrok/requirements.txt @@ -3,4 +3,3 @@ six >= 1.10 python_dateutil >= 2.5.3 setuptools >= 21.0.0 urllib3 >= 1.15.1 -openziti >= 0.8.1 \ No newline at end of file diff --git a/sdk/python/sdk/zrok/zrok_api/__init__.py b/sdk/python/sdk/zrok/zrok_api/__init__.py index fd150b7b..158ae3cf 100644 --- a/sdk/python/sdk/zrok/zrok_api/__init__.py +++ b/sdk/python/sdk/zrok/zrok_api/__init__.py @@ -41,6 +41,7 @@ from zrok_api.models.error_message import ErrorMessage from zrok_api.models.frontend import Frontend from zrok_api.models.frontends import Frontends from zrok_api.models.identity_body import IdentityBody +from zrok_api.models.inline_response200 import InlineResponse200 from zrok_api.models.inline_response201 import InlineResponse201 from zrok_api.models.invite_request import InviteRequest from zrok_api.models.invite_token_generate_request import InviteTokenGenerateRequest @@ -57,6 +58,7 @@ from zrok_api.models.register_request import RegisterRequest from zrok_api.models.register_response import RegisterResponse from zrok_api.models.reset_password_request import ResetPasswordRequest from zrok_api.models.reset_password_request_body import ResetPasswordRequestBody +from zrok_api.models.reset_token_body import ResetTokenBody from zrok_api.models.share import Share from zrok_api.models.share_request import ShareRequest from zrok_api.models.share_response import ShareResponse diff --git a/sdk/python/sdk/zrok/zrok_api/api/account_api.py b/sdk/python/sdk/zrok/zrok_api/api/account_api.py index 4682090f..4e2c91ea 100644 --- a/sdk/python/sdk/zrok/zrok_api/api/account_api.py +++ b/sdk/python/sdk/zrok/zrok_api/api/account_api.py @@ -493,6 +493,99 @@ class AccountApi(object): _request_timeout=params.get('_request_timeout'), collection_formats=collection_formats) + def reset_token(self, **kwargs): # noqa: E501 + """reset_token # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.reset_token(async_req=True) + >>> result = thread.get() + + :param async_req bool + :param ResetTokenBody body: + :return: InlineResponse200 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.reset_token_with_http_info(**kwargs) # noqa: E501 + else: + (data) = self.reset_token_with_http_info(**kwargs) # noqa: E501 + return data + + def reset_token_with_http_info(self, **kwargs): # noqa: E501 + """reset_token # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.reset_token_with_http_info(async_req=True) + >>> result = thread.get() + + :param async_req bool + :param ResetTokenBody body: + :return: InlineResponse200 + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['body'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method reset_token" % key + ) + params[key] = val + del params['kwargs'] + + collection_formats = {} + + path_params = {} + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in params: + body_params = params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/zrok.v1+json']) # noqa: E501 + + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + ['application/zrok.v1+json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/resetToken', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse200', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + def verify(self, **kwargs): # noqa: E501 """verify # noqa: E501 diff --git a/sdk/python/sdk/zrok/zrok_api/configuration.py b/sdk/python/sdk/zrok/zrok_api/configuration.py index 9fd585c4..35e5b1f6 100644 --- a/sdk/python/sdk/zrok/zrok_api/configuration.py +++ b/sdk/python/sdk/zrok/zrok_api/configuration.py @@ -219,9 +219,12 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)): :return: The token for basic HTTP authentication. """ - return urllib3.util.make_headers( - basic_auth=self.username + ':' + self.password - ).get('authorization') + token = "" + if self.username or self.password: + token = urllib3.util.make_headers( + basic_auth=self.username + ':' + self.password + ).get('authorization') + return token def auth_settings(self): """Gets Auth Settings dict for api client. diff --git a/sdk/python/sdk/zrok/zrok_api/models/__init__.py b/sdk/python/sdk/zrok/zrok_api/models/__init__.py index dc6bd93d..71b0fa1a 100644 --- a/sdk/python/sdk/zrok/zrok_api/models/__init__.py +++ b/sdk/python/sdk/zrok/zrok_api/models/__init__.py @@ -31,6 +31,7 @@ from zrok_api.models.error_message import ErrorMessage from zrok_api.models.frontend import Frontend from zrok_api.models.frontends import Frontends from zrok_api.models.identity_body import IdentityBody +from zrok_api.models.inline_response200 import InlineResponse200 from zrok_api.models.inline_response201 import InlineResponse201 from zrok_api.models.invite_request import InviteRequest from zrok_api.models.invite_token_generate_request import InviteTokenGenerateRequest @@ -47,6 +48,7 @@ from zrok_api.models.register_request import RegisterRequest from zrok_api.models.register_response import RegisterResponse from zrok_api.models.reset_password_request import ResetPasswordRequest from zrok_api.models.reset_password_request_body import ResetPasswordRequestBody +from zrok_api.models.reset_token_body import ResetTokenBody from zrok_api.models.share import Share from zrok_api.models.share_request import ShareRequest from zrok_api.models.share_response import ShareResponse diff --git a/sdk/python/sdk/zrok/zrok_api/models/inline_response200.py b/sdk/python/sdk/zrok/zrok_api/models/inline_response200.py new file mode 100644 index 00000000..a972a3fb --- /dev/null +++ b/sdk/python/sdk/zrok/zrok_api/models/inline_response200.py @@ -0,0 +1,110 @@ +# coding: utf-8 + +""" + zrok + + zrok client access # noqa: E501 + + OpenAPI spec version: 0.3.0 + + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class InlineResponse200(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'token': 'str' + } + + attribute_map = { + 'token': 'token' + } + + def __init__(self, token=None): # noqa: E501 + """InlineResponse200 - a model defined in Swagger""" # noqa: E501 + self._token = None + self.discriminator = None + if token is not None: + self.token = token + + @property + def token(self): + """Gets the token of this InlineResponse200. # noqa: E501 + + + :return: The token of this InlineResponse200. # noqa: E501 + :rtype: str + """ + return self._token + + @token.setter + def token(self, token): + """Sets the token of this InlineResponse200. + + + :param token: The token of this InlineResponse200. # noqa: E501 + :type: str + """ + + self._token = token + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(InlineResponse200, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse200): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/sdk/python/sdk/zrok/zrok_api/models/reset_token_body.py b/sdk/python/sdk/zrok/zrok_api/models/reset_token_body.py new file mode 100644 index 00000000..a7004ed3 --- /dev/null +++ b/sdk/python/sdk/zrok/zrok_api/models/reset_token_body.py @@ -0,0 +1,110 @@ +# coding: utf-8 + +""" + zrok + + zrok client access # noqa: E501 + + OpenAPI spec version: 0.3.0 + + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class ResetTokenBody(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'email_address': 'str' + } + + attribute_map = { + 'email_address': 'emailAddress' + } + + def __init__(self, email_address=None): # noqa: E501 + """ResetTokenBody - a model defined in Swagger""" # noqa: E501 + self._email_address = None + self.discriminator = None + if email_address is not None: + self.email_address = email_address + + @property + def email_address(self): + """Gets the email_address of this ResetTokenBody. # noqa: E501 + + + :return: The email_address of this ResetTokenBody. # noqa: E501 + :rtype: str + """ + return self._email_address + + @email_address.setter + def email_address(self, email_address): + """Sets the email_address of this ResetTokenBody. + + + :param email_address: The email_address of this ResetTokenBody. # noqa: E501 + :type: str + """ + + self._email_address = email_address + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(ResetTokenBody, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ResetTokenBody): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/sdk/python/sdk/zrok/zrok_api/rest.py b/sdk/python/sdk/zrok/zrok_api/rest.py index 21bc091b..311a61cb 100644 --- a/sdk/python/sdk/zrok/zrok_api/rest.py +++ b/sdk/python/sdk/zrok/zrok_api/rest.py @@ -42,11 +42,11 @@ class RESTResponse(io.IOBase): def getheaders(self): """Returns a dictionary of the response headers.""" - return self.urllib3_response.getheaders() + return self.urllib3_response.headers def getheader(self, name, default=None): """Returns a given response header.""" - return self.urllib3_response.getheader(name, default) + return self.urllib3_response.headers.get(name, default) class RESTClientObject(object): diff --git a/specs/zrok.yml b/specs/zrok.yml index 275e7a73..a64d3696 100644 --- a/specs/zrok.yml +++ b/specs/zrok.yml @@ -121,6 +121,30 @@ paths: 500: description: internal server error + /resetToken: + post: + tags: + - account + operationId: resetToken + parameters: + - name: body + in: body + schema: + properties: + emailAddress: + type: string + responses: + 200: + description: token reset + schema: + properties: + token: + type: string + 404: + description: account not found + 500: + description: internal server error + /verify: post: tags: diff --git a/ui/src/api/account.js b/ui/src/api/account.js index 4c1f52fd..bf1901a4 100644 --- a/ui/src/api/account.js +++ b/ui/src/api/account.js @@ -77,6 +77,21 @@ export function resetPasswordRequest(options) { return gateway.request(resetPasswordRequestOperation, parameters) } +/** + * @param {object} options Optional options + * @param {object} [options.body] + * @return {Promise} token reset + */ +export function resetToken(options) { + if (!options) options = {} + const parameters = { + body: { + body: options.body + } + } + return gateway.request(resetTokenOperation, parameters) +} + /** * @param {object} options Optional options * @param {module:types.verifyRequest} [options.body] @@ -122,6 +137,12 @@ const resetPasswordRequestOperation = { method: 'post' } +const resetTokenOperation = { + path: '/resetToken', + contentTypes: ['application/zrok.v1+json'], + method: 'post' +} + const verifyOperation = { path: '/verify', contentTypes: ['application/zrok.v1+json'], diff --git a/ui/src/console/detail/account/AccountDetail.js b/ui/src/console/detail/account/AccountDetail.js index 5ca50391..07736825 100644 --- a/ui/src/console/detail/account/AccountDetail.js +++ b/ui/src/console/detail/account/AccountDetail.js @@ -6,6 +6,7 @@ import SecretToggle from "../../SecretToggle"; import React from "react"; import MetricsTab from "./MetricsTab"; import EnvironmentsTab from "./EnvironmentsTab"; +import ActionsTab from "./ActionsTab"; const AccountDetail = (props) => { const customProperties = { @@ -25,6 +26,9 @@ const AccountDetail = (props) => { + + + ); diff --git a/ui/src/console/detail/account/ActionsTab.js b/ui/src/console/detail/account/ActionsTab.js new file mode 100644 index 00000000..2ca9a935 --- /dev/null +++ b/ui/src/console/detail/account/ActionsTab.js @@ -0,0 +1,34 @@ +import React, {useState} from "react"; +import ResetToken from "./actions/ResetToken"; + +const ActionsTab = (props) => { + const [actionState, setActionState] = useState("menu") + + const [showResetTokenModal, setShowResetTokenModal] = useState(false); + const openResetTokenModal = () => setShowResetTokenModal(true); + const closeResetTokenModal = () => setShowResetTokenModal(false); + + let defaultActionsTabComponent = ( +
+ + +
+ ) + const returnState = () => { + setActionState("menu") + } + + const renderActions = () => { + switch (actionState) { + default: + return defaultActionsTabComponent + } + } + + return ( + renderActions() + ) +} + +export default ActionsTab; + diff --git a/ui/src/console/detail/account/actions/ResetToken.js b/ui/src/console/detail/account/actions/ResetToken.js new file mode 100644 index 00000000..087aae79 --- /dev/null +++ b/ui/src/console/detail/account/actions/ResetToken.js @@ -0,0 +1,29 @@ +import Modal from "react-bootstrap/Modal"; +import { Button, Container, Form, Row } from "react-bootstrap"; +import * as account from "../../../../api/account"; + +const ResetToken = (props) => { + + let resetToken = () => { + console.log("I should reset my token") + account.resetToken({ body: { "emailAddress": props.user.email } }).then(resp => { + console.log(resp) + }).catch(err => { + console.log("err", err); + }); + } + + return ( +
+ + Are you Sure? + + TEST + + + +
+ ) +} + +export default ResetToken; \ No newline at end of file From 6a29ac0117273e8c764076f7d7f716309b3479de Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 31 Jan 2024 14:35:28 -0600 Subject: [PATCH 2/7] reset token updates --- controller/resetToken.go | 31 ++++++++++++++++++- controller/store/environment.go | 20 ++++++++++++ controller/store/frontend.go | 20 ++++++++++++ controller/store/password_reset_request.go | 12 +++++++ controller/store/share.go | 20 ++++++++++++ ui/src/App.js | 19 +++++++++--- .../detail/account/actions/ResetToken.js | 18 +++++++++-- ui/src/console/login/Login.js | 1 + 8 files changed, 132 insertions(+), 9 deletions(-) diff --git a/controller/resetToken.go b/controller/resetToken.go index b0290ae4..743c86a4 100644 --- a/controller/resetToken.go +++ b/controller/resetToken.go @@ -37,6 +37,35 @@ func (handler *resetTokenHandler) Handle(params account.ResetTokenParams) middle } // Need to create new token and invalidate all other resources + token, err := createToken() + if err != nil { + logrus.Errorf("error creating token for request '%v': %v", params.Body.EmailAddress, err) + return account.NewResetTokenInternalServerError() + } + + a.Token = token + + if _, err := str.UpdateAccount(a, tx); err != nil { + logrus.Errorf("error updating account for request '%v': %v", params.Body.EmailAddress, err) + return account.NewResetTokenInternalServerError() + } + + if err := str.DeletePasswordResetRequestByAccountId(a.Id, tx); err != nil { + logrus.Errorf("error deleting password reset requests for request '%v', but continuing on: %v", params.Body.EmailAddress, err) + } + + environmentIds, err := str.DeleteEnvironmentByAccountID(a.Id, tx) + if err != nil { + logrus.Errorf("error deleting environments for request '%v', but continuing on: %v", params.Body.EmailAddress, err) + } + + if err := str.DeleteFrontendsByEnvironmentIds(tx, environmentIds...); err != nil { + logrus.Errorf("error deleting frontends for request '%v', but continuing on: %v", params.Body.EmailAddress, err) + } + + if err := str.DeleteSharesByEnvironmentIds(tx, environmentIds...); err != nil { + logrus.Errorf("error deleting shares for request '%v', but continuing on: %v", params.Body.EmailAddress, err) + } if err := tx.Commit(); err != nil { logrus.Errorf("error committing '%v' (%v): %v", params.Body.EmailAddress, a.Email, err) @@ -45,5 +74,5 @@ func (handler *resetTokenHandler) Handle(params account.ResetTokenParams) middle logrus.Infof("reset token for '%v'", a.Email) - return account.NewResetTokenOK() + return account.NewResetTokenOK().WithPayload(&account.ResetTokenOKBody{Token: token}) } diff --git a/controller/store/environment.go b/controller/store/environment.go index 4ee783a3..c4e6f93f 100644 --- a/controller/store/environment.go +++ b/controller/store/environment.go @@ -82,3 +82,23 @@ func (str *Store) DeleteEnvironment(id int, tx *sqlx.Tx) error { } return nil } + +func (str *Store) DeleteEnvironmentByAccountID(accountId int, tx *sqlx.Tx) ([]int, error) { + stmt, err := tx.Prepare("update environments set updated_at = current_timestamp, deleted = true where account_id = $1 returning id") + if err != nil { + return nil, errors.Wrap(err, "error preparing environments delete by account_id statement") + } + rows, err := stmt.Query(accountId) + if err != nil { + return nil, errors.Wrap(err, "error executing environments delete by account_id statement") + } + var is []int + for rows.Next() { + var i int + if err := rows.Scan(&i); err != nil { + return nil, errors.Wrap(err, "error scanning environment id") + } + is = append(is, i) + } + return is, nil +} diff --git a/controller/store/frontend.go b/controller/store/frontend.go index 8d34145e..88b59050 100644 --- a/controller/store/frontend.go +++ b/controller/store/frontend.go @@ -1,8 +1,10 @@ package store import ( + "fmt" "github.com/jmoiron/sqlx" "github.com/pkg/errors" + "strings" ) type Frontend struct { @@ -146,3 +148,21 @@ func (str *Store) DeleteFrontend(id int, tx *sqlx.Tx) error { } return nil } + +func (str *Store) DeleteFrontendsByEnvironmentIds(tx *sqlx.Tx, environmentIds ...int) error { + queryStrs := make([]string, 0, len(environmentIds)) + queryVals := make([]interface{}, 0, len(environmentIds)) + for i, v := range environmentIds { + queryStrs = append(queryStrs, fmt.Sprintf("$%d", i)) + queryVals = append(queryVals, v) + } + stmt, err := tx.Prepare(fmt.Sprintf("update frontends set updated_at = current_timestamp, deleted = true where environment_id in (%s)", strings.Join(queryStrs, ","))) + if err != nil { + return errors.Wrap(err, "error preparing frontends delete by environment_id statement") + } + _, err = stmt.Exec(queryVals...) + if err != nil { + return errors.Wrap(err, "error executing frontends delete by environment_id statement") + } + return nil +} diff --git a/controller/store/password_reset_request.go b/controller/store/password_reset_request.go index a6a7b60d..5a98cfb1 100644 --- a/controller/store/password_reset_request.go +++ b/controller/store/password_reset_request.go @@ -98,3 +98,15 @@ func (str *Store) DeleteMultiplePasswordResetRequests(ids []int, tx *sqlx.Tx) er } return nil } + +func (str *Store) DeletePasswordResetRequestByAccountId(accountId int, tx *sqlx.Tx) error { + stmt, err := tx.Prepare("update password_reset_requests set updated_at = current_timestamp, deleted = true where account_id = $1") + if err != nil { + return errors.Wrap(err, "error preparing password_reset_requests by account_id delete statement") + } + _, err = stmt.Exec(accountId) + if err != nil { + return errors.Wrap(err, "error executing password_reset_requests by account_id delete statement") + } + return nil +} diff --git a/controller/store/share.go b/controller/store/share.go index 8161fc2d..2f3f5496 100644 --- a/controller/store/share.go +++ b/controller/store/share.go @@ -1,8 +1,10 @@ package store import ( + "fmt" "github.com/jmoiron/sqlx" "github.com/pkg/errors" + "strings" ) type Share struct { @@ -111,3 +113,21 @@ func (str *Store) DeleteShare(id int, tx *sqlx.Tx) error { } return nil } + +func (str *Store) DeleteSharesByEnvironmentIds(tx *sqlx.Tx, environmentIds ...int) error { + queryStrs := make([]string, 0, len(environmentIds)) + queryVals := make([]interface{}, 0, len(environmentIds)) + for i, v := range environmentIds { + queryStrs = append(queryStrs, fmt.Sprintf("$%d", i)) + queryVals = append(queryVals, v) + } + stmt, err := tx.Prepare(fmt.Sprintf("update shares set updated_at = current_timestamp, deleted = true where environment_id in (%s)", strings.Join(queryStrs, ","))) + if err != nil { + return errors.Wrap(err, "error preparing Shares delete by environment_id statement") + } + _, err = stmt.Exec(queryVals...) + if err != nil { + return errors.Wrap(err, "error executing Shares delete by environment_id statement") + } + return nil +} diff --git a/ui/src/App.js b/ui/src/App.js index aed64f78..a7d14e2d 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -9,12 +9,21 @@ const App = () => { const [user, setUser] = useState(); useEffect(() => { - const localUser = localStorage.getItem("user"); - if(localUser) { - setUser(JSON.parse(localUser)); - console.log("reloaded user", localUser); + function checkUserData() { + const localUser = localStorage.getItem("user"); + if(localUser) { + console.log(localUser) + setUser(JSON.parse(localUser)); + console.log("reloaded user", localUser); + } } - }, []); + + document.addEventListener('storage', checkUserData) + + return () => { + document.removeEventListener('storage', checkUserData) + } + }, []); const logout = () => { setUser(null); diff --git a/ui/src/console/detail/account/actions/ResetToken.js b/ui/src/console/detail/account/actions/ResetToken.js index 087aae79..8ff9baae 100644 --- a/ui/src/console/detail/account/actions/ResetToken.js +++ b/ui/src/console/detail/account/actions/ResetToken.js @@ -8,18 +8,30 @@ const ResetToken = (props) => { console.log("I should reset my token") account.resetToken({ body: { "emailAddress": props.user.email } }).then(resp => { console.log(resp) + let user = JSON.parse(localStorage.getItem('user')) + localStorage.setItem('user', JSON.stringify({ + "email": user.email, + "token": resp.data.token + })); + document.dispatchEvent(new Event('storage')) }).catch(err => { console.log("err", err); }); + props.onHide(); } return (
- Are you Sure? + WARNING - Are you Sure? - TEST - +
+ Reseting your token will remove all environments, frontends, and shares you've created. +
+
+ + +
diff --git a/ui/src/console/login/Login.js b/ui/src/console/login/Login.js index 454cb655..0a9591ea 100644 --- a/ui/src/console/login/Login.js +++ b/ui/src/console/login/Login.js @@ -40,6 +40,7 @@ const Login = (props) => { localStorage.setItem('user', JSON.stringify(user)) console.log(user) console.log('login succeeded', resp) + document.dispatchEvent(new Event('storage')) } else { console.log('login failed') setMessage(errorMessage); From e764103a68feee787d9a8481a1e358dc50a8fc29 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 14 Feb 2024 13:49:31 -0600 Subject: [PATCH 3/7] fix up js linting --- ui/src/console/detail/account/ActionsTab.js | 3 --- ui/src/console/detail/account/actions/ResetToken.js | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ui/src/console/detail/account/ActionsTab.js b/ui/src/console/detail/account/ActionsTab.js index 2ca9a935..0a930812 100644 --- a/ui/src/console/detail/account/ActionsTab.js +++ b/ui/src/console/detail/account/ActionsTab.js @@ -14,9 +14,6 @@ const ActionsTab = (props) => { ) - const returnState = () => { - setActionState("menu") - } const renderActions = () => { switch (actionState) { diff --git a/ui/src/console/detail/account/actions/ResetToken.js b/ui/src/console/detail/account/actions/ResetToken.js index 8ff9baae..8ad02271 100644 --- a/ui/src/console/detail/account/actions/ResetToken.js +++ b/ui/src/console/detail/account/actions/ResetToken.js @@ -1,5 +1,5 @@ import Modal from "react-bootstrap/Modal"; -import { Button, Container, Form, Row } from "react-bootstrap"; +import { Button } from "react-bootstrap"; import * as account from "../../../../api/account"; const ResetToken = (props) => { From 1eac1ad94198d3c16ceb14c577249f052a151f92 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 15 Feb 2024 01:00:05 -0600 Subject: [PATCH 4/7] removed some deletes after token resetting --- controller/resetToken.go | 22 +----- controller/store/environment.go | 20 ----- controller/store/frontend.go | 20 ----- controller/store/password_reset_request.go | 12 --- controller/store/share.go | 20 ----- rest_client_zrok/account/account_client.go | 5 +- rest_server_zrok/embedded_spec.go | 10 +++ .../operations/account/reset_token.go | 25 +++++-- rest_server_zrok/operations/zrok_api.go | 2 +- .../sdk/zrok/zrok_api/api/account_api.py | 2 +- specs/zrok.yml | 2 + ui/src/api/account.js | 7 +- .../detail/account/actions/ResetToken.js | 74 ++++++++++++++++--- 13 files changed, 109 insertions(+), 112 deletions(-) diff --git a/controller/resetToken.go b/controller/resetToken.go index 743c86a4..38d9dfa3 100644 --- a/controller/resetToken.go +++ b/controller/resetToken.go @@ -2,6 +2,7 @@ package controller import ( "github.com/go-openapi/runtime/middleware" + "github.com/openziti/zrok/rest_model_zrok" "github.com/openziti/zrok/rest_server_zrok/operations/account" "github.com/sirupsen/logrus" ) @@ -12,7 +13,7 @@ func newResetTokenHandler() *resetTokenHandler { return &resetTokenHandler{} } -func (handler *resetTokenHandler) Handle(params account.ResetTokenParams) middleware.Responder { +func (handler *resetTokenHandler) Handle(params account.ResetTokenParams, principal *rest_model_zrok.Principal) middleware.Responder { if params.Body.EmailAddress == "" { logrus.Error("missing email") return account.NewResetTokenNotFound() @@ -37,7 +38,7 @@ func (handler *resetTokenHandler) Handle(params account.ResetTokenParams) middle } // Need to create new token and invalidate all other resources - token, err := createToken() + token, err := CreateToken() if err != nil { logrus.Errorf("error creating token for request '%v': %v", params.Body.EmailAddress, err) return account.NewResetTokenInternalServerError() @@ -50,23 +51,6 @@ func (handler *resetTokenHandler) Handle(params account.ResetTokenParams) middle return account.NewResetTokenInternalServerError() } - if err := str.DeletePasswordResetRequestByAccountId(a.Id, tx); err != nil { - logrus.Errorf("error deleting password reset requests for request '%v', but continuing on: %v", params.Body.EmailAddress, err) - } - - environmentIds, err := str.DeleteEnvironmentByAccountID(a.Id, tx) - if err != nil { - logrus.Errorf("error deleting environments for request '%v', but continuing on: %v", params.Body.EmailAddress, err) - } - - if err := str.DeleteFrontendsByEnvironmentIds(tx, environmentIds...); err != nil { - logrus.Errorf("error deleting frontends for request '%v', but continuing on: %v", params.Body.EmailAddress, err) - } - - if err := str.DeleteSharesByEnvironmentIds(tx, environmentIds...); err != nil { - logrus.Errorf("error deleting shares for request '%v', but continuing on: %v", params.Body.EmailAddress, err) - } - if err := tx.Commit(); err != nil { logrus.Errorf("error committing '%v' (%v): %v", params.Body.EmailAddress, a.Email, err) return account.NewResetTokenInternalServerError() diff --git a/controller/store/environment.go b/controller/store/environment.go index c4e6f93f..4ee783a3 100644 --- a/controller/store/environment.go +++ b/controller/store/environment.go @@ -82,23 +82,3 @@ func (str *Store) DeleteEnvironment(id int, tx *sqlx.Tx) error { } return nil } - -func (str *Store) DeleteEnvironmentByAccountID(accountId int, tx *sqlx.Tx) ([]int, error) { - stmt, err := tx.Prepare("update environments set updated_at = current_timestamp, deleted = true where account_id = $1 returning id") - if err != nil { - return nil, errors.Wrap(err, "error preparing environments delete by account_id statement") - } - rows, err := stmt.Query(accountId) - if err != nil { - return nil, errors.Wrap(err, "error executing environments delete by account_id statement") - } - var is []int - for rows.Next() { - var i int - if err := rows.Scan(&i); err != nil { - return nil, errors.Wrap(err, "error scanning environment id") - } - is = append(is, i) - } - return is, nil -} diff --git a/controller/store/frontend.go b/controller/store/frontend.go index 88b59050..8d34145e 100644 --- a/controller/store/frontend.go +++ b/controller/store/frontend.go @@ -1,10 +1,8 @@ package store import ( - "fmt" "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "strings" ) type Frontend struct { @@ -148,21 +146,3 @@ func (str *Store) DeleteFrontend(id int, tx *sqlx.Tx) error { } return nil } - -func (str *Store) DeleteFrontendsByEnvironmentIds(tx *sqlx.Tx, environmentIds ...int) error { - queryStrs := make([]string, 0, len(environmentIds)) - queryVals := make([]interface{}, 0, len(environmentIds)) - for i, v := range environmentIds { - queryStrs = append(queryStrs, fmt.Sprintf("$%d", i)) - queryVals = append(queryVals, v) - } - stmt, err := tx.Prepare(fmt.Sprintf("update frontends set updated_at = current_timestamp, deleted = true where environment_id in (%s)", strings.Join(queryStrs, ","))) - if err != nil { - return errors.Wrap(err, "error preparing frontends delete by environment_id statement") - } - _, err = stmt.Exec(queryVals...) - if err != nil { - return errors.Wrap(err, "error executing frontends delete by environment_id statement") - } - return nil -} diff --git a/controller/store/password_reset_request.go b/controller/store/password_reset_request.go index 5a98cfb1..a6a7b60d 100644 --- a/controller/store/password_reset_request.go +++ b/controller/store/password_reset_request.go @@ -98,15 +98,3 @@ func (str *Store) DeleteMultiplePasswordResetRequests(ids []int, tx *sqlx.Tx) er } return nil } - -func (str *Store) DeletePasswordResetRequestByAccountId(accountId int, tx *sqlx.Tx) error { - stmt, err := tx.Prepare("update password_reset_requests set updated_at = current_timestamp, deleted = true where account_id = $1") - if err != nil { - return errors.Wrap(err, "error preparing password_reset_requests by account_id delete statement") - } - _, err = stmt.Exec(accountId) - if err != nil { - return errors.Wrap(err, "error executing password_reset_requests by account_id delete statement") - } - return nil -} diff --git a/controller/store/share.go b/controller/store/share.go index 2f3f5496..8161fc2d 100644 --- a/controller/store/share.go +++ b/controller/store/share.go @@ -1,10 +1,8 @@ package store import ( - "fmt" "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "strings" ) type Share struct { @@ -113,21 +111,3 @@ func (str *Store) DeleteShare(id int, tx *sqlx.Tx) error { } return nil } - -func (str *Store) DeleteSharesByEnvironmentIds(tx *sqlx.Tx, environmentIds ...int) error { - queryStrs := make([]string, 0, len(environmentIds)) - queryVals := make([]interface{}, 0, len(environmentIds)) - for i, v := range environmentIds { - queryStrs = append(queryStrs, fmt.Sprintf("$%d", i)) - queryVals = append(queryVals, v) - } - stmt, err := tx.Prepare(fmt.Sprintf("update shares set updated_at = current_timestamp, deleted = true where environment_id in (%s)", strings.Join(queryStrs, ","))) - if err != nil { - return errors.Wrap(err, "error preparing Shares delete by environment_id statement") - } - _, err = stmt.Exec(queryVals...) - if err != nil { - return errors.Wrap(err, "error executing Shares delete by environment_id statement") - } - return nil -} diff --git a/rest_client_zrok/account/account_client.go b/rest_client_zrok/account/account_client.go index 991869eb..83049a8b 100644 --- a/rest_client_zrok/account/account_client.go +++ b/rest_client_zrok/account/account_client.go @@ -40,7 +40,7 @@ type ClientService interface { ResetPasswordRequest(params *ResetPasswordRequestParams, opts ...ClientOption) (*ResetPasswordRequestCreated, error) - ResetToken(params *ResetTokenParams, opts ...ClientOption) (*ResetTokenOK, error) + ResetToken(params *ResetTokenParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ResetTokenOK, error) Verify(params *VerifyParams, opts ...ClientOption) (*VerifyOK, error) @@ -240,7 +240,7 @@ func (a *Client) ResetPasswordRequest(params *ResetPasswordRequestParams, opts . /* ResetToken reset token API */ -func (a *Client) ResetToken(params *ResetTokenParams, opts ...ClientOption) (*ResetTokenOK, error) { +func (a *Client) ResetToken(params *ResetTokenParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ResetTokenOK, error) { // TODO: Validate the params before sending if params == nil { params = NewResetTokenParams() @@ -254,6 +254,7 @@ func (a *Client) ResetToken(params *ResetTokenParams, opts ...ClientOption) (*Re Schemes: []string{"http"}, Params: params, Reader: &ResetTokenReader{formats: a.formats}, + AuthInfo: authInfo, Context: params.Context, Client: params.HTTPClient, } diff --git a/rest_server_zrok/embedded_spec.go b/rest_server_zrok/embedded_spec.go index ab549645..c2a6856d 100644 --- a/rest_server_zrok/embedded_spec.go +++ b/rest_server_zrok/embedded_spec.go @@ -834,6 +834,11 @@ func init() { }, "/resetToken": { "post": { + "security": [ + { + "key": [] + } + ], "tags": [ "account" ], @@ -2497,6 +2502,11 @@ func init() { }, "/resetToken": { "post": { + "security": [ + { + "key": [] + } + ], "tags": [ "account" ], diff --git a/rest_server_zrok/operations/account/reset_token.go b/rest_server_zrok/operations/account/reset_token.go index 25f7dc8f..1c278616 100644 --- a/rest_server_zrok/operations/account/reset_token.go +++ b/rest_server_zrok/operations/account/reset_token.go @@ -12,19 +12,21 @@ import ( "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" + + "github.com/openziti/zrok/rest_model_zrok" ) // ResetTokenHandlerFunc turns a function with the right signature into a reset token handler -type ResetTokenHandlerFunc func(ResetTokenParams) middleware.Responder +type ResetTokenHandlerFunc func(ResetTokenParams, *rest_model_zrok.Principal) middleware.Responder // Handle executing the request and returning a response -func (fn ResetTokenHandlerFunc) Handle(params ResetTokenParams) middleware.Responder { - return fn(params) +func (fn ResetTokenHandlerFunc) Handle(params ResetTokenParams, principal *rest_model_zrok.Principal) middleware.Responder { + return fn(params, principal) } // ResetTokenHandler interface for that can handle valid reset token params type ResetTokenHandler interface { - Handle(ResetTokenParams) middleware.Responder + Handle(ResetTokenParams, *rest_model_zrok.Principal) middleware.Responder } // NewResetToken creates a new http.Handler for the reset token operation @@ -48,12 +50,25 @@ func (o *ResetToken) ServeHTTP(rw http.ResponseWriter, r *http.Request) { *r = *rCtx } var Params = NewResetTokenParams() + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + *r = *aCtx + } + var principal *rest_model_zrok.Principal + if uprinc != nil { + principal = uprinc.(*rest_model_zrok.Principal) // this is really a rest_model_zrok.Principal, I promise + } + 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 + res := o.Handler.Handle(Params, principal) // actually handle the request o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/rest_server_zrok/operations/zrok_api.go b/rest_server_zrok/operations/zrok_api.go index d64baa8b..fa03ad3e 100644 --- a/rest_server_zrok/operations/zrok_api.go +++ b/rest_server_zrok/operations/zrok_api.go @@ -115,7 +115,7 @@ func NewZrokAPI(spec *loads.Document) *ZrokAPI { AccountResetPasswordRequestHandler: account.ResetPasswordRequestHandlerFunc(func(params account.ResetPasswordRequestParams) middleware.Responder { return middleware.NotImplemented("operation account.ResetPasswordRequest has not yet been implemented") }), - AccountResetTokenHandler: account.ResetTokenHandlerFunc(func(params account.ResetTokenParams) middleware.Responder { + AccountResetTokenHandler: account.ResetTokenHandlerFunc(func(params account.ResetTokenParams, principal *rest_model_zrok.Principal) middleware.Responder { return middleware.NotImplemented("operation account.ResetToken has not yet been implemented") }), ShareShareHandler: share.ShareHandlerFunc(func(params share.ShareParams, principal *rest_model_zrok.Principal) middleware.Responder { diff --git a/sdk/python/sdk/zrok/zrok_api/api/account_api.py b/sdk/python/sdk/zrok/zrok_api/api/account_api.py index 4e2c91ea..77e0528b 100644 --- a/sdk/python/sdk/zrok/zrok_api/api/account_api.py +++ b/sdk/python/sdk/zrok/zrok_api/api/account_api.py @@ -568,7 +568,7 @@ class AccountApi(object): ['application/zrok.v1+json']) # noqa: E501 # Authentication setting - auth_settings = [] # noqa: E501 + auth_settings = ['key'] # noqa: E501 return self.api_client.call_api( '/resetToken', 'POST', diff --git a/specs/zrok.yml b/specs/zrok.yml index 05efbfc6..96afb2e0 100644 --- a/specs/zrok.yml +++ b/specs/zrok.yml @@ -125,6 +125,8 @@ paths: post: tags: - account + security: + - key: [] operationId: resetToken parameters: - name: body diff --git a/ui/src/api/account.js b/ui/src/api/account.js index bf1901a4..545dd7fb 100644 --- a/ui/src/api/account.js +++ b/ui/src/api/account.js @@ -140,7 +140,12 @@ const resetPasswordRequestOperation = { const resetTokenOperation = { path: '/resetToken', contentTypes: ['application/zrok.v1+json'], - method: 'post' + method: 'post', + security: [ + { + id: 'key' + } + ] } const verifyOperation = { diff --git a/ui/src/console/detail/account/actions/ResetToken.js b/ui/src/console/detail/account/actions/ResetToken.js index 8ad02271..e8045556 100644 --- a/ui/src/console/detail/account/actions/ResetToken.js +++ b/ui/src/console/detail/account/actions/ResetToken.js @@ -1,8 +1,26 @@ +import React, {useRef, useState} from "react"; import Modal from "react-bootstrap/Modal"; -import { Button } from "react-bootstrap"; +import {mdiContentCopy} from "@mdi/js"; +import Icon from "@mdi/react"; +import { Button, Overlay, Tooltip } from "react-bootstrap"; import * as account from "../../../../api/account"; const ResetToken = (props) => { + const target = useRef(null); + const [showTooltip, setShowTooltip] = useState(false); + + const handleCopy = async () => { + let copiedText = document.getElementById("zrok-token").innerHTML; + try { + await navigator.clipboard.writeText(copiedText); + + setShowTooltip(true); + setTimeout(() => setShowTooltip(false), 1000); + + } catch(err) { + console.error("failed to copy", err); + } + } let resetToken = () => { console.log("I should reset my token") @@ -14,26 +32,60 @@ const ResetToken = (props) => { "token": resp.data.token })); document.dispatchEvent(new Event('storage')) + setModalBody(( +
+

You will need to update your environment file ($HOME/.zrok/environmetn.json)

+ Token: {resp.data.token}{' '} + +
+ )); + setModalHeader(( + Token Reset Successful + )) }).catch(err => { console.log("err", err); }); - props.onHide(); } + let hide = () => { + setModalBody(defaultModal) + props.onHide() + } + + let defaultHeader = (WARNING - Are you Sure?) + let defaultModal = ( +
+
+
Reseting your token will revoke access from any CLI environments.
+
You will need to update $HOME/.zrok/environments.yml with your new token.
+
+
+ + +
+
+ ); + + const [modalBody, setModalBody] = useState(defaultModal); + const [modalHeader, setModalHeader] = useState(defaultHeader); + + + return (
- - WARNING - Are you Sure? + + {modalHeader} -
- Reseting your token will remove all environments, frontends, and shares you've created. -
-
- - -
+ {modalBody}
+ + {(props) => ( + + Copied! + + )} +
) } From 4437d44553bd26ccb50bafbd2828cfdfecae502a Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 15 Feb 2024 13:10:44 -0500 Subject: [PATCH 5/7] changelog (#191) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f03c4e80..39a9dbc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## v0.4.25 +FEATURE: The web console now supports revoking your current account token and generating a new one (https://github.com/openziti/zrok/issues/191) + CHANGE: Creating a reserved share checks for token collision and returns a more appropriate error message (https://github.com/openziti/zrok/issues/531) CHANGE: Update UI to add a 'true' value on `reserved` boolean (https://github.com/openziti/zrok/issues/443) From 73340f0f6f9227726527801dd618bf26e06d1e06 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 15 Feb 2024 13:58:36 -0500 Subject: [PATCH 6/7] refactor and cleanup token regeneration ui (#191) --- ui/package-lock.json | 12 +++--- ui/src/console/detail/account/ActionsTab.js | 39 ++++++++++--------- .../detail/account/actions/ResetToken.js | 32 ++++++++------- 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 65c57a25..d8b49a7c 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -6196,9 +6196,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001519", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", - "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==", + "version": "1.0.30001587", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", + "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", "funding": [ { "type": "opencollective", @@ -24274,9 +24274,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001519", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", - "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==" + "version": "1.0.30001587", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", + "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==" }, "canvas-color-tracker": { "version": "1.1.6", diff --git a/ui/src/console/detail/account/ActionsTab.js b/ui/src/console/detail/account/ActionsTab.js index 0a930812..f90520f1 100644 --- a/ui/src/console/detail/account/ActionsTab.js +++ b/ui/src/console/detail/account/ActionsTab.js @@ -1,31 +1,34 @@ import React, {useState} from "react"; import ResetToken from "./actions/ResetToken"; +import {Button} from "react-bootstrap"; const ActionsTab = (props) => { - const [actionState, setActionState] = useState("menu") - const [showResetTokenModal, setShowResetTokenModal] = useState(false); const openResetTokenModal = () => setShowResetTokenModal(true); const closeResetTokenModal = () => setShowResetTokenModal(false); - let defaultActionsTabComponent = ( -
- + return ( +
+

Regenerate your account token (DANGER!)?

+

+ Regenerating your account token will stop all environments and shares from operating properly! +

+

+ You will need to manually edit your + ${HOME}/.zrok/environment.json files (in each environment) to use the new + zrok_token . Updating these files will restore the functionality of your environments. +

+

+ Alternatively, you can just zrok disable any enabled environments and re-enable using the + new account token. Running zrok disable will delete your environments and + any shares they contain (including reserved shares). So if you have environments and reserved shares you + need to preserve, your best bet is to update the zrok_token in those environments as + described above. +

+
) - - const renderActions = () => { - switch (actionState) { - default: - return defaultActionsTabComponent - } - } - - return ( - renderActions() - ) } -export default ActionsTab; - +export default ActionsTab; \ No newline at end of file diff --git a/ui/src/console/detail/account/actions/ResetToken.js b/ui/src/console/detail/account/actions/ResetToken.js index e8045556..8a9b266a 100644 --- a/ui/src/console/detail/account/actions/ResetToken.js +++ b/ui/src/console/detail/account/actions/ResetToken.js @@ -23,7 +23,6 @@ const ResetToken = (props) => { } let resetToken = () => { - console.log("I should reset my token") account.resetToken({ body: { "emailAddress": props.user.email } }).then(resp => { console.log(resp) let user = JSON.parse(localStorage.getItem('user')) @@ -34,13 +33,19 @@ const ResetToken = (props) => { document.dispatchEvent(new Event('storage')) setModalBody((
-

You will need to update your environment file ($HOME/.zrok/environmetn.json)

- Token: {resp.data.token}{' '} +

+ You will need to update your environment files ${HOME}/.zrok/environment.json + with the new zrok_token . +

+

+ Your new zrok_token is: {resp.data.token}{' '} +

+
)); setModalHeader(( - Token Reset Successful + Account Token Regenerated! )) }).catch(err => { console.log("err", err); @@ -48,29 +53,26 @@ const ResetToken = (props) => { } let hide = () => { + setModalHeader(defaultHeader) setModalBody(defaultModal) props.onHide() } - let defaultHeader = (WARNING - Are you Sure?) + let defaultHeader = (Are you sure?) let defaultModal = (
-
-
Reseting your token will revoke access from any CLI environments.
-
You will need to update $HOME/.zrok/environments.yml with your new token.
-
-
- - -
+

Did you read the warning on the previous screen? This action will reset all of your active environments and shares!

+

You will need to update each of your ${HOME}/.zrok/environments.yml files with your new token!

+

+ + +

); const [modalBody, setModalBody] = useState(defaultModal); const [modalHeader, setModalHeader] = useState(defaultHeader); - - return (
From 28d30022072ce2e7ccea558658859bc4ce456e27 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 15 Feb 2024 14:08:28 -0500 Subject: [PATCH 7/7] fix for reserved unique name collision checking --- controller/share.go | 19 +++++++++---------- controller/store/share.go | 8 ++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/controller/share.go b/controller/share.go index e2fbbcf5..f2b73758 100644 --- a/controller/share.go +++ b/controller/share.go @@ -72,6 +72,15 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr logrus.Errorf("invalid unique name '%v' for account '%v'", uniqueName, principal.Email) return share.NewShareUnprocessableEntity() } + shareExists, err := str.ShareWithTokenExists(uniqueName, trx) + if err != nil { + logrus.Errorf("error checking share for token collision: %v", err) + return share.NewUpdateShareInternalServerError() + } + if shareExists { + logrus.Errorf("token '%v' already exists; cannot create share", uniqueName) + return share.NewShareConflict() + } shrToken = uniqueName } @@ -135,16 +144,6 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr sshr.FrontendEndpoint = &sshr.ShareMode } - sh, err := str.FindShareWithToken(sshr.Token, trx) - if err != nil { - logrus.Errorf("error checking share for token collision: %v", err) - return share.NewShareInternalServerError() - } - if sh != nil { - logrus.Errorf("token '%v' already exists; cannot create share", sshr.Token) - return share.NewShareConflict() - } - sid, err := str.CreateShare(envId, sshr, trx) if err != nil { logrus.Errorf("error creating share record: %v", err) diff --git a/controller/store/share.go b/controller/store/share.go index 8161fc2d..365e5e03 100644 --- a/controller/store/share.go +++ b/controller/store/share.go @@ -63,6 +63,14 @@ func (str *Store) FindShareWithToken(shrToken string, tx *sqlx.Tx) (*Share, erro return shr, nil } +func (str *Store) ShareWithTokenExists(shrToken string, tx *sqlx.Tx) (bool, error) { + count := 0 + if err := tx.QueryRowx("select count(0) from shares where token = $1 and not deleted", shrToken).Scan(&count); err != nil { + return true, errors.Wrap(err, "error selecting share count by token") + } + return count > 0, nil +} + func (str *Store) FindShareWithZIdAndDeleted(zId string, tx *sqlx.Tx) (*Share, error) { shr := &Share{} if err := tx.QueryRowx("select * from shares where z_id = $1", zId).StructScan(shr); err != nil {