feat(shorebird_code_push_protocol): add AuthProvider (#1745)
This commit is contained in:
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user