feat(shorebird_code_push_protocol): add AuthProvider (#1745)

This commit is contained in:
Bryan Oltman
2024-02-22 11:57:10 -05:00
committed by GitHub
parent 978381152d
commit 5c4f806a0c
8 changed files with 51 additions and 22 deletions
@@ -22,7 +22,7 @@ final authRef = create(Auth.new);
Auth get auth => read(authRef);
typedef ObtainAccessCredentials = Future<oauth2.AccessCredentials> Function(
AuthProvider authProvider,
oauth2.AuthProvider authProvider,
oauth2.ClientId clientId,
List<String> scopes,
http.Client client,
@@ -30,7 +30,7 @@ typedef ObtainAccessCredentials = Future<oauth2.AccessCredentials> Function(
);
typedef RefreshCredentials = Future<oauth2.AccessCredentials> Function(
AuthProvider authProvider,
oauth2.AuthProvider authProvider,
oauth2.ClientId clientId,
oauth2.AccessCredentials credentials,
http.Client client,
@@ -168,7 +168,7 @@ class Auth {
}
Future<AccessCredentials> loginCI(
AuthProvider authProvider, {
oauth2.AuthProvider authProvider, {
required void Function(String) prompt,
}) async {
final client = http.Client();
@@ -198,7 +198,7 @@ class Auth {
}
Future<void> login(
AuthProvider authProvider, {
oauth2.AuthProvider authProvider, {
required void Function(String) prompt,
}) async {
if (_credentials != null) {
@@ -330,7 +330,7 @@ extension OauthAuthProvider on Jwt {
}
}
extension OauthValues on AuthProvider {
extension OauthValues on oauth2.AuthProvider {
oauth2.ClientId get clientId {
switch (runtimeType) {
case oauth2.GoogleAuthProvider:
@@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:io' hide Platform;
import 'package:cli_util/cli_util.dart';
import 'package:googleapis_auth/googleapis_auth.dart';
import 'package:googleapis_auth/googleapis_auth.dart' as oauth2;
import 'package:http/http.dart' as http;
import 'package:jwt/jwt.dart' show Jwt, JwtPayload;
import 'package:mason_logger/mason_logger.dart';
@@ -22,7 +22,7 @@ import 'package:test/test.dart';
import '../fakes.dart';
import '../mocks.dart';
class FakeProvider extends AuthProvider {
class FakeProvider extends oauth2.AuthProvider {
@override
Uri get authorizationEndpoint => Uri.https('example.com');
@@ -66,8 +66,8 @@ void main() {
group('JwtClaims', () {
group('email', () {
test('returns null when idToken is not a valid jwt', () {
final credentials = AccessCredentials(
AccessToken(
final credentials = oauth2.AccessCredentials(
oauth2.AccessToken(
'Bearer',
'accessToken',
DateTime.now().add(const Duration(minutes: 10)).toUtc(),
@@ -142,18 +142,18 @@ void main() {
const idToken =
'''eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI1MjMzMDIyMzMyOTMtZWlhNWFudG0wdGd2ZWsyNDB0NDZvcmN0a3RpYWJyZWsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI1MjMzMDIyMzMyOTMtZWlhNWFudG0wdGd2ZWsyNDB0NDZvcmN0a3RpYWJyZWsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMjM0NSIsImhkIjoic2hvcmViaXJkLmRldiIsImVtYWlsIjoidGVzdEBlbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaWF0IjoxMjM0LCJleHAiOjY3ODl9.MYbITALvKsGYTYjw1o7AQ0ObkqRWVBSr9cFYJrvA46g''';
const email = 'test@email.com';
const user = User(id: 42, email: email);
const user = User(id: 42, email: email, authProvider: AuthProvider.google);
const refreshToken = '';
const scopes = <String>[];
final googleAuthProvider = GoogleAuthProvider();
final microsoftAuthProvider = MicrosoftAuthProvider();
final accessToken = AccessToken(
final accessToken = oauth2.AccessToken(
'Bearer',
'accessToken',
DateTime.now().add(const Duration(minutes: 10)).toUtc(),
);
final accessCredentials = AccessCredentials(
final accessCredentials = oauth2.AccessCredentials(
accessToken,
refreshToken,
scopes,
@@ -244,7 +244,7 @@ void main() {
),
);
final onRefreshCredentialsCalls = <AccessCredentials>[];
final onRefreshCredentialsCalls = <oauth2.AccessCredentials>[];
final client = AuthenticatedClient.token(
token: token,
@@ -262,7 +262,7 @@ void main() {
expect(
onRefreshCredentialsCalls,
equals([
isA<AccessCredentials>()
isA<oauth2.AccessCredentials>()
.having((c) => c.idToken, 'token', idToken),
]),
);
@@ -279,7 +279,7 @@ void main() {
HttpStatus.ok,
),
);
final onRefreshCredentialsCalls = <AccessCredentials>[];
final onRefreshCredentialsCalls = <oauth2.AccessCredentials>[];
final client = AuthenticatedClient.token(
token: token,
httpClient: httpClient,
@@ -318,9 +318,9 @@ void main() {
const expiredIdToken =
'''eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI1MjMzMDIyMzMyOTMtZWlhNWFudG0wdGd2ZWsyNDB0NDZvcmN0a3RpYWJyZWsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI1MjMzMDIyMzMyOTMtZWlhNWFudG0wdGd2ZWsyNDB0NDZvcmN0a3RpYWJyZWsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMjM0NSIsImhkIjoic2hvcmViaXJkLmRldiIsImVtYWlsIjoidGVzdEBlbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaWF0IjoxMjM0LCJleHAiOjY3ODl9.MYbITALvKsGYTYjw1o7AQ0ObkqRWVBSr9cFYJrvA46g''';
final onRefreshCredentialsCalls = <AccessCredentials>[];
final expiredCredentials = AccessCredentials(
AccessToken(
final onRefreshCredentialsCalls = <oauth2.AccessCredentials>[];
final expiredCredentials = oauth2.AccessCredentials(
oauth2.AccessToken(
'Bearer',
'accessToken',
DateTime.now().subtract(const Duration(minutes: 1)).toUtc(),
@@ -346,7 +346,7 @@ void main() {
expect(
onRefreshCredentialsCalls,
equals([
isA<AccessCredentials>()
isA<oauth2.AccessCredentials>()
.having((c) => c.idToken, 'token', idToken),
]),
);
@@ -363,7 +363,7 @@ void main() {
HttpStatus.ok,
),
);
final onRefreshCredentialsCalls = <AccessCredentials>[];
final onRefreshCredentialsCalls = <oauth2.AccessCredentials>[];
final client = AuthenticatedClient.credentials(
credentials: accessCredentials,
httpClient: httpClient,
@@ -431,7 +431,7 @@ void main() {
() async {
final client = auth.client;
expect(client, isA<http.Client>());
expect(client, isNot(isA<AutoRefreshingAuthClient>()));
expect(client, isNot(isA<oauth2.AutoRefreshingAuthClient>()));
});
});
@@ -89,7 +89,11 @@ void main() {
});
group('getCurrentUser', () {
const user = User(id: 123, email: 'tester@shorebird.dev');
const user = User(
id: 123,
email: 'tester@shorebird.dev',
authProvider: AuthProvider.google,
);
test('makes the correct request', () async {
codePushClient.getCurrentUser().ignore();
@@ -1110,6 +1114,7 @@ void main() {
id: 1,
email: 'tester@shorebird.dev',
displayName: userName,
authProvider: AuthProvider.microsoft,
);
test('makes the correct request', () async {
@@ -0,0 +1,8 @@
/// The authentication provider used to sign in the user.
enum AuthProvider {
/// The user authenticated using their Google account with our GCP project.
google,
/// The user authenticated with their Azure/Entra account.
microsoft,
}
@@ -1,5 +1,6 @@
export 'app.dart';
export 'app_metadata.dart';
export 'auth_provider.dart';
export 'channel.dart';
export 'error_response.dart';
export 'patch.dart';
@@ -1,4 +1,5 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
part 'user.g.dart';
@@ -11,6 +12,7 @@ class User {
const User({
required this.id,
required this.email,
required this.authProvider,
this.hasActiveSubscription = false,
this.displayName,
this.stripeCustomerId,
@@ -36,4 +38,7 @@ class User {
/// The user's Stripe customer ID, if they have one.
final String? stripeCustomerId;
/// The SSO provider used to create the user.
final AuthProvider authProvider;
}
@@ -15,6 +15,8 @@ User _$UserFromJson(Map<String, dynamic> json) => $checkedCreate(
final val = User(
id: $checkedConvert('id', (v) => v as int),
email: $checkedConvert('email', (v) => v as String),
authProvider: $checkedConvert(
'auth_provider', (v) => $enumDecode(_$AuthProviderEnumMap, v)),
hasActiveSubscription: $checkedConvert(
'has_active_subscription', (v) => v as bool? ?? false),
displayName: $checkedConvert('display_name', (v) => v as String?),
@@ -24,6 +26,7 @@ User _$UserFromJson(Map<String, dynamic> json) => $checkedCreate(
return val;
},
fieldKeyMap: const {
'authProvider': 'auth_provider',
'hasActiveSubscription': 'has_active_subscription',
'displayName': 'display_name',
'stripeCustomerId': 'stripe_customer_id'
@@ -36,4 +39,10 @@ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'display_name': instance.displayName,
'has_active_subscription': instance.hasActiveSubscription,
'stripe_customer_id': instance.stripeCustomerId,
'auth_provider': _$AuthProviderEnumMap[instance.authProvider]!,
};
const _$AuthProviderEnumMap = {
AuthProvider.google: 'google',
AuthProvider.microsoft: 'microsoft',
};
@@ -9,6 +9,7 @@ void main() {
email: 'test@shorebird.dev',
stripeCustomerId: 'test-customer-id',
displayName: 'Test User',
authProvider: AuthProvider.google,
);
expect(
User.fromJson(user.toJson()).toJson(),