Skip to content

Commit

Permalink
feat: Add Custom Prompt Support (#341)
Browse files Browse the repository at this point in the history
  • Loading branch information
m3talsmith authored Feb 1, 2024
1 parent 4173045 commit f9765fc
Show file tree
Hide file tree
Showing 10 changed files with 1,415 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ vendor/

### Local .env file
.env
.envrc
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
Expand Down
5 changes: 5 additions & 0 deletions management/management.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions management/management.gen_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 123 additions & 1 deletion management/prompt.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package management

import "context"
import (
"context"
"encoding/json"
"fmt"
)

// Prompt is used within the Login Page.
//
Expand All @@ -16,6 +20,73 @@ type Prompt struct {
WebAuthnPlatformFirstFactor *bool `json:"webauthn_platform_first_factor,omitempty"`
}

// PartialsPrompt to be used for Custom Prompt Partials.
//
// See: https://auth0.com/docs/sign-up-prompt-customizations
type PartialsPrompt struct {
FormContentStart string `json:"form-content-start,omitempty"`
FormContentEnd string `json:"form-content-end,omitempty"`
FormFooterStart string `json:"form-footer-start,omitempty"`
FormFooterEnd string `json:"form-footer-end,omitempty"`
SecondaryActionsStart string `json:"secondary-actions-start,omitempty"`
SecondaryActionsEnd string `json:"secondary-actions-end,omitempty"`

// Segment for custom prompt
Segment PartialsPromptSegment `json:"-"`
}

// MarshalJSON implements a custom Marshaler.
func (c *PartialsPrompt) MarshalJSON() ([]byte, error) {
body := map[string]PartialsPrompt{string(c.Segment): *c}
return json.Marshal(body)
}

// UnmarshalJSON implements a custom Unmarshaler.
func (c *PartialsPrompt) UnmarshalJSON(data []byte) error {
var body map[string]struct{ PartialsPrompt }
if err := json.Unmarshal(data, &body); err != nil {
return err
}

for k, v := range body {
*c = v.PartialsPrompt
c.Segment = PartialsPromptSegment(k)
}
return nil
}

// PartialsPromptSegment defines the partials segment that we are managing.
type PartialsPromptSegment string

const (
// PartialsPromptSignup represents the signup segment.
PartialsPromptSignup PartialsPromptSegment = "signup"

// PartialsPromptSignupID represents the signup-id segment.
PartialsPromptSignupID PartialsPromptSegment = "signup-id"

// PartialsPromptSignupPassword represents the signup-password segment.
PartialsPromptSignupPassword PartialsPromptSegment = "signup-password"

// PartialsPromptLogin represents the login segment.
PartialsPromptLogin PartialsPromptSegment = "login"

// PartialsPromptLoginID represents the login-id segment.
PartialsPromptLoginID PartialsPromptSegment = "login-id"

// PartialsPromptLoginPassword represents the login-password segment.
PartialsPromptLoginPassword PartialsPromptSegment = "login-password"
)

var validPartialsPromptSegments = []PartialsPromptSegment{
PartialsPromptSignup,
PartialsPromptSignupID,
PartialsPromptSignupPassword,
PartialsPromptLogin,
PartialsPromptLoginID,
PartialsPromptLoginPassword,
}

// PromptManager is used for managing a Prompt.
type PromptManager manager

Expand Down Expand Up @@ -49,3 +120,54 @@ func (m *PromptManager) SetCustomText(ctx context.Context, p string, l string, b
err = m.management.Request(ctx, "PUT", m.management.URI("prompts", p, "custom-text", l), &b, opts...)
return
}

// CreatePartials creates new custom prompt partials for a given segment.
//
// See: https://auth0.com/docs/sign-up-prompt-customizations#use-the-api-to-edit-custom-prompts
func (m *PromptManager) CreatePartials(ctx context.Context, c *PartialsPrompt, opts ...RequestOption) error {
if err := validatePartialsPromptSegment(c.Segment); err != nil {
return err
}
return m.management.Request(ctx, "PUT", m.management.URI("prompts", string(c.Segment), "partials"), c, opts...)
}

// UpdatePartials updates custom prompt partials for a given segment.
//
// See: https://auth0.com/docs/sign-up-prompt-customizations#use-the-api-to-edit-custom-prompts
func (m *PromptManager) UpdatePartials(ctx context.Context, c *PartialsPrompt, opts ...RequestOption) error {
if err := validatePartialsPromptSegment(c.Segment); err != nil {
return err
}
return m.management.Request(ctx, "PUT", m.management.URI("prompts", string(c.Segment), "partials"), c, opts...)
}

// ReadPartials reads custom prompt partials for a given segment.
//
// See: https://auth0.com/docs/sign-up-prompt-customizations#use-the-api-to-edit-custom-prompts
func (m *PromptManager) ReadPartials(ctx context.Context, segment PartialsPromptSegment, opts ...RequestOption) (c *PartialsPrompt, err error) {
if err := validatePartialsPromptSegment(segment); err != nil {
return nil, err
}
c = &PartialsPrompt{Segment: segment}
err = m.management.Request(ctx, "GET", m.management.URI("prompts", string(segment), "partials"), c, opts...)
return
}

// DeletePartials deletes custom prompt partials for a given segment.
//
// See: https://auth0.com/docs/sign-up-prompt-customizations#use-the-api-to-edit-custom-prompts
func (m *PromptManager) DeletePartials(ctx context.Context, c *PartialsPrompt, opts ...RequestOption) error {
if err := validatePartialsPromptSegment(c.Segment); err != nil {
return err
}
return m.management.Request(ctx, "PUT", m.management.URI("prompts", string(c.Segment), "partials"), &PartialsPrompt{}, opts...)
}

func validatePartialsPromptSegment(segment PartialsPromptSegment) error {
for _, p := range validPartialsPromptSegments {
if p == segment {
return nil
}
}
return fmt.Errorf("invalid custom segment: %s", segment)
}
125 changes: 125 additions & 0 deletions management/prompt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,128 @@ func TestPromptCustomText(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "Welcome", texts["login"].(map[string]interface{})["title"])
}

