|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:convert' show base64Url, jsonDecode, jsonEncode, utf8;
|
|
|
|
|
import 'dart:developer' as developer;
|
|
|
|
|
|
|
|
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
|
|
@@ -265,23 +265,27 @@ class AuthNotifier extends _$AuthNotifier {
|
|
|
|
|
final oidcService = OidcServiceWeb();
|
|
|
|
|
final tokens = await oidcService.exchangeCode(code, callbackState);
|
|
|
|
|
|
|
|
|
|
// Step 2: Sync with core-api to get user profile and roles
|
|
|
|
|
developer.log('Syncing with core-api', name: 'auth');
|
|
|
|
|
final authDatasource = ref.read(authDatasourceProvider);
|
|
|
|
|
final syncResponse = await authDatasource.syncUser(tokens.accessToken);
|
|
|
|
|
// Step 2: Decode JWT to extract user info (skip core-api sync)
|
|
|
|
|
final claims = _decodeJwtClaims(tokens.accessToken);
|
|
|
|
|
final userName = claims['name'] as String? ??
|
|
|
|
|
claims['preferred_username'] as String? ??
|
|
|
|
|
'User';
|
|
|
|
|
final userEmail = claims['email'] as String? ?? '';
|
|
|
|
|
final authentikId = claims['sub'] as String?;
|
|
|
|
|
final groups = (claims['groups'] as List<dynamic>?)?.cast<String>() ?? [];
|
|
|
|
|
|
|
|
|
|
// Step 3: Store credentials and user data
|
|
|
|
|
developer.log('JWT claims: name=$userName, email=$userEmail, groups=$groups', name: 'auth');
|
|
|
|
|
|
|
|
|
|
// Step 3: Store credentials and user data from JWT
|
|
|
|
|
await _storeAuth(
|
|
|
|
|
accessToken: tokens.accessToken,
|
|
|
|
|
refreshToken: tokens.refreshToken,
|
|
|
|
|
expiresAt: tokens.expiresAt,
|
|
|
|
|
userId: syncResponse.userId,
|
|
|
|
|
authentikId: syncResponse.authentikId,
|
|
|
|
|
userName: syncResponse.name,
|
|
|
|
|
userEmail: syncResponse.email,
|
|
|
|
|
avatarUrl: syncResponse.avatarUrl,
|
|
|
|
|
roles: syncResponse.roles,
|
|
|
|
|
preferences: syncResponse.preferences,
|
|
|
|
|
authentikId: authentikId,
|
|
|
|
|
userName: userName,
|
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
// Roles from groups - for now just store group names
|
|
|
|
|
// Full role parsing can be done later if needed
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
state = AsyncData(AuthState(
|
|
|
|
@@ -289,19 +293,12 @@ class AuthNotifier extends _$AuthNotifier {
|
|
|
|
|
accessToken: tokens.accessToken,
|
|
|
|
|
refreshToken: tokens.refreshToken,
|
|
|
|
|
expiresAt: tokens.expiresAt,
|
|
|
|
|
userId: syncResponse.userId,
|
|
|
|
|
authentikId: syncResponse.authentikId,
|
|
|
|
|
userName: syncResponse.name,
|
|
|
|
|
userEmail: syncResponse.email,
|
|
|
|
|
avatarUrl: syncResponse.avatarUrl,
|
|
|
|
|
roles: syncResponse.roles,
|
|
|
|
|
preferences: syncResponse.preferences,
|
|
|
|
|
authentikId: authentikId,
|
|
|
|
|
userName: userName,
|
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
developer.log(
|
|
|
|
|
'Authenticated as ${syncResponse.name} with ${syncResponse.roles.length} roles',
|
|
|
|
|
name: 'auth',
|
|
|
|
|
);
|
|
|
|
|
developer.log('Authenticated as $userName', name: 'auth');
|
|
|
|
|
|
|
|
|
|
// Clean up the URL by removing the query parameters
|
|
|
|
|
web_utils.replaceUrl('/');
|
|
|
|
@@ -314,6 +311,35 @@ class AuthNotifier extends _$AuthNotifier {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Decode JWT payload without verification (validation happens server-side).
|
|
|
|
|
Map<String, dynamic> _decodeJwtClaims(String jwt) {
|
|
|
|
|
try {
|
|
|
|
|
final parts = jwt.split('.');
|
|
|
|
|
if (parts.length != 3) {
|
|
|
|
|
developer.log('Invalid JWT format', name: 'auth');
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Decode the payload (second part)
|
|
|
|
|
String payload = parts[1];
|
|
|
|
|
// Add padding if needed for base64
|
|
|
|
|
switch (payload.length % 4) {
|
|
|
|
|
case 2:
|
|
|
|
|
payload += '==';
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
payload += '=';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final decoded = utf8.decode(base64Url.decode(payload));
|
|
|
|
|
return jsonDecode(decoded) as Map<String, dynamic>;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
developer.log('Failed to decode JWT: $e', name: 'auth');
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Sign out and clear stored credentials.
|
|
|
|
|
Future<void> signOut() async {
|
|
|
|
|
await _clearStoredAuth();
|
|
|
|
|