Merge pull request #546 from openziti/self-service-password

Password change feature (#148) Refactoring and fortification of token regeneration (#191)
This commit is contained in:
Michael Quigley 2024-02-16 16:41:39 -05:00 committed by GitHub
commit 778e50c056
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 1792 additions and 129 deletions

View File

@ -2,6 +2,8 @@
## v0.4.25 ## v0.4.25
FEATURE: New action in the web console that allows changing the password of the logged-in account (https://github.com/openziti/zrok/issues/148)
FEATURE: The web console now supports revoking your current account token and generating a new one (https://github.com/openziti/zrok/issues/191) FEATURE: The web console now supports revoking your current account token and generating a new one (https://github.com/openziti/zrok/issues/191)
CHANGE: When specifying OAuth configuration for public shares from the `zrok share public` or `zrok reserve` public commands, the flags and functionality for restricting the allowed email addresses of the authenticating users has changed. The old flag was `--oauth-email-domains`, which took a string value that needed to be contained in the user's email address. The new flag is `--oauth-email-address-patterns`, which accepts a glob-style filter, using https://github.com/gobwas/glob (https://github.com/openziti/zrok/issues/413) CHANGE: When specifying OAuth configuration for public shares from the `zrok share public` or `zrok reserve` public commands, the flags and functionality for restricting the allowed email addresses of the authenticating users has changed. The old flag was `--oauth-email-domains`, which took a string value that needed to be contained in the user's email address. The new flag is `--oauth-email-address-patterns`, which accepts a glob-style filter, using https://github.com/gobwas/glob (https://github.com/openziti/zrok/issues/413)

View File

@ -0,0 +1,75 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti/zrok/controller/config"
"github.com/openziti/zrok/rest_model_zrok"
"github.com/openziti/zrok/rest_server_zrok/operations/account"
"github.com/sirupsen/logrus"
)
type changePasswordHandler struct {
cfg *config.Config
}
func newChangePasswordHandler(cfg *config.Config) *changePasswordHandler {
return &changePasswordHandler{
cfg: cfg,
}
}
func (handler *changePasswordHandler) Handle(params account.ChangePasswordParams, principal *rest_model_zrok.Principal) middleware.Responder {
if params.Body == nil || params.Body.Email == "" || params.Body.OldPassword == "" || params.Body.NewPassword == "" {
logrus.Error("missing email, old, or new password")
return account.NewChangePasswordUnauthorized()
}
logrus.Infof("received change password request for email '%v'", params.Body.Email)
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return account.NewChangePasswordUnauthorized()
}
defer func() { _ = tx.Rollback() }()
a, err := str.FindAccountWithEmail(params.Body.Email, tx)
if err != nil {
logrus.Errorf("error finding account '%v': %v", params.Body.Email, err)
return account.NewChangePasswordUnauthorized()
}
ohpwd, err := rehashPassword(params.Body.OldPassword, a.Salt)
if err != nil {
logrus.Errorf("error hashing password for '%v': %v", params.Body.Email, err)
return account.NewChangePasswordUnauthorized()
}
if a.Password != ohpwd.Password {
logrus.Errorf("password mismatch for account '%v'", params.Body.Email)
return account.NewChangePasswordUnauthorized()
}
if err := validatePassword(handler.cfg, params.Body.NewPassword); err != nil {
logrus.Errorf("password not valid for request '%v': %v", a.Email, err)
return account.NewChangePasswordUnprocessableEntity().WithPayload(rest_model_zrok.ErrorMessage(err.Error()))
}
nhpwd, err := HashPassword(params.Body.NewPassword)
if err != nil {
logrus.Errorf("error hashing password for '%v': %v", a.Email, err)
return account.NewChangePasswordInternalServerError()
}
a.Salt = nhpwd.Salt
a.Password = nhpwd.Password
if _, err := str.UpdateAccount(a, tx); err != nil {
logrus.Errorf("error updating for '%v': %v", a.Email, err)
return account.NewChangePasswordInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing '%v': %v", a.Email, err)
return account.NewChangePasswordInternalServerError()
}
logrus.Infof("change password for '%v'", a.Email)
return account.NewChangePasswordOK()
}

View File

@ -44,6 +44,7 @@ func Run(inCfg *config.Config) error {
api := operations.NewZrokAPI(swaggerSpec) api := operations.NewZrokAPI(swaggerSpec)
api.KeyAuth = newZrokAuthenticator(cfg).authenticate api.KeyAuth = newZrokAuthenticator(cfg).authenticate
api.AccountChangePasswordHandler = newChangePasswordHandler(cfg)
api.AccountInviteHandler = newInviteHandler(cfg) api.AccountInviteHandler = newInviteHandler(cfg)
api.AccountLoginHandler = account.LoginHandlerFunc(loginHandler) api.AccountLoginHandler = account.LoginHandlerFunc(loginHandler)
api.AccountRegisterHandler = newRegisterHandler(cfg) api.AccountRegisterHandler = newRegisterHandler(cfg)

View File

@ -14,11 +14,12 @@ func newResetTokenHandler() *resetTokenHandler {
} }
func (handler *resetTokenHandler) Handle(params account.ResetTokenParams, principal *rest_model_zrok.Principal) middleware.Responder { func (handler *resetTokenHandler) Handle(params account.ResetTokenParams, principal *rest_model_zrok.Principal) middleware.Responder {
if params.Body.EmailAddress == "" { logrus.Infof("received token regeneration request for email '%v'", principal.Email)
logrus.Error("missing email")
if params.Body.EmailAddress != principal.Email {
logrus.Errorf("mismatched account '%v' for '%v'", params.Body.EmailAddress, principal.Email)
return account.NewResetTokenNotFound() return account.NewResetTokenNotFound()
} }
logrus.Infof("received token reset request for email '%v'", params.Body.EmailAddress)
tx, err := str.Begin() tx, err := str.Begin()
if err != nil { if err != nil {
@ -56,7 +57,7 @@ func (handler *resetTokenHandler) Handle(params account.ResetTokenParams, princi
return account.NewResetTokenInternalServerError() return account.NewResetTokenInternalServerError()
} }
logrus.Infof("reset token for '%v'", a.Email) logrus.Infof("regenerated token '%v' for '%v'", a.Token, a.Email)
return account.NewResetTokenOK().WithPayload(&account.ResetTokenOKBody{Token: token}) return account.NewResetTokenOK().WithPayload(&account.ResetTokenOKBody{Token: token})
} }

View File

@ -30,6 +30,8 @@ type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods // ClientService is the interface for Client methods
type ClientService interface { type ClientService interface {
ChangePassword(params *ChangePasswordParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ChangePasswordOK, error)
Invite(params *InviteParams, opts ...ClientOption) (*InviteCreated, error) Invite(params *InviteParams, opts ...ClientOption) (*InviteCreated, error)
Login(params *LoginParams, opts ...ClientOption) (*LoginOK, error) Login(params *LoginParams, opts ...ClientOption) (*LoginOK, error)
@ -47,6 +49,45 @@ type ClientService interface {
SetTransport(transport runtime.ClientTransport) SetTransport(transport runtime.ClientTransport)
} }
/*
ChangePassword change password API
*/
func (a *Client) ChangePassword(params *ChangePasswordParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ChangePasswordOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewChangePasswordParams()
}
op := &runtime.ClientOperation{
ID: "changePassword",
Method: "POST",
PathPattern: "/changePassword",
ProducesMediaTypes: []string{"application/zrok.v1+json"},
ConsumesMediaTypes: []string{"application/zrok.v1+json"},
Schemes: []string{"http"},
Params: params,
Reader: &ChangePasswordReader{formats: a.formats},
AuthInfo: authInfo,
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.(*ChangePasswordOK)
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 changePassword: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/* /*
Invite invite API Invite invite API
*/ */

View File

@ -0,0 +1,150 @@
// 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"
"github.com/openziti/zrok/rest_model_zrok"
)
// NewChangePasswordParams creates a new ChangePasswordParams 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 NewChangePasswordParams() *ChangePasswordParams {
return &ChangePasswordParams{
timeout: cr.DefaultTimeout,
}
}
// NewChangePasswordParamsWithTimeout creates a new ChangePasswordParams object
// with the ability to set a timeout on a request.
func NewChangePasswordParamsWithTimeout(timeout time.Duration) *ChangePasswordParams {
return &ChangePasswordParams{
timeout: timeout,
}
}
// NewChangePasswordParamsWithContext creates a new ChangePasswordParams object
// with the ability to set a context for a request.
func NewChangePasswordParamsWithContext(ctx context.Context) *ChangePasswordParams {
return &ChangePasswordParams{
Context: ctx,
}
}
// NewChangePasswordParamsWithHTTPClient creates a new ChangePasswordParams object
// with the ability to set a custom HTTPClient for a request.
func NewChangePasswordParamsWithHTTPClient(client *http.Client) *ChangePasswordParams {
return &ChangePasswordParams{
HTTPClient: client,
}
}
/*
ChangePasswordParams contains all the parameters to send to the API endpoint
for the change password operation.
Typically these are written to a http.Request.
*/
type ChangePasswordParams struct {
// Body.
Body *rest_model_zrok.ChangePasswordRequest
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the change password params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ChangePasswordParams) WithDefaults() *ChangePasswordParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the change password params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ChangePasswordParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the change password params
func (o *ChangePasswordParams) WithTimeout(timeout time.Duration) *ChangePasswordParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the change password params
func (o *ChangePasswordParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the change password params
func (o *ChangePasswordParams) WithContext(ctx context.Context) *ChangePasswordParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the change password params
func (o *ChangePasswordParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the change password params
func (o *ChangePasswordParams) WithHTTPClient(client *http.Client) *ChangePasswordParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the change password params
func (o *ChangePasswordParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the change password params
func (o *ChangePasswordParams) WithBody(body *rest_model_zrok.ChangePasswordRequest) *ChangePasswordParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the change password params
func (o *ChangePasswordParams) SetBody(body *rest_model_zrok.ChangePasswordRequest) {
o.Body = body
}
// WriteToRequest writes these params to a swagger request
func (o *ChangePasswordParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if o.Body != nil {
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@ -0,0 +1,349 @@
// 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 (
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/openziti/zrok/rest_model_zrok"
)
// ChangePasswordReader is a Reader for the ChangePassword structure.
type ChangePasswordReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ChangePasswordReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewChangePasswordOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 400:
result := NewChangePasswordBadRequest()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
case 401:
result := NewChangePasswordUnauthorized()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
case 422:
result := NewChangePasswordUnprocessableEntity()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
case 500:
result := NewChangePasswordInternalServerError()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("[POST /changePassword] changePassword", response, response.Code())
}
}
// NewChangePasswordOK creates a ChangePasswordOK with default headers values
func NewChangePasswordOK() *ChangePasswordOK {
return &ChangePasswordOK{}
}
/*
ChangePasswordOK describes a response with status code 200, with default header values.
changed password
*/
type ChangePasswordOK struct {
}
// IsSuccess returns true when this change password o k response has a 2xx status code
func (o *ChangePasswordOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this change password o k response has a 3xx status code
func (o *ChangePasswordOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this change password o k response has a 4xx status code
func (o *ChangePasswordOK) IsClientError() bool {
return false
}
// IsServerError returns true when this change password o k response has a 5xx status code
func (o *ChangePasswordOK) IsServerError() bool {
return false
}
// IsCode returns true when this change password o k response a status code equal to that given
func (o *ChangePasswordOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the change password o k response
func (o *ChangePasswordOK) Code() int {
return 200
}
func (o *ChangePasswordOK) Error() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordOK ", 200)
}
func (o *ChangePasswordOK) String() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordOK ", 200)
}
func (o *ChangePasswordOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}
// NewChangePasswordBadRequest creates a ChangePasswordBadRequest with default headers values
func NewChangePasswordBadRequest() *ChangePasswordBadRequest {
return &ChangePasswordBadRequest{}
}
/*
ChangePasswordBadRequest describes a response with status code 400, with default header values.
password not changed
*/
type ChangePasswordBadRequest struct {
}
// IsSuccess returns true when this change password bad request response has a 2xx status code
func (o *ChangePasswordBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this change password bad request response has a 3xx status code
func (o *ChangePasswordBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this change password bad request response has a 4xx status code
func (o *ChangePasswordBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this change password bad request response has a 5xx status code
func (o *ChangePasswordBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this change password bad request response a status code equal to that given
func (o *ChangePasswordBadRequest) IsCode(code int) bool {
return code == 400
}
// Code gets the status code for the change password bad request response
func (o *ChangePasswordBadRequest) Code() int {
return 400
}
func (o *ChangePasswordBadRequest) Error() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordBadRequest ", 400)
}
func (o *ChangePasswordBadRequest) String() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordBadRequest ", 400)
}
func (o *ChangePasswordBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}
// NewChangePasswordUnauthorized creates a ChangePasswordUnauthorized with default headers values
func NewChangePasswordUnauthorized() *ChangePasswordUnauthorized {
return &ChangePasswordUnauthorized{}
}
/*
ChangePasswordUnauthorized describes a response with status code 401, with default header values.
unauthorized
*/
type ChangePasswordUnauthorized struct {
}
// IsSuccess returns true when this change password unauthorized response has a 2xx status code
func (o *ChangePasswordUnauthorized) IsSuccess() bool {
return false
}
// IsRedirect returns true when this change password unauthorized response has a 3xx status code
func (o *ChangePasswordUnauthorized) IsRedirect() bool {
return false
}
// IsClientError returns true when this change password unauthorized response has a 4xx status code
func (o *ChangePasswordUnauthorized) IsClientError() bool {
return true
}
// IsServerError returns true when this change password unauthorized response has a 5xx status code
func (o *ChangePasswordUnauthorized) IsServerError() bool {
return false
}
// IsCode returns true when this change password unauthorized response a status code equal to that given
func (o *ChangePasswordUnauthorized) IsCode(code int) bool {
return code == 401
}
// Code gets the status code for the change password unauthorized response
func (o *ChangePasswordUnauthorized) Code() int {
return 401
}
func (o *ChangePasswordUnauthorized) Error() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordUnauthorized ", 401)
}
func (o *ChangePasswordUnauthorized) String() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordUnauthorized ", 401)
}
func (o *ChangePasswordUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}
// NewChangePasswordUnprocessableEntity creates a ChangePasswordUnprocessableEntity with default headers values
func NewChangePasswordUnprocessableEntity() *ChangePasswordUnprocessableEntity {
return &ChangePasswordUnprocessableEntity{}
}
/*
ChangePasswordUnprocessableEntity describes a response with status code 422, with default header values.
password validation failure
*/
type ChangePasswordUnprocessableEntity struct {
Payload rest_model_zrok.ErrorMessage
}
// IsSuccess returns true when this change password unprocessable entity response has a 2xx status code
func (o *ChangePasswordUnprocessableEntity) IsSuccess() bool {
return false
}
// IsRedirect returns true when this change password unprocessable entity response has a 3xx status code
func (o *ChangePasswordUnprocessableEntity) IsRedirect() bool {
return false
}
// IsClientError returns true when this change password unprocessable entity response has a 4xx status code
func (o *ChangePasswordUnprocessableEntity) IsClientError() bool {
return true
}
// IsServerError returns true when this change password unprocessable entity response has a 5xx status code
func (o *ChangePasswordUnprocessableEntity) IsServerError() bool {
return false
}
// IsCode returns true when this change password unprocessable entity response a status code equal to that given
func (o *ChangePasswordUnprocessableEntity) IsCode(code int) bool {
return code == 422
}
// Code gets the status code for the change password unprocessable entity response
func (o *ChangePasswordUnprocessableEntity) Code() int {
return 422
}
func (o *ChangePasswordUnprocessableEntity) Error() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordUnprocessableEntity %+v", 422, o.Payload)
}
func (o *ChangePasswordUnprocessableEntity) String() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordUnprocessableEntity %+v", 422, o.Payload)
}
func (o *ChangePasswordUnprocessableEntity) GetPayload() rest_model_zrok.ErrorMessage {
return o.Payload
}
func (o *ChangePasswordUnprocessableEntity) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewChangePasswordInternalServerError creates a ChangePasswordInternalServerError with default headers values
func NewChangePasswordInternalServerError() *ChangePasswordInternalServerError {
return &ChangePasswordInternalServerError{}
}
/*
ChangePasswordInternalServerError describes a response with status code 500, with default header values.
internal server error
*/
type ChangePasswordInternalServerError struct {
}
// IsSuccess returns true when this change password internal server error response has a 2xx status code
func (o *ChangePasswordInternalServerError) IsSuccess() bool {
return false
}
// IsRedirect returns true when this change password internal server error response has a 3xx status code
func (o *ChangePasswordInternalServerError) IsRedirect() bool {
return false
}
// IsClientError returns true when this change password internal server error response has a 4xx status code
func (o *ChangePasswordInternalServerError) IsClientError() bool {
return false
}
// IsServerError returns true when this change password internal server error response has a 5xx status code
func (o *ChangePasswordInternalServerError) IsServerError() bool {
return true
}
// IsCode returns true when this change password internal server error response a status code equal to that given
func (o *ChangePasswordInternalServerError) IsCode(code int) bool {
return code == 500
}
// Code gets the status code for the change password internal server error response
func (o *ChangePasswordInternalServerError) Code() int {
return 500
}
func (o *ChangePasswordInternalServerError) Error() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordInternalServerError ", 500)
}
func (o *ChangePasswordInternalServerError) String() string {
return fmt.Sprintf("[POST /changePassword][%d] changePasswordInternalServerError ", 500)
}
func (o *ChangePasswordInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}

View File

@ -0,0 +1,56 @@
// Code generated by go-swagger; DO NOT EDIT.
package rest_model_zrok
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ChangePasswordRequest change password request
//
// swagger:model changePasswordRequest
type ChangePasswordRequest struct {
// email
Email string `json:"email,omitempty"`
// new password
NewPassword string `json:"newPassword,omitempty"`
// old password
OldPassword string `json:"oldPassword,omitempty"`
}
// Validate validates this change password request
func (m *ChangePasswordRequest) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this change password request based on context it is used
func (m *ChangePasswordRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *ChangePasswordRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ChangePasswordRequest) UnmarshalBinary(b []byte) error {
var res ChangePasswordRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@ -74,6 +74,48 @@ func init() {
} }
} }
}, },
"/changePassword": {
"post": {
"security": [
{
"key": []
}
],
"tags": [
"account"
],
"operationId": "changePassword",
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/changePasswordRequest"
}
}
],
"responses": {
"200": {
"description": "changed password"
},
"400": {
"description": "password not changed"
},
"401": {
"description": "unauthorized"
},
"422": {
"description": "password validation failure",
"schema": {
"$ref": "#/definitions/errorMessage"
}
},
"500": {
"description": "internal server error"
}
}
}
},
"/configuration": { "/configuration": {
"get": { "get": {
"tags": [ "tags": [
@ -1115,6 +1157,20 @@ func init() {
} }
} }
}, },
"changePasswordRequest": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"newPassword": {
"type": "string"
},
"oldPassword": {
"type": "string"
}
}
},
"configuration": { "configuration": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1745,6 +1801,48 @@ func init() {
} }
} }
}, },
"/changePassword": {
"post": {
"security": [
{
"key": []
}
],
"tags": [
"account"
],
"operationId": "changePassword",
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/changePasswordRequest"
}
}
],
"responses": {
"200": {
"description": "changed password"
},
"400": {
"description": "password not changed"
},
"401": {
"description": "unauthorized"
},
"422": {
"description": "password validation failure",
"schema": {
"$ref": "#/definitions/errorMessage"
}
},
"500": {
"description": "internal server error"
}
}
}
},
"/configuration": { "/configuration": {
"get": { "get": {
"tags": [ "tags": [
@ -2786,6 +2884,20 @@ func init() {
} }
} }
}, },
"changePasswordRequest": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"newPassword": {
"type": "string"
},
"oldPassword": {
"type": "string"
}
}
},
"configuration": { "configuration": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -0,0 +1,71 @@
// 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 (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/openziti/zrok/rest_model_zrok"
)
// ChangePasswordHandlerFunc turns a function with the right signature into a change password handler
type ChangePasswordHandlerFunc func(ChangePasswordParams, *rest_model_zrok.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn ChangePasswordHandlerFunc) Handle(params ChangePasswordParams, principal *rest_model_zrok.Principal) middleware.Responder {
return fn(params, principal)
}
// ChangePasswordHandler interface for that can handle valid change password params
type ChangePasswordHandler interface {
Handle(ChangePasswordParams, *rest_model_zrok.Principal) middleware.Responder
}
// NewChangePassword creates a new http.Handler for the change password operation
func NewChangePassword(ctx *middleware.Context, handler ChangePasswordHandler) *ChangePassword {
return &ChangePassword{Context: ctx, Handler: handler}
}
/*
ChangePassword swagger:route POST /changePassword account changePassword
ChangePassword change password API
*/
type ChangePassword struct {
Context *middleware.Context
Handler ChangePasswordHandler
}
func (o *ChangePassword) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewChangePasswordParams()
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, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@ -0,0 +1,76 @@
// 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"
"github.com/openziti/zrok/rest_model_zrok"
)
// NewChangePasswordParams creates a new ChangePasswordParams object
//
// There are no default values defined in the spec.
func NewChangePasswordParams() ChangePasswordParams {
return ChangePasswordParams{}
}
// ChangePasswordParams contains all the bound params for the change password operation
// typically these are obtained from a http.Request
//
// swagger:parameters changePassword
type ChangePasswordParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
In: body
*/
Body *rest_model_zrok.ChangePasswordRequest
}
// 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 NewChangePasswordParams() beforehand.
func (o *ChangePasswordParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body rest_model_zrok.ChangePasswordRequest
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
}

