You've already forked flutter-rp-example
First commit
This commit is contained in:
212
lib/services/auth/auth.dart
Normal file
212
lib/services/auth/auth.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
30
lib/services/auth/auth_view_model.dart
Normal file
30
lib/services/auth/auth_view_model.dart
Normal 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
44
lib/services/gcs.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
31
lib/services/grpc_service.dart
Normal file
31
lib/services/grpc_service.dart
Normal 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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user