diff --git a/interfaces/auth/microsoft_entra_interface.dart b/interfaces/auth/microsoft_entra_interface.dart new file mode 100644 index 0000000..e0a30e0 --- /dev/null +++ b/interfaces/auth/microsoft_entra_interface.dart @@ -0,0 +1,105 @@ +import 'package:aad_oauth/aad_oauth.dart'; +import 'package:aad_oauth/model/config.dart'; +import 'package:arcane_framework/arcane_framework.dart'; +import 'package:flutter/material.dart'; + +class MicrosoftEntraInterface implements ArcaneAuthInterface { + MicrosoftEntraInterface._internal(); + static final ArcaneAuthInterface _instance = + MicrosoftEntraInterface._internal(); + static ArcaneAuthInterface get I => _instance; + + static bool _mocked = false; + + AadOAuth? _oauth; + + @override + Future? get refreshToken async { + if (!await isSignedIn) return null; + + final result = await _oauth?.refreshToken(); + + if (result == null) return null; + + return result.fold( + (l) => null, + (r) async => r.refreshToken, + ); + } + + @override + Future get accessToken async => + _oauth?.getAccessToken() ?? Future.value(null); + + @override + Future get isSignedIn async => + _oauth?.hasCachedAccountInformation ?? Future.value(false); + + @override + Future init() async { + if (_mocked) return; + if (_oauth != null) return; + + final String? tenant = AppEnv.valueOf(EnvVar.adSsoTenant); + final String? clientId = AppEnv.valueOf(EnvVar.adSsoClientId); + final String? scope = AppEnv.valueOf(EnvVar.adSsoScope); + final String? redirectUri = AppEnv.valueOf(EnvVar.adSsoRedirectUri); + + if (tenant == null) throw const FormatException('Tenant must not be null.'); + if (clientId == null) + throw const FormatException('Client ID must not be null.'); + if (scope == null) throw const FormatException('Scope must not be null.'); + if (redirectUri == null) + throw const FormatException('Redirect URI must not be null.'); + + final Config config = Config( + tenant: tenant, + clientId: clientId, + scope: scope, + redirectUri: redirectUri, + navigatorKey: + navigatorKey, // Should be a GlobalKey() in the app's router + ); + + _oauth = AadOAuth(config); + } + + @override + Future> login( + {T? input, Future Function()? onLoggedIn}) async { + if (_mocked) return Result.ok(null); + if (_oauth == null) await init(); + + if (await isSignedIn) return Result.ok(null); + + final result = await _oauth!.login(); + + return result.fold( + (l) { + print(l); + return Result.error('Microsoft Authentication Failed!'); + }, + (r) async { + if (onLoggedIn != null) await onLoggedIn(); + return Result.ok(null); + }, + ); + } + + @override + Future> logout() async { + if (_mocked) return Result.ok(null); + if (_oauth == null) await init(); + + if (!await isSignedIn) return Result.error('Not signed in'); + + await _oauth?.logout(); + + return Result.ok(null); + } + + @visibleForTesting + static void setMocked() { + _mocked = true; + } +} diff --git a/interfaces/logging/dev_log_interface.dart b/interfaces/logging/dev_log_interface.dart new file mode 100644 index 0000000..9d9b066 --- /dev/null +++ b/interfaces/logging/dev_log_interface.dart @@ -0,0 +1,43 @@ +import 'dart:developer' as dev; + +import 'package:arcane_framework/arcane_framework.dart'; +import 'package:arcane_helper_utils/arcane_helper_utils.dart'; +import 'package:flutter/foundation.dart'; + +class DevLog implements LoggingInterface { + static final DevLog _instance = DevLog._internal(); + static DevLog get I => _instance; + + final bool _initialized = true; + + @override + bool get initialized => I._initialized; + + DevLog._internal(); + + @visibleForTesting + void setMocked() => _mocked = true; + bool _mocked = false; + + @override + void log( + String message, { + Map? metadata, + Level? level, + StackTrace? stackTrace, + Object? extra, + }) { + // If the string is too long, the message won't print. This _shouldn't_ be + // the case with `log`, yet here we are. + for (final String part in message.splitByLength(256)) { + dev.log(part); + } + } + + @override + Future init() async { + if (_mocked) return null; + + return I; + } +}