mirror of
https://github.com/openziti/zrok.git
synced 2024-11-26 18:13:52 +01:00
Merge pull request #326 from openziti/v0.4.0_password_requirements
Enhanced password requirements and relevant ui changes (#167)
This commit is contained in:
commit
ee17f2f3d3
@ -10,6 +10,8 @@ FEATURE: New limits implementation based on the new metrics infrastructure (http
|
|||||||
|
|
||||||
FEATURE: The invite mechanism has been reworked to improve user experience. The configuration has been moved to the `admin` stanza of the controller configuration and now includes a boolean flag indicating whether or not the instance allows new invitations to be created, and also includes contact details for requesting a new invite. These values are used by the `zrok invite` command to provide a smoother end-user invite experience https://github.com/openziti/zrok/issues/229)
|
FEATURE: The invite mechanism has been reworked to improve user experience. The configuration has been moved to the `admin` stanza of the controller configuration and now includes a boolean flag indicating whether or not the instance allows new invitations to be created, and also includes contact details for requesting a new invite. These values are used by the `zrok invite` command to provide a smoother end-user invite experience https://github.com/openziti/zrok/issues/229)
|
||||||
|
|
||||||
|
FEATURE: New password strength checking rules and configuration. See the example configuration file (`etc/ctrl.yml`) for details about how to configure the strength checking rules (https://github.com/openziti/zrok/issues/167)
|
||||||
|
|
||||||
CHANGE: The controller configuration version bumps from `v: 2` to `v: 3` to support all of the new `v0.4` functionality. See the [example ctrl.yml](etc/ctrl.yml) for details on the new configuration.
|
CHANGE: The controller configuration version bumps from `v: 2` to `v: 3` to support all of the new `v0.4` functionality. See the [example ctrl.yml](etc/ctrl.yml) for details on the new configuration.
|
||||||
|
|
||||||
CHANGE: The underlying database store now utilizes a `deleted` flag on all tables to implement "soft deletes". This was necessary for the new metrics infrastructure, where we need to account for metrics data that arrived after the lifetime of a share or environment; and also we're going to need this for limits, where we need to see historical information about activity in the past (https://github.com/openziti/zrok/issues/262)
|
CHANGE: The underlying database store now utilizes a `deleted` flag on all tables to implement "soft deletes". This was necessary for the new metrics infrastructure, where we need to account for metrics data that arrived after the lifetime of a share or environment; and also we're going to need this for limits, where we need to see historical information about activity in the past (https://github.com/openziti/zrok/issues/262)
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/openziti/zrok/controller/emailUi"
|
"github.com/openziti/zrok/controller/emailUi"
|
||||||
"github.com/openziti/zrok/controller/env"
|
"github.com/openziti/zrok/controller/env"
|
||||||
"github.com/openziti/zrok/controller/limits"
|
"github.com/openziti/zrok/controller/limits"
|
||||||
"github.com/openziti/zrok/controller/metrics"
|
"github.com/openziti/zrok/controller/metrics"
|
||||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/michaelquigley/cf"
|
"github.com/michaelquigley/cf"
|
||||||
"github.com/openziti/zrok/controller/store"
|
"github.com/openziti/zrok/controller/store"
|
||||||
@ -24,6 +25,7 @@ type Config struct {
|
|||||||
Limits *limits.Config
|
Limits *limits.Config
|
||||||
Maintenance *MaintenanceConfig
|
Maintenance *MaintenanceConfig
|
||||||
Metrics *metrics.Config
|
Metrics *metrics.Config
|
||||||
|
Passwords *PasswordsConfig
|
||||||
Registration *RegistrationConfig
|
Registration *RegistrationConfig
|
||||||
ResetPassword *ResetPasswordConfig
|
ResetPassword *ResetPasswordConfig
|
||||||
Store *store.Config
|
Store *store.Config
|
||||||
@ -31,11 +33,11 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AdminConfig struct {
|
type AdminConfig struct {
|
||||||
Secrets []string `cf:"+secret"`
|
|
||||||
TouLink string
|
|
||||||
InvitesOpen bool
|
InvitesOpen bool
|
||||||
InviteTokenStrategy string
|
InviteTokenStrategy string
|
||||||
InviteTokenContact string
|
InviteTokenContact string
|
||||||
|
Secrets []string `cf:"+secret"`
|
||||||
|
TouLink string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EndpointConfig struct {
|
type EndpointConfig struct {
|
||||||
@ -43,6 +45,19 @@ type EndpointConfig struct {
|
|||||||
Port int
|
Port int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MaintenanceConfig struct {
|
||||||
|
ResetPassword *ResetPasswordMaintenanceConfig
|
||||||
|
Registration *RegistrationMaintenanceConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type PasswordsConfig struct {
|
||||||
|
Length int
|
||||||
|
RequireCapital bool
|
||||||
|
RequireNumeric bool
|
||||||
|
RequireSpecial bool
|
||||||
|
ValidSpecialCharacters string
|
||||||
|
}
|
||||||
|
|
||||||
type RegistrationConfig struct {
|
type RegistrationConfig struct {
|
||||||
RegistrationUrlTemplate string
|
RegistrationUrlTemplate string
|
||||||
}
|
}
|
||||||
@ -51,11 +66,6 @@ type ResetPasswordConfig struct {
|
|||||||
ResetUrlTemplate string
|
ResetUrlTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
type MaintenanceConfig struct {
|
|
||||||
ResetPassword *ResetPasswordMaintenanceConfig
|
|
||||||
Registration *RegistrationMaintenanceConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type RegistrationMaintenanceConfig struct {
|
type RegistrationMaintenanceConfig struct {
|
||||||
ExpirationTimeout time.Duration
|
ExpirationTimeout time.Duration
|
||||||
CheckFrequency time.Duration
|
CheckFrequency time.Duration
|
||||||
@ -83,6 +93,13 @@ func DefaultConfig() *Config {
|
|||||||
BatchLimit: 500,
|
BatchLimit: 500,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Passwords: &PasswordsConfig{
|
||||||
|
Length: 8,
|
||||||
|
RequireCapital: true,
|
||||||
|
RequireNumeric: true,
|
||||||
|
RequireSpecial: true,
|
||||||
|
ValidSpecialCharacters: `!@$&*_-., "#%'()+/:;<=>?[\]^{|}~`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,15 @@ func (ch *configurationHandler) Handle(_ metadata.ConfigurationParams) middlewar
|
|||||||
if cfg.Admin != nil {
|
if cfg.Admin != nil {
|
||||||
data.TouLink = cfg.Admin.TouLink
|
data.TouLink = cfg.Admin.TouLink
|
||||||
data.InviteTokenContact = cfg.Admin.InviteTokenContact
|
data.InviteTokenContact = cfg.Admin.InviteTokenContact
|
||||||
|
if cfg.Passwords != nil {
|
||||||
|
data.PasswordRequirements = &rest_model_zrok.PasswordRequirements{
|
||||||
|
Length: int64(cfg.Passwords.Length),
|
||||||
|
RequireCapital: cfg.Passwords.RequireCapital,
|
||||||
|
RequireNumeric: cfg.Passwords.RequireNumeric,
|
||||||
|
RequireSpecial: cfg.Passwords.RequireSpecial,
|
||||||
|
ValidSpecialCharacters: cfg.Passwords.ValidSpecialCharacters,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return metadata.NewConfigurationOK().WithPayload(data)
|
return metadata.NewConfigurationOK().WithPayload(data)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/openziti/zrok/controller/config"
|
"github.com/openziti/zrok/controller/config"
|
||||||
"github.com/openziti/zrok/controller/limits"
|
"github.com/openziti/zrok/controller/limits"
|
||||||
"github.com/openziti/zrok/controller/metrics"
|
"github.com/openziti/zrok/controller/metrics"
|
||||||
@ -34,8 +35,8 @@ func Run(inCfg *config.Config) error {
|
|||||||
api.KeyAuth = newZrokAuthenticator(cfg).authenticate
|
api.KeyAuth = newZrokAuthenticator(cfg).authenticate
|
||||||
api.AccountInviteHandler = newInviteHandler(cfg)
|
api.AccountInviteHandler = newInviteHandler(cfg)
|
||||||
api.AccountLoginHandler = account.LoginHandlerFunc(loginHandler)
|
api.AccountLoginHandler = account.LoginHandlerFunc(loginHandler)
|
||||||
api.AccountRegisterHandler = newRegisterHandler()
|
api.AccountRegisterHandler = newRegisterHandler(cfg)
|
||||||
api.AccountResetPasswordHandler = newResetPasswordHandler()
|
api.AccountResetPasswordHandler = newResetPasswordHandler(cfg)
|
||||||
api.AccountResetPasswordRequestHandler = newResetPasswordRequestHandler()
|
api.AccountResetPasswordRequestHandler = newResetPasswordRequestHandler()
|
||||||
api.AccountVerifyHandler = newVerifyHandler()
|
api.AccountVerifyHandler = newVerifyHandler()
|
||||||
api.AdminCreateFrontendHandler = newCreateFrontendHandler()
|
api.AdminCreateFrontendHandler = newCreateFrontendHandler()
|
||||||
|
@ -2,16 +2,21 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-openapi/runtime/middleware"
|
"github.com/go-openapi/runtime/middleware"
|
||||||
|
"github.com/openziti/zrok/controller/config"
|
||||||
"github.com/openziti/zrok/controller/store"
|
"github.com/openziti/zrok/controller/store"
|
||||||
"github.com/openziti/zrok/rest_model_zrok"
|
"github.com/openziti/zrok/rest_model_zrok"
|
||||||
"github.com/openziti/zrok/rest_server_zrok/operations/account"
|
"github.com/openziti/zrok/rest_server_zrok/operations/account"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type registerHandler struct{}
|
type registerHandler struct {
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
func newRegisterHandler() *registerHandler {
|
func newRegisterHandler(cfg *config.Config) *registerHandler {
|
||||||
return ®isterHandler{}
|
return ®isterHandler{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func (h *registerHandler) Handle(params account.RegisterParams) middleware.Responder {
|
func (h *registerHandler) Handle(params account.RegisterParams) middleware.Responder {
|
||||||
if params.Body == nil || params.Body.Token == "" || params.Body.Password == "" {
|
if params.Body == nil || params.Body.Token == "" || params.Body.Password == "" {
|
||||||
@ -38,6 +43,12 @@ func (h *registerHandler) Handle(params account.RegisterParams) middleware.Respo
|
|||||||
logrus.Errorf("error creating token for request '%v' (%v): %v", params.Body.Token, ar.Email, err)
|
logrus.Errorf("error creating token for request '%v' (%v): %v", params.Body.Token, ar.Email, err)
|
||||||
return account.NewRegisterInternalServerError()
|
return account.NewRegisterInternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validatePassword(h.cfg, params.Body.Password); err != nil {
|
||||||
|
logrus.Errorf("password not valid for request '%v', (%v): %v", params.Body.Token, ar.Email, err)
|
||||||
|
return account.NewRegisterUnprocessableEntity().WithPayload(rest_model_zrok.ErrorMessage(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
hpwd, err := hashPassword(params.Body.Password)
|
hpwd, err := hashPassword(params.Body.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("error hashing password for request '%v' (%v): %v", params.Body.Token, ar.Email, err)
|
logrus.Errorf("error hashing password for request '%v' (%v): %v", params.Body.Token, ar.Email, err)
|
||||||
|
@ -2,14 +2,20 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-openapi/runtime/middleware"
|
"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/openziti/zrok/rest_server_zrok/operations/account"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type resetPasswordHandler struct{}
|
type resetPasswordHandler struct {
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
func newResetPasswordHandler() *resetPasswordHandler {
|
func newResetPasswordHandler(cfg *config.Config) *resetPasswordHandler {
|
||||||
return &resetPasswordHandler{}
|
return &resetPasswordHandler{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *resetPasswordHandler) Handle(params account.ResetPasswordParams) middleware.Responder {
|
func (handler *resetPasswordHandler) Handle(params account.ResetPasswordParams) middleware.Responder {
|
||||||
@ -41,6 +47,12 @@ func (handler *resetPasswordHandler) Handle(params account.ResetPasswordParams)
|
|||||||
logrus.Errorf("account '%v' for '%v' deleted", a.Email, a.Token)
|
logrus.Errorf("account '%v' for '%v' deleted", a.Email, a.Token)
|
||||||
return account.NewResetPasswordNotFound()
|
return account.NewResetPasswordNotFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validatePassword(handler.cfg, params.Body.Password); err != nil {
|
||||||
|
logrus.Errorf("password not valid for request '%v', (%v): %v", params.Body.Token, a.Email, err)
|
||||||
|
return account.NewResetPasswordUnprocessableEntity().WithPayload(rest_model_zrok.ErrorMessage(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
hpwd, err := hashPassword(params.Body.Password)
|
hpwd, err := hashPassword(params.Body.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("error hashing password for '%v' (%v): %v", params.Body.Token, a.Email, err)
|
logrus.Errorf("error hashing password for '%v' (%v): %v", params.Body.Token, a.Email, err)
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
errors2 "github.com/go-openapi/errors"
|
errors2 "github.com/go-openapi/errors"
|
||||||
"github.com/jaevor/go-nanoid"
|
"github.com/jaevor/go-nanoid"
|
||||||
"github.com/openziti/zrok/controller/config"
|
"github.com/openziti/zrok/controller/config"
|
||||||
"github.com/openziti/zrok/rest_model_zrok"
|
"github.com/openziti/zrok/rest_model_zrok"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type zrokAuthenticator struct {
|
type zrokAuthenticator struct {
|
||||||
@ -87,3 +90,43 @@ func realRemoteAddress(req *http.Request) string {
|
|||||||
func proxyUrl(shrToken, template string) string {
|
func proxyUrl(shrToken, template string) string {
|
||||||
return strings.Replace(template, "{token}", shrToken, -1)
|
return strings.Replace(template, "{token}", shrToken, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validatePassword(cfg *config.Config, password string) error {
|
||||||
|
if cfg.Passwords.Length > len(password) {
|
||||||
|
return fmt.Errorf("password length: expected (%d), got (%d)", cfg.Passwords.Length, len(password))
|
||||||
|
}
|
||||||
|
if cfg.Passwords.RequireCapital {
|
||||||
|
if !hasCapital(password) {
|
||||||
|
return fmt.Errorf("password requires capital, found none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.Passwords.RequireNumeric {
|
||||||
|
if !hasNumeric(password) {
|
||||||
|
return fmt.Errorf("password requires numeric, found none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.Passwords.RequireSpecial {
|
||||||
|
if !strings.ContainsAny(password, cfg.Passwords.ValidSpecialCharacters) {
|
||||||
|
return fmt.Errorf("password requires special character, found none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasCapital(check string) bool {
|
||||||
|
for _, c := range check {
|
||||||
|
if unicode.IsUpper(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasNumeric(check string) bool {
|
||||||
|
for _, c := range check {
|
||||||
|
if unicode.IsDigit(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
10
etc/ctrl.yml
10
etc/ctrl.yml
@ -140,6 +140,16 @@ metrics:
|
|||||||
org: zrok
|
org: zrok
|
||||||
token: "<INFLUX TOKEN>"
|
token: "<INFLUX TOKEN>"
|
||||||
|
|
||||||
|
# Configure password requirements for user accounts.
|
||||||
|
#
|
||||||
|
#passwords:
|
||||||
|
# length: 8
|
||||||
|
# require_capital: true
|
||||||
|
# require_numeric: true
|
||||||
|
# require_special: true
|
||||||
|
# # Denote which characters satisfy the `require_special` requirement. Note the need to escape specific characters.
|
||||||
|
# valid_special_characters: "\"\\`'~!@#$%^&*()[],./"
|
||||||
|
|
||||||
# Configure the generated URL for the registration email. The registration token will be appended to this URL.
|
# Configure the generated URL for the registration email. The registration token will be appended to this URL.
|
||||||
#
|
#
|
||||||
registration:
|
registration:
|
||||||
|
@ -35,6 +35,12 @@ func (o *RegisterReader) ReadResponse(response runtime.ClientResponse, consumer
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, result
|
return nil, result
|
||||||
|
case 422:
|
||||||
|
result := NewRegisterUnprocessableEntity()
|
||||||
|
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, result
|
||||||
case 500:
|
case 500:
|
||||||
result := NewRegisterInternalServerError()
|
result := NewRegisterInternalServerError()
|
||||||
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
||||||
@ -160,6 +166,67 @@ func (o *RegisterNotFound) readResponse(response runtime.ClientResponse, consume
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRegisterUnprocessableEntity creates a RegisterUnprocessableEntity with default headers values
|
||||||
|
func NewRegisterUnprocessableEntity() *RegisterUnprocessableEntity {
|
||||||
|
return &RegisterUnprocessableEntity{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
RegisterUnprocessableEntity describes a response with status code 422, with default header values.
|
||||||
|
|
||||||
|
password validation failure
|
||||||
|
*/
|
||||||
|
type RegisterUnprocessableEntity struct {
|
||||||
|
Payload rest_model_zrok.ErrorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess returns true when this register unprocessable entity response has a 2xx status code
|
||||||
|
func (o *RegisterUnprocessableEntity) IsSuccess() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRedirect returns true when this register unprocessable entity response has a 3xx status code
|
||||||
|
func (o *RegisterUnprocessableEntity) IsRedirect() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClientError returns true when this register unprocessable entity response has a 4xx status code
|
||||||
|
func (o *RegisterUnprocessableEntity) IsClientError() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServerError returns true when this register unprocessable entity response has a 5xx status code
|
||||||
|
func (o *RegisterUnprocessableEntity) IsServerError() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCode returns true when this register unprocessable entity response a status code equal to that given
|
||||||
|
func (o *RegisterUnprocessableEntity) IsCode(code int) bool {
|
||||||
|
return code == 422
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *RegisterUnprocessableEntity) Error() string {
|
||||||
|
return fmt.Sprintf("[POST /register][%d] registerUnprocessableEntity %+v", 422, o.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *RegisterUnprocessableEntity) String() string {
|
||||||
|
return fmt.Sprintf("[POST /register][%d] registerUnprocessableEntity %+v", 422, o.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *RegisterUnprocessableEntity) GetPayload() rest_model_zrok.ErrorMessage {
|
||||||
|
return o.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *RegisterUnprocessableEntity) 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
|
||||||
|
}
|
||||||
|
|
||||||
// NewRegisterInternalServerError creates a RegisterInternalServerError with default headers values
|
// NewRegisterInternalServerError creates a RegisterInternalServerError with default headers values
|
||||||
func NewRegisterInternalServerError() *RegisterInternalServerError {
|
func NewRegisterInternalServerError() *RegisterInternalServerError {
|
||||||
return &RegisterInternalServerError{}
|
return &RegisterInternalServerError{}
|
||||||
|
@ -7,9 +7,12 @@ package account
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/go-openapi/runtime"
|
"github.com/go-openapi/runtime"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
|
|
||||||
|
"github.com/openziti/zrok/rest_model_zrok"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResetPasswordReader is a Reader for the ResetPassword structure.
|
// ResetPasswordReader is a Reader for the ResetPassword structure.
|
||||||
@ -32,6 +35,12 @@ func (o *ResetPasswordReader) ReadResponse(response runtime.ClientResponse, cons
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, result
|
return nil, result
|
||||||
|
case 422:
|
||||||
|
result := NewResetPasswordUnprocessableEntity()
|
||||||
|
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, result
|
||||||
case 500:
|
case 500:
|
||||||
result := NewResetPasswordInternalServerError()
|
result := NewResetPasswordInternalServerError()
|
||||||
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
||||||
@ -145,6 +154,67 @@ func (o *ResetPasswordNotFound) readResponse(response runtime.ClientResponse, co
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewResetPasswordUnprocessableEntity creates a ResetPasswordUnprocessableEntity with default headers values
|
||||||
|
func NewResetPasswordUnprocessableEntity() *ResetPasswordUnprocessableEntity {
|
||||||
|
return &ResetPasswordUnprocessableEntity{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ResetPasswordUnprocessableEntity describes a response with status code 422, with default header values.
|
||||||
|
|
||||||
|
password validation failure
|
||||||
|
*/
|
||||||
|
type ResetPasswordUnprocessableEntity struct {
|
||||||
|
Payload rest_model_zrok.ErrorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess returns true when this reset password unprocessable entity response has a 2xx status code
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) IsSuccess() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRedirect returns true when this reset password unprocessable entity response has a 3xx status code
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) IsRedirect() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClientError returns true when this reset password unprocessable entity response has a 4xx status code
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) IsClientError() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServerError returns true when this reset password unprocessable entity response has a 5xx status code
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) IsServerError() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCode returns true when this reset password unprocessable entity response a status code equal to that given
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) IsCode(code int) bool {
|
||||||
|
return code == 422
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) Error() string {
|
||||||
|
return fmt.Sprintf("[POST /resetPassword][%d] resetPasswordUnprocessableEntity %+v", 422, o.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) String() string {
|
||||||
|
return fmt.Sprintf("[POST /resetPassword][%d] resetPasswordUnprocessableEntity %+v", 422, o.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) GetPayload() rest_model_zrok.ErrorMessage {
|
||||||
|
return o.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) 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
|
||||||
|
}
|
||||||
|
|
||||||
// NewResetPasswordInternalServerError creates a ResetPasswordInternalServerError with default headers values
|
// NewResetPasswordInternalServerError creates a ResetPasswordInternalServerError with default headers values
|
||||||
func NewResetPasswordInternalServerError() *ResetPasswordInternalServerError {
|
func NewResetPasswordInternalServerError() *ResetPasswordInternalServerError {
|
||||||
return &ResetPasswordInternalServerError{}
|
return &ResetPasswordInternalServerError{}
|
||||||
|
@ -8,6 +8,7 @@ package rest_model_zrok
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/errors"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
"github.com/go-openapi/swag"
|
"github.com/go-openapi/swag"
|
||||||
)
|
)
|
||||||
@ -23,6 +24,9 @@ type Configuration struct {
|
|||||||
// invites open
|
// invites open
|
||||||
InvitesOpen bool `json:"invitesOpen,omitempty"`
|
InvitesOpen bool `json:"invitesOpen,omitempty"`
|
||||||
|
|
||||||
|
// password requirements
|
||||||
|
PasswordRequirements *PasswordRequirements `json:"passwordRequirements,omitempty"`
|
||||||
|
|
||||||
// requires invite token
|
// requires invite token
|
||||||
RequiresInviteToken bool `json:"requiresInviteToken,omitempty"`
|
RequiresInviteToken bool `json:"requiresInviteToken,omitempty"`
|
||||||
|
|
||||||
@ -35,11 +39,64 @@ type Configuration struct {
|
|||||||
|
|
||||||
// Validate validates this configuration
|
// Validate validates this configuration
|
||||||
func (m *Configuration) Validate(formats strfmt.Registry) error {
|
func (m *Configuration) Validate(formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.validatePasswordRequirements(formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextValidate validates this configuration based on context it is used
|
func (m *Configuration) validatePasswordRequirements(formats strfmt.Registry) error {
|
||||||
|
if swag.IsZero(m.PasswordRequirements) { // not required
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.PasswordRequirements != nil {
|
||||||
|
if err := m.PasswordRequirements.Validate(formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("passwordRequirements")
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("passwordRequirements")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validate this configuration based on the context it is used
|
||||||
func (m *Configuration) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
func (m *Configuration) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.contextValidatePasswordRequirements(ctx, formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Configuration) contextValidatePasswordRequirements(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
if m.PasswordRequirements != nil {
|
||||||
|
if err := m.PasswordRequirements.ContextValidate(ctx, formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("passwordRequirements")
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("passwordRequirements")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
62
rest_model_zrok/password_requirements.go
Normal file
62
rest_model_zrok/password_requirements.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PasswordRequirements password requirements
|
||||||
|
//
|
||||||
|
// swagger:model passwordRequirements
|
||||||
|
type PasswordRequirements struct {
|
||||||
|
|
||||||
|
// length
|
||||||
|
Length int64 `json:"length,omitempty"`
|
||||||
|
|
||||||
|
// require capital
|
||||||
|
RequireCapital bool `json:"requireCapital,omitempty"`
|
||||||
|
|
||||||
|
// require numeric
|
||||||
|
RequireNumeric bool `json:"requireNumeric,omitempty"`
|
||||||
|
|
||||||
|
// require special
|
||||||
|
RequireSpecial bool `json:"requireSpecial,omitempty"`
|
||||||
|
|
||||||
|
// valid special characters
|
||||||
|
ValidSpecialCharacters string `json:"validSpecialCharacters,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this password requirements
|
||||||
|
func (m *PasswordRequirements) Validate(formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validates this password requirements based on context it is used
|
||||||
|
func (m *PasswordRequirements) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *PasswordRequirements) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *PasswordRequirements) UnmarshalBinary(b []byte) error {
|
||||||
|
var res PasswordRequirements
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
@ -754,6 +754,12 @@ func init() {
|
|||||||
"404": {
|
"404": {
|
||||||
"description": "request not found"
|
"description": "request not found"
|
||||||
},
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "password validation failure",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/errorMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "internal server error"
|
"description": "internal server error"
|
||||||
}
|
}
|
||||||
@ -782,6 +788,12 @@ func init() {
|
|||||||
"404": {
|
"404": {
|
||||||
"description": "request not found"
|
"description": "request not found"
|
||||||
},
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "password validation failure",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/errorMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "internal server error"
|
"description": "internal server error"
|
||||||
}
|
}
|
||||||
@ -1062,6 +1074,9 @@ func init() {
|
|||||||
"invitesOpen": {
|
"invitesOpen": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"passwordRequirements": {
|
||||||
|
"$ref": "#/definitions/passwordRequirements"
|
||||||
|
},
|
||||||
"requiresInviteToken": {
|
"requiresInviteToken": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@ -1295,6 +1310,26 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"passwordRequirements": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"length": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"requireCapital": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"requireNumeric": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"requireSpecial": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"validSpecialCharacters": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"principal": {
|
"principal": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -2315,6 +2350,12 @@ func init() {
|
|||||||
"404": {
|
"404": {
|
||||||
"description": "request not found"
|
"description": "request not found"
|
||||||
},
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "password validation failure",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/errorMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "internal server error"
|
"description": "internal server error"
|
||||||
}
|
}
|
||||||
@ -2343,6 +2384,12 @@ func init() {
|
|||||||
"404": {
|
"404": {
|
||||||
"description": "request not found"
|
"description": "request not found"
|
||||||
},
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "password validation failure",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/errorMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "internal server error"
|
"description": "internal server error"
|
||||||
}
|
}
|
||||||
@ -2623,6 +2670,9 @@ func init() {
|
|||||||
"invitesOpen": {
|
"invitesOpen": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"passwordRequirements": {
|
||||||
|
"$ref": "#/definitions/passwordRequirements"
|
||||||
|
},
|
||||||
"requiresInviteToken": {
|
"requiresInviteToken": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@ -2856,6 +2906,26 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"passwordRequirements": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"length": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"requireCapital": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"requireNumeric": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"requireSpecial": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"validSpecialCharacters": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"principal": {
|
"principal": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -83,6 +83,49 @@ func (o *RegisterNotFound) WriteResponse(rw http.ResponseWriter, producer runtim
|
|||||||
rw.WriteHeader(404)
|
rw.WriteHeader(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterUnprocessableEntityCode is the HTTP code returned for type RegisterUnprocessableEntity
|
||||||
|
const RegisterUnprocessableEntityCode int = 422
|
||||||
|
|
||||||
|
/*
|
||||||
|
RegisterUnprocessableEntity password validation failure
|
||||||
|
|
||||||
|
swagger:response registerUnprocessableEntity
|
||||||
|
*/
|
||||||
|
type RegisterUnprocessableEntity struct {
|
||||||
|
|
||||||
|
/*
|
||||||
|
In: Body
|
||||||
|
*/
|
||||||
|
Payload rest_model_zrok.ErrorMessage `json:"body,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRegisterUnprocessableEntity creates RegisterUnprocessableEntity with default headers values
|
||||||
|
func NewRegisterUnprocessableEntity() *RegisterUnprocessableEntity {
|
||||||
|
|
||||||
|
return &RegisterUnprocessableEntity{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPayload adds the payload to the register unprocessable entity response
|
||||||
|
func (o *RegisterUnprocessableEntity) WithPayload(payload rest_model_zrok.ErrorMessage) *RegisterUnprocessableEntity {
|
||||||
|
o.Payload = payload
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPayload sets the payload to the register unprocessable entity response
|
||||||
|
func (o *RegisterUnprocessableEntity) SetPayload(payload rest_model_zrok.ErrorMessage) {
|
||||||
|
o.Payload = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteResponse to the client
|
||||||
|
func (o *RegisterUnprocessableEntity) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterInternalServerErrorCode is the HTTP code returned for type RegisterInternalServerError
|
// RegisterInternalServerErrorCode is the HTTP code returned for type RegisterInternalServerError
|
||||||
const RegisterInternalServerErrorCode int = 500
|
const RegisterInternalServerErrorCode int = 500
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-openapi/runtime"
|
"github.com/go-openapi/runtime"
|
||||||
|
|
||||||
|
"github.com/openziti/zrok/rest_model_zrok"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResetPasswordOKCode is the HTTP code returned for type ResetPasswordOK
|
// ResetPasswordOKCode is the HTTP code returned for type ResetPasswordOK
|
||||||
@ -61,6 +63,49 @@ func (o *ResetPasswordNotFound) WriteResponse(rw http.ResponseWriter, producer r
|
|||||||
rw.WriteHeader(404)
|
rw.WriteHeader(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetPasswordUnprocessableEntityCode is the HTTP code returned for type ResetPasswordUnprocessableEntity
|
||||||
|
const ResetPasswordUnprocessableEntityCode int = 422
|
||||||
|
|
||||||
|
/*
|
||||||
|
ResetPasswordUnprocessableEntity password validation failure
|
||||||
|
|
||||||
|
swagger:response resetPasswordUnprocessableEntity
|
||||||
|
*/
|
||||||
|
type ResetPasswordUnprocessableEntity struct {
|
||||||
|
|
||||||
|
/*
|
||||||
|
In: Body
|
||||||
|
*/
|
||||||
|
Payload rest_model_zrok.ErrorMessage `json:"body,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResetPasswordUnprocessableEntity creates ResetPasswordUnprocessableEntity with default headers values
|
||||||
|
func NewResetPasswordUnprocessableEntity() *ResetPasswordUnprocessableEntity {
|
||||||
|
|
||||||
|
return &ResetPasswordUnprocessableEntity{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPayload adds the payload to the reset password unprocessable entity response
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) WithPayload(payload rest_model_zrok.ErrorMessage) *ResetPasswordUnprocessableEntity {
|
||||||
|
o.Payload = payload
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPayload sets the payload to the reset password unprocessable entity response
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) SetPayload(payload rest_model_zrok.ErrorMessage) {
|
||||||
|
o.Payload = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteResponse to the client
|
||||||
|
func (o *ResetPasswordUnprocessableEntity) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ResetPasswordInternalServerErrorCode is the HTTP code returned for type ResetPasswordInternalServerError
|
// ResetPasswordInternalServerErrorCode is the HTTP code returned for type ResetPasswordInternalServerError
|
||||||
const ResetPasswordInternalServerErrorCode int = 500
|
const ResetPasswordInternalServerErrorCode int = 500
|
||||||
|
|
||||||
|
@ -72,6 +72,10 @@ paths:
|
|||||||
$ref: "#/definitions/registerResponse"
|
$ref: "#/definitions/registerResponse"
|
||||||
404:
|
404:
|
||||||
description: request not found
|
description: request not found
|
||||||
|
422:
|
||||||
|
description: password validation failure
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/errorMessage'
|
||||||
500:
|
500:
|
||||||
description: internal server error
|
description: internal server error
|
||||||
|
|
||||||
@ -90,6 +94,10 @@ paths:
|
|||||||
description: password reset
|
description: password reset
|
||||||
404:
|
404:
|
||||||
description: request not found
|
description: request not found
|
||||||
|
422:
|
||||||
|
description: password validation failure
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/errorMessage'
|
||||||
500:
|
500:
|
||||||
description: internal server error
|
description: internal server error
|
||||||
|
|
||||||
@ -678,6 +686,8 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
inviteTokenContact:
|
inviteTokenContact:
|
||||||
type: string
|
type: string
|
||||||
|
passwordRequirements:
|
||||||
|
$ref: "#/definitions/passwordRequirements"
|
||||||
|
|
||||||
createFrontendRequest:
|
createFrontendRequest:
|
||||||
type: object
|
type: object
|
||||||
@ -840,6 +850,20 @@ definitions:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/definitions/environmentAndResources"
|
$ref: "#/definitions/environmentAndResources"
|
||||||
|
|
||||||
|
passwordRequirements:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
length:
|
||||||
|
type: integer
|
||||||
|
requireCapital:
|
||||||
|
type: boolean
|
||||||
|
requireNumeric:
|
||||||
|
type: boolean
|
||||||
|
requireSpecial:
|
||||||
|
type: boolean
|
||||||
|
validSpecialCharacters:
|
||||||
|
type: string
|
||||||
|
|
||||||
principal:
|
principal:
|
||||||
type: object
|
type: object
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
* @property {boolean} invitesOpen
|
* @property {boolean} invitesOpen
|
||||||
* @property {boolean} requiresInviteToken
|
* @property {boolean} requiresInviteToken
|
||||||
* @property {string} inviteTokenContact
|
* @property {string} inviteTokenContact
|
||||||
|
* @property {module:types.passwordRequirements} passwordRequirements
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -166,6 +167,17 @@
|
|||||||
* @property {module:types.environmentAndResources[]} environments
|
* @property {module:types.environmentAndResources[]} environments
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef passwordRequirements
|
||||||
|
* @memberof module:types
|
||||||
|
*
|
||||||
|
* @property {number} length
|
||||||
|
* @property {boolean} requireCapital
|
||||||
|
* @property {boolean} requireNumeric
|
||||||
|
* @property {boolean} requireSpecial
|
||||||
|
* @property {string} validSpecialCharacters
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef principal
|
* @typedef principal
|
||||||
* @memberof module:types
|
* @memberof module:types
|
||||||
|
99
ui/src/components/password.js
Normal file
99
ui/src/components/password.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import React, {useEffect, useState, Fragment} from "react";
|
||||||
|
import {Button, Container, Form, Row} from "react-bootstrap";
|
||||||
|
|
||||||
|
const PasswordForm = (props) => {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [confirm, setConfirm] = useState('');
|
||||||
|
|
||||||
|
const passwordMismatchMessage = <h2 className={"errorMessage"}>Entered passwords do not match!</h2>
|
||||||
|
const passwordTooShortMessage = <h2 className={"errorMessage"}>Entered password too short! ({props.passwordLength} characters, minimum)</h2>
|
||||||
|
const passwordRequiresCapitalMessage = <h2 className={"errorMessage"}>Entered password requires a capital letter!</h2>
|
||||||
|
const passwordRequiresNumericMessage = <h2 className={"errorMessage"}>Entered password requires a digit!</h2>
|
||||||
|
const passwordRequiresSpecialMessage = <h2 className={"errorMessage"}>Entered password requires a special character! ({props.passwordValidSpecialCharacters.split("").join(" ")})</h2>
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (confirm === "" && password === "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (password.length < props.passwordLength) {
|
||||||
|
props.setMessage(passwordTooShortMessage)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.passwordRequireCapital && !/[A-Z]/.test(password)) {
|
||||||
|
props.setMessage(passwordRequiresCapitalMessage)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.passwordRequireNumeric && !/\d/.test(password)) {
|
||||||
|
props.setMessage(passwordRequiresNumericMessage)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.passwordRequireSpecial) {
|
||||||
|
if (!props.passwordValidSpecialCharacters.split("").some(v => password.includes(v))) {
|
||||||
|
props.setMessage(passwordRequiresSpecialMessage)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (confirm !== password) {
|
||||||
|
props.setMessage(passwordMismatchMessage)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
props.setParentPassword(password)
|
||||||
|
}, [password, confirm])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{
|
||||||
|
(props.passwordLength > 0 || props.passwordRequireCapital || props.passwordRequireNumeric || props.passwordRequireSpecial) &&
|
||||||
|
<h2>Password Requirements</h2>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.passwordLength > 0 &&
|
||||||
|
<Row>Minimum Length of {props.passwordLength} </Row>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.passwordRequireCapital &&
|
||||||
|
<Row>Requires at least 1 Capital Letter</Row>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.passwordRequireNumeric &&
|
||||||
|
<Row>Requires at least 1 Digit</Row>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.passwordRequireSpecial &&
|
||||||
|
<Fragment>
|
||||||
|
<Row>Requires at least 1 Special Character</Row>
|
||||||
|
<Row>{props.passwordValidSpecialCharacters.split("").join(" ")}</Row>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
<Container className={"fullscreen-body"}>
|
||||||
|
<Form.Group controlId={"password"}>
|
||||||
|
<Form.Control
|
||||||
|
type={"password"}
|
||||||
|
placeholder={"Set Password"}
|
||||||
|
onChange={t => { props.setMessage(null); setPassword(t.target.value);}}
|
||||||
|
value={password}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group controlId={"confirm"}>
|
||||||
|
<Form.Control
|
||||||
|
type={"password"}
|
||||||
|
placeholder={"Confirm Password"}
|
||||||
|
onChange={t => { props.setMessage(null); setConfirm(t.target.value);}}
|
||||||
|
value={confirm}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
</Container>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
PasswordForm.defaultProps = {
|
||||||
|
passwordLength: 0,
|
||||||
|
passwordRequireCapital: false,
|
||||||
|
passwordRequireNumeric: false,
|
||||||
|
passwordRequireSpecial: false,
|
||||||
|
passwordValidSpecialCharacters: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PasswordForm;
|
@ -3,26 +3,33 @@ import * as account from "../api/account";
|
|||||||
import * as metadata from "../api/metadata"
|
import * as metadata from "../api/metadata"
|
||||||
import Success from "./Success";
|
import Success from "./Success";
|
||||||
import {Button, Container, Form, Row} from "react-bootstrap";
|
import {Button, Container, Form, Row} from "react-bootstrap";
|
||||||
|
import PasswordForm from "../components/password";
|
||||||
|
|
||||||
const SetPasswordForm = (props) => {
|
const SetPasswordForm = (props) => {
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [confirm, setConfirm] = useState('');
|
|
||||||
const [message, setMessage] = useState();
|
const [message, setMessage] = useState();
|
||||||
const [authToken, setAuthToken] = useState('');
|
const [authToken, setAuthToken] = useState('');
|
||||||
const [complete, setComplete] = useState(false);
|
const [complete, setComplete] = useState(false);
|
||||||
const [tou, setTou] = useState();
|
const [tou, setTou] = useState();
|
||||||
|
const [passwordLength, setPasswordLength] = useState(10);
|
||||||
|
const [passwordRequireCapital, setPasswordRequireCapital] = useState(true);
|
||||||
|
const [passwordRequireNumeric, setPasswordRequireNumeric] = useState(true);
|
||||||
|
const [passwordRequireSpecial, setPasswordRequireSpecial] = useState(true);
|
||||||
|
const [passwordValidSpecialCharacters, setPasswordValidSpecialCharacters] = useState("");
|
||||||
|
|
||||||
const passwordMismatchMessage = <h2 className={"errorMessage"}>Entered passwords do not match!</h2>
|
|
||||||
const passwordTooShortMessage = <h2 className={"errorMessage"}>Entered password too short! (4 characters, minimum)</h2>
|
|
||||||
const registerFailed = <h2 className={"errorMessage"}>Account creation failed!</h2>
|
const registerFailed = <h2 className={"errorMessage"}>Account creation failed!</h2>
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
metadata.configuration().then(resp => {
|
metadata.configuration().then(resp => {
|
||||||
console.log(resp)
|
|
||||||
if(!resp.error) {
|
if(!resp.error) {
|
||||||
if (resp.data.touLink !== null && resp.data.touLink.trim() !== "") {
|
if (resp.data.touLink !== null && resp.data.touLink.trim() !== "") {
|
||||||
setTou(resp.data.touLink)
|
setTou(resp.data.touLink)
|
||||||
}
|
}
|
||||||
|
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 => {
|
}).catch(err => {
|
||||||
console.log("err", err);
|
console.log("err", err);
|
||||||
@ -31,14 +38,7 @@ const SetPasswordForm = (props) => {
|
|||||||
|
|
||||||
const handleSubmit = async e => {
|
const handleSubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if(confirm.length < 4) {
|
if (password !== undefined && password !== "") {
|
||||||
setMessage(passwordTooShortMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(confirm !== password) {
|
|
||||||
setMessage(passwordMismatchMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
account.register({body: {"token": props.token, "password": password}})
|
account.register({body: {"token": props.token, "password": password}})
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if(!resp.error) {
|
if(!resp.error) {
|
||||||
@ -54,6 +54,7 @@ const SetPasswordForm = (props) => {
|
|||||||
console.log("resp", resp);
|
console.log("resp", resp);
|
||||||
setMessage(registerFailed);
|
setMessage(registerFailed);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!complete) {
|
if(!complete) {
|
||||||
@ -72,23 +73,14 @@ const SetPasswordForm = (props) => {
|
|||||||
<Container className={"fullscreen-form"}>
|
<Container className={"fullscreen-form"}>
|
||||||
<Row>
|
<Row>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<Form.Group controlId={"password"}>
|
<PasswordForm
|
||||||
<Form.Control
|
setMessage={setMessage}
|
||||||
type={"password"}
|
passwordLength={passwordLength}
|
||||||
placeholder={"Set Password"}
|
passwordRequireCapital={passwordRequireCapital}
|
||||||
onChange={t => { setMessage(null); setPassword(t.target.value); }}
|
passwordRequireNumeric={passwordRequireNumeric}
|
||||||
value={password}
|
passwordRequireSpecial={passwordRequireSpecial}
|
||||||
/>
|
passwordValidSpecialCharacters={passwordValidSpecialCharacters}
|
||||||
</Form.Group>
|
setParentPassword={setPassword}/>
|
||||||
|
|
||||||
<Form.Group controlId={"confirm"}>
|
|
||||||
<Form.Control
|
|
||||||
type={"password"}
|
|
||||||
placeholder={"Confirm Password"}
|
|
||||||
onChange={t => { setMessage(null); setConfirm(t.target.value); }}
|
|
||||||
value={confirm}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
<Button variant={"light"} type={"submit"}>Register Account</Button>
|
<Button variant={"light"} type={"submit"}>Register Account</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -1,29 +1,39 @@
|
|||||||
import {useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import * as account from '../api/account';
|
import * as account from '../api/account';
|
||||||
|
import * as metadata from "../api/metadata"
|
||||||
import {Button, Container, Form, Row} from "react-bootstrap";
|
import {Button, Container, Form, Row} from "react-bootstrap";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import PasswordForm from "../components/password";
|
||||||
|
|
||||||
const SetNewPassword = (props) => {
|
const SetNewPassword = (props) => {
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [confirm, setConfirm] = useState('');
|
|
||||||
const [message, setMessage] = useState();
|
const [message, setMessage] = useState();
|
||||||
const [complete, setComplete] = useState(false);
|
const [complete, setComplete] = useState(false);
|
||||||
|
const [passwordLength, setPasswordLength] = useState(10);
|
||||||
const passwordMismatchMessage = <h2 className={"errorMessage"}>Entered passwords do not match!</h2>
|
const [passwordRequireCapital, setPasswordRequireCapital] = useState(true);
|
||||||
const passwordTooShortMessage = <h2 className={"errorMessage"}>Entered password too short! (4 characters, minimum)</h2>
|
const [passwordRequireNumeric, setPasswordRequireNumeric] = useState(true);
|
||||||
|
const [passwordRequireSpecial, setPasswordRequireSpecial] = useState(true);
|
||||||
|
const [passwordValidSpecialCharacters, setPasswordValidSpecialCharacters] = useState("");
|
||||||
|
|
||||||
const errorMessage = <h2 className={"errorMessage"}>Reset Password Failed!</h2>;
|
const errorMessage = <h2 className={"errorMessage"}>Reset Password Failed!</h2>;
|
||||||
|
|
||||||
|
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("err", err);
|
||||||
|
});
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleSubmit = async e => {
|
const handleSubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if(confirm.length < 4) {
|
if (password !== undefined && password !== "") {
|
||||||
setMessage(passwordTooShortMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(confirm !== password) {
|
|
||||||
setMessage(passwordMismatchMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
account.resetPassword({body: {"token": props.token, "password": password}})
|
account.resetPassword({body: {"token": props.token, "password": password}})
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if(!resp.error) {
|
if(!resp.error) {
|
||||||
@ -36,6 +46,7 @@ const SetNewPassword = (props) => {
|
|||||||
.catch(resp => {
|
.catch(resp => {
|
||||||
setMessage(errorMessage);
|
setMessage(errorMessage);
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!complete) {
|
if(!complete) {
|
||||||
@ -51,23 +62,14 @@ const SetNewPassword = (props) => {
|
|||||||
<Container className={"fullscreen-form"}>
|
<Container className={"fullscreen-form"}>
|
||||||
<Row>
|
<Row>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<Form.Group controlId={"password"}>
|
<PasswordForm
|
||||||
<Form.Control
|
setMessage={setMessage}
|
||||||
type={"password"}
|
passwordLength={passwordLength}
|
||||||
placeholder={"Set Password"}
|
passwordRequireCapital={passwordRequireCapital}
|
||||||
onChange={t => { setMessage(null); setPassword(t.target.value); }}
|
passwordRequireNumeric={passwordRequireNumeric}
|
||||||
value={password}
|
passwordRequireSpecial={passwordRequireSpecial}
|
||||||
/>
|
passwordValidSpecialCharacters={passwordValidSpecialCharacters}
|
||||||
</Form.Group>
|
setParentPassword={setPassword}/>
|
||||||
|
|
||||||
<Form.Group controlId={"confirm"}>
|
|
||||||
<Form.Control
|
|
||||||
type={"password"}
|
|
||||||
placeholder={"Confirm Password"}
|
|
||||||
onChange={t => { setMessage(null); setConfirm(t.target.value); }}
|
|
||||||
value={confirm}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Button variant={"light"} type={"submit"}>Reset Password</Button>
|
<Button variant={"light"} type={"submit"}>Reset Password</Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
Loading…
Reference in New Issue
Block a user