Skip to content

Commit

Permalink
chore(app): improve login flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin-Frost committed Jul 25, 2022
1 parent bbb9595 commit 3a3dab9
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 89 deletions.
6 changes: 6 additions & 0 deletions app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ PODS:
- Flutter
- flutter_share (0.0.1):
- Flutter
- flutter_web_auth (0.4.1):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- path_provider_ios (0.0.1):
Expand All @@ -22,6 +24,7 @@ DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_share (from `.symlinks/plugins/flutter_share/ios`)
- flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- sensors_plus (from `.symlinks/plugins/sensors_plus/ios`)
Expand All @@ -37,6 +40,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
flutter_share:
:path: ".symlinks/plugins/flutter_share/ios"
flutter_web_auth:
:path: ".symlinks/plugins/flutter_web_auth/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_ios:
Expand All @@ -53,6 +58,7 @@ SPEC CHECKSUMS:
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
flutter_share: 4be0208963c60b537e6255ed2ce1faae61cd9ac2
flutter_web_auth: 09a0abd245f1a07a3ff4dcf1247a048d89ee12a9
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
sensors_plus: 5717760720f7e6acd96fdbd75b7428f5ad755ec2
Expand Down
5 changes: 4 additions & 1 deletion app/lib/common/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ final annotationServerIp = Uri.https(
);
final annotationServerUrl = annotationServerIp.replace(path: 'api/v1');
final labServerUrl = labServerIp.replace(path: 'api/v1');
final keycloakUrl = labServerIp;
final keycloakUrl = Uri.https(
'keycloak.k8s.kunst.me',
'',
);
final cpicMaxCacheTime = Duration(days: 90);
const maxCachedMedications = 10;
const cpicLookupUrl =
Expand Down
32 changes: 24 additions & 8 deletions app/lib/login/models/lab.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
import '../../common/module.dart';

class Lab {
Lab(this.name, this.authUrl, this.endpoint);
Lab({
required this.name,
required this.authUrl,
required this.tokenUrl,
required this.endpoint,
});

String name;
String authUrl;
Uri authUrl;
Uri tokenUrl;
String endpoint;
}

final labs = [
Lab(
'Illumina Solutions Center Berlin',
'$keycloakUrl/auth/realms/pharme',
'$labServerUrl/star-alleles',
name: 'Illumina Solutions Center Berlin',
authUrl: keycloakUrl.replace(
path: '/auth/realms/pharme/protocol/openid-connect/auth',
),
tokenUrl: keycloakUrl.replace(
path: '/auth/realms/pharme/protocol/openid-connect/token',
),
endpoint: '$labServerUrl/star-alleles',
),
Lab(
'Mount Sinai Hospital (NYC)',
'$keycloakUrl/auth/realms/pharme',
'$labServerUrl/star-alleles',
name: 'Mount Sinai Hospital (NYC)',
authUrl: keycloakUrl.replace(
path: '/auth/realms/pharme/protocol/openid-connect/auth',
),
tokenUrl: keycloakUrl.replace(
path: '/auth/realms/pharme/protocol/openid-connect/token',
),
endpoint: '$labServerUrl/star-alleles',
)
];
82 changes: 55 additions & 27 deletions app/lib/login/pages/cubit.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'dart:convert' show jsonDecode;

import 'package:flutter/services.dart';
import 'package:flutter_web_auth/flutter_web_auth.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:openid_client/openid_client_io.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:http/http.dart' as http;

import '../../../common/module.dart';
import '../models/lab.dart';
Expand All @@ -16,10 +18,25 @@ class LoginPageCubit extends Cubit<LoginPageState> {
// signInAndLoadUserData authenticates a user with a Lab and fetches their
// genomic data from it's endpoint.
Future<void> signInAndLoadUserData(BuildContext context, Lab lab) async {
emit(LoginPageState.loadingUserData());

try {
// authenticate
final token = await _getAccessToken(context, lab.authUrl);
emit(LoginPageState.loadingUserData());
String token;
try {
token = await _getAccessToken(
context,
authUrl: lab.authUrl,
tokenUrl: lab.tokenUrl,
);
} on PlatformException catch (e) {
if (e.code == 'CANCELED') {
revertToInitialState();
} else {
emit(LoginPageState.error(context.l10n.err_generic));
}
return;
}

// get data
await fetchAndSaveDiplotypes(token, lab.endpoint);
Expand All @@ -34,30 +51,41 @@ class LoginPageCubit extends Cubit<LoginPageState> {
}
}

Future<String> _getAccessToken(BuildContext context, String authUrl) async {
final uri = Uri.parse(authUrl);
Future<String> _getAccessToken(
BuildContext context, {
required Uri authUrl,
required Uri tokenUrl,
}) async {
const clientId = 'pharme-app';
final scopes = List<String>.of(['openid', 'profile']);
const port = 4200;

final issuer = await Issuer.discover(uri);
final client = Client(issuer, clientId);

final authenticator = Authenticator(
client,
scopes: scopes,
port: port,
urlLancher: (url) async {
if (await canLaunchUrlString(url)) {
await launchUrlString(url);
} else {
throw Exception(context.l10n.err_could_not_launch(url));
}
},
const callbackUrlScheme = 'localhost';

// Construct the url
final url = authUrl.replace(queryParameters: {
'response_type': 'code',
'client_id': clientId,
'redirect_uri': '$callbackUrlScheme:/',
'scope': 'openid profile',
});

// Present the dialog to the user
final result = await FlutterWebAuth.authenticate(
url: url.toString(),
callbackUrlScheme: callbackUrlScheme,
);
final credentials = await authenticator.authorize();
await closeInAppWebView();
return credentials.getTokenResponse().then((res) => res.accessToken ?? '');

// Extract code from resulting url
final code = Uri.parse(result).queryParameters['code'];

// Use this code to get an access token
final response = await http.post(tokenUrl, body: {
'client_id': clientId,
'redirect_uri': '$callbackUrlScheme:/',
'grant_type': 'authorization_code',
'code': code,
});

// Get the access token from the response
return jsonDecode(response.body)['access_token'] as String;
}
}

Expand Down
6 changes: 4 additions & 2 deletions app/lib/login/pages/login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ class _LoginPageState extends State<LoginPage> {
width: double.infinity,
child: ElevatedButton(
onPressed: () async {
final found = labs.firstWhere((el) => el.name == dropdownValue);
final selectedLab = labs.firstWhere(
(el) => el.name == dropdownValue,
);
await context
.read<LoginPageCubit>()
.signInAndLoadUserData(context, found);
.signInAndLoadUserData(context, selectedLab);
},
style: ButtonStyle(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
Expand Down
Loading

0 comments on commit 3a3dab9

Please sign in to comment.