func TestPromptManager_ReadPartials(t *testing.T) {
configureHTTPTestRecordings(t)

customDomain := givenACustomDomain(t)
_ = givenAUniversalLogin(t)
prompt := PartialsPromptSignup
expected := &PartialsPrompt{Segment: prompt}
got, err := api.Prompt.ReadPartials(context.Background(), prompt)

assert.NoError(t, err)
assert.Equal(t, expected, got)

t.Cleanup(func() {
cleanupUniversalLogin(t)
cleanupCustomDomain(t, customDomain.GetID())
})
}

func TestPromptManager_CreatePartials(t *testing.T) {
configureHTTPTestRecordings(t)

customDomain := givenACustomDomain(t)
_ = givenAUniversalLogin(t)
prompt := PartialsPromptSignup
original := &PartialsPrompt{Segment: prompt}
expected := &PartialsPrompt{Segment: prompt, FormContentStart: `<div>Test Content</div>`}

err := api.Prompt.CreatePartials(context.Background(), expected)
assert.NoError(t, err)

got, err := api.Prompt.ReadPartials(context.Background(), prompt)
assert.NoError(t, err)

assert.Equal(t, expected, got)
assert.NotEqual(t, original, got)

t.Cleanup(func() {
cleanupPartialsPrompt(t, customDomain.GetID(), prompt)
})
}

func TestPromptManager_UpdatePartials(t *testing.T) {
configureHTTPTestRecordings(t)

customDomain := givenACustomDomain(t)
_ = givenAUniversalLogin(t)
prompt := PartialsPromptSignup
original := &PartialsPrompt{Segment: prompt, FormContentStart: `<div>Test Content</div>`}

err := api.Prompt.CreatePartials(context.Background(), original)
assert.NoError(t, err)

expected := &PartialsPrompt{Segment: prompt, FormContentStart: `<div>Updated Test Content</div>`}
err = api.Prompt.UpdatePartials(context.Background(), expected)
assert.NoError(t, err)

got, err := api.Prompt.ReadPartials(context.Background(), prompt)
assert.NoError(t, err)

assert.Equal(t, expected, got)
assert.NotEqual(t, original, expected)

t.Cleanup(func() {
cleanupPartialsPrompt(t, customDomain.GetID(), prompt)
})
}

func TestPromptManager_DeletePartials(t *testing.T) {
configureHTTPTestRecordings(t)

customDomain := givenACustomDomain(t)
_ = givenAUniversalLogin(t)
prompt := PartialsPromptSignup
original := &PartialsPrompt{Segment: prompt, FormContentStart: `<div>Test Content</div>`}

err := api.Prompt.CreatePartials(context.Background(), original)
assert.NoError(t, err)

expected := &PartialsPrompt{Segment: prompt}
err = api.Prompt.DeletePartials(context.Background(), expected)
assert.NoError(t, err)

got, err := api.Prompt.ReadPartials(context.Background(), prompt)
assert.NoError(t, err)

assert.Equal(t, expected, got)
assert.NotEqual(t, original, expected)

t.Cleanup(func() {
cleanupPartialsPrompt(t, customDomain.GetID(), prompt)
})
}

func givenAUniversalLogin(t *testing.T) *BrandingUniversalLogin {
t.Helper()

body := `<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>`
ul := &BrandingUniversalLogin{
Body: auth0.String(body),
}

err := api.Branding.SetUniversalLogin(context.Background(), ul)
assert.NoError(t, err)

return ul
}

func cleanupPartialsPrompt(t *testing.T, customDomainID string, prompt PartialsPromptSegment) {
t.Helper()

c := &PartialsPrompt{Segment: prompt}
err := api.Prompt.DeletePartials(context.Background(), c)
assert.NoError(t, err)

cleanupUniversalLogin(t)
cleanupCustomDomain(t, customDomainID)
}

func cleanupUniversalLogin(t *testing.T) {
t.Helper()

err := api.Branding.DeleteUniversalLogin(context.Background())
assert.NoError(t, err)
}
Loading

0 comments on commit f9765fc

Please sign in to comment.