Nathan SOULIER fff5617757 First commit
2025-03-24 10:12:56 +01:00

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;
}
}