213 lines
5.8 KiB
Dart
213 lines
5.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
|
import 'dart:convert' show jsonDecode;
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
|
|
class User {
|
|
const User(
|
|
{required this.firstname, required this.lastname, required this.email});
|
|
final String firstname;
|
|
final String lastname;
|
|
final String email;
|
|
|
|
static User fromJson(jsonDecode) {
|
|
if (jsonDecode == "") {
|
|
return const User(firstname: '', lastname: '', email: '');
|
|
}
|
|
return User(
|
|
firstname: jsonDecode['firstname'],
|
|
lastname: jsonDecode['lastname'],
|
|
email: jsonDecode['email']);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'firstname': firstname,
|
|
'lastname': lastname,
|
|
'email': email,
|
|
};
|
|
}
|
|
|
|
class TokenRefresher {
|
|
final AuthService authService;
|
|
final Duration refreshInterval;
|
|
late Timer _timer;
|
|
|
|
TokenRefresher(this.authService, {required this.refreshInterval});
|
|
|
|
void start() {
|
|
_timer = Timer.periodic(refreshInterval, (timer) async {
|
|
try {
|
|
await authService.doRefreshToken();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Failed to refresh token');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void stop() {
|
|
_timer.cancel();
|
|
}
|
|
}
|
|
|
|
class AuthService {
|
|
String clientId;
|
|
String host;
|
|
String redirectUri;
|
|
String realm;
|
|
String callbackUrlScheme;
|
|
User? user;
|
|
TokenRefresher? tokenRefresher;
|
|
String? accessToken = '';
|
|
String? refreshToken = '';
|
|
|
|
AuthService(
|
|
{required this.host,
|
|
required this.realm,
|
|
required this.clientId,
|
|
required this.redirectUri,
|
|
required this.callbackUrlScheme}) ;
|
|
|
|
// Add init method to initialize the AuthService, it retrives the access token from the shared preferences
|
|
Future<void> init() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
accessToken = prefs.getString('accessToken');
|
|
refreshToken = prefs.getString('refreshToken');
|
|
}
|
|
|
|
Future<bool> login() async {
|
|
// Build the url
|
|
final url =
|
|
Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/auth', {
|
|
'response_type': 'code',
|
|
'client_id': clientId,
|
|
'redirect_uri': '$callbackUrlScheme:/$redirectUri',
|
|
'scope': 'openid',
|
|
});
|
|
|
|
// Present the dialog to the user
|
|
try {
|
|
final result = await FlutterWebAuth2.authenticate(
|
|
url: url.toString(), callbackUrlScheme: callbackUrlScheme);
|
|
// Extract code from resulting url
|
|
final code = Uri.parse(result).queryParameters['code'];
|
|
|
|
// Use this code to get an access token
|
|
final tokenUrl =
|
|
Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/token');
|
|
var response = await http.post(tokenUrl, body: {
|
|
'client_id': clientId,
|
|
'grant_type': 'authorization_code',
|
|
'code': code,
|
|
'redirect_uri': '$callbackUrlScheme:/$redirectUri',
|
|
});
|
|
if (response.statusCode != 200) {
|
|
throw Exception('Failed to login');
|
|
}
|
|
|
|
// Get the access token from the response
|
|
await setAccessToken(jsonDecode(response.body)['access_token'] as String);
|
|
await setRefreshToken(jsonDecode(response.body)['refresh_token'] as String);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
|
|
tokenRefresher = TokenRefresher(this, refreshInterval: const Duration(seconds: 60));
|
|
tokenRefresher!.start();
|
|
|
|
return true;
|
|
}
|
|
|
|
Future<bool> logout() async {
|
|
if (accessToken == null || accessToken == "") {
|
|
throw Exception('Not logged in');
|
|
}
|
|
|
|
final logoutUrl =
|
|
Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/logout');
|
|
|
|
final response = await http.post(logoutUrl, body: {
|
|
'client_id': clientId,
|
|
'refresh_token': refreshToken,
|
|
});
|
|
if (response.statusCode != 204) {
|
|
throw Exception('Failed to logout');
|
|
}
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.clear();
|
|
user = null;
|
|
|
|
tokenRefresher!.stop();
|
|
|
|
return true;
|
|
}
|
|
|
|
Future<bool> doRefreshToken() async {
|
|
if (refreshToken == "") {
|
|
throw Exception('Not logged in');
|
|
}
|
|
|
|
final tokenUrl =
|
|
Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/token');
|
|
final response = await http.post(tokenUrl, body: {
|
|
'client_id': clientId,
|
|
'grant_type': 'refresh_token',
|
|
'refresh_token': refreshToken,
|
|
});
|
|
if (response.statusCode != 200) {
|
|
throw Exception('Failed to refresh token: ${response.body}');
|
|
}
|
|
|
|
// Get the access token from the response
|
|
await setAccessToken(jsonDecode(response.body)['access_token'] as String);
|
|
await setRefreshToken(jsonDecode(response.body)['refresh_token'] as String);
|
|
return true;
|
|
}
|
|
|
|
Future<bool> isLoggedIn() async {
|
|
user = await getUser();
|
|
return user != null;
|
|
}
|
|
|
|
Future<void> setAccessToken(String token) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
accessToken = token;
|
|
}
|
|
|
|
Future<void> setRefreshToken(String token) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
prefs.setString('refreshToken', token);
|
|
refreshToken = token;
|
|
}
|
|
|
|
Future<User?> getUser() async {
|
|
if (this.user != null) {
|
|
return this.user!;
|
|
}
|
|
|
|
final userInfoEndpoint =
|
|
Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/userinfo');
|
|
var response = await http.get(userInfoEndpoint, headers: {
|
|
'Authorization': 'Bearer $accessToken',
|
|
});
|
|
if (response.statusCode != 200) {
|
|
return null;
|
|
}
|
|
final userMap = jsonDecode(response.body) as Map<String, dynamic>;
|
|
var user = User(
|
|
firstname: userMap['given_name'] as String,
|
|
lastname: userMap['family_name'] as String,
|
|
email: userMap['email'] as String);
|
|
|
|
this.user = user;
|
|
|
|
return user;
|
|
}
|
|
}
|