First commit

This commit is contained in:
Nathan SOULIER
2025-03-17 13:58:47 +01:00
commit fff5617757
159 changed files with 6972 additions and 0 deletions

212
lib/services/auth/auth.dart Normal file
View File

@@ -0,0 +1,212 @@
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;
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/cupertino.dart';
import 'package:sampleapp/services/auth/auth.dart';
class AuthViewModel extends ChangeNotifier {
final AuthService authService;
bool loggingIn = false;
bool loggingOut = false;
AuthViewModel(this.authService);
Future<bool> login() {
return Future.delayed(Duration.zero, () async {
loggingIn = true;
notifyListeners();
await authService.login();
loggingIn = false;
notifyListeners();
return authService.isLoggedIn();
});
}
Future<bool> logout() async {
loggingOut = true;
notifyListeners();
await authService.logout();
loggingOut = false;
notifyListeners();
return !(await authService.isLoggedIn());
}
}

44
lib/services/gcs.dart Normal file
View File

@@ -0,0 +1,44 @@
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
class GcsService {
// ========================================
// ======== GCS GOOGLE API SERVICE ========
static const GCS_GOOGLE_BASE_URI = "https://storage.googleapis.com";
static const GCS_GOOGLE_UPLOAD_ENDPOINT = "/upload/storage/v1/b/";
static const GET_BUCKET_STS_GOOGLE_ENDPOINT =
"/api.GcsApi/GetBucketSTSGoogle";
Future<bool> uploadImage(String token, String bucketName, String resourceName,
String fileName, File file) async {
var headersData = {
"Authorization": "Bearer $token",
"Content-Type": "image/png",
};
var paramsData = {
'uploadType': 'multipart/related; boundary=image/png',
'name': "$resourceName$fileName",
};
var uploadURI = "$GCS_GOOGLE_BASE_URI$GCS_GOOGLE_UPLOAD_ENDPOINT";
var query = paramsData.entries.map((p) => '${p.key}=${p.value}').join('&');
var request = http.MultipartRequest(
"POST", Uri.parse("$uploadURI$bucketName/o?$resourceName$query"));
request.headers.addAll(headersData);
request.files.add(await http.MultipartFile.fromPath(fileName, file.path,
contentType: MediaType('image', 'png')));
var response = await request.send();
if (response.statusCode != 200) {
throw Exception('Failed to upload image.');
}
return response.statusCode == 200;
}
}

View File

@@ -0,0 +1,31 @@
import 'package:grpc/grpc.dart';
import 'package:sampleapp/services/auth/auth.dart';
class AuthInterceptor extends ClientInterceptor {
final AuthService authService;
AuthInterceptor(this.authService);
@override
ResponseFuture<R> interceptUnary<Q, R>(
ClientMethod<Q, R> method, Q request, CallOptions options, invoker) {
final metadata = <String, String>{};
metadata['authorization'] = 'Bearer ${authService.accessToken}';
options = options.mergedWith(CallOptions(metadata: metadata));
return invoker(method, request, options);
}
}
class GrpcClient {
static Client initializeClient(AuthService authService, String host, int port, Client Function(ClientChannel, List<ClientInterceptor>) clientFactory) {
final interceptor = AuthInterceptor(authService);
final channel = ClientChannel(
host,
port: port,
options: const ChannelOptions(
credentials: ChannelCredentials.secure(),
),
);
return clientFactory(channel, [interceptor]);
}
}