Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
54b8114
Merge dev into master
google-oss-bot May 21, 2020
cef91ac
Merge dev into master
google-oss-bot Jun 16, 2020
77177c7
Merge dev into master
google-oss-bot Oct 22, 2020
a957589
Merge dev into master
google-oss-bot Jan 28, 2021
eb0d2a0
Merge dev into master
google-oss-bot Mar 24, 2021
05378ef
Merge dev into master
google-oss-bot Mar 29, 2021
4121c50
Merge dev into master
google-oss-bot Apr 14, 2021
928b104
Merge dev into master
google-oss-bot Jun 2, 2021
02cde4f
Merge dev into master
google-oss-bot Nov 4, 2021
6b40682
Merge dev into master
google-oss-bot Dec 15, 2021
e60757f
Merge dev into master
google-oss-bot Jan 20, 2022
bb055ed
Merge dev into master
google-oss-bot Apr 6, 2022
23a1f17
Merge dev into master
google-oss-bot Oct 6, 2022
1d24577
Merge dev into master
google-oss-bot Nov 10, 2022
61c6c04
Merge dev into master
google-oss-bot Apr 6, 2023
32af2b8
[chore] Release 4.12.0 (#561)
lahirumaramba Jun 22, 2023
02300a8
Revert "[chore] Release 4.12.0 (#561)" (#565)
lahirumaramba Jul 11, 2023
74c9bd5
Merge dev into master
google-oss-bot Jul 12, 2023
37c7936
Merge dev into master
google-oss-bot Sep 25, 2023
b04387e
Merge dev into master
google-oss-bot Nov 23, 2023
87b867c
Merge dev into master
google-oss-bot Apr 10, 2024
6a28190
Merge dev into master
google-oss-bot May 30, 2024
c3be6f2
Merge dev into master
google-oss-bot Oct 24, 2024
afeaa15
Merge dev into master
google-oss-bot Dec 5, 2024
570427a
Merge dev into master
google-oss-bot Feb 13, 2025
fe866a0
Merge dev into master
google-oss-bot Jun 5, 2025
db240e4
Merge dev into master
google-oss-bot Jun 11, 2025
9ca523c
add feature to create email action link of verify and change email
tyahha May 20, 2022
dfb7d16
add test for VerifyAndChangeEmailLink
tyahha May 20, 2022
da74cd2
add tenant test for VerifyAndChangeEmailLink
tyahha May 20, 2022
cf66b07
add tenant integration test for VerifyAndChangeEmailLink
tyahha May 20, 2022
b8a379f
fix error message in test
tyahha Jun 8, 2022
2be3556
refactor email action link generation to use functional options
utamori Jun 4, 2026
21f8594
Merge branch 'dev' into dev
macastelaz Jun 5, 2026
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
42 changes: 38 additions & 4 deletions auth/email_action_links.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,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
Expand All @@ -77,6 +78,17 @@ func (c *baseClient) EmailVerificationLink(ctx context.Context, email string) (s
return c.EmailVerificationLinkWithSettings(ctx, email, nil)
}

// emailActionLinkOption modifies the request payload sent to the email action link generation API.
type emailActionLinkOption func(payload map[string]interface{})

// withNewEmail adds the newEmail field to the request payload. Used when generating links for
// verify-and-change-email flows.
func withNewEmail(newEmail string) emailActionLinkOption {
return func(payload map[string]interface{}) {
payload["newEmail"] = newEmail
}
}

// EmailVerificationLinkWithSettings generates the out-of-band email action link for email verification flows for the
// specified email address, using the action code settings provided.
func (c *baseClient) EmailVerificationLinkWithSettings(
Expand Down Expand Up @@ -104,8 +116,25 @@ func (c *baseClient) EmailSignInLink(
return c.generateEmailActionLink(ctx, emailLinkSignIn, email, settings)
}

// VerifyAndChangeEmailLink generates the out-of-band email action link for email verification and change flows for the
// specified current email address and new 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 current email address and new email address, using the action code settings provided.
func (c *baseClient) VerifyAndChangeEmailLinkWithSettings(
ctx context.Context, email string, newEmail string, settings *ActionCodeSettings) (string, error) {
if newEmail == "" {
return "", errors.New("newEmail must not be empty")
}
return c.generateEmailActionLink(ctx, verifyAndChangeEmail, email, settings, withNewEmail(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,
opts ...emailActionLinkOption) (string, error) {

if email == "" {
return "", errors.New("email must not be empty")
Expand All @@ -120,6 +149,11 @@ func (c *baseClient) generateEmailActionLink(
"email": email,
"returnOobLink": true,
}

for _, opt := range opts {
opt(payload)
}

if settings != nil {
settingsMap, err := settings.toMap()
if err != nil {
Expand Down
65 changes: 65 additions & 0 deletions auth/email_action_links_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const (
testActionLink = "https://test.link"
testActionLinkFormat = `{"oobLink": %q}`
testEmail = "user@domain.com"
testNewEmail = "user-new@domain.com"
)

var testActionLinkResponse = []byte(fmt.Sprintf(testActionLinkFormat, testActionLink))
Expand Down Expand Up @@ -243,6 +244,21 @@ func TestEmailActionLinkNoEmail(t *testing.T) {
if _, err := client.EmailSignInLink(context.Background(), "", testActionCodeSettings); err == nil {
t.Errorf("EmailSignInLink('') = nil; want error")
}

if _, err := client.VerifyAndChangeEmailLink(context.Background(), "", testNewEmail); err == nil {
t.Errorf("VerifyAndChangeEmailLink('') = nil; want error")
}
}

func TestVerifyAndChangeEmailLinkNoNewEmail(t *testing.T) {
client := &Client{
baseClient: &baseClient{},
}

want := "newEmail must not be empty"
if _, err := client.VerifyAndChangeEmailLink(context.Background(), testEmail, ""); err == nil || err.Error() != want {
t.Errorf("VerifyAndChangeEmailLink('') = %v; want = %q", err, want)
}
}

func TestEmailVerificationLinkInvalidSettings(t *testing.T) {
Expand Down Expand Up @@ -312,6 +328,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)
Expand Down
28 changes: 28 additions & 0 deletions auth/tenant_mgt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,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()
Expand Down
17 changes: 17 additions & 0 deletions integration/auth/tenant_mgt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,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)
Expand Down
Loading