View File

@ -0,0 +1,157 @@
// 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"
"github.com/openziti/zrok/rest_model_zrok"
)
// ChangePasswordOKCode is the HTTP code returned for type ChangePasswordOK
const ChangePasswordOKCode int = 200
/*
ChangePasswordOK changed password
swagger:response changePasswordOK
*/
type ChangePasswordOK struct {
}
// NewChangePasswordOK creates ChangePasswordOK with default headers values
func NewChangePasswordOK() *ChangePasswordOK {
return &ChangePasswordOK{}
}
// WriteResponse to the client
func (o *ChangePasswordOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
// ChangePasswordBadRequestCode is the HTTP code returned for type ChangePasswordBadRequest
const ChangePasswordBadRequestCode int = 400
/*
ChangePasswordBadRequest password not changed
swagger:response changePasswordBadRequest
*/
type ChangePasswordBadRequest struct {
}
// NewChangePasswordBadRequest creates ChangePasswordBadRequest with default headers values
func NewChangePasswordBadRequest() *ChangePasswordBadRequest {
return &ChangePasswordBadRequest{}
}
// WriteResponse to the client
func (o *ChangePasswordBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(400)
}
// ChangePasswordUnauthorizedCode is the HTTP code returned for type ChangePasswordUnauthorized
const ChangePasswordUnauthorizedCode int = 401
/*
ChangePasswordUnauthorized unauthorized
swagger:response changePasswordUnauthorized
*/
type ChangePasswordUnauthorized struct {
}
// NewChangePasswordUnauthorized creates ChangePasswordUnauthorized with default headers values
func NewChangePasswordUnauthorized() *ChangePasswordUnauthorized {
return &ChangePasswordUnauthorized{}
}
// WriteResponse to the client
func (o *ChangePasswordUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(401)
}
// ChangePasswordUnprocessableEntityCode is the HTTP code returned for type ChangePasswordUnprocessableEntity
const ChangePasswordUnprocessableEntityCode int = 422
/*
ChangePasswordUnprocessableEntity password validation failure
swagger:response changePasswordUnprocessableEntity
*/
type ChangePasswordUnprocessableEntity struct {
/*
In: Body
*/
Payload rest_model_zrok.ErrorMessage `json:"body,omitempty"`
}
// NewChangePasswordUnprocessableEntity creates ChangePasswordUnprocessableEntity with default headers values
func NewChangePasswordUnprocessableEntity() *ChangePasswordUnprocessableEntity {
return &ChangePasswordUnprocessableEntity{}
}
// WithPayload adds the payload to the change password unprocessable entity response
func (o *ChangePasswordUnprocessableEntity) WithPayload(payload rest_model_zrok.ErrorMessage) *ChangePasswordUnprocessableEntity {
o.Payload = payload
return o
}
// SetPayload sets the payload to the change password unprocessable entity response
func (o *ChangePasswordUnprocessableEntity) SetPayload(payload rest_model_zrok.ErrorMessage) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ChangePasswordUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(422)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
// ChangePasswordInternalServerErrorCode is the HTTP code returned for type ChangePasswordInternalServerError
const ChangePasswordInternalServerErrorCode int = 500
/*
ChangePasswordInternalServerError internal server error
swagger:response changePasswordInternalServerError
*/
type ChangePasswordInternalServerError struct {
}
// NewChangePasswordInternalServerError creates ChangePasswordInternalServerError with default headers values
func NewChangePasswordInternalServerError() *ChangePasswordInternalServerError {
return &ChangePasswordInternalServerError{}
}
// WriteResponse to the client
func (o *ChangePasswordInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(500)
}

View File

@ -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"
)
// ChangePasswordURL generates an URL for the change password operation
type ChangePasswordURL 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 *ChangePasswordURL) WithBasePath(bp string) *ChangePasswordURL {
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 *ChangePasswordURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *ChangePasswordURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/changePassword"
_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 *ChangePasswordURL) 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 *ChangePasswordURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *ChangePasswordURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on ChangePasswordURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on ChangePasswordURL")
}
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 *ChangePasswordURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@ -52,6 +52,9 @@ func NewZrokAPI(spec *loads.Document) *ZrokAPI {
ShareAccessHandler: share.AccessHandlerFunc(func(params share.AccessParams, principal *rest_model_zrok.Principal) middleware.Responder { ShareAccessHandler: share.AccessHandlerFunc(func(params share.AccessParams, principal *rest_model_zrok.Principal) middleware.Responder {
return middleware.NotImplemented("operation share.Access has not yet been implemented") return middleware.NotImplemented("operation share.Access has not yet been implemented")
}), }),
AccountChangePasswordHandler: account.ChangePasswordHandlerFunc(func(params account.ChangePasswordParams, principal *rest_model_zrok.Principal) middleware.Responder {
return middleware.NotImplemented("operation account.ChangePassword has not yet been implemented")
}),
MetadataConfigurationHandler: metadata.ConfigurationHandlerFunc(func(params metadata.ConfigurationParams) middleware.Responder { MetadataConfigurationHandler: metadata.ConfigurationHandlerFunc(func(params metadata.ConfigurationParams) middleware.Responder {
return middleware.NotImplemented("operation metadata.Configuration has not yet been implemented") return middleware.NotImplemented("operation metadata.Configuration has not yet been implemented")
}), }),
@ -191,6 +194,8 @@ type ZrokAPI struct {
// ShareAccessHandler sets the operation handler for the access operation // ShareAccessHandler sets the operation handler for the access operation
ShareAccessHandler share.AccessHandler ShareAccessHandler share.AccessHandler
// AccountChangePasswordHandler sets the operation handler for the change password operation
AccountChangePasswordHandler account.ChangePasswordHandler
// MetadataConfigurationHandler sets the operation handler for the configuration operation // MetadataConfigurationHandler sets the operation handler for the configuration operation
MetadataConfigurationHandler metadata.ConfigurationHandler MetadataConfigurationHandler metadata.ConfigurationHandler
// AdminCreateFrontendHandler sets the operation handler for the create frontend operation // AdminCreateFrontendHandler sets the operation handler for the create frontend operation
@ -333,6 +338,9 @@ func (o *ZrokAPI) Validate() error {
if o.ShareAccessHandler == nil { if o.ShareAccessHandler == nil {
unregistered = append(unregistered, "share.AccessHandler") unregistered = append(unregistered, "share.AccessHandler")
} }
if o.AccountChangePasswordHandler == nil {
unregistered = append(unregistered, "account.ChangePasswordHandler")
}
if o.MetadataConfigurationHandler == nil { if o.MetadataConfigurationHandler == nil {
unregistered = append(unregistered, "metadata.ConfigurationHandler") unregistered = append(unregistered, "metadata.ConfigurationHandler")
} }
@ -523,6 +531,10 @@ func (o *ZrokAPI) initHandlerCache() {
o.handlers["POST"] = make(map[string]http.Handler) o.handlers["POST"] = make(map[string]http.Handler)
} }
o.handlers["POST"]["/access"] = share.NewAccess(o.context, o.ShareAccessHandler) o.handlers["POST"]["/access"] = share.NewAccess(o.context, o.ShareAccessHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/changePassword"] = account.NewChangePassword(o.context, o.AccountChangePasswordHandler)
if o.handlers["GET"] == nil { if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler) o.handlers["GET"] = make(map[string]http.Handler)
} }

View File

@ -27,6 +27,7 @@ from zrok_api.configuration import Configuration
from zrok_api.models.access_request import AccessRequest from zrok_api.models.access_request import AccessRequest
from zrok_api.models.access_response import AccessResponse from zrok_api.models.access_response import AccessResponse
from zrok_api.models.auth_user import AuthUser from zrok_api.models.auth_user import AuthUser
from zrok_api.models.change_password_request import ChangePasswordRequest
from zrok_api.models.configuration import Configuration from zrok_api.models.configuration import Configuration
from zrok_api.models.create_frontend_request import CreateFrontendRequest from zrok_api.models.create_frontend_request import CreateFrontendRequest
from zrok_api.models.create_frontend_response import CreateFrontendResponse from zrok_api.models.create_frontend_response import CreateFrontendResponse

View File

@ -32,6 +32,99 @@ class AccountApi(object):
api_client = ApiClient() api_client = ApiClient()
self.api_client = api_client self.api_client = api_client
def change_password(self, **kwargs): # noqa: E501
"""change_password # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.change_password(async_req=True)
>>> result = thread.get()
:param async_req bool
:param ChangePasswordRequest body:
:return: None
If the method is called asynchronously,
returns the request thread.
"""
kwargs['_return_http_data_only'] = True
if kwargs.get('async_req'):
return self.change_password_with_http_info(**kwargs) # noqa: E501
else:
(data) = self.change_password_with_http_info(**kwargs) # noqa: E501
return data
def change_password_with_http_info(self, **kwargs): # noqa: E501
"""change_password # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.change_password_with_http_info(async_req=True)
>>> result = thread.get()
:param async_req bool
:param ChangePasswordRequest body:
:return: None
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 change_password" % 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 = ['key'] # noqa: E501
return self.api_client.call_api(
'/changePassword', 'POST',
path_params,
query_params,
header_params,
body=body_params,
post_params=form_params,
files=local_var_files,
response_type=None, # 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 invite(self, **kwargs): # noqa: E501 def invite(self, **kwargs): # noqa: E501
"""invite # noqa: E501 """invite # noqa: E501

View File

@ -17,6 +17,7 @@ from __future__ import absolute_import
from zrok_api.models.access_request import AccessRequest from zrok_api.models.access_request import AccessRequest
from zrok_api.models.access_response import AccessResponse from zrok_api.models.access_response import AccessResponse
from zrok_api.models.auth_user import AuthUser from zrok_api.models.auth_user import AuthUser
from zrok_api.models.change_password_request import ChangePasswordRequest
from zrok_api.models.configuration import Configuration from zrok_api.models.configuration import Configuration
from zrok_api.models.create_frontend_request import CreateFrontendRequest from zrok_api.models.create_frontend_request import CreateFrontendRequest
from zrok_api.models.create_frontend_response import CreateFrontendResponse from zrok_api.models.create_frontend_response import CreateFrontendResponse

View File

@ -0,0 +1,162 @@
# 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 ChangePasswordRequest(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': 'str',
'old_password': 'str',
'new_password': 'str'
}
attribute_map = {
'email': 'email',
'old_password': 'oldPassword',
'new_password': 'newPassword'
}
def __init__(self, email=None, old_password=None, new_password=None): # noqa: E501
"""ChangePasswordRequest - a model defined in Swagger""" # noqa: E501
self._email = None
self._old_password = None
self._new_password = None
self.discriminator = None
if email is not None:
self.email = email
if old_password is not None:
self.old_password = old_password
if new_password is not None:
self.new_password = new_password
@property
def email(self):
"""Gets the email of this ChangePasswordRequest. # noqa: E501
:return: The email of this ChangePasswordRequest. # noqa: E501
:rtype: str
"""
return self._email
@email.setter
def email(self, email):
"""Sets the email of this ChangePasswordRequest.
:param email: The email of this ChangePasswordRequest. # noqa: E501
:type: str
"""
self._email = email
@property
def old_password(self):
"""Gets the old_password of this ChangePasswordRequest. # noqa: E501
:return: The old_password of this ChangePasswordRequest. # noqa: E501
:rtype: str
"""
return self._old_password
@old_password.setter
def old_password(self, old_password):
"""Sets the old_password of this ChangePasswordRequest.
:param old_password: The old_password of this ChangePasswordRequest. # noqa: E501
:type: str
"""
self._old_password = old_password
@property
def new_password(self):
"""Gets the new_password of this ChangePasswordRequest. # noqa: E501
:return: The new_password of this ChangePasswordRequest. # noqa: E501
:rtype: str
"""
return self._new_password
@new_password.setter
def new_password(self, new_password):
"""Sets the new_password of this ChangePasswordRequest.
:param new_password: The new_password of this ChangePasswordRequest. # noqa: E501
:type: str
"""
self._new_password = new_password
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(ChangePasswordRequest, 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, ChangePasswordRequest):
return False
return self.__dict__ == other.__dict__
def __ne__(self, other):
"""Returns true if both objects are not equal"""
return not self == other

