slight reworking on the configuration-based invite system to include open registration flag, contact details, and store strategy. moved to 'admin' stanza (#229)

This commit is contained in:
Michael Quigley 2023-05-22 15:42:20 -04:00
parent 41c30e4158
commit b334ff50b2
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
10 changed files with 86 additions and 40 deletions

View File

@ -8,6 +8,8 @@ FEATURE: New metrics infrastructure based on OpenZiti usage events (https://gith
FEATURE: New limits implementation based on the new metrics infrastructure (https://github.com/openziti/zrok/issues/235). See the [v0.4 Limits Guide](docs/guides/metrics-and-limits/configuring-limits.md) for more information. FEATURE: New limits implementation based on the new metrics infrastructure (https://github.com/openziti/zrok/issues/235). See the [v0.4 Limits Guide](docs/guides/metrics-and-limits/configuring-limits.md) for more information.
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)
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)

View File

@ -62,7 +62,13 @@ func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) {
} }
if md != nil { if md != nil {
cmd.tui.RequireToken(md.GetPayload().RegistrationRequiresToken) if !md.GetPayload().InvitesOpen {
apiEndpoint, _ := zrd.ApiEndpoint()
tui.Error(fmt.Sprintf("'%v' is not currently accepting new users", apiEndpoint), nil)
}
cmd.tui.invitesOpen = md.GetPayload().InvitesOpen
cmd.tui.RequiresInviteToken(md.GetPayload().RequiresInviteToken)
cmd.tui.invitesContact = md.GetPayload().InviteTokenContact
} }
if _, err := tea.NewProgram(&cmd.tui).Run(); err != nil { if _, err := tea.NewProgram(&cmd.tui).Run(); err != nil {
@ -97,14 +103,16 @@ func (cmd *inviteCommand) endpointError(apiEndpoint, _ string) {
} }
type inviteTui struct { type inviteTui struct {
focusIndex int focusIndex int
msg string msg string
emailInputs []textinput.Model emailInputs []textinput.Model
tokenInput textinput.Model tokenInput textinput.Model
cursorMode textinput.CursorMode cursorMode textinput.CursorMode
done bool done bool
requireToken bool invitesOpen bool
maxIndex int requireInviteToken bool
invitesContact string
maxIndex int
msgOk string msgOk string
msgMismatch string msgMismatch string
@ -208,7 +216,7 @@ func (m *inviteTui) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.emailInputs[i].PromptStyle = m.noStyle m.emailInputs[i].PromptStyle = m.noStyle
m.emailInputs[i].TextStyle = m.noStyle m.emailInputs[i].TextStyle = m.noStyle
} }
if m.requireToken { if m.requireInviteToken {
if m.focusIndex == 2 { if m.focusIndex == 2 {
cmds[2] = m.tokenInput.Focus() cmds[2] = m.tokenInput.Focus()
m.tokenInput.PromptStyle = m.focusedStyle m.tokenInput.PromptStyle = m.focusedStyle
@ -234,7 +242,7 @@ func (m *inviteTui) updateInputs(msg tea.Msg) tea.Cmd {
for i := range m.emailInputs { for i := range m.emailInputs {
m.emailInputs[i], cmds[i] = m.emailInputs[i].Update(msg) m.emailInputs[i], cmds[i] = m.emailInputs[i].Update(msg)
} }
if m.requireToken { if m.requireInviteToken {
m.tokenInput, cmds[2] = m.tokenInput.Update(msg) m.tokenInput, cmds[2] = m.tokenInput.Update(msg)
} }
return tea.Batch(cmds...) return tea.Batch(cmds...)
@ -242,14 +250,19 @@ func (m *inviteTui) updateInputs(msg tea.Msg) tea.Cmd {
func (m inviteTui) View() string { func (m inviteTui) View() string {
var b strings.Builder var b strings.Builder
b.WriteString(fmt.Sprintf("\n%v\n\n", m.msg)) b.WriteString(fmt.Sprintf("\n%v\n\n", m.msg))
if m.requireInviteToken && m.invitesContact != "" {
b.WriteString(fmt.Sprintf("If you don't already have one, request an invite token at: %v\n\n", m.invitesContact))
}
for i := 0; i < len(m.emailInputs); i++ { for i := 0; i < len(m.emailInputs); i++ {
b.WriteString(m.emailInputs[i].View()) b.WriteString(m.emailInputs[i].View())
b.WriteRune('\n') b.WriteRune('\n')
} }
if m.requireToken { if m.requireInviteToken {
b.WriteString(m.tokenInput.View()) b.WriteString(m.tokenInput.View())
b.WriteRune('\n') b.WriteRune('\n')
} }
@ -263,8 +276,8 @@ func (m inviteTui) View() string {
return b.String() return b.String()
} }
func (m *inviteTui) RequireToken(require bool) { func (m *inviteTui) RequiresInviteToken(require bool) {
m.requireToken = require m.requireInviteToken = require
if require { if require {
m.maxIndex = 3 m.maxIndex = 3
} else { } else {

View File

@ -31,8 +31,11 @@ type Config struct {
} }
type AdminConfig struct { type AdminConfig struct {
Secrets []string `cf:"+secret"` Secrets []string `cf:"+secret"`
TouLink string TouLink string
InvitesOpen bool
InviteTokenStrategy string
InviteTokenContact string
} }
type EndpointConfig struct { type EndpointConfig struct {
@ -42,7 +45,6 @@ type EndpointConfig struct {
type RegistrationConfig struct { type RegistrationConfig struct {
RegistrationUrlTemplate string RegistrationUrlTemplate string
TokenStrategy string
} }
type ResetPasswordConfig struct { type ResetPasswordConfig struct {

View File

@ -19,18 +19,14 @@ func newConfigurationHandler(cfg *config.Config) *configurationHandler {
} }
func (ch *configurationHandler) Handle(_ metadata.ConfigurationParams) middleware.Responder { func (ch *configurationHandler) Handle(_ metadata.ConfigurationParams) middleware.Responder {
tou := ""
if cfg.Admin != nil {
tou = cfg.Admin.TouLink
}
tokenRequired := false
if cfg.Registration != nil {
tokenRequired = cfg.Registration.TokenStrategy == "store"
}
data := &rest_model_zrok.Configuration{ data := &rest_model_zrok.Configuration{
Version: build.String(), Version: build.String(),
TouLink: tou, InvitesOpen: cfg.Admin != nil && cfg.Admin.InvitesOpen,
RegistrationRequiresToken: tokenRequired, RequiresInviteToken: cfg.Registration != nil && cfg.Admin.InviteTokenStrategy == "store",
}
if cfg.Admin != nil {
data.TouLink = cfg.Admin.TouLink
data.InviteTokenContact = cfg.Admin.InviteTokenContact
} }
return metadata.NewConfigurationOK().WithPayload(data) return metadata.NewConfigurationOK().WithPayload(data)
} }

View File

@ -38,7 +38,7 @@ func (h *inviteHandler) Handle(params account.InviteParams) middleware.Responder
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
if h.cfg.Registration != nil && h.cfg.Registration.TokenStrategy == "store" { if h.cfg.Admin != nil && h.cfg.Admin.InviteTokenStrategy == "store" {
inviteToken, err := str.FindInviteTokenByToken(params.Body.Token, tx) inviteToken, err := str.FindInviteTokenByToken(params.Body.Token, tx)
if err != nil { if err != nil {
logrus.Errorf("cannot get invite token '%v' for '%v': %v", params.Body.Token, params.Body.Email, err) logrus.Errorf("cannot get invite token '%v' for '%v': %v", params.Body.Token, params.Body.Email, err)

View File

@ -19,9 +19,22 @@ admin:
# #
secrets: secrets:
- 77623cad-1847-4d6d-8ffe-37defc33c909 - 77623cad-1847-4d6d-8ffe-37defc33c909
# if `tou_link` is present, the frontend will display the "Terms of Use" link on the login and registration forms #
# If `tou_link` is present, the frontend will display the "Terms of Use" link on the login and registration forms
# #
tou_link: '<a href="https://google.com" target="_">Terms and Conditions</a>' tou_link: '<a href="https://google.com" target="_">Terms and Conditions</a>'
#
# To allow open invites to your `zrok` instance, set `invites_open` to `true`
#
invites_open: true
#
# Set `token_strategy` to `store` to require an invite token.
#
#token_strategy: store
#
# Set `invite_token_contact` to include an email address or a URL where an invite token can be requested
#
invite_token_contact: invites@zrok.io
# The `bridge` section configures the `zrok controller metrics bridge`, specifying the source and sink where OpenZiti # The `bridge` section configures the `zrok controller metrics bridge`, specifying the source and sink where OpenZiti
# `fabric.usage` events are consumed and then sent into `zrok`. For production environments, we recommend that you use # `fabric.usage` events are consumed and then sent into `zrok`. For production environments, we recommend that you use
@ -131,10 +144,6 @@ metrics:
# #
registration: registration:
registration_url_template: https://zrok.server.com/register registration_url_template: https://zrok.server.com/register
#
# Set `token_strategy` to `store` to require an invite token.
#
#token_strategy: store
# Configure the generated URL for password resets. The reset token will be appended to this URL. # Configure the generated URL for password resets. The reset token will be appended to this URL.
# #

View File

@ -17,8 +17,14 @@ import (
// swagger:model configuration // swagger:model configuration
type Configuration struct { type Configuration struct {
// registration requires token // invite token contact
RegistrationRequiresToken bool `json:"registrationRequiresToken,omitempty"` InviteTokenContact string `json:"inviteTokenContact,omitempty"`
// invites open
InvitesOpen bool `json:"invitesOpen,omitempty"`
// requires invite token
RequiresInviteToken bool `json:"requiresInviteToken,omitempty"`
// tou link // tou link
TouLink string `json:"touLink,omitempty"` TouLink string `json:"touLink,omitempty"`

View File

@ -1056,7 +1056,13 @@ func init() {
"configuration": { "configuration": {
"type": "object", "type": "object",
"properties": { "properties": {
"registrationRequiresToken": { "inviteTokenContact": {
"type": "string"
},
"invitesOpen": {
"type": "boolean"
},
"requiresInviteToken": {
"type": "boolean" "type": "boolean"
}, },
"touLink": { "touLink": {
@ -2611,7 +2617,13 @@ func init() {
"configuration": { "configuration": {
"type": "object", "type": "object",
"properties": { "properties": {
"registrationRequiresToken": { "inviteTokenContact": {
"type": "string"
},
"invitesOpen": {
"type": "boolean"
},
"requiresInviteToken": {
"type": "boolean" "type": "boolean"
}, },
"touLink": { "touLink": {

View File

@ -672,8 +672,12 @@ definitions:
type: string type: string
touLink: touLink:
type: string type: string
registrationRequiresToken: invitesOpen:
type: boolean type: boolean
requiresInviteToken:
type: boolean
inviteTokenContact:
type: string
createFrontendRequest: createFrontendRequest:
type: object type: object

View File

@ -31,7 +31,9 @@
* *
* @property {string} version * @property {string} version
* @property {string} touLink * @property {string} touLink
* @property {boolean} registrationRequiresToken * @property {boolean} invitesOpen
* @property {boolean} requiresInviteToken
* @property {string} inviteTokenContact
*/ */
/** /**