From 518d6cd7da411c7a2efa0574eb02bd9c9afd3510 Mon Sep 17 00:00:00 2001 From: Bill Parrott Date: Thu, 1 Dec 2022 21:44:10 -0600 Subject: [PATCH] feat(themes): add support for WebAuthN-related theme files (fixes #159) (#162) * feat(themes): add support for WebAuthN-related theme files (fixes #159) * docs: update docs for new theme files --- docs/resources/theme.md | 12 +++ fusionauth/resource_fusionauth_themes.go | 66 ++++++++++++++ fusionauth/resource_fusionauth_themes_test.go | 90 ++++++++++++------- go.mod | 2 +- go.sum | 6 +- 5 files changed, 138 insertions(+), 38 deletions(-) diff --git a/docs/resources/theme.md b/docs/resources/theme.md index f13a96f..d7e68d0 100644 --- a/docs/resources/theme.md +++ b/docs/resources/theme.md @@ -16,6 +16,9 @@ resource "fusionauth_theme" "mytheme" { account_two_factor_disable = "[#ftl/]" account_two_factor_enable = "[#ftl/]" account_two_factor_index = "[#ftl/]" + account_webauthn_add = "[#ftl/]" + account_webauthn_delete = "[#ftl/]" + account_webauthn_index = "[#ftl/]" email_complete = "[#ftl/]" email_sent = "[#ftl/]" email_verification_required = "[#ftl/]" @@ -37,6 +40,9 @@ resource "fusionauth_theme" "mytheme" { oauth2_two_factor = "[#ftl/]" oauth2_two_factor_methods = "[#ftl/]" oauth2_wait = "[#ftl/]" + oauth2_webauthn = "[#ftl/]" + oauth2_webauthn_reauth = "[#ftl/]" + oauth2_webauthn_reauth_enable = "[#ftl/]" password_change = "[#ftl/]" password_complete = "[#ftl/]" password_forgot = "[#ftl/]" @@ -66,6 +72,9 @@ resource "fusionauth_theme" "mytheme" { * `account_two_factor_disable` - (Optional) A FreeMarker template that is rendered when the user requests the /account/two-factor/disable path. This page contains a form that accepts a verification code used to disable a multi-factor authentication method. * `account_two_factor_enable` - (Optional) A FreeMarker template that is rendered when the user requests the /account/two-factor/enable path. This page contains a form that accepts a verification code used to enable a multi-factor authentication method. Additionally, this page contains presentation of recovery codes when a user enables multi-factor authentication for the first time. * `account_two_factor_index` - (Optional) A FreeMarker template that is rendered when the user requests the /account/two-factor path. This page displays an authenticated user’s configured multi-factor authentication methods. Additionally, it provides links to enable and disable a method. +* `account_webauthn_add` - (Optional) A FreeMarker template that is rendered when the user requests the /account/webauthn/add path. This page contains a form that allows a user to register a new WebAuthn passkey. +* `account_webauthn_delete` - (Optional) A FreeMarker template that is rendered when the user requests the /account/webauthn/delete path. This page contains a form that allows a user to delete a WebAuthn passkey. +* `account_webauthn_index` - (Optional) A FreeMarker template that is rendered when the user requests the /account/webauthn/ path. This page displays an authenticated user’s registered WebAuthn passkeys. Additionally, it provides links to delete an existing passkey and register a new passkey. * `email_complete` - (Optional) A FreeMarker template that is rendered when the user requests the /email/complete path. This page is used after a user has verified their email address by clicking the URL in the email. After FusionAuth has updated their user object to indicate that their email was verified, the browser is redirected to this page. * `email_sent` - (Optional) A FreeMarker template that is rendered when the user requests the /email/sent path. This page is used after a user has asked for the verification email to be resent. This can happen if the URL in the email expired and the user clicked it. In this case, the user can provide their email address again and FusionAuth will resend the email. After the user submits their email and FusionAuth re-sends a verification email to them, the browser is redirected to this page. * `email_verification_required` - (Optional) A FreeMarker template that is rendered when the user requests the /email/verification-required path. This page is rendered when a user is required to verify their email address prior to being allowed to proceed with login. This occurs when Unverified behavior is set to Gated in email verification settings on the Tenant. @@ -87,6 +96,9 @@ resource "fusionauth_theme" "mytheme" { * `oauth2_two_factor` - (Optional) A FreeMarker template that is rendered when the user requests the /oauth2/two-factor path. This page is used if the user has two-factor authentication enabled and they need to type in their code again. FusionAuth will properly handle the processing on the back end. This page contains the form that the user will put their code into. * `oauth2_two_factor_methods` - (Optional) A FreeMarker template that is rendered when the user requests the /oauth2/two-factor-methods path. This page contains a form providing a user with their configured multi-factor authentication options that they may use to complete the authentication challenge. * `oauth2_wait` - (Optional) A FreeMarker template that is rendered when the user requests the /oauth2/wait path. This page is rendered when FusionAuth is waiting for an external provider to complete an out of band authentication request. For example, during a HYPR login this page will be displayed until the user completes authentication. +* `oauth2_webauthn` - (Optional) A FreeMarker template that is rendered when the user requests the /oauth2/webauthn path. This page contains a form where a user can enter their loginId (username or email address) to authenticate with one of their registered WebAuthn passkeys. This page uses the WebAuthn bootstrap workflow. +* `oauth2_webauthn_reauth` - (Optional) A FreeMarker template that is rendered when the user requests the /oauth2/webauthn-reauth path. This page contains a form that lists the WebAuthn passkeys currently available for re-authentication. A user can select one of the listed passkeys to authenticate using the corresponding passkey and user account. +* `oauth2_webauthn_reauth_enable` - (Optional) A FreeMarker template that is rendered when the user requests the /oauth2/webauthn-reauth-enable path. This page contains two forms. One allows the user to select one of their existing WebAuthn passkeys to use for re-authentication. The other allows the user to register a new WebAuthn passkey for re-authentication. * `password_change` - (Optional) A FreeMarker template that is rendered when the user requests the /password/change path. This page is used if the user is required to change their password or if they have requested a password reset. This page contains the form that allows the user to provide a new password. * `password_complete` - (Optional) A FreeMarker template that is rendered when the user requests the /password/complete path. This page is used after the user has successfully updated their password, or reset it. This page should instruct the user that their password was updated and that they need to login again. * `password_forgot` - (Optional) A FreeMarker template that is rendered when the user requests the /password/forgot path. This page is used when a user starts the forgot password workflow. This page renders the form where the user types in their email address. diff --git a/fusionauth/resource_fusionauth_themes.go b/fusionauth/resource_fusionauth_themes.go index 9255baf..ab0952e 100644 --- a/fusionauth/resource_fusionauth_themes.go +++ b/fusionauth/resource_fusionauth_themes.go @@ -83,6 +83,27 @@ func newTheme() *schema.Resource { Description: "A FreeMarker template that is rendered when the user requests the /account/two-factor path. This page displays an authenticated user’s configured multi-factor authentication methods. Additionally, it provides links to enable and disable a method.", DiffSuppressFunc: diffSuppressTemplate, }, + "account_webauthn_add": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "A FreeMarker template that is rendered when the user requests the /account/webauthn/add path. This page contains a form that allows a user to register a new WebAuthn passkey.", + DiffSuppressFunc: diffSuppressTemplate, + }, + "account_webauthn_delete": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "A FreeMarker template that is rendered when the user requests the /account/webauthn/delete path. This page contains a form that allows a user to delete a WebAuthn passkey.", + DiffSuppressFunc: diffSuppressTemplate, + }, + "account_webauthn_index": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "A FreeMarker template that is rendered when the user requests the /account/webauthn/ path. This page displays an authenticated user’s registered WebAuthn passkeys. Additionally, it provides links to delete an existing passkey and register a new passkey.", + DiffSuppressFunc: diffSuppressTemplate, + }, "email_complete": { Type: schema.TypeString, Optional: true, @@ -230,6 +251,27 @@ func newTheme() *schema.Resource { Description: "A FreeMarker template that is rendered when the user requests the /oauth2/wait path. This page is rendered when FusionAuth is waiting for an external provider to complete an out of band authentication request. For example, during a HYPR login this page will be displayed until the user completes authentication.", DiffSuppressFunc: diffSuppressTemplate, }, + "oauth2_webauthn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "A FreeMarker template that is rendered when the user requests the /oauth2/webauthn path. This page contains a form where a user can enter their loginId (username or email address) to authenticate with one of their registered WebAuthn passkeys. This page uses the WebAuthn bootstrap workflow.", + DiffSuppressFunc: diffSuppressTemplate, + }, + "oauth2_webauthn_reauth": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "A FreeMarker template that is rendered when the user requests the /oauth2/webauthn-reauth path. This page contains a form that lists the WebAuthn passkeys currently available for re-authentication. A user can select one of the listed passkeys to authenticate using the corresponding passkey and user account.", + DiffSuppressFunc: diffSuppressTemplate, + }, + "oauth2_webauthn_reauth_enable": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "A FreeMarker template that is rendered when the user requests the /oauth2/webauthn-reauth-enable path. This page contains two forms. One allows the user to select one of their existing WebAuthn passkeys to use for re-authentication. The other allows the user to register a new WebAuthn passkey for re-authentication.", + DiffSuppressFunc: diffSuppressTemplate, + }, "password_change": { Type: schema.TypeString, Optional: true, @@ -337,6 +379,9 @@ func buildTheme(data *schema.ResourceData) fusionauth.Theme { AccountTwoFactorDisable: data.Get("account_two_factor_disable").(string), AccountTwoFactorEnable: data.Get("account_two_factor_enable").(string), AccountTwoFactorIndex: data.Get("account_two_factor_index").(string), + AccountWebAuthnAdd: data.Get("account_webauthn_add").(string), + AccountWebAuthnDelete: data.Get("account_webauthn_delete").(string), + AccountWebAuthnIndex: data.Get("account_webauthn_index").(string), EmailComplete: data.Get("email_complete").(string), EmailSend: data.Get("email_send").(string), EmailSent: data.Get("email_sent").(string), @@ -359,6 +404,9 @@ func buildTheme(data *schema.ResourceData) fusionauth.Theme { Oauth2TwoFactor: data.Get("oauth2_two_factor").(string), Oauth2TwoFactorMethods: data.Get("oauth2_two_factor_methods").(string), Oauth2Wait: data.Get("oauth2_wait").(string), + Oauth2WebAuthn: data.Get("oauth2_webauthn").(string), + Oauth2WebAuthnReauth: data.Get("oauth2_webauthn_reauth").(string), + Oauth2WebAuthnReauthEnable: data.Get("oauth2_webauthn_reauth_enable").(string), PasswordChange: data.Get("password_change").(string), PasswordComplete: data.Get("password_complete").(string), PasswordForgot: data.Get("password_forgot").(string), @@ -494,6 +542,15 @@ func buildResourceDataFromTheme(t fusionauth.Theme, data *schema.ResourceData) d if err := data.Set("account_two_factor_index", t.Templates.AccountTwoFactorIndex); err != nil { return diag.Errorf("theme.account_two_factor_index: %s", err.Error()) } + if err := data.Set("account_webauthn_add", t.Templates.AccountWebAuthnAdd); err != nil { + return diag.Errorf("theme.account_webauthn_add: %s", err.Error()) + } + if err := data.Set("account_webauthn_delete", t.Templates.AccountWebAuthnDelete); err != nil { + return diag.Errorf("theme.account_webauthn_delete: %s", err.Error()) + } + if err := data.Set("account_webauthn_index", t.Templates.AccountWebAuthnIndex); err != nil { + return diag.Errorf("theme.account_webauthn_index: %s", err.Error()) + } if err := data.Set("email_complete", t.Templates.EmailComplete); err != nil { return diag.Errorf("theme.email_complete: %s", err.Error()) } @@ -548,6 +605,15 @@ func buildResourceDataFromTheme(t fusionauth.Theme, data *schema.ResourceData) d if err := data.Set("oauth2_wait", t.Templates.Oauth2Wait); err != nil { return diag.Errorf("theme.oauth2_wait: %s", err.Error()) } + if err := data.Set("oauth2_webauthn", t.Templates.Oauth2WebAuthn); err != nil { + return diag.Errorf("theme.oauth2_webauthn: %s", err.Error()) + } + if err := data.Set("oauth2_webauthn_reauth", t.Templates.Oauth2WebAuthnReauth); err != nil { + return diag.Errorf("theme.oauth2_webauthn_reauth: %s", err.Error()) + } + if err := data.Set("oauth2_webauthn_reauth_enable", t.Templates.Oauth2WebAuthnReauthEnable); err != nil { + return diag.Errorf("theme.oauth2_webauthn_reauth_enable: %s", err.Error()) + } if err := data.Set("password_change", t.Templates.PasswordChange); err != nil { return diag.Errorf("theme.password_change: %s", err.Error()) } diff --git a/fusionauth/resource_fusionauth_themes_test.go b/fusionauth/resource_fusionauth_themes_test.go index a4739d8..a98e7af 100644 --- a/fusionauth/resource_fusionauth_themes_test.go +++ b/fusionauth/resource_fusionauth_themes_test.go @@ -78,6 +78,9 @@ func testThemeAccTestCheckFuncs( resource.TestCheckResourceAttr(tfResourcePath, "account_two_factor_disable", templates.AccountTwoFactorDisable), resource.TestCheckResourceAttr(tfResourcePath, "account_two_factor_enable", templates.AccountTwoFactorEnable), resource.TestCheckResourceAttr(tfResourcePath, "account_two_factor_index", templates.AccountTwoFactorIndex), + resource.TestCheckResourceAttr(tfResourcePath, "account_webauthn_add", templates.AccountWebAuthnAdd), + resource.TestCheckResourceAttr(tfResourcePath, "account_webauthn_delete", templates.AccountWebAuthnDelete), + resource.TestCheckResourceAttr(tfResourcePath, "account_webauthn_index", templates.AccountWebAuthnIndex), resource.TestCheckResourceAttr(tfResourcePath, "email_complete", templates.EmailComplete), // resource.TestCheckResourceAttr(tfResourcePath, "email_send", startTemplates.EmailSend), // DEPRECATED resource.TestCheckResourceAttr(tfResourcePath, "email_sent", templates.EmailSent), @@ -100,6 +103,9 @@ func testThemeAccTestCheckFuncs( resource.TestCheckResourceAttr(tfResourcePath, "oauth2_two_factor", templates.Oauth2TwoFactor), resource.TestCheckResourceAttr(tfResourcePath, "oauth2_two_factor_methods", templates.Oauth2TwoFactorMethods), resource.TestCheckResourceAttr(tfResourcePath, "oauth2_wait", templates.Oauth2Wait), + resource.TestCheckResourceAttr(tfResourcePath, "oauth2_webauthn", templates.Oauth2WebAuthn), + resource.TestCheckResourceAttr(tfResourcePath, "oauth2_webauthn_reauth", templates.Oauth2WebAuthnReauth), + resource.TestCheckResourceAttr(tfResourcePath, "oauth2_webauthn_reauth_enable", templates.Oauth2WebAuthnReauthEnable), resource.TestCheckResourceAttr(tfResourcePath, "password_change", templates.PasswordChange), resource.TestCheckResourceAttr(tfResourcePath, "password_complete", templates.PasswordComplete), resource.TestCheckResourceAttr(tfResourcePath, "password_forgot", templates.PasswordForgot), @@ -180,6 +186,9 @@ func generateFusionAuthTemplate() fusionauth.Templates { AccountTwoFactorDisable: randString20(), AccountTwoFactorEnable: randString20(), AccountTwoFactorIndex: randString20(), + AccountWebAuthnAdd: randString20(), + AccountWebAuthnDelete: randString20(), + AccountWebAuthnIndex: randString20(), EmailComplete: randString20(), EmailSent: randString20(), EmailVerificationRequired: randString20(), @@ -201,6 +210,9 @@ func generateFusionAuthTemplate() fusionauth.Templates { Oauth2TwoFactor: randString20(), Oauth2TwoFactorMethods: randString20(), Oauth2Wait: randString20(), + Oauth2WebAuthn: randString20(), + Oauth2WebAuthnReauth: randString20(), + Oauth2WebAuthnReauthEnable: randString20(), PasswordChange: randString20(), PasswordComplete: randString20(), PasswordForgot: randString20(), @@ -238,41 +250,47 @@ resource "fusionauth_theme" "test_%[1]s" { account_two_factor_disable = "%[6]s" account_two_factor_enable = "%[7]s" account_two_factor_index = "%[8]s" - email_complete = "%[9]s" - email_sent = "%[10]s" - email_verification_required = "%[11]s" - email_verify = "%[12]s" - helpers = "%[13]s" - index = "%[14]s" - oauth2_authorize = "%[15]s" - oauth2_authorized_not_registered = "%[16]s" - oauth2_child_registration_not_allowed = "%[17]s" - oauth2_child_registration_not_allowed_complete = "%[18]s" - oauth2_complete_registration = "%[19]s" - oauth2_device = "%[20]s" - oauth2_device_complete = "%[21]s" - oauth2_error = "%[22]s" - oauth2_logout = "%[23]s" - oauth2_passwordless = "%[24]s" - oauth2_register = "%[25]s" - oauth2_start_idp_link = "%[26]s" - oauth2_two_factor = "%[27]s" - oauth2_two_factor_methods = "%[28]s" - oauth2_wait = "%[29]s" - password_change = "%[30]s" - password_complete = "%[31]s" - password_forgot = "%[32]s" - password_sent = "%[33]s" - registration_complete = "%[34]s" - registration_sent = "%[35]s" - registration_verification_required = "%[36]s" - registration_verify = "%[37]s" - samlv2_logout = "%[38]s" - unauthorized = "%[39]s" + account_webauthn_add = "%[9]s" + account_webauthn_delete = "%[10]s" + account_webauthn_index = "%[11]s" + email_complete = "%[12]s" + email_sent = "%[13]s" + email_verification_required = "%[14]s" + email_verify = "%[15]s" + helpers = "%[16]s" + index = "%[17]s" + oauth2_authorize = "%[18]s" + oauth2_authorized_not_registered = "%[19]s" + oauth2_child_registration_not_allowed = "%[20]s" + oauth2_child_registration_not_allowed_complete = "%[21]s" + oauth2_complete_registration = "%[22]s" + oauth2_device = "%[23]s" + oauth2_device_complete = "%[24]s" + oauth2_error = "%[25]s" + oauth2_logout = "%[26]s" + oauth2_passwordless = "%[27]s" + oauth2_register = "%[28]s" + oauth2_start_idp_link = "%[29]s" + oauth2_two_factor = "%[30]s" + oauth2_two_factor_methods = "%[31]s" + oauth2_wait = "%[32]s" + oauth2_webauthn = "%[33]s" + oauth2_webauthn_reauth = "%[34]s" + oauth2_webauthn_reauth_enable = "%[35]s" + password_change = "%[36]s" + password_complete = "%[37]s" + password_forgot = "%[38]s" + password_sent = "%[39]s" + registration_complete = "%[40]s" + registration_sent = "%[41]s" + registration_verification_required = "%[42]s" + registration_verify = "%[43]s" + samlv2_logout = "%[44]s" + unauthorized = "%[45]s" # Deprecated Properties - email_send = "%[40]s" - registration_send = "%[41]s" + email_send = "%[46]s" + registration_send = "%[47]s" } `, resourceName, @@ -283,6 +301,9 @@ resource "fusionauth_theme" "test_%[1]s" { templates.AccountTwoFactorDisable, templates.AccountTwoFactorEnable, templates.AccountTwoFactorIndex, + templates.AccountWebAuthnAdd, + templates.AccountWebAuthnDelete, + templates.AccountWebAuthnIndex, templates.EmailComplete, templates.EmailSent, templates.EmailVerificationRequired, @@ -304,6 +325,9 @@ resource "fusionauth_theme" "test_%[1]s" { templates.Oauth2TwoFactor, templates.Oauth2TwoFactorMethods, templates.Oauth2Wait, + templates.Oauth2WebAuthn, + templates.Oauth2WebAuthnReauth, + templates.Oauth2WebAuthnReauthEnable, templates.PasswordChange, templates.PasswordComplete, templates.PasswordForgot, diff --git a/go.mod b/go.mod index 2cf36c5..0335df6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gpsinsight/terraform-provider-fusionauth go 1.18 require ( - github.com/FusionAuth/go-client v0.0.0-20220928202602-4f85e4f2101f + github.com/FusionAuth/go-client v0.0.0-20221122022225-f59b115f1ce4 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/terraform-plugin-sdk/v2 v2.14.0 diff --git a/go.sum b/go.sum index 4938b49..7374c6c 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/FusionAuth/go-client v0.0.0-20220429153148-63e95adfac4e h1:1GkB6QE01BlB7+OCwWBBiUM/xBqMA5vpFgr7C/fF2ck= -github.com/FusionAuth/go-client v0.0.0-20220429153148-63e95adfac4e/go.mod h1:SyRrXMJAzMVQLiJjKfQUR59dRI3jPyZv+BXIZ//HwE4= -github.com/FusionAuth/go-client v0.0.0-20220928202602-4f85e4f2101f h1:a7M72XffsaShvWiJVyCMEXzXJWVU9GzkUrDxVaxlFnQ= -github.com/FusionAuth/go-client v0.0.0-20220928202602-4f85e4f2101f/go.mod h1:SyRrXMJAzMVQLiJjKfQUR59dRI3jPyZv+BXIZ//HwE4= +github.com/FusionAuth/go-client v0.0.0-20221122022225-f59b115f1ce4 h1:CbUKbP2hpU1hCthmn7cUnbdwO54HCZwTYedBVacZ6R4= +github.com/FusionAuth/go-client v0.0.0-20221122022225-f59b115f1ce4/go.mod h1:SyRrXMJAzMVQLiJjKfQUR59dRI3jPyZv+BXIZ//HwE4= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=