Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions example/lib/features/create_acl_inherited_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ library;

import 'package:flutter/material.dart';

import 'package:demopod/constants/app.dart';

import 'package:solidpod/solidpod.dart' show writePod, setInheritKeyDir;

import 'package:demopod/constants/app.dart';

// A widget to create a resource with inherited ACL.
//
// The resource will be created inside a parent directory and the ACL of that
Expand Down
8 changes: 4 additions & 4 deletions example/lib/features/edit_keyvalue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ library;

import 'package:flutter/material.dart';

import 'package:demopod/constants/app.dart';
import 'package:demopod/dialogs/alert.dart';
import 'package:demopod/utils/rdf.dart';
import 'package:editable/editable.dart';
import 'package:solidpod/solidpod.dart' show isUserLoggedIn, writePod;
import 'package:solidui/solidui.dart' show getKeyFromUserIfRequired;

import 'package:solidpod/solidpod.dart' show isUserLoggedIn, writePod;
import 'package:demopod/constants/app.dart';
import 'package:demopod/dialogs/alert.dart';
import 'package:demopod/utils/rdf.dart';

class KeyValueEdit extends StatefulWidget {
/// Constructor
Expand Down
4 changes: 2 additions & 2 deletions example/lib/features/file_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ library;

import 'package:flutter/material.dart';

import 'package:demopod/dialogs/alert.dart';
import 'package:file_picker/file_picker.dart';

import 'package:solidpod/solidpod.dart';

import 'package:demopod/dialogs/alert.dart';

class FileService extends StatefulWidget {
const FileService({required this.child, required this.webId, super.key});
final String webId;
Expand Down
4 changes: 2 additions & 2 deletions example/lib/features/view_keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ library;

import 'package:flutter/material.dart';

import 'package:solidpod/solidpod.dart' show KeyManager;

import 'package:demopod/constants/app.dart';
import 'package:demopod/utils/rdf.dart' show getEncKeyContent;

import 'package:solidpod/solidpod.dart' show KeyManager;

/// A widget to show the user all the encryption keys stored in their Solid Pod.

class ViewKeys extends StatefulWidget {
Expand Down
5 changes: 3 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ library;

import 'package:flutter/material.dart';

import 'package:demopod/home.dart';
import 'package:demopod/utils/is_desktop.dart';
import 'package:solidui/solidui.dart' show SolidLogin, InfoButtonStyle;
import 'package:window_manager/window_manager.dart';

import 'package:demopod/home.dart';
import 'package:demopod/utils/is_desktop.dart';

void main() async {
// Remove [debugPrint] messages from production code.

Expand Down
1 change: 0 additions & 1 deletion example/lib/utils/rdf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
library;

import 'package:rdflib/rdflib.dart';

import 'package:solidpod/solidpod.dart' show getWebId;

// Namespace for keys
Expand Down
39 changes: 29 additions & 10 deletions lib/src/solid/authenticate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,28 @@ final List<String> _scopes = <String>[
///
/// [context] of the current widget is required for the authenticate process.
///
/// [wasAlreadyLoggedIn] is an optional pre-computed login status. When provided
/// it avoids a redundant call to [isUserLoggedIn] (and the associated secure
/// storage read + token-expiry check) that the caller may have already done.
///
/// Return a list containing authentication data: user's webId; profile data.
///
/// Error Handling: The function has a catch all to return null if any exception
/// occurs during the authentication process.

Future<List<dynamic>?> solidAuthenticate(
String serverId,
BuildContext context,
) async {
BuildContext context, {
bool? wasAlreadyLoggedIn,
}) async {
try {
final loggedIn = await isUserLoggedIn();
// Use the caller-supplied value when available to avoid a redundant
// isUserLoggedIn() call (secure storage read + possible token refresh).
final loggedIn = wasAlreadyLoggedIn ?? await isUserLoggedIn();
Map<dynamic, dynamic>? authData;
if (loggedIn) {
authData = await AuthDataManager.loadAuthData();
if (authData == null) {
// Fall through to re-authenticate
}
// authData == null means refresh failed; fall through to re-authenticate
}

// If not logged in or load failed, perform new authentication
Expand Down Expand Up @@ -112,22 +117,36 @@ Future<List<dynamic>?> solidAuthenticate(
return null;
}

// Proceed to fetch profile data with the authenticated credentials
// Fetch profile data. When the user entered a WebID URL (profile/card#me)
// as the server ID, [getIssuer] already fetched the profile document to
// extract the OIDC issuer URI. Reuse that cached body to avoid a second
// HTTP GET to the same URL.
final profCardUrl = webId.replaceAll('#me', '');
final profData = utf8.decode(await getResource(profCardUrl));
var profData = getCachedIssuerProfileBody(profCardUrl);
// If the issuer was resolved from a plain URI (not a WebID profile URL),
// the profile body was not pre-fetched. Fetch it now with the
// authenticated access token.
profData ??= utf8.decode(await getResource(profCardUrl));
Comment on lines +125 to +129
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCachedIssuerProfileBody() is referenced here but there is no definition in this repository. If this relies on package:solid_auth exporting that API, please ensure the minimum solid_auth version in pubspec actually includes it (otherwise consumers pinned to 0.1.28 will fail to compile), or add a local implementation/abstraction within this package.

Copilot uses AI. Check for mistakes.
AuthDataManager.setCachedProfData(profData);

return [authData, webId, profData];
}

// Already logged in successfully - fetch profile data
// Already logged in successfully - return cached or freshly-fetched profile.
final webId = await AuthDataManager.getWebId();
if (webId == null || webId.isEmpty) {
await logoutPod();
return null;
}

// Use the in-memory profile cache to avoid an unnecessary HTTP request on
// every cached-session login.
final profCardUrl = webId.replaceAll('#me', '');
final profData = utf8.decode(await getResource(profCardUrl));
var profData = AuthDataManager.getCachedProfData();
if (profData == null) {
profData = utf8.decode(await getResource(profCardUrl));
AuthDataManager.setCachedProfData(profData);
}

return [authData, webId, profData];
} on Object catch (e) {
Expand Down
28 changes: 28 additions & 0 deletions lib/src/solid/utils/authdata_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ class AuthDataManager {
/// The authentication response
static Credential? _authResponse;

/// In-memory cache for the last valid [TokenResponse].
/// Avoids repeated JSON deserialization and JWT decoding on every
/// [getTokensForResource] call (e.g. the 13 parallel Pod structure checks).
static TokenResponse? _cachedTokenResponse;

/// In-memory cache for the user's profile card (Turtle/RDF string).
/// Avoids re-fetching on every cached-session login.
static String? _cachedProfData;

/// Returns the cached profile card data, or null if not yet fetched.
static String? getCachedProfData() => _cachedProfData;

/// Stores profile card data in the in-memory cache.
static void setCachedProfData(String data) => _cachedProfData = data;

/// The string key for storing auth data in secure storage
static const String _authDataSecureStorageKey = '_solid_auth_data';

Expand Down Expand Up @@ -95,6 +110,7 @@ class AuthDataManager {
_rsaInfo = authData['rsaInfo'] as Map<dynamic,
dynamic>; // Note that use Map<String, dynamic> does not seem to work
_authResponse = authData['authResponse'] as Credential;
_cachedTokenResponse = authData['tokenResponse'] as TokenResponse;

await writeToSecureStorage(
_authDataSecureStorageKey,
Expand Down Expand Up @@ -165,6 +181,10 @@ class AuthDataManager {
_rsaInfo = null;
_authResponse = null;
}
_cachedTokenResponse = null;

// Clear in-memory profile cache on logout.
_cachedProfData = null;

// Notify listeners that auth state has changed
authStateNotifier.value = false;
Expand All @@ -189,6 +209,13 @@ class AuthDataManager {

/// Returns the (updated) token response
static Future<TokenResponse?> _getTokenResponse() async {
// Fast path: return cached token if still valid, skipping JSON
// deserialization and JWT decoding on every call.
if (_cachedTokenResponse?.accessToken != null &&
!JwtDecoder.isExpired(_cachedTokenResponse!.accessToken!)) {
return _cachedTokenResponse;
}

if (_authResponse == null) {
final loaded = await _loadData();
if (!loaded) {
Expand Down Expand Up @@ -221,6 +248,7 @@ class AuthDataManager {
);
// TODO dc 20250106: Save refreshed token in secure storage
}
_cachedTokenResponse = tokenResponse; // Update in-memory cache
return tokenResponse;
} on Object {
// debugPrint('AuthDataManager => _getTokenResponse() failed: $e');
Expand Down
Loading
Loading