Skip to content

Commit 4f24515

Browse files
authored
fix: Deferring credential loading until required (#361)
1 parent cdf28fb commit 4f24515

File tree

5 files changed

+94
-59
lines changed

5 files changed

+94
-59
lines changed

auth/auth.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,18 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
5757
signer cryptoSigner
5858
err error
5959
)
60+
61+
creds, _ := transport.Creds(ctx, conf.Opts...)
62+
6063
// Initialize a signer by following the go/firebase-admin-sign protocol.
61-
if conf.Creds != nil && len(conf.Creds.JSON) > 0 {
64+
if creds != nil && len(creds.JSON) > 0 {
6265
// If the SDK was initialized with a service account, use it to sign bytes.
63-
signer, err = signerFromCreds(conf.Creds.JSON)
66+
signer, err = signerFromCreds(creds.JSON)
6467
if err != nil && err != errNotAServiceAcct {
6568
return nil, err
6669
}
6770
}
71+
6872
if signer == nil {
6973
if conf.ServiceAccountID != "" {
7074
// If the SDK was initialized with a service account email, use it with the IAM service

auth/auth_test.go

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
)
3535

3636
const (
37+
credEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
3738
testProjectID = "mock-project-id"
3839
testVersion = "test-version"
3940
)
@@ -82,7 +83,6 @@ func TestNewClientWithServiceAccountCredentials(t *testing.T) {
8283
t.Fatal(err)
8384
}
8485
client, err := NewClient(context.Background(), &internal.AuthConfig{
85-
Creds: creds,
8686
Opts: optsWithServiceAcct,
8787
ProjectID: creds.ProjectID,
8888
Version: testVersion,
@@ -176,7 +176,6 @@ func TestNewClientWithUserCredentials(t *testing.T) {
176176
}`),
177177
}
178178
conf := &internal.AuthConfig{
179-
Creds: creds,
180179
Opts: []option.ClientOption{option.WithCredentials(creds)},
181180
Version: testVersion,
182181
}
@@ -206,7 +205,11 @@ func TestNewClientWithMalformedCredentials(t *testing.T) {
206205
creds := &google.DefaultCredentials{
207206
JSON: []byte("not json"),
208207
}
209-
conf := &internal.AuthConfig{Creds: creds}
208+
conf := &internal.AuthConfig{
209+
Opts: []option.ClientOption{
210+
option.WithCredentials(creds),
211+
},
212+
}
210213
if c, err := NewClient(context.Background(), conf); c != nil || err == nil {
211214
t.Errorf("NewClient() = (%v,%v); want = (nil, error)", c, err)
212215
}
@@ -222,12 +225,61 @@ func TestNewClientWithInvalidPrivateKey(t *testing.T) {
222225
t.Fatal(err)
223226
}
224227
creds := &google.DefaultCredentials{JSON: b}
225-
conf := &internal.AuthConfig{Creds: creds}
228+
conf := &internal.AuthConfig{
229+
Opts: []option.ClientOption{
230+
option.WithCredentials(creds),
231+
},
232+
}
226233
if c, err := NewClient(context.Background(), conf); c != nil || err == nil {
227234
t.Errorf("NewClient() = (%v,%v); want = (nil, error)", c, err)
228235
}
229236
}
230237

238+
func TestNewClientAppDefaultCredentialsWithInvalidFile(t *testing.T) {
239+
current := os.Getenv(credEnvVar)
240+
241+
if err := os.Setenv(credEnvVar, "../testdata/non_existing.json"); err != nil {
242+
t.Fatal(err)
243+
}
244+
defer os.Setenv(credEnvVar, current)
245+
246+
conf := &internal.AuthConfig{}
247+
if c, err := NewClient(context.Background(), conf); c != nil || err == nil {
248+
t.Errorf("Auth() = (%v, %v); want (nil, error)", c, err)
249+
}
250+
}
251+
252+
func TestNewClientInvalidCredentialFile(t *testing.T) {
253+
invalidFiles := []string{
254+
"testdata",
255+
"testdata/plain_text.txt",
256+
}
257+
258+
ctx := context.Background()
259+
for _, tc := range invalidFiles {
260+
conf := &internal.AuthConfig{
261+
Opts: []option.ClientOption{
262+
option.WithCredentialsFile(tc),
263+
},
264+
}
265+
if c, err := NewClient(ctx, conf); c != nil || err == nil {
266+
t.Errorf("Auth() = (%v, %v); want (nil, error)", c, err)
267+
}
268+
}
269+
}
270+
271+
func TestNewClientExplicitNoAuth(t *testing.T) {
272+
ctx := context.Background()
273+
conf := &internal.AuthConfig{
274+
Opts: []option.ClientOption{
275+
option.WithoutAuthentication(),
276+
},
277+
}
278+
if c, err := NewClient(ctx, conf); c == nil || err != nil {
279+
t.Errorf("Auth() = (%v, %v); want (auth, nil)", c, err)
280+
}
281+
}
282+
231283
func TestCustomToken(t *testing.T) {
232284
client := &Client{
233285
signer: testSigner,
@@ -298,8 +350,7 @@ func TestCustomTokenError(t *testing.T) {
298350
func TestCustomTokenInvalidCredential(t *testing.T) {
299351
ctx := context.Background()
300352
conf := &internal.AuthConfig{
301-
Creds: nil,
302-
Opts: optsWithTokenSource,
353+
Opts: optsWithTokenSource,
303354
}
304355
s, err := NewClient(ctx, conf)
305356
if err != nil {

firebase.go

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131
"firebase.google.com/go/internal"
3232
"firebase.google.com/go/messaging"
3333
"firebase.google.com/go/storage"
34-
"golang.org/x/oauth2/google"
3534
"google.golang.org/api/option"
3635
"google.golang.org/api/transport"
3736
)
@@ -47,7 +46,6 @@ const firebaseEnvName = "FIREBASE_CONFIG"
4746
// An App holds configuration and state common to all Firebase services that are exposed from the SDK.
4847
type App struct {
4948
authOverride map[string]interface{}
50-
creds *google.DefaultCredentials
5149
dbURL string
5250
projectID string
5351
serviceAccountID string
@@ -67,7 +65,6 @@ type Config struct {
6765
// Auth returns an instance of auth.Client.
6866
func (a *App) Auth(ctx context.Context) (*auth.Client, error) {
6967
conf := &internal.AuthConfig{
70-
Creds: a.creds,
7168
ProjectID: a.projectID,
7269
Opts: a.opts,
7370
ServiceAccountID: a.serviceAccountID,
@@ -142,36 +139,21 @@ func (a *App) Messaging(ctx context.Context) (*messaging.Client, error) {
142139
func NewApp(ctx context.Context, config *Config, opts ...option.ClientOption) (*App, error) {
143140
o := []option.ClientOption{option.WithScopes(internal.FirebaseScopes...)}
144141
o = append(o, opts...)
145-
creds, err := transport.Creds(ctx, o...)
146-
if err != nil {
147-
return nil, err
148-
}
149142
if config == nil {
143+
var err error
150144
if config, err = getConfigDefaults(); err != nil {
151145
return nil, err
152146
}
153147
}
154148

155-
var pid string
156-
if config.ProjectID != "" {
157-
pid = config.ProjectID
158-
} else if creds.ProjectID != "" {
159-
pid = creds.ProjectID
160-
} else {
161-
pid = os.Getenv("GOOGLE_CLOUD_PROJECT")
162-
if pid == "" {
163-
pid = os.Getenv("GCLOUD_PROJECT")
164-
}
165-
}
166-
149+
pid := getProjectID(ctx, config, o...)
167150
ao := defaultAuthOverrides
168151
if config.AuthOverride != nil {
169152
ao = *config.AuthOverride
170153
}
171154

172155
return &App{
173156
authOverride: ao,
174-
creds: creds,
175157
dbURL: config.DatabaseURL,
176158
projectID: pid,
177159
serviceAccountID: config.ServiceAccountID,
@@ -213,3 +195,20 @@ func getConfigDefaults() (*Config, error) {
213195
}
214196
return fbc, nil
215197
}
198+
199+
func getProjectID(ctx context.Context, config *Config, opts ...option.ClientOption) string {
200+
if config.ProjectID != "" {
201+
return config.ProjectID
202+
}
203+
204+
creds, _ := transport.Creds(ctx, opts...)
205+
if creds != nil && creds.ProjectID != "" {
206+
return creds.ProjectID
207+
}
208+
209+
if pid := os.Getenv("GOOGLE_CLOUD_PROJECT"); pid != "" {
210+
return pid
211+
}
212+
213+
return os.Getenv("GCLOUD_PROJECT")
214+
}

firebase_test.go

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ func TestServiceAcctFile(t *testing.T) {
5858
if len(app.opts) != 2 {
5959
t.Errorf("Client opts: %d; want: 2", len(app.opts))
6060
}
61-
if app.creds == nil {
62-
t.Error("Credentials: nil; want creds")
63-
} else if len(app.creds.JSON) == 0 {
64-
t.Error("JSON: empty; want; non-empty")
65-
}
6661
}
6762

6863
func TestClientOptions(t *testing.T) {
@@ -116,11 +111,6 @@ func TestRefreshTokenFile(t *testing.T) {
116111
if len(app.opts) != 2 {
117112
t.Errorf("Client opts: %d; want: 2", len(app.opts))
118113
}
119-
if app.creds == nil {
120-
t.Error("Credentials: nil; want creds")
121-
} else if len(app.creds.JSON) == 0 {
122-
t.Error("JSON: empty; want; non-empty")
123-
}
124114
}
125115

126116
func TestRefreshTokenFileWithConfig(t *testing.T) {
@@ -135,11 +125,6 @@ func TestRefreshTokenFileWithConfig(t *testing.T) {
135125
if len(app.opts) != 2 {
136126
t.Errorf("Client opts: %d; want: 2", len(app.opts))
137127
}
138-
if app.creds == nil {
139-
t.Error("Credentials: nil; want creds")
140-
} else if len(app.creds.JSON) == 0 {
141-
t.Error("JSON: empty; want; non-empty")
142-
}
143128
}
144129

145130
func TestRefreshTokenWithEnvVar(t *testing.T) {
@@ -158,11 +143,6 @@ func TestRefreshTokenWithEnvVar(t *testing.T) {
158143
if app.projectID != "mock-project-id" {
159144
t.Errorf("[env=%s] Project ID: %q; want: mock-project-id", varName, app.projectID)
160145
}
161-
if app.creds == nil {
162-
t.Errorf("[env=%s] Credentials: nil; want creds", varName)
163-
} else if len(app.creds.JSON) == 0 {
164-
t.Errorf("[env=%s] JSON: empty; want; non-empty", varName)
165-
}
166146
}
167147
for _, varName := range []string{"GCLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT"} {
168148
verify(varName)
@@ -185,11 +165,6 @@ func TestAppDefault(t *testing.T) {
185165
if len(app.opts) != 1 {
186166
t.Errorf("Client opts: %d; want: 1", len(app.opts))
187167
}
188-
if app.creds == nil {
189-
t.Error("Credentials: nil; want creds")
190-
} else if len(app.creds.JSON) == 0 {
191-
t.Error("JSON: empty; want; non-empty")
192-
}
193168
}
194169

195170
func TestAppDefaultWithInvalidFile(t *testing.T) {
@@ -201,8 +176,8 @@ func TestAppDefaultWithInvalidFile(t *testing.T) {
201176
defer os.Setenv(credEnvVar, current)
202177

203178
app, err := NewApp(context.Background(), nil)
204-
if app != nil || err == nil {
205-
t.Errorf("NewApp() = (%v, %v); want: (nil, error)", app, err)
179+
if app == nil || err != nil {
180+
t.Fatalf("NewApp() = (%v, %v); want = (app, nil)", app, err)
206181
}
207182
}
208183

@@ -215,12 +190,20 @@ func TestInvalidCredentialFile(t *testing.T) {
215190
ctx := context.Background()
216191
for _, tc := range invalidFiles {
217192
app, err := NewApp(ctx, nil, option.WithCredentialsFile(tc))
218-
if app != nil || err == nil {
219-
t.Errorf("NewApp(%q) = (%v, %v); want: (nil, error)", tc, app, err)
193+
if app == nil || err != nil {
194+
t.Fatalf("NewApp() = (%v, %v); want = (app, nil)", app, err)
220195
}
221196
}
222197
}
223198

199+
func TestExplicitNoAuth(t *testing.T) {
200+
ctx := context.Background()
201+
app, err := NewApp(ctx, nil, option.WithoutAuthentication())
202+
if app == nil || err != nil {
203+
t.Fatalf("NewApp() = (%v, %v); want = (app, nil)", app, err)
204+
}
205+
}
206+
224207
func TestAuth(t *testing.T) {
225208
ctx := context.Background()
226209
app, err := NewApp(ctx, nil, option.WithCredentialsFile("testdata/service_account.json"))

internal/internal.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"time"
2121

2222
"golang.org/x/oauth2"
23-
"golang.org/x/oauth2/google"
2423
"google.golang.org/api/option"
2524
)
2625

@@ -40,7 +39,6 @@ var SystemClock = &systemClock{}
4039
// AuthConfig represents the configuration of Firebase Auth service.
4140
type AuthConfig struct {
4241
Opts []option.ClientOption
43-
Creds *google.DefaultCredentials
4442
ProjectID string
4543
ServiceAccountID string
4644
Version string

0 commit comments

Comments
 (0)