From 2b3b6ed012992b400bf1f1535cea57b4fcb54530 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 18 Jan 2024 11:14:32 -0600 Subject: [PATCH 01/12] updated password reset requests. Fixes #452 --- controller/store/password_reset_request.go | 2 +- ...17_v0_4_23_password_reset_request_unique.sql | 7 +++++++ ...17_v0_4_23_password_reset_request_unique.sql | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql create mode 100644 controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql diff --git a/controller/store/password_reset_request.go b/controller/store/password_reset_request.go index a6a7b60d..75a1f3e4 100644 --- a/controller/store/password_reset_request.go +++ b/controller/store/password_reset_request.go @@ -17,7 +17,7 @@ type PasswordResetRequest struct { } func (str *Store) CreatePasswordResetRequest(prr *PasswordResetRequest, tx *sqlx.Tx) (int, error) { - stmt, err := tx.Prepare("insert into password_reset_requests (account_id, token) values ($1, $2) ON CONFLICT(account_id) DO UPDATE SET token=$2 returning id") + stmt, err := tx.Prepare("insert into password_reset_requests (account_id, token) values ($1, $2) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing password_reset_requests insert statement") } diff --git a/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql b/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql new file mode 100644 index 00000000..00b1ec97 --- /dev/null +++ b/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql @@ -0,0 +1,7 @@ +-- +migrate Up + +-- remove the old unique index (users might need multiple password resets) +ALTER TABLE password_reset_requests DROP CONSTRAINT password_reset_requests_account_id_key; + +-- add new constraint which doesnt mind having multiple resets for account ids +ALTER TABLE password_reset_requests ADD CONSTRAINT password_reset_requests_account_id_key FOREIGN KEY (account_id) REFERENCES accounts (id) on delete cascade; diff --git a/controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql b/controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql new file mode 100644 index 00000000..880018f5 --- /dev/null +++ b/controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql @@ -0,0 +1,17 @@ +-- +migrate Up + +alter table password_reset_requests rename to password_reset_requests_old; + +CREATE TABLE password_reset_requests ( + id integer primary key, + token string not null unique, + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + account_id integer not null constraint fk_accounts_password_reset_requests references accounts on delete cascade, + deleted boolean not null default(false), + + constraint chk_token check(token <> '') +); + +insert into password_reset_requests select * from password_reset_requests_old; +drop table password_reset_requests_old; \ No newline at end of file From bba9377b9f7158cf25ea9b301bcb26dba5008194 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 25 Jan 2024 09:55:45 -0600 Subject: [PATCH 02/12] 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 f77404b4db6c4769f60e83348a27f8c3eca53870 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 25 Jan 2024 09:58:55 -0600 Subject: [PATCH 03/12] update casing --- .../postgresql/017_v0_4_23_password_reset_request_unique.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql b/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql index 00b1ec97..120023cf 100644 --- a/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql +++ b/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql @@ -4,4 +4,4 @@ ALTER TABLE password_reset_requests DROP CONSTRAINT password_reset_requests_account_id_key; -- add new constraint which doesnt mind having multiple resets for account ids -ALTER TABLE password_reset_requests ADD CONSTRAINT password_reset_requests_account_id_key FOREIGN KEY (account_id) REFERENCES accounts (id) on delete cascade; +ALTER TABLE password_reset_requests ADD CONSTRAINT password_reset_requests_account_id_key FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE; From 6a29ac0117273e8c764076f7d7f716309b3479de Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 31 Jan 2024 14:35:28 -0600 Subject: [PATCH 04/12] 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 8d51fce2b8d61e3a683522313df64ac388a7a411 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 7 Feb 2024 15:07:43 -0600 Subject: [PATCH 05/12] remove cascading deletes --- .../postgresql/017_v0_4_23_password_reset_request_unique.sql | 2 +- .../sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql b/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql index 120023cf..9606e67a 100644 --- a/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql +++ b/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql @@ -4,4 +4,4 @@ ALTER TABLE password_reset_requests DROP CONSTRAINT password_reset_requests_account_id_key; -- add new constraint which doesnt mind having multiple resets for account ids -ALTER TABLE password_reset_requests ADD CONSTRAINT password_reset_requests_account_id_key FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE; +ALTER TABLE password_reset_requests ADD CONSTRAINT password_reset_requests_account_id_key FOREIGN KEY (account_id) REFERENCES accounts (id); diff --git a/controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql b/controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql index 880018f5..0e9850d6 100644 --- a/controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql +++ b/controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql @@ -7,7 +7,7 @@ CREATE TABLE password_reset_requests ( token string not null unique, created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), - account_id integer not null constraint fk_accounts_password_reset_requests references accounts on delete cascade, + account_id integer not null constraint fk_accounts_password_reset_requests references accounts, deleted boolean not null default(false), constraint chk_token check(token <> '') From d57d72387f890643181c17fff66262ad98fa946f Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 14 Feb 2024 13:06:04 -0600 Subject: [PATCH 06/12] few small fixes --- CHANGELOG.md | 4 ++++ controller/store/password_reset_request.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee11b0c3..47a5e472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.24 + +FIX: Updated password reset to handle multiple reset requests. + ## v0.4.23 CHANGE: Improved OpenZiti resource cleanup resilience. Previous resource cleanup would stop when an error was encountered at any stage of the cleanup process (serps, sps, config, service). New cleanup implementation logs errors but continues to clean up anything that it can (https://github.com/openziti/zrok/issues/533) diff --git a/controller/store/password_reset_request.go b/controller/store/password_reset_request.go index 75a1f3e4..2b14ce5f 100644 --- a/controller/store/password_reset_request.go +++ b/controller/store/password_reset_request.go @@ -7,6 +7,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) type PasswordResetRequest struct { @@ -17,6 +18,10 @@ type PasswordResetRequest struct { } func (str *Store) CreatePasswordResetRequest(prr *PasswordResetRequest, tx *sqlx.Tx) (int, error) { + if err := str.DeletePasswordResetRequestsByAccountId(prr.AccountId, tx); err != nil { + logrus.Errorf("unable to delete old password reset requests for account '%v', but continuing: %v", prr.AccountId, err) + } + stmt, err := tx.Prepare("insert into password_reset_requests (account_id, token) values ($1, $2) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing password_reset_requests insert statement") @@ -98,3 +103,15 @@ func (str *Store) DeleteMultiplePasswordResetRequests(ids []int, tx *sqlx.Tx) er } return nil } + +func (str *Store) DeletePasswordResetRequestsByAccountId(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 delete by account_id statement") + } + _, err = stmt.Exec(accountId) + if err != nil { + return errors.Wrap(err, "error executing password_reset_requests delete by account_id statement") + } + return nil +} From e764103a68feee787d9a8481a1e358dc50a8fc29 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 14 Feb 2024 13:49:31 -0600 Subject: [PATCH 07/12] 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 8555439410ed47e43af334ed6329677bf9b1ea1f Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 14 Feb 2024 13:51:32 -0600 Subject: [PATCH 08/12] update migration number from merge --- ...t_unique.sql => 018_v0_4_23_password_reset_request_unique.sql} | 0 ...t_unique.sql => 018_v0_4_23_password_reset_request_unique.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename controller/store/sql/postgresql/{017_v0_4_23_password_reset_request_unique.sql => 018_v0_4_23_password_reset_request_unique.sql} (100%) rename controller/store/sql/sqlite3/{017_v0_4_23_password_reset_request_unique.sql => 018_v0_4_23_password_reset_request_unique.sql} (100%) diff --git a/controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql b/controller/store/sql/postgresql/018_v0_4_23_password_reset_request_unique.sql similarity index 100% rename from controller/store/sql/postgresql/017_v0_4_23_password_reset_request_unique.sql rename to controller/store/sql/postgresql/018_v0_4_23_password_reset_request_unique.sql diff --git a/controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql b/controller/store/sql/sqlite3/018_v0_4_23_password_reset_request_unique.sql similarity index 100% rename from controller/store/sql/sqlite3/017_v0_4_23_password_reset_request_unique.sql rename to controller/store/sql/sqlite3/018_v0_4_23_password_reset_request_unique.sql From 1eac1ad94198d3c16ceb14c577249f052a151f92 Mon Sep 17 00:00:00 2001 From: Cam Date: Thu, 15 Feb 2024 01:00:05 -0600 Subject: [PATCH 09/12] 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 10/12] 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 11/12] 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 12/12] 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 {