Skip to content

add 'VERIFY_AND_CHANGE_EMAIL' linkType for generateEmailActionLink #498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions auth/email_action_links.go
Original file line number Diff line number Diff line change
@@ -64,9 +64,10 @@ func (settings *ActionCodeSettings) toMap() (map[string]interface{}, error) {
type linkType string

const (
emailLinkSignIn linkType = "EMAIL_SIGNIN"
emailVerification linkType = "VERIFY_EMAIL"
passwordReset linkType = "PASSWORD_RESET"
emailLinkSignIn linkType = "EMAIL_SIGNIN"
emailVerification linkType = "VERIFY_EMAIL"
passwordReset linkType = "PASSWORD_RESET"
verifyAndChangeEmail linkType = "VERIFY_AND_CHANGE_EMAIL"
)

// EmailVerificationLink generates the out-of-band email action link for email verification flows for the specified
@@ -79,7 +80,7 @@ func (c *baseClient) EmailVerificationLink(ctx context.Context, email string) (s
// specified email address, using the action code settings provided.
func (c *baseClient) EmailVerificationLinkWithSettings(
ctx context.Context, email string, settings *ActionCodeSettings) (string, error) {
return c.generateEmailActionLink(ctx, emailVerification, email, settings)
return c.generateEmailActionLink(ctx, emailVerification, email, settings, nil)
}

// PasswordResetLink generates the out-of-band email action link for password reset flows for the specified email
@@ -92,18 +93,31 @@ func (c *baseClient) PasswordResetLink(ctx context.Context, email string) (strin
// specified email address, using the action code settings provided.
func (c *baseClient) PasswordResetLinkWithSettings(
ctx context.Context, email string, settings *ActionCodeSettings) (string, error) {
return c.generateEmailActionLink(ctx, passwordReset, email, settings)
return c.generateEmailActionLink(ctx, passwordReset, email, settings, nil)
}

// EmailSignInLink generates the out-of-band email action link for email link sign-in flows, using the action
// code settings provided.
func (c *baseClient) EmailSignInLink(
ctx context.Context, email string, settings *ActionCodeSettings) (string, error) {
return c.generateEmailActionLink(ctx, emailLinkSignIn, email, settings)
return c.generateEmailActionLink(ctx, emailLinkSignIn, email, settings, nil)
}

// VerifyAndChangeEmailLink generates the out-of-band email action link for email verification and change flows for the
// specified email address.
func (c *baseClient) VerifyAndChangeEmailLink(ctx context.Context, email string, newEmail string) (string, error) {
return c.VerifyAndChangeEmailLinkWithSettings(ctx, email, newEmail, nil)
}

// VerifyAndChangeEmailLinkWithSettings generates the out-of-band email action link for email verification and change
// flows for the specified email address, using the action code settings provided.
func (c *baseClient) VerifyAndChangeEmailLinkWithSettings(
ctx context.Context, email string, newEmail string, settings *ActionCodeSettings) (string, error) {
return c.generateEmailActionLink(ctx, verifyAndChangeEmail, email, settings, &newEmail)
}

func (c *baseClient) generateEmailActionLink(
ctx context.Context, linkType linkType, email string, settings *ActionCodeSettings) (string, error) {
ctx context.Context, linkType linkType, email string, settings *ActionCodeSettings, newEmail *string) (string, error) {

if email == "" {
return "", errors.New("email must not be empty")
@@ -118,6 +132,14 @@ func (c *baseClient) generateEmailActionLink(
"email": email,
"returnOobLink": true,
}

if linkType == verifyAndChangeEmail {
if newEmail == nil {
return "", errors.New("newEmail must not be nil when linkType is verifyAndChangeEmail")
}
payload["newEmail"] = *newEmail
}

if settings != nil {
settingsMap, err := settings.toMap()
if err != nil {
50 changes: 50 additions & 0 deletions auth/email_action_links_test.go
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ const (
testActionLink = "https://test.link"
testActionLinkFormat = `{"oobLink": %q}`
testEmail = "[email protected]"
testNewEmail = "[email protected]"
)

var testActionLinkResponse = []byte(fmt.Sprintf(testActionLinkFormat, testActionLink))
@@ -309,6 +310,55 @@ func TestEmailVerificationLinkError(t *testing.T) {
}
}

func TestVerifyAndChangeEmailLink(t *testing.T) {
s := echoServer(testActionLinkResponse, t)
defer s.Close()

link, err := s.Client.VerifyAndChangeEmailLink(context.Background(), testEmail, testNewEmail)
if err != nil {
t.Fatal(err)
}
if link != testActionLink {
t.Errorf("TestVerifyAndChangeEmailLink() = %q; want = %q", link, testActionLink)
}

want := map[string]interface{}{
"requestType": "VERIFY_AND_CHANGE_EMAIL",
"email": testEmail,
"returnOobLink": true,
"newEmail": testNewEmail,
}
if err := checkActionLinkRequest(want, s); err != nil {
t.Fatalf("TestVerifyAndChangeEmailLink() %v", err)
}
}

func TestVerifyAndChangeEmailLinkWithSettings(t *testing.T) {
s := echoServer(testActionLinkResponse, t)
defer s.Close()

link, err := s.Client.VerifyAndChangeEmailLinkWithSettings(context.Background(), testEmail, testNewEmail, testActionCodeSettings)
if err != nil {
t.Fatal(err)
}
if link != testActionLink {
t.Errorf("VerifyAndChangeEmailLinkWithSettings() = %q; want = %q", link, testActionLink)
}

want := map[string]interface{}{
"requestType": "VERIFY_AND_CHANGE_EMAIL",
"email": testEmail,
"returnOobLink": true,
"newEmail": testNewEmail,
}
for k, v := range testActionCodeSettingsMap {
want[k] = v
}
if err := checkActionLinkRequest(want, s); err != nil {
t.Fatalf("checkActionLinkRequest() = %v", err)
}
}

func checkActionLinkRequest(want map[string]interface{}, s *mockAuthServer) error {
wantURL := "/projects/mock-project-id/accounts:sendOobCode"
return checkActionLinkRequestWithURL(want, wantURL, s)
28 changes: 28 additions & 0 deletions auth/tenant_mgt_test.go
Original file line number Diff line number Diff line change
@@ -569,6 +569,34 @@ func TestTenantEmailSignInLink(t *testing.T) {
}
}

func TestTenantVerifyAndChangeEmail(t *testing.T) {
s := echoServer(testActionLinkResponse, t)
defer s.Close()

client, err := s.Client.TenantManager.AuthForTenant("tenantID")
if err != nil {
t.Fatalf("AuthForTenant() = %v", err)
}

link, err := client.VerifyAndChangeEmailLink(context.Background(), testEmail, testNewEmail)
if err != nil {
t.Fatal(err)
}
if link != testActionLink {
t.Errorf("VerifyAndChangeEmailLink() = %q; want = %q", link, testActionLink)
}

want := map[string]interface{}{
"requestType": "VERIFY_AND_CHANGE_EMAIL",
"email": testEmail,
"returnOobLink": true,
"newEmail": testNewEmail,
}
if err := checkActionLinkRequestWithURL(want, wantEmailActionURL, s); err != nil {
t.Fatalf("checkActionLinkRequestWithURL() = %v", err)
}
}

func TestTenantOIDCProviderConfig(t *testing.T) {
s := echoServer([]byte(oidcConfigResponse), t)
defer s.Close()
17 changes: 17 additions & 0 deletions integration/auth/tenant_mgt_test.go
Original file line number Diff line number Diff line change
@@ -348,6 +348,23 @@ func testTenantAwareUserManagement(t *testing.T, id string) {
}
})

t.Run("VerifyAndChangeEmailLink()", func(t *testing.T) {
newEmail := "new-" + want.Email
link, err := tenantClient.VerifyAndChangeEmailLink(context.Background(), want.Email, newEmail)
if err != nil {
t.Fatalf("VerifyAndChangeEmailLink() = %v", err)
}

tenant, err := extractTenantID(link)
if err != nil {
t.Fatalf("extractTenantID(%s) = %v", link, err)
}

if id != tenant {
t.Fatalf("VerifyAndChangeEmailLink() TenantID = %q; want = %q", tenant, id)
}
})

t.Run("RevokeRefreshTokens()", func(t *testing.T) {
validSinceMillis := time.Now().Unix() * 1000
time.Sleep(1 * time.Second)