From 32af2b8141bddb7230dd4db7e6612703f2dfc25b Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 22 Jun 2023 18:05:56 -0400 Subject: [PATCH 1/4] [chore] Release 4.12.0 (#561) - Release 4.12.0 --- firebase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase.go b/firebase.go index a06829b1..fb3b0b47 100644 --- a/firebase.go +++ b/firebase.go @@ -39,7 +39,7 @@ import ( var defaultAuthOverrides = make(map[string]interface{}) // Version of the Firebase Go Admin SDK. -const Version = "4.11.0" +const Version = "4.12.0" // firebaseEnvName is the name of the environment variable with the Config. const firebaseEnvName = "FIREBASE_CONFIG" From 02300a8e865290c35d0ff92ff241ee38ccd814a7 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Tue, 11 Jul 2023 12:21:57 -0400 Subject: [PATCH 2/4] Revert "[chore] Release 4.12.0 (#561)" (#565) This reverts commit 32af2b8141bddb7230dd4db7e6612703f2dfc25b. --- firebase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase.go b/firebase.go index fb3b0b47..a06829b1 100644 --- a/firebase.go +++ b/firebase.go @@ -39,7 +39,7 @@ import ( var defaultAuthOverrides = make(map[string]interface{}) // Version of the Firebase Go Admin SDK. -const Version = "4.12.0" +const Version = "4.11.0" // firebaseEnvName is the name of the environment variable with the Config. const firebaseEnvName = "FIREBASE_CONFIG" From 46458d681ee8dfdee70fe21a783a3517647efa22 Mon Sep 17 00:00:00 2001 From: Hiroaki Takada Date: Fri, 20 Dec 2024 15:12:55 +0900 Subject: [PATCH 3/4] add: Fetch id token from refresh token --- auth/auth.go | 104 ++++++++++++++++++++++++++-------- integration/auth/auth_test.go | 42 ++++++++++++++ 2 files changed, 123 insertions(+), 23 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index a7028698..d4857757 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -18,8 +18,11 @@ package auth import ( "context" + "encoding/json" "errors" "fmt" + "net/http" + "net/url" "os" "strings" "time" @@ -146,23 +149,25 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error) } idToolkitV1Endpoint := fmt.Sprintf("%s/v1", baseURL) idToolkitV2Endpoint := fmt.Sprintf("%s/v2", baseURL) + secureToolkitV1Endpoint := fmt.Sprintf("%s/v1", "https://securetoken.googleapis.com") userManagementEndpoint := idToolkitV1Endpoint providerConfigEndpoint := idToolkitV2Endpoint tenantMgtEndpoint := idToolkitV2Endpoint projectMgtEndpoint := idToolkitV2Endpoint base := &baseClient{ - userManagementEndpoint: userManagementEndpoint, - providerConfigEndpoint: providerConfigEndpoint, - tenantMgtEndpoint: tenantMgtEndpoint, - projectMgtEndpoint: projectMgtEndpoint, - projectID: conf.ProjectID, - httpClient: hc, - idTokenVerifier: idTokenVerifier, - cookieVerifier: cookieVerifier, - signer: signer, - clock: internal.SystemClock, - isEmulator: isEmulator, + secureToolkitV1Endpoint: secureToolkitV1Endpoint, // here + userManagementEndpoint: userManagementEndpoint, + providerConfigEndpoint: providerConfigEndpoint, + tenantMgtEndpoint: tenantMgtEndpoint, + projectMgtEndpoint: projectMgtEndpoint, + projectID: conf.ProjectID, + httpClient: hc, + idTokenVerifier: idTokenVerifier, + cookieVerifier: cookieVerifier, + signer: signer, + clock: internal.SystemClock, + isEmulator: isEmulator, } return &Client{ baseClient: base, @@ -170,6 +175,58 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error) }, nil } +type TokenResponse struct { + ExpiresIn string `json:"expires_in"` + TokenType string `json:"token_type"` + RefreshToken string `json:"refresh_token"` + IDToken string `json:"id_token"` + UserID string `json:"user_id"` + ProjectID string `json:"project_id"` +} + +func fetchIdTokenByRefreshToken(apiKey, refreshToken, endpointBase string) (TokenResponse, error) { + endpoint := fmt.Sprintf("%s/token?key=%s", endpointBase, apiKey) + + form := url.Values{} + form.Add("grant_type", "refresh_token") + form.Add("refresh_token", refreshToken) + + req, err := http.NewRequest("POST", endpoint, strings.NewReader(form.Encode())) + if err != nil { + return TokenResponse{}, err + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return TokenResponse{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return TokenResponse{}, fmt.Errorf("fetchIdTokenByRefreshToken: %s", resp.Status) + } + + var tokenResp TokenResponse + err = json.NewDecoder(resp.Body).Decode(&tokenResp) + if err != nil { + return TokenResponse{}, err + } + + return tokenResp, nil +} + + +func (c *baseClient) ExchangeIdToken(ctx context.Context, apikey string, refreshToken string) (string, error) { + res, err := fetchIdTokenByRefreshToken(apikey, refreshToken, c.secureToolkitV1Endpoint) + if err != nil { + return "", err + } + return res.IDToken, nil +} + // CustomToken creates a signed custom authentication token with the specified user ID. // // The resulting JWT can be used in a Firebase client SDK to trigger an authentication flow. See @@ -274,18 +331,19 @@ type FirebaseInfo struct { // baseClient exposes the APIs common to both auth.Client and auth.TenantClient. type baseClient struct { - userManagementEndpoint string - providerConfigEndpoint string - tenantMgtEndpoint string - projectMgtEndpoint string - projectID string - tenantID string - httpClient *internal.HTTPClient - idTokenVerifier *tokenVerifier - cookieVerifier *tokenVerifier - signer cryptoSigner - clock internal.Clock - isEmulator bool + secureToolkitV1Endpoint string + userManagementEndpoint string + providerConfigEndpoint string + tenantMgtEndpoint string + projectMgtEndpoint string + projectID string + tenantID string + httpClient *internal.HTTPClient + idTokenVerifier *tokenVerifier + cookieVerifier *tokenVerifier + signer cryptoSigner + clock internal.Clock + isEmulator bool } func (c *baseClient) withTenantID(tenantID string) *baseClient { diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index 66809972..08fa5e85 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -135,6 +135,48 @@ func TestCustomTokenWithClaims(t *testing.T) { } } +func TestExchangeIdToken(t *testing.T) { + uid := "fetch_id_token_by_refresh_token" + ct, err := client.CustomToken(context.Background(), uid) + if err != nil { + t.Fatal(err) + } + idt, err := signInWithCustomToken(ct) + if err != nil { + t.Fatal(err) + } + defer deleteUser(uid) + + vt, err := client.VerifyIDTokenAndCheckRevoked(context.Background(), idt) + if err != nil { + t.Fatal(err) + } + if vt.UID != uid { + t.Errorf("UID = %q; want UID = %q", vt.UID, uid) + } + + apiKey, err := internal.APIKey() + if err != nil { + t.Errorf("internal.APIKey() = %v; want = ", err) + } + + time.Sleep(time.Second) + newIdt, err := client.ExchangeIdToken(context.Background(), apiKey, "mock-refresh-token") + if err != nil { + t.Fatal(err) + } + + if idt == newIdt { + // The new ID token should be different from the old one. + t.Errorf("ID token = %q; want ID token != %q", newIdt, idt) + } + + _, err = client.VerifyIDTokenAndCheckRevoked(context.Background(), newIdt) + if err != nil { + t.Errorf("VerifyIDTokenAndCheckRevoked(); err = %s; want err = ", err) + } +} + func TestRevokeRefreshTokens(t *testing.T) { uid := "user_revoked" ct, err := client.CustomToken(context.Background(), uid) From e9eddfd4fcc0b9e33266ea57d33a6dd9c8e28fa5 Mon Sep 17 00:00:00 2001 From: Hiroaki Takada Date: Fri, 20 Dec 2024 15:50:54 +0900 Subject: [PATCH 4/4] use context --- auth/auth.go | 67 ++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index d4857757..b615d5f6 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -184,43 +184,42 @@ type TokenResponse struct { ProjectID string `json:"project_id"` } -func fetchIdTokenByRefreshToken(apiKey, refreshToken, endpointBase string) (TokenResponse, error) { - endpoint := fmt.Sprintf("%s/token?key=%s", endpointBase, apiKey) - - form := url.Values{} - form.Add("grant_type", "refresh_token") - form.Add("refresh_token", refreshToken) - - req, err := http.NewRequest("POST", endpoint, strings.NewReader(form.Encode())) - if err != nil { - return TokenResponse{}, err - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return TokenResponse{}, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return TokenResponse{}, fmt.Errorf("fetchIdTokenByRefreshToken: %s", resp.Status) - } - - var tokenResp TokenResponse - err = json.NewDecoder(resp.Body).Decode(&tokenResp) - if err != nil { - return TokenResponse{}, err - } - - return tokenResp, nil +func fetchIdTokenByRefreshToken(ctx context.Context, apiKey, refreshToken, endpointBase string) (TokenResponse, error) { + endpoint := fmt.Sprintf("%s/token?key=%s", endpointBase, apiKey) + + form := url.Values{} + form.Add("grant_type", "refresh_token") + form.Add("refresh_token", refreshToken) + + req, err := http.NewRequestWithContext(ctx, "POST", endpoint, strings.NewReader(form.Encode())) + if err != nil { + return TokenResponse{}, err + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return TokenResponse{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return TokenResponse{}, fmt.Errorf("fetchIdTokenByRefreshToken: %s", resp.Status) + } + + var tokenResp TokenResponse + err = json.NewDecoder(resp.Body).Decode(&tokenResp) + if err != nil { + return TokenResponse{}, err + } + + return tokenResp, nil } - func (c *baseClient) ExchangeIdToken(ctx context.Context, apikey string, refreshToken string) (string, error) { - res, err := fetchIdTokenByRefreshToken(apikey, refreshToken, c.secureToolkitV1Endpoint) + res, err := fetchIdTokenByRefreshToken(ctx, apikey, refreshToken, c.secureToolkitV1Endpoint) if err != nil { return "", err }