View File

@ -15,6 +15,32 @@ paths:
# #
# account # account
# #
/changePassword:
post:
tags:
- account
security:
- key: []
operationId: changePassword
parameters:
- name: body
in: body
schema:
$ref: '#/definitions/changePasswordRequest'
responses:
200:
description: changed password
400:
description: password not changed
401:
description: unauthorized
422:
description: password validation failure
schema:
$ref: '#/definitions/errorMessage'
500:
description: internal server error
/invite: /invite:
post: post:
tags: tags:
@ -703,6 +729,16 @@ definitions:
password: password:
type: string type: string
changePasswordRequest:
type: object
properties:
email:
type: string
oldPassword:
type: string
newPassword:
type: string
configuration: configuration:
type: object type: object
properties: properties:

View File

@ -2,6 +2,21 @@
// Auto-generated, edits will be overwritten // Auto-generated, edits will be overwritten
import * as gateway from './gateway' import * as gateway from './gateway'
/**
* @param {object} options Optional options
* @param {module:types.changePasswordRequest} [options.body]
* @return {Promise<object>} changed password
*/
export function changePassword(options) {
if (!options) options = {}
const parameters = {
body: {
body: options.body
}
}
return gateway.request(changePasswordOperation, parameters)
}
/** /**
* @param {object} options Optional options * @param {object} options Optional options
* @param {module:types.inviteRequest} [options.body] * @param {module:types.inviteRequest} [options.body]
@ -107,6 +122,17 @@ export function verify(options) {
return gateway.request(verifyOperation, parameters) return gateway.request(verifyOperation, parameters)
} }
const changePasswordOperation = {
path: '/changePassword',
contentTypes: ['application/zrok.v1+json'],
method: 'post',
security: [
{
id: 'key'
}
]
}
const inviteOperation = { const inviteOperation = {
path: '/invite', path: '/invite',
contentTypes: ['application/zrok.v1+json'], contentTypes: ['application/zrok.v1+json'],

View File

@ -25,6 +25,15 @@
* @property {string} password * @property {string} password
*/ */
/**
* @typedef changePasswordRequest
* @memberof module:types
*
* @property {string} email
* @property {string} oldPassword
* @property {string} newPassword
*/
/** /**
* @typedef configuration * @typedef configuration
* @memberof module:types * @memberof module:types

View File

@ -45,28 +45,28 @@ const PasswordForm = (props) => {
<Fragment> <Fragment>
{ {
(props.passwordLength > 0 || props.passwordRequireCapital || props.passwordRequireNumeric || props.passwordRequireSpecial) && (props.passwordLength > 0 || props.passwordRequireCapital || props.passwordRequireNumeric || props.passwordRequireSpecial) &&
<h2>Password Requirements</h2> <h2 style={{justifyContent: "center"}}>Password Requirements</h2>
} }
{ {
props.passwordLength > 0 && props.passwordLength > 0 &&
<Row>Minimum Length of {props.passwordLength} </Row> <Row style={{justifyContent: "center"}}>Minimum Length of {props.passwordLength} </Row>
} }
{ {
props.passwordRequireCapital && props.passwordRequireCapital &&
<Row>Requires at least 1 Capital Letter</Row> <Row style={{justifyContent: "center"}}>Requires at least 1 Capital Letter</Row>
} }
{ {
props.passwordRequireNumeric && props.passwordRequireNumeric &&
<Row>Requires at least 1 Digit</Row> <Row style={{justifyContent: "center"}}>Requires at least 1 Digit</Row>
} }
{ {
props.passwordRequireSpecial && props.passwordRequireSpecial &&
<Fragment> <Fragment>
<Row>Requires at least 1 Special Character</Row> <Row style={{justifyContent: "center"}}>Requires at least 1 Special Character</Row>
<Row>{props.passwordValidSpecialCharacters.split("").join(" ")}</Row> <Row style={{justifyContent: "center"}}>{props.passwordValidSpecialCharacters.split("").join(" ")}</Row>
</Fragment> </Fragment>
} }
<Container className={"fullscreen-body"}> <Container>
<Form.Group controlId={"password"}> <Form.Group controlId={"password"}>
<Form.Control <Form.Control
type={"password"} type={"password"}

View File

@ -1,33 +1,49 @@
import React, {useState} from "react"; import React, {useState} from "react";
import ResetToken from "./actions/ResetToken"; import ChangePassword from "./actions/ChangePassword";
import {Button} from "react-bootstrap"; import {Button} from "react-bootstrap";
import RegenerateToken from "./actions/RegenerateToken";
const ActionsTab = (props) => { const ActionsTab = (props) => {
const [showResetTokenModal, setShowResetTokenModal] = useState(false); const [showRegenerateTokenModal, setShowRegenerateTokenModal] = useState(false);
const openResetTokenModal = () => setShowResetTokenModal(true); const openRegenerateTokenModal = () => setShowRegenerateTokenModal(true);
const closeResetTokenModal = () => setShowResetTokenModal(false); const closeRegenerateTokenModal = () => setShowRegenerateTokenModal(false);
const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
const openChangePasswordModal = () => setShowChangePasswordModal(true);
const closeChangePasswordModal = () => setShowChangePasswordModal(false);
return ( return (
<div className={"actions-tab"}> <div className={"actions-tab"}>
<h3>Regenerate your account token <strong>(DANGER!)</strong>?</h3> <div id={"change-password"} style={{"padding-top": "10px"}}>
<p> <h3>Change Password?</h3>
Regenerating your account token will stop all environments and shares from operating properly! <p>Change your password here. Note that this will <strong>not</strong> log you out of any already logged in sessions.</p>
</p> <Button variant={"danger"} onClick={openChangePasswordModal}>Change Password</Button>
<p> <ChangePassword show={showChangePasswordModal} onHide={closeChangePasswordModal} user={props.user}/>
You will need to <strong>manually</strong> edit your </div>
<code> &#36;&#123;HOME&#125;/.zrok/environment.json</code> files (in each environment) to use the new
<code> zrok_token</code> . Updating these files will restore the functionality of your environments. <hr/>
</p>
<p> <div id={"token-regeneration"}>
Alternatively, you can just <code>zrok disable</code> any enabled environments and re-enable using the <h3>Regenerate your account token <strong>(DANGER!)</strong>?</h3>
new account token. Running <code>zrok disable</code> will <strong>delete</strong> your environments and <p>
any shares they contain (including reserved shares). So if you have environments and reserved shares you Regenerating your account token will stop all environments and shares from operating properly!
need to preserve, your best bet is to update the <code>zrok_token</code> in those environments as </p>
described above. <p>
</p> You will need to <strong>manually</strong> edit your
<Button variant={"danger"} onClick={openResetTokenModal}>Regenerate Account Token</Button> <code> &#36;&#123;HOME&#125;/.zrok/environment.json</code> files (in each environment) to use the new
<ResetToken show={showResetTokenModal} onHide={closeResetTokenModal} user={props.user}/> <code> zrok_token</code> . Updating these files will restore the functionality of your environments.
</div> </p>
<p>
Alternatively, you can just <code>zrok disable</code> any enabled environments and re-enable using the
new account token. Running <code>zrok disable</code> will <strong>delete</strong> 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 <code>zrok_token</code> in those environments as
described above.
</p>
<Button variant={"danger"} onClick={openRegenerateTokenModal}>Regenerate Account Token</Button>
<RegenerateToken show={showRegenerateTokenModal} onHide={closeRegenerateTokenModal} user={props.user}/>
</div>
</div>
) )
} }

View File

@ -0,0 +1,142 @@
import React, {useEffect, useState} from "react";
import * as account from "../../../../api/account";
import {Button, Container, Form, Row} from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import * as metadata from "../../../../api/metadata";
const validatePassword = (password, l, rc, rn, rs, spc, cb) => {
if(password.length < l) {
cb(false, "Entered password is too short! (" + l + " characters minimum)!");
return;
}
if(rc && !/[A-Z]/.test(password)) {
cb(false, "Entered password requires a capital letter!");
return;
}
if(rn && !/\d/.test(password)) {
cb(false, "Entered password requires a digit!");
return;
}
if(rs) {
if(!spc.split("").some(v => password.includes(v))) {
cb(false, "Entered password requires a special character!");
return;
}
}
return cb(true, "");
}
const ChangePassword = (props) => {
const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [message, setMessage] = useState('');
const [passwordLength, setPasswordLength] = useState(8);
const [passwordRequireCapital, setPasswordRequireCapital] = useState(true);
const [passwordRequireNumeric, setPasswordRequireNumeric] = useState(true);
const [passwordRequireSpecial, setPasswordRequireSpecial] = useState(true);
const [passwordValidSpecialCharacters, setPasswordValidSpecialCharacters] = useState("");
useEffect(() => {
metadata.configuration().then(resp => {
if (!resp.error) {
setPasswordLength(resp.data.passwordRequirements.length)
setPasswordRequireCapital(resp.data.passwordRequirements.requireCapital)
setPasswordRequireNumeric(resp.data.passwordRequirements.requireNumeric)
setPasswordRequireSpecial(resp.data.passwordRequirements.requireSpecial)
setPasswordValidSpecialCharacters(resp.data.passwordRequirements.validSpecialCharacters)
}
}).catch(err => {
console.log("error getting configuration", err);
});
}, [])
const handleSubmit = async e => {
e.preventDefault();
let ok = false;
validatePassword(newPassword,
passwordLength,
passwordRequireCapital,
passwordRequireNumeric,
passwordRequireSpecial,
passwordValidSpecialCharacters, (isOk, msg) => { ok = isOk; setMessage(msg); })
if(!ok) {
return;
}
if(confirmPassword !== newPassword) {
setMessage("New password and confirmation do not match!");
return;
}
account.changePassword({ body: { oldPassword: oldPassword, newPassword: newPassword, email: props.user.email } })
.then(resp => {
if (!resp.error) {
console.log("resp", resp)
setMessage("Password successfully changed!");
} else {
setMessage("Failure changing password! Is old password correct?");
}
}).catch(resp => {
console.log("resp", resp)
setMessage("Failure changing password! Is old password correct?")
})
}
let hide = () => {
props.onHide();
setMessage("");
setOldPassword("");
setNewPassword("");
setConfirmPassword("");
}
return (
<Modal show={props.show} onHide={hide} size={"md"} centered>
<Modal.Header closeButton>Change Password</Modal.Header>
<Modal.Body>
<Form onSubmit={handleSubmit}>
<Container>
<Form.Group controlId={"oldPassword"}>
<Form.Control
type={"password"}
placeholder={"Old Password"}
onChange={t => { setMessage(''); setOldPassword(t.target.value); }}
value={oldPassword}
style={{ marginBottom: "1em" }}
/>
</Form.Group>
<Form.Group controlId={"newPassword"}>
<Form.Control
type={"password"}
placeholder={"New Password"}
onChange={t => { setMessage(''); setNewPassword(t.target.value); }}
value={newPassword}
style={{ marginBottom: "1em" }}
/>
</Form.Group>
<Form.Group controlId={"confirmPassword"}>
<Form.Control
type={"password"}
placeholder={"Confirm Password"}
onChange={t => { setMessage(''); setConfirmPassword(t.target.value); }}
value={confirmPassword}
/>
</Form.Group>
<Row style={{ justifyContent: "center", marginTop: "1em" }}>
<p style={{ color: "red" }}>{message}</p>
</Row>
<Row style={{ justifyContent: "right", marginTop: "1em" }}>
<Button variant={"danger"} type={"submit"}>Change Password</Button>
</Row>
</Container>
</Form>
</Modal.Body>
</Modal>
);
}
export default ChangePassword;

View File

@ -0,0 +1,82 @@
import Modal from "react-bootstrap/Modal";
import {Button, Container, Form, Row} from "react-bootstrap";
import React, {useState} from "react";
import * as account from "../../../../api/account";
const RegenerateToken = (props) => {
const [confirmEmail, setConfirmEmail] = useState('');
const [message, setMessage] = useState('');
const hide = () => {
props.onHide();
setConfirmEmail('');
setMessage('');
};
const handleSubmit = (event) => {
event.preventDefault();
if(confirmEmail !== props.user.email) {
setMessage("Email address confirmation does not match!");
return;
}
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'));
setMessage("Your new account token is: " + resp.data.token);
}).catch(err => {
setMessage("Account token regeneration failed!");
console.log("account token regeneration failed", err);
});
};
return (
<Modal show={props.show} onHide={hide} size={"md"} centered>
<Modal.Header closeButton>Are you very sure?</Modal.Header>
<Modal.Body>
<Form onSubmit={handleSubmit}>
<Container>
<p>
Did you read the warning on the previous screen? This action will reset all of your active
environments and shares!
</p>
<p>
You will need to update each of
your <code> &#36;&#123;HOME&#125;/.zrok/environments.yml</code> files
with your new token to allow them to continue working!
</p>
<p>
Hit <code> Escape </code> or click the 'X' to abort!
</p>
<Form.Group controlId={"confirmEmail"}>
<Form.Control
placeholder={"Confirm Your Email Address"}
onChange={t => {
setMessage('');
setConfirmEmail(t.target.value);
}}
value={confirmEmail}
style={{marginBottom: "1em"}}
/>
</Form.Group>
<Row style={{ justifyContent: "center", marginTop: "1em" }}>
<p style={{ color: "red" }}>{message}</p>
</Row>
<Row style={{ justifyContent: "right", marginTop: "1em" }}>
<Button variant={"danger"} type={"submit"}>Regenerate Account Token</Button>
</Row>
</Container>
</Form>
</Modal.Body>
</Modal>
);
};
export default RegenerateToken;

View File

@ -1,95 +0,0 @@
import React, {useRef, useState} from "react";
import Modal from "react-bootstrap/Modal";
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 = () => {
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'))
setModalBody((
<div>
<p>
You will need to update your environment files <code> &#36;&#123;HOME&#125;/.zrok/environment.json </code>
with the new <code> zrok_token </code>.
</p>
<p>
Your new <code> zrok_token </code> is: <code><span id={"zrok-token"}>{resp.data.token}</span></code>{' '}
<Icon ref={target} path={mdiContentCopy} size={0.7} onClick={handleCopy}/>
</p>
</div>
));
setModalHeader((
<span>Account Token Regenerated!</span>
))
}).catch(err => {
console.log("err", err);
});
}
let hide = () => {
setModalHeader(defaultHeader)
setModalBody(defaultModal)
props.onHide()
}
let defaultHeader = (<span>Are you sure?</span>)
let defaultModal = (
<div>
<p>Did you read the warning on the previous screen? This action will reset all of your active environments and shares!</p>
<p>You will need to update each of your <code> &#36;&#123;HOME&#125;/.zrok/environments.yml</code> files with your new token!</p>
<p align={"right"}>
<Button onClick={props.onHide}>Cancel</Button>
<Button variant={"danger"} onClick={resetToken}>Regenerate Token</Button>
</p>
</div>
);
const [modalBody, setModalBody] = useState(defaultModal);
const [modalHeader, setModalHeader] = useState(defaultHeader);
return (
<div>
<Modal show={props.show} onHide={hide} centered>
<Modal.Header closeButton>{modalHeader}</Modal.Header>
<Modal.Body>
{modalBody}
</Modal.Body>
</Modal>
<Overlay target={target.current} show={showTooltip} placement={"bottom"}>
{(props) => (
<Tooltip id={"copy-tooltip"} {...props}>
Copied!
</Tooltip>
)}
</Overlay>
</div>
)
}
export default ResetToken;