Skip to content
This repository has been archived by the owner on Jan 9, 2024. It is now read-only.

feat: authentication repository #3

Closed
wants to merge 7 commits into from
Closed
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
7 changes: 0 additions & 7 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@
"lib/main_development.dart"
]
},
{
"name": "Launch staging",
"request": "launch",
"type": "dart",
"program": "lib/main_staging.dart",
"args": ["--flavor", "staging", "--target", "lib/main_staging.dart"]
},
{
"name": "Launch production",
"request": "launch",
Expand Down
23 changes: 15 additions & 8 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
import 'package:api_client/api_client.dart';
import 'package:authentication_repository/authentication_repository.dart';
import 'package:dash_ai_search/counter/counter.dart';
import 'package:dash_ai_search/l10n/l10n.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class App extends StatelessWidget {
const App({
required this.apiClient,
required this.user,
super.key,
});

final ApiClient apiClient;
final User user;

@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
appBarTheme: AppBarTheme(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
return Provider.value(
value: user,
child: MaterialApp(
theme: ThemeData(
appBarTheme: AppBarTheme(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
useMaterial3: true,
),
useMaterial3: true,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const CounterPage(),
),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const CounterPage(),
);
}
}
15 changes: 11 additions & 4 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:developer';

import 'package:bloc/bloc.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/widgets.dart';

class AppBlocObserver extends BlocObserver {
Expand All @@ -20,14 +21,20 @@ class AppBlocObserver extends BlocObserver {
}
}

Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
typedef BootstrapBuilder = FutureOr<Widget> Function(FirebaseAuth);

Future<void> bootstrap(BootstrapBuilder builder) async {
WidgetsFlutterBinding.ensureInitialized();

FlutterError.onError = (details) {
log(details.exceptionAsString(), stackTrace: details.stack);
};

Bloc.observer = const AppBlocObserver();

// Add cross-flavor configuration here

runApp(await builder());
runApp(
await builder(
FirebaseAuth.instance,
),
);
}
26 changes: 20 additions & 6 deletions lib/main_development.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import 'dart:async';

import 'package:api_client/api_client.dart';
import 'package:authentication_repository/authentication_repository.dart';
import 'package:dash_ai_search/app/app.dart';
import 'package:dash_ai_search/bootstrap.dart';
import 'package:firebase_core/firebase_core.dart';

void main() async {
await Firebase.initializeApp();

unawaited(
bootstrap((firebaseAuth) async {
final authenticationRepository = AuthenticationRepository(
firebaseAuth: firebaseAuth,
);

void main() {
bootstrap(
() {
final apiClient = ApiClient(
baseUrl: 'http://localhost:8080',
idTokenStream: const Stream.empty(),
refreshIdToken: () async => Future.value(),
idTokenStream: authenticationRepository.idToken,
refreshIdToken: authenticationRepository.refreshIdToken,
);

await authenticationRepository.signInAnonymously();
await authenticationRepository.idToken.first;

return App(
apiClient: apiClient,
user: await authenticationRepository.user.first,
);
},
}),
);
}
26 changes: 20 additions & 6 deletions lib/main_production.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import 'dart:async';

import 'package:api_client/api_client.dart';
import 'package:authentication_repository/authentication_repository.dart';
import 'package:dash_ai_search/app/app.dart';
import 'package:dash_ai_search/bootstrap.dart';
import 'package:firebase_core/firebase_core.dart';

void main() async {
await Firebase.initializeApp();

unawaited(
bootstrap((firebaseAuth) async {
final authenticationRepository = AuthenticationRepository(
firebaseAuth: firebaseAuth,
);

void main() {
bootstrap(
() {
final apiClient = ApiClient(
baseUrl: 'http://production',
idTokenStream: const Stream.empty(),
refreshIdToken: () async => Future.value(),
idTokenStream: authenticationRepository.idToken,
refreshIdToken: authenticationRepository.refreshIdToken,
);

await authenticationRepository.signInAnonymously();
await authenticationRepository.idToken.first;

return App(
apiClient: apiClient,
user: await authenticationRepository.user.first,
);
},
}),
);
}
19 changes: 0 additions & 19 deletions lib/main_staging.dart

This file was deleted.

44 changes: 44 additions & 0 deletions packages/authentication_repository/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# VSCode related
.vscode/*

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
pubspec.lock

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Test related
coverage
12 changes: 12 additions & 0 deletions packages/authentication_repository/README.md
RuiMiguel marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Authentication Repository

[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason)
[![License: MIT][license_badge]][license_link]

Repository to manage authentication.

[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
1 change: 1 addition & 0 deletions packages/authentication_repository/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.5.1.0.yaml
20 changes: 20 additions & 0 deletions packages/authentication_repository/coverage_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Repository to manage authentication.s
library authentication_repository;

export 'src/authentication_repository.dart';
export 'src/models/models.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'dart:async';

import 'package:authentication_repository/authentication_repository.dart';
import 'package:firebase_auth/firebase_auth.dart' as fb;

/// {@template authentication_exception}
/// Exception thrown when an authentication process fails.
/// {@endtemplate}
class AuthenticationException implements Exception {
/// {@macro authentication_exception}
const AuthenticationException(this.error, this.stackTrace);

/// The error that was caught.
final Object error;

/// The stack trace associated with the error.
final StackTrace stackTrace;
}

/// {@template authentication_repository}
/// Repository to manage authentication.
/// {@endtemplate}
class AuthenticationRepository {
/// {@macro authentication_repository}
AuthenticationRepository({
fb.FirebaseAuth? firebaseAuth,
}) : _firebaseAuth = firebaseAuth ?? fb.FirebaseAuth.instance,
_userController = StreamController<User>.broadcast();

final fb.FirebaseAuth _firebaseAuth;
final StreamController<User> _userController;
StreamSubscription<fb.User?>? _firebaseUserSubscription;

/// Stream of [User] which will emit the current user when
/// the authentication state changes.
///
/// Emits [User.unauthenticated] if the user is not authenticated.
Stream<User> get user {
_firebaseUserSubscription ??=
_firebaseAuth.authStateChanges().listen((firebaseUser) {
_userController.add(
firebaseUser?.toUser ?? User.unauthenticated,
);
});

return _userController.stream;
}

/// Stream of id tokens that can be used to authenticate with Firebase.
Stream<String?> get idToken {
return _firebaseAuth
.idTokenChanges()
.asyncMap((user) => user?.getIdToken());
}

/// Refreshes the id token.
Future<String?> refreshIdToken() async {
final user = _firebaseAuth.currentUser;
return user?.getIdToken(true);
}

/// Sign in the user anonymously.
///
/// If the sign in fails, an [AuthenticationException] is thrown.
Future<void> signInAnonymously() async {
try {
final userCredential = await _firebaseAuth.signInAnonymously();
_userController.add(userCredential.toUser);
} on Exception catch (error, stackTrace) {
throw AuthenticationException(error, stackTrace);
}
}

/// Disposes any internal resources.
void dispose() {
_firebaseUserSubscription?.cancel();
_userController.close();
}
}

extension on fb.User {
User get toUser => User(id: uid);
}

extension on fb.UserCredential {
User get toUser => user?.toUser ?? User.unauthenticated;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'user.dart';
20 changes: 20 additions & 0 deletions packages/authentication_repository/lib/src/models/user.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:equatable/equatable.dart';

/// {@template user}
/// User model
/// {@endtemplate}
class User extends Equatable {
/// {@macro user}
const User({
required this.id,
});

/// The current user's id.
final String id;

/// Represents an unauthenticated user.
static const unauthenticated = User(id: '');

@override
List<Object?> get props => [id];
}
Loading