Skip to content

Commit 528738a

Browse files
authored
feat: Add Google Cloud Identity Platform authenticator (#1123)
Background: In a future PR, we will introduce an authenticator http middleware that will call Authenticate. This change is the Google Cloud Identity Platform (GCIP) implementation of that Authenticate interface. GCIP uses firebase under the hood. This implementation comes straight from the admin SDK docs: https://firebase.google.com/docs/auth/admin/verify-id-tokens#go This change also adds a new User type that the middleware will expect. Other changes: - Run go mod tidy on each package via `make go-tidy` - Needed after importing `firebaseauth.Token` into `lib`
1 parent 01e5d47 commit 528738a

File tree

7 files changed

+186
-0
lines changed

7 files changed

+186
-0
lines changed

lib/auth/gcip_authenticator.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package auth
16+
17+
import (
18+
"context"
19+
20+
firebaseauth "firebase.google.com/go/v4/auth"
21+
)
22+
23+
// GCIPAuthenticator is a struct that authenticates users using Firebase Auth,
24+
// which is part of Google Cloud Identity Platform (GCIP).
25+
// It implements the UserAuthClient interface.
26+
type GCIPAuthenticator struct {
27+
UserAuthClient
28+
}
29+
30+
// UserAuthClient is an interface that defines the methods for interacting with a user authentication provider.
31+
type UserAuthClient interface {
32+
VerifyIDToken(context.Context, string) (*firebaseauth.Token, error)
33+
}
34+
35+
// NewGCIPAuthenticator creates a new GCIPAuthenticator instance.
36+
func NewGCIPAuthenticator(client UserAuthClient) *GCIPAuthenticator {
37+
return &GCIPAuthenticator{
38+
client,
39+
}
40+
}
41+
42+
// Authenticate authenticates a user using the provided ID token.
43+
func (a GCIPAuthenticator) Authenticate(ctx context.Context, idToken string) (*User, error) {
44+
// Verify the ID token using the Firebase Auth client.
45+
token, err := a.VerifyIDToken(ctx, idToken)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
// Create a new user object using the user's ID from the token.
51+
return &User{
52+
ID: token.UID,
53+
}, nil
54+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package auth
16+
17+
import (
18+
"context"
19+
"errors"
20+
"reflect"
21+
"testing"
22+
23+
firebaseauth "firebase.google.com/go/v4/auth"
24+
)
25+
26+
// TestGCIPAuthenticator tests the GCIPAuthenticator functions.
27+
func TestGCIPAuthenticator(t *testing.T) {
28+
tests := []struct {
29+
name string
30+
idToken string
31+
mockVerifyFn func(context.Context, string) (*firebaseauth.Token, error)
32+
expectedUser *User
33+
expectedError bool
34+
}{
35+
{
36+
name: "Successful authentication",
37+
idToken: "valid_id_token",
38+
mockVerifyFn: func(_ context.Context, _ string) (*firebaseauth.Token, error) {
39+
// nolint:exhaustruct // WONTFIX -third part struct used for testing
40+
return &firebaseauth.Token{UID: "123"}, nil
41+
},
42+
expectedUser: &User{ID: "123"},
43+
expectedError: false,
44+
},
45+
{
46+
name: "Authentication failure",
47+
idToken: "invalid_id_token",
48+
mockVerifyFn: func(_ context.Context, _ string) (*firebaseauth.Token, error) {
49+
return nil, errors.New("invalid ID token")
50+
},
51+
expectedUser: nil,
52+
expectedError: true,
53+
},
54+
}
55+
56+
for _, tc := range tests {
57+
t.Run(tc.name, func(t *testing.T) {
58+
// Create a mock UserAuthClient.
59+
mockUserAuthClient := &MockUserAuthClient{
60+
verifyIDTokenFn: tc.mockVerifyFn,
61+
}
62+
63+
// Create a GCIPAuthenticator using the mock client.
64+
authenticator := NewGCIPAuthenticator(mockUserAuthClient)
65+
66+
// Authenticate the user.
67+
user, err := authenticator.Authenticate(context.Background(), tc.idToken)
68+
69+
// Check if the error matches the expected outcome.
70+
if tc.expectedError && err == nil {
71+
t.Fatal("Expected authentication to fail, but it succeeded")
72+
} else if !tc.expectedError && err != nil {
73+
t.Fatalf("Failed to authenticate: %v", err)
74+
}
75+
76+
// Check if the user matches the expected value.
77+
if !reflect.DeepEqual(tc.expectedUser, user) {
78+
t.Errorf("Expected user to be '%+v', got '%+v'", tc.expectedUser, user)
79+
}
80+
})
81+
}
82+
}
83+
84+
// MockUserAuthClient is a mock implementation of the UserAuthClient interface.
85+
type MockUserAuthClient struct {
86+
verifyIDTokenFn func(context.Context, string) (*firebaseauth.Token, error)
87+
}
88+
89+
// VerifyIDToken verifies an ID token.
90+
func (m *MockUserAuthClient) VerifyIDToken(ctx context.Context, idToken string) (*firebaseauth.Token, error) {
91+
if m.verifyIDTokenFn == nil {
92+
panic("verifyIDTokenFn not set")
93+
}
94+
95+
return m.verifyIDTokenFn(ctx, idToken)
96+
}

lib/auth/types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package auth
16+
17+
// User contains the details of an authenticated user.
18+
type User struct {
19+
ID string
20+
}

lib/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
cloud.google.com/go/datastore v1.20.0
99
cloud.google.com/go/spanner v1.73.0
1010
cloud.google.com/go/storage v1.49.0
11+
firebase.google.com/go/v4 v4.15.1
1112
github.com/GoogleChrome/webstatus.dev/lib/gen v0.0.0-20241229174745-146e1a05eafd
1213
github.com/antlr4-go/antlr/v4 v4.13.1
1314
github.com/deckarep/golang-set v1.8.0
@@ -35,6 +36,7 @@ require (
3536
github.com/getkin/kin-openapi v0.128.0 // indirect
3637
github.com/go-openapi/jsonpointer v0.21.0 // indirect
3738
github.com/go-openapi/swag v0.23.0 // indirect
39+
github.com/golang/protobuf v1.5.4 // indirect
3840
github.com/google/go-github/v65 v65.0.0 // indirect
3941
github.com/invopop/yaml v0.3.1 // indirect
4042
github.com/josharian/intern v1.0.0 // indirect
@@ -51,6 +53,7 @@ require (
5153
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.9.0 // indirect
5254
go.opentelemetry.io/otel/log v0.9.0 // indirect
5355
go.opentelemetry.io/otel/sdk/log v0.9.0 // indirect
56+
google.golang.org/appengine/v2 v2.0.2 // indirect
5457
)
5558

5659
require (

lib/go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcP
625625
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
626626
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
627627
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
628+
firebase.google.com/go/v4 v4.15.1 h1:tR2dzKw1MIfCfG2bhAyxa5KQ57zcE7iFKmeYClET6ZM=
629+
firebase.google.com/go/v4 v4.15.1/go.mod h1:eunxbsh4UXI2rA8po3sOiebvWYuW0DVxAdZFO0I6wdY=
628630
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
629631
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
630632
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
@@ -1272,6 +1274,7 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
12721274
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
12731275
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
12741276
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
1277+
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
12751278
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
12761279
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
12771280
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
@@ -1602,6 +1605,8 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
16021605
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
16031606
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
16041607
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
1608+
google.golang.org/appengine/v2 v2.0.2 h1:MSqyWy2shDLwG7chbwBJ5uMyw6SNqJzhJHNDwYB0Akk=
1609+
google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4HoVEdMMYQR/8E=
16051610
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
16061611
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
16071612
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=

workflows/steps/services/uma_export/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require (
2424
cloud.google.com/go/monitoring v1.22.0 // indirect
2525
cloud.google.com/go/secretmanager v1.14.2 // indirect
2626
cloud.google.com/go/spanner v1.73.0 // indirect
27+
firebase.google.com/go/v4 v4.15.1 // indirect
2728
github.com/GoogleChrome/webstatus.dev/lib/gen v0.0.0-20241229174745-146e1a05eafd // indirect
2829
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect
2930
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
@@ -42,6 +43,7 @@ require (
4243
github.com/go-openapi/jsonpointer v0.21.0 // indirect
4344
github.com/go-openapi/swag v0.23.0 // indirect
4445
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
46+
github.com/golang/protobuf v1.5.4 // indirect
4547
github.com/gomodule/redigo v1.9.2 // indirect
4648
github.com/google/go-github/v65 v65.0.0 // indirect
4749
github.com/google/go-querystring v1.1.0 // indirect
@@ -80,6 +82,7 @@ require (
8082
golang.org/x/text v0.21.0 // indirect
8183
golang.org/x/time v0.8.0 // indirect
8284
google.golang.org/api v0.214.0 // indirect
85+
google.golang.org/appengine/v2 v2.0.2 // indirect
8386
google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 // indirect
8487
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect
8588
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect

workflows/steps/services/uma_export/go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,8 @@ cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcP
621621
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
622622
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
623623
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
624+
firebase.google.com/go/v4 v4.15.1 h1:tR2dzKw1MIfCfG2bhAyxa5KQ57zcE7iFKmeYClET6ZM=
625+
firebase.google.com/go/v4 v4.15.1/go.mod h1:eunxbsh4UXI2rA8po3sOiebvWYuW0DVxAdZFO0I6wdY=
624626
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
625627
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
626628
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
@@ -1181,6 +1183,7 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
11811183
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
11821184
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
11831185
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
1186+
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
11841187
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
11851188
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
11861189
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
@@ -1502,6 +1505,8 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
15021505
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
15031506
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
15041507
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
1508+
google.golang.org/appengine/v2 v2.0.2 h1:MSqyWy2shDLwG7chbwBJ5uMyw6SNqJzhJHNDwYB0Akk=
1509+
google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4HoVEdMMYQR/8E=
15051510
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
15061511
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
15071512
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=

0 commit comments

Comments
 (0)