Skip to content

Commit a957589

Browse files
Merge dev into master
2 parents 77177c7 + 11fd169 commit a957589

10 files changed

+198
-44
lines changed

auth/auth.go

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"os"
2324
"strings"
2425
"time"
2526

@@ -28,9 +29,11 @@ import (
2829
)
2930

3031
const (
31-
authErrorCode = "authErrorCode"
32-
firebaseAudience = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
33-
oneHourInSeconds = 3600
32+
authErrorCode = "authErrorCode"
33+
emulatorHostEnvVar = "FIREBASE_AUTH_EMULATOR_HOST"
34+
defaultAuthURL = "https://identitytoolkit.googleapis.com"
35+
firebaseAudience = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
36+
oneHourInSeconds = 3600
3437

3538
// SDK-generated error codes
3639
idTokenRevoked = "ID_TOKEN_REVOKED"
@@ -58,18 +61,27 @@ type Client struct {
5861
// Auth service through firebase.App.
5962
func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error) {
6063
var (
61-
signer cryptoSigner
62-
err error
64+
isEmulator bool
65+
signer cryptoSigner
66+
err error
6367
)
6468

65-
creds, _ := transport.Creds(ctx, conf.Opts...)
69+
authEmulatorHost := os.Getenv(emulatorHostEnvVar)
70+
if authEmulatorHost != "" {
71+
isEmulator = true
72+
signer = emulatedSigner{}
73+
}
74+
75+
if signer == nil {
76+
creds, _ := transport.Creds(ctx, conf.Opts...)
6677

67-
// Initialize a signer by following the go/firebase-admin-sign protocol.
68-
if creds != nil && len(creds.JSON) > 0 {
69-
// If the SDK was initialized with a service account, use it to sign bytes.
70-
signer, err = signerFromCreds(creds.JSON)
71-
if err != nil && err != errNotAServiceAcct {
72-
return nil, err
78+
// Initialize a signer by following the go/firebase-admin-sign protocol.
79+
if creds != nil && len(creds.JSON) > 0 {
80+
// If the SDK was initialized with a service account, use it to sign bytes.
81+
signer, err = signerFromCreds(creds.JSON)
82+
if err != nil && err != errNotAServiceAcct {
83+
return nil, err
84+
}
7385
}
7486
}
7587

@@ -91,12 +103,12 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
91103
}
92104
}
93105

94-
idTokenVerifier, err := newIDTokenVerifier(ctx, conf.ProjectID)
106+
idTokenVerifier, err := newIDTokenVerifier(ctx, conf.ProjectID, isEmulator)
95107
if err != nil {
96108
return nil, err
97109
}
98110

99-
cookieVerifier, err := newSessionCookieVerifier(ctx, conf.ProjectID)
111+
cookieVerifier, err := newSessionCookieVerifier(ctx, conf.ProjectID, isEmulator)
100112
if err != nil {
101113
return nil, err
102114
}
@@ -112,9 +124,20 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
112124
internal.WithHeader("X-Client-Version", fmt.Sprintf("Go/Admin/%s", conf.Version)),
113125
}
114126

127+
baseURL := defaultAuthURL
128+
if isEmulator {
129+
baseURL = fmt.Sprintf("http://%s/identitytoolkit.googleapis.com", authEmulatorHost)
130+
}
131+
idToolkitV1Endpoint := fmt.Sprintf("%s/v1", baseURL)
132+
idToolkitV2Beta1Endpoint := fmt.Sprintf("%s/v2beta1", baseURL)
133+
userManagementEndpoint := idToolkitV1Endpoint
134+
providerConfigEndpoint := idToolkitV2Beta1Endpoint
135+
tenantMgtEndpoint := idToolkitV2Beta1Endpoint
136+
115137
base := &baseClient{
116-
userManagementEndpoint: idToolkitV1Endpoint,
138+
userManagementEndpoint: userManagementEndpoint,
117139
providerConfigEndpoint: providerConfigEndpoint,
140+
tenantMgtEndpoint: tenantMgtEndpoint,
118141
projectID: conf.ProjectID,
119142
httpClient: hc,
120143
idTokenVerifier: idTokenVerifier,
@@ -177,7 +200,7 @@ func (c *baseClient) CustomTokenWithClaims(ctx context.Context, uid string, devC
177200

178201
now := c.clock.Now().Unix()
179202
info := &jwtInfo{
180-
header: jwtHeader{Algorithm: "RS256", Type: "JWT"},
203+
header: jwtHeader{Algorithm: c.signer.Algorithm(), Type: "JWT"},
181204
payload: &customToken{
182205
Iss: iss,
183206
Sub: iss,
@@ -234,6 +257,7 @@ type FirebaseInfo struct {
234257
type baseClient struct {
235258
userManagementEndpoint string
236259
providerConfigEndpoint string
260+
tenantMgtEndpoint string
237261
projectID string
238262
tenantID string
239263
httpClient *internal.HTTPClient

auth/auth_test.go

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ import (
3434
)
3535

3636
const (
37-
credEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
38-
testProjectID = "mock-project-id"
39-
testVersion = "test-version"
37+
credEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
38+
testProjectID = "mock-project-id"
39+
testVersion = "test-version"
40+
defaultIDToolkitV1Endpoint = "https://identitytoolkit.googleapis.com/v1"
41+
defaultIDToolkitV2Beta1Endpoint = "https://identitytoolkit.googleapis.com/v2beta1"
4042
)
4143

4244
var (
@@ -280,6 +282,38 @@ func TestNewClientExplicitNoAuth(t *testing.T) {
280282
}
281283
}
282284

285+
func TestNewClientEmulatorHostEnvVar(t *testing.T) {
286+
emulatorHost := "localhost:9099"
287+
idToolkitV1Endpoint := "http://localhost:9099/identitytoolkit.googleapis.com/v1"
288+
idToolkitV2Beta1Endpoint := "http://localhost:9099/identitytoolkit.googleapis.com/v2beta1"
289+
290+
os.Setenv(emulatorHostEnvVar, emulatorHost)
291+
defer os.Unsetenv(emulatorHostEnvVar)
292+
293+
conf := &internal.AuthConfig{
294+
Opts: []option.ClientOption{
295+
option.WithoutAuthentication(),
296+
},
297+
}
298+
client, err := NewClient(context.Background(), conf)
299+
if err != nil {
300+
t.Fatal(err)
301+
}
302+
baseClient := client.baseClient
303+
if baseClient.userManagementEndpoint != idToolkitV1Endpoint {
304+
t.Errorf("baseClient.userManagementEndpoint = %q; want = %q", baseClient.userManagementEndpoint, idToolkitV1Endpoint)
305+
}
306+
if baseClient.providerConfigEndpoint != idToolkitV2Beta1Endpoint {
307+
t.Errorf("baseClient.providerConfigEndpoint = %q; want = %q", baseClient.providerConfigEndpoint, idToolkitV2Beta1Endpoint)
308+
}
309+
if baseClient.tenantMgtEndpoint != idToolkitV2Beta1Endpoint {
310+
t.Errorf("baseClient.tenantMgtEndpoint = %q; want = %q", baseClient.tenantMgtEndpoint, idToolkitV2Beta1Endpoint)
311+
}
312+
if _, ok := baseClient.signer.(emulatedSigner); !ok {
313+
t.Errorf("baseClient.signer = %#v; want = %#v", baseClient.signer, emulatedSigner{})
314+
}
315+
}
316+
283317
func TestCustomToken(t *testing.T) {
284318
client := &Client{
285319
baseClient: &baseClient{
@@ -663,6 +697,27 @@ func TestVerifyIDTokenWithNoProjectID(t *testing.T) {
663697
}
664698
}
665699

700+
func TestVerifyIDTokenInEmulatorMode(t *testing.T) {
701+
os.Setenv(emulatorHostEnvVar, "localhost:9099")
702+
defer os.Unsetenv(emulatorHostEnvVar)
703+
704+
conf := &internal.AuthConfig{
705+
ProjectID: "",
706+
Opts: optsWithTokenSource,
707+
}
708+
c, err := NewClient(context.Background(), conf)
709+
if err != nil {
710+
t.Fatal(err)
711+
}
712+
713+
defer func() {
714+
if r := recover(); r == nil {
715+
t.Error("VeridyIDToken() didn't panic")
716+
}
717+
}()
718+
c.VerifyIDToken(context.Background(), testIDToken)
719+
}
720+
666721
func TestCustomTokenVerification(t *testing.T) {
667722
client := &Client{
668723
baseClient: &baseClient{
@@ -682,7 +737,7 @@ func TestCustomTokenVerification(t *testing.T) {
682737
}
683738

684739
func TestCertificateRequestError(t *testing.T) {
685-
tv, err := newIDTokenVerifier(context.Background(), testProjectID)
740+
tv, err := newIDTokenVerifier(context.Background(), testProjectID, false)
686741
if err != nil {
687742
t.Fatal(err)
688743
}
@@ -1022,7 +1077,7 @@ func signerForTests(ctx context.Context) (cryptoSigner, error) {
10221077
}
10231078

10241079
func idTokenVerifierForTests(ctx context.Context) (*tokenVerifier, error) {
1025-
tv, err := newIDTokenVerifier(ctx, testProjectID)
1080+
tv, err := newIDTokenVerifier(ctx, testProjectID, false)
10261081
if err != nil {
10271082
return nil, err
10281083
}
@@ -1036,7 +1091,7 @@ func idTokenVerifierForTests(ctx context.Context) (*tokenVerifier, error) {
10361091
}
10371092

10381093
func cookieVerifierForTests(ctx context.Context) (*tokenVerifier, error) {
1039-
tv, err := newSessionCookieVerifier(ctx, testProjectID)
1094+
tv, err := newSessionCookieVerifier(ctx, testProjectID, false)
10401095
if err != nil {
10411096
return nil, err
10421097
}
@@ -1163,23 +1218,26 @@ func checkCookieVerifier(tv *tokenVerifier, projectID string) error {
11631218
}
11641219

11651220
func checkBaseClient(client *Client, wantProjectID string) error {
1166-
umc := client.baseClient
1167-
if umc.userManagementEndpoint != idToolkitV1Endpoint {
1168-
return fmt.Errorf("userManagementEndpoint = %q; want = %q", umc.userManagementEndpoint, idToolkitV1Endpoint)
1221+
baseClient := client.baseClient
1222+
if baseClient.userManagementEndpoint != defaultIDToolkitV1Endpoint {
1223+
return fmt.Errorf("userManagementEndpoint = %q; want = %q", baseClient.userManagementEndpoint, defaultIDToolkitV1Endpoint)
1224+
}
1225+
if baseClient.providerConfigEndpoint != defaultIDToolkitV2Beta1Endpoint {
1226+
return fmt.Errorf("providerConfigEndpoint = %q; want = %q", baseClient.providerConfigEndpoint, defaultIDToolkitV2Beta1Endpoint)
11691227
}
1170-
if umc.providerConfigEndpoint != providerConfigEndpoint {
1171-
return fmt.Errorf("providerConfigEndpoint = %q; want = %q", umc.providerConfigEndpoint, providerConfigEndpoint)
1228+
if baseClient.tenantMgtEndpoint != defaultIDToolkitV2Beta1Endpoint {
1229+
return fmt.Errorf("providerConfigEndpoint = %q; want = %q", baseClient.providerConfigEndpoint, defaultIDToolkitV2Beta1Endpoint)
11721230
}
1173-
if umc.projectID != wantProjectID {
1174-
return fmt.Errorf("projectID = %q; want = %q", umc.projectID, wantProjectID)
1231+
if baseClient.projectID != wantProjectID {
1232+
return fmt.Errorf("projectID = %q; want = %q", baseClient.projectID, wantProjectID)
11751233
}
11761234

11771235
req, err := http.NewRequest(http.MethodGet, "https://firebase.google.com", nil)
11781236
if err != nil {
11791237
return err
11801238
}
11811239

1182-
for _, opt := range umc.httpClient.Opts {
1240+
for _, opt := range baseClient.httpClient.Opts {
11831241
opt(req)
11841242
}
11851243
version := req.Header.Get("X-Client-Version")

auth/provider_config.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import (
2828
)
2929

3030
const (
31-
providerConfigEndpoint = "https://identitytoolkit.googleapis.com/v2beta1"
32-
3331
maxConfigs = 100
3432

3533
idpEntityIDKey = "idpConfig.idpEntityId"

auth/tenant_mgt.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ import (
2626
"google.golang.org/api/iterator"
2727
)
2828

29-
const (
30-
tenantMgtEndpoint = "https://identitytoolkit.googleapis.com/v2beta1"
31-
)
32-
3329
// Tenant represents a tenant in a multi-tenant application.
3430
//
3531
// Multi-tenancy support requires Google Cloud's Identity Platform (GCIP). To learn more about GCIP,
@@ -88,7 +84,7 @@ type TenantManager struct {
8884
func newTenantManager(client *internal.HTTPClient, conf *internal.AuthConfig, base *baseClient) *TenantManager {
8985
return &TenantManager{
9086
base: base,
91-
endpoint: tenantMgtEndpoint,
87+
endpoint: base.tenantMgtEndpoint,
9288
projectID: conf.ProjectID,
9389
httpClient: client,
9490
}

auth/token_generator.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ import (
3333
"firebase.google.com/go/v4/internal"
3434
)
3535

36+
const (
37+
algorithmNone = "none"
38+
algorithmRS256 = "RS256"
39+
40+
emulatorEmail = "firebase-auth-emulator@example.com"
41+
)
42+
3643
type jwtHeader struct {
3744
Algorithm string `json:"alg"`
3845
Type string `json:"typ"`
@@ -88,6 +95,7 @@ type serviceAccount struct {
8895

8996
// cryptoSigner is used to cryptographically sign data, and query the identity of the signer.
9097
type cryptoSigner interface {
98+
Algorithm() string
9199
Sign(context.Context, []byte) ([]byte, error)
92100
Email(context.Context) (string, error)
93101
}
@@ -133,6 +141,10 @@ func newServiceAccountSigner(sa serviceAccount) (*serviceAccountSigner, error) {
133141
}, nil
134142
}
135143

144+
func (s serviceAccountSigner) Algorithm() string {
145+
return algorithmRS256
146+
}
147+
136148
func (s serviceAccountSigner) Sign(ctx context.Context, b []byte) ([]byte, error) {
137149
hash := sha256.New()
138150
hash.Write(b)
@@ -173,6 +185,10 @@ func newIAMSigner(ctx context.Context, config *internal.AuthConfig) (*iamSigner,
173185
}, nil
174186
}
175187

188+
func (s iamSigner) Algorithm() string {
189+
return algorithmRS256
190+
}
191+
176192
func (s iamSigner) Sign(ctx context.Context, b []byte) ([]byte, error) {
177193
account, err := s.Email(ctx)
178194
if err != nil {
@@ -245,3 +261,17 @@ func (s iamSigner) callMetadataService(ctx context.Context) (string, error) {
245261

246262
return result, nil
247263
}
264+
265+
type emulatedSigner struct{}
266+
267+
func (s emulatedSigner) Algorithm() string {
268+
return algorithmNone
269+
}
270+
271+
func (s emulatedSigner) Email(context.Context) (string, error) {
272+
return emulatorEmail, nil
273+
}
274+
275+
func (s emulatedSigner) Sign(context.Context, []byte) ([]byte, error) {
276+
return []byte(""), nil
277+
}

0 commit comments

Comments
 (0)