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 {
@ -103,7 +109,9 @@ type inviteTui struct {
tokenInput textinput.Model tokenInput textinput.Model
cursorMode textinput.CursorMode cursorMode textinput.CursorMode
done bool done bool
requireToken bool invitesOpen bool
requireInviteToken bool
invitesContact string
maxIndex int maxIndex int
msgOk string msgOk 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

@ -33,6 +33,9 @@ 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
*/ */
/** /**