You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
samples/compass_app/app/test/ui/auth/logout_button_test.dart

75 lines
2.6 KiB

import 'package:compass_app/domain/models/itinerary_config/itinerary_config.dart';
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
import 'package:compass_app/ui/auth/logout/view_models/logout_viewmodel.dart';
import 'package:compass_app/ui/auth/logout/widgets/logout_button.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail_image_network/mocktail_image_network.dart';
import '../../../testing/app.dart';
Compass App: WIP Auth logic refactor (#2394) This PR follows up with the conversation we had via email and cleans up the auth implementation. **New `AuthRepository`** The `AuthTokenRepository`, `AuthLoginComponent` and `AuthLogoutComponent` have been refactored into a single `AuthRepository`. This `AuthRepository` exposes the `isAuthenticated` boolean, which is used by `go_router` to handle redirects. Also, has two methods, `login(email, pass)` and `logout()`, similar to what we had in the components. The implementation uses the new `SharedPreferencesService`, which is a service to access the `shared_preferences` plugin to store the `String` token. **New `AuthApiClient`** The `ApiClient` has been split in two. The `/login` REST call is moved to the `AuthApiClient`, while the other calls that require an auth token remain in the `ApiClient`. **`ApiClient` credentials** The main open question is, what is the best way to pass the auth token to the api client? I've found several alternatives: 1. `ApiClient` has a `token` setter which the `AuthRepository` calls to set the token - Implemented in this PR. - Follows our architecture design. - `AuthRepository` should be the source of truth, but the `ApiClient.token` could be set externally by mistake, so the single source of truth principle gets blurry. 2. `ApiClient` calls to the `AuthRepository` to obtain the token. - This is what the code was doing before. - `AuthRepository` is the source of truth. - But the dependency "Service to Repository" breaks our architecture design principle. 3. `ApiClient` calls to the `SharedPreferencesService` to obtain the token ( - `SharedPreferencesService` is the source of truth, as the `ApiClient` doesn't need the `AuthRepository`. - We see a "Service calling a Service" which maybe breaks our architecture design principle? 4. The `ApiClient` doesn't hold the token at all, intead, each call requires us to pass a token. - Tried to implement it but becomes very complex. - Makes other repositories depend on the `AuthRepository` or the `SharedPreferencesService`. Personal take: I have implemented 1 in this PR because it is the one that follows best the architecture recommendations. However I see some pitfalls, e.g. the `AuthRepository` needs to fetch the token from the `SharedPreferencesService` at least once on app start and pass it to the `ApiClient`. While on the solution 2 that's never a problem, since the `AuthRepository` is being called from the `ApiClient` and so it is always the source of truth for authentication. I am looking forward for your thoughts @ericwindmill ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
import '../../../testing/fakes/repositories/fake_auth_repository.dart';
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
import '../../../testing/fakes/repositories/fake_itinerary_config_repository.dart';
import '../../../testing/mocks.dart';
void main() {
group('LogoutButton test', () {
late MockGoRouter goRouter;
Compass App: WIP Auth logic refactor (#2394) This PR follows up with the conversation we had via email and cleans up the auth implementation. **New `AuthRepository`** The `AuthTokenRepository`, `AuthLoginComponent` and `AuthLogoutComponent` have been refactored into a single `AuthRepository`. This `AuthRepository` exposes the `isAuthenticated` boolean, which is used by `go_router` to handle redirects. Also, has two methods, `login(email, pass)` and `logout()`, similar to what we had in the components. The implementation uses the new `SharedPreferencesService`, which is a service to access the `shared_preferences` plugin to store the `String` token. **New `AuthApiClient`** The `ApiClient` has been split in two. The `/login` REST call is moved to the `AuthApiClient`, while the other calls that require an auth token remain in the `ApiClient`. **`ApiClient` credentials** The main open question is, what is the best way to pass the auth token to the api client? I've found several alternatives: 1. `ApiClient` has a `token` setter which the `AuthRepository` calls to set the token - Implemented in this PR. - Follows our architecture design. - `AuthRepository` should be the source of truth, but the `ApiClient.token` could be set externally by mistake, so the single source of truth principle gets blurry. 2. `ApiClient` calls to the `AuthRepository` to obtain the token. - This is what the code was doing before. - `AuthRepository` is the source of truth. - But the dependency "Service to Repository" breaks our architecture design principle. 3. `ApiClient` calls to the `SharedPreferencesService` to obtain the token ( - `SharedPreferencesService` is the source of truth, as the `ApiClient` doesn't need the `AuthRepository`. - We see a "Service calling a Service" which maybe breaks our architecture design principle? 4. The `ApiClient` doesn't hold the token at all, intead, each call requires us to pass a token. - Tried to implement it but becomes very complex. - Makes other repositories depend on the `AuthRepository` or the `SharedPreferencesService`. Personal take: I have implemented 1 in this PR because it is the one that follows best the architecture recommendations. However I see some pitfalls, e.g. the `AuthRepository` needs to fetch the token from the `SharedPreferencesService` at least once on app start and pass it to the `ApiClient`. While on the solution 2 that's never a problem, since the `AuthRepository` is being called from the `ApiClient` and so it is always the source of truth for authentication. I am looking forward for your thoughts @ericwindmill ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
late FakeAuthRepository fakeAuthRepository;
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
late FakeItineraryConfigRepository fakeItineraryConfigRepository;
late LogoutViewModel viewModel;
setUp(() {
goRouter = MockGoRouter();
Compass App: WIP Auth logic refactor (#2394) This PR follows up with the conversation we had via email and cleans up the auth implementation. **New `AuthRepository`** The `AuthTokenRepository`, `AuthLoginComponent` and `AuthLogoutComponent` have been refactored into a single `AuthRepository`. This `AuthRepository` exposes the `isAuthenticated` boolean, which is used by `go_router` to handle redirects. Also, has two methods, `login(email, pass)` and `logout()`, similar to what we had in the components. The implementation uses the new `SharedPreferencesService`, which is a service to access the `shared_preferences` plugin to store the `String` token. **New `AuthApiClient`** The `ApiClient` has been split in two. The `/login` REST call is moved to the `AuthApiClient`, while the other calls that require an auth token remain in the `ApiClient`. **`ApiClient` credentials** The main open question is, what is the best way to pass the auth token to the api client? I've found several alternatives: 1. `ApiClient` has a `token` setter which the `AuthRepository` calls to set the token - Implemented in this PR. - Follows our architecture design. - `AuthRepository` should be the source of truth, but the `ApiClient.token` could be set externally by mistake, so the single source of truth principle gets blurry. 2. `ApiClient` calls to the `AuthRepository` to obtain the token. - This is what the code was doing before. - `AuthRepository` is the source of truth. - But the dependency "Service to Repository" breaks our architecture design principle. 3. `ApiClient` calls to the `SharedPreferencesService` to obtain the token ( - `SharedPreferencesService` is the source of truth, as the `ApiClient` doesn't need the `AuthRepository`. - We see a "Service calling a Service" which maybe breaks our architecture design principle? 4. The `ApiClient` doesn't hold the token at all, intead, each call requires us to pass a token. - Tried to implement it but becomes very complex. - Makes other repositories depend on the `AuthRepository` or the `SharedPreferencesService`. Personal take: I have implemented 1 in this PR because it is the one that follows best the architecture recommendations. However I see some pitfalls, e.g. the `AuthRepository` needs to fetch the token from the `SharedPreferencesService` at least once on app start and pass it to the `ApiClient`. While on the solution 2 that's never a problem, since the `AuthRepository` is being called from the `ApiClient` and so it is always the source of truth for authentication. I am looking forward for your thoughts @ericwindmill ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
fakeAuthRepository = FakeAuthRepository();
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
// Setup a token, should be cleared after logout
Compass App: WIP Auth logic refactor (#2394) This PR follows up with the conversation we had via email and cleans up the auth implementation. **New `AuthRepository`** The `AuthTokenRepository`, `AuthLoginComponent` and `AuthLogoutComponent` have been refactored into a single `AuthRepository`. This `AuthRepository` exposes the `isAuthenticated` boolean, which is used by `go_router` to handle redirects. Also, has two methods, `login(email, pass)` and `logout()`, similar to what we had in the components. The implementation uses the new `SharedPreferencesService`, which is a service to access the `shared_preferences` plugin to store the `String` token. **New `AuthApiClient`** The `ApiClient` has been split in two. The `/login` REST call is moved to the `AuthApiClient`, while the other calls that require an auth token remain in the `ApiClient`. **`ApiClient` credentials** The main open question is, what is the best way to pass the auth token to the api client? I've found several alternatives: 1. `ApiClient` has a `token` setter which the `AuthRepository` calls to set the token - Implemented in this PR. - Follows our architecture design. - `AuthRepository` should be the source of truth, but the `ApiClient.token` could be set externally by mistake, so the single source of truth principle gets blurry. 2. `ApiClient` calls to the `AuthRepository` to obtain the token. - This is what the code was doing before. - `AuthRepository` is the source of truth. - But the dependency "Service to Repository" breaks our architecture design principle. 3. `ApiClient` calls to the `SharedPreferencesService` to obtain the token ( - `SharedPreferencesService` is the source of truth, as the `ApiClient` doesn't need the `AuthRepository`. - We see a "Service calling a Service" which maybe breaks our architecture design principle? 4. The `ApiClient` doesn't hold the token at all, intead, each call requires us to pass a token. - Tried to implement it but becomes very complex. - Makes other repositories depend on the `AuthRepository` or the `SharedPreferencesService`. Personal take: I have implemented 1 in this PR because it is the one that follows best the architecture recommendations. However I see some pitfalls, e.g. the `AuthRepository` needs to fetch the token from the `SharedPreferencesService` at least once on app start and pass it to the `ApiClient`. While on the solution 2 that's never a problem, since the `AuthRepository` is being called from the `ApiClient` and so it is always the source of truth for authentication. I am looking forward for your thoughts @ericwindmill ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
fakeAuthRepository.token = 'TOKEN';
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
// Setup an ItineraryConfig with some data, should be cleared after logout
fakeItineraryConfigRepository = FakeItineraryConfigRepository(
itineraryConfig: const ItineraryConfig(continent: 'CONTINENT'));
viewModel = LogoutViewModel(
Compass App: WIP Auth logic refactor (#2394) This PR follows up with the conversation we had via email and cleans up the auth implementation. **New `AuthRepository`** The `AuthTokenRepository`, `AuthLoginComponent` and `AuthLogoutComponent` have been refactored into a single `AuthRepository`. This `AuthRepository` exposes the `isAuthenticated` boolean, which is used by `go_router` to handle redirects. Also, has two methods, `login(email, pass)` and `logout()`, similar to what we had in the components. The implementation uses the new `SharedPreferencesService`, which is a service to access the `shared_preferences` plugin to store the `String` token. **New `AuthApiClient`** The `ApiClient` has been split in two. The `/login` REST call is moved to the `AuthApiClient`, while the other calls that require an auth token remain in the `ApiClient`. **`ApiClient` credentials** The main open question is, what is the best way to pass the auth token to the api client? I've found several alternatives: 1. `ApiClient` has a `token` setter which the `AuthRepository` calls to set the token - Implemented in this PR. - Follows our architecture design. - `AuthRepository` should be the source of truth, but the `ApiClient.token` could be set externally by mistake, so the single source of truth principle gets blurry. 2. `ApiClient` calls to the `AuthRepository` to obtain the token. - This is what the code was doing before. - `AuthRepository` is the source of truth. - But the dependency "Service to Repository" breaks our architecture design principle. 3. `ApiClient` calls to the `SharedPreferencesService` to obtain the token ( - `SharedPreferencesService` is the source of truth, as the `ApiClient` doesn't need the `AuthRepository`. - We see a "Service calling a Service" which maybe breaks our architecture design principle? 4. The `ApiClient` doesn't hold the token at all, intead, each call requires us to pass a token. - Tried to implement it but becomes very complex. - Makes other repositories depend on the `AuthRepository` or the `SharedPreferencesService`. Personal take: I have implemented 1 in this PR because it is the one that follows best the architecture recommendations. However I see some pitfalls, e.g. the `AuthRepository` needs to fetch the token from the `SharedPreferencesService` at least once on app start and pass it to the `ApiClient`. While on the solution 2 that's never a problem, since the `AuthRepository` is being called from the `ApiClient` and so it is always the source of truth for authentication. I am looking forward for your thoughts @ericwindmill ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
authRepository: fakeAuthRepository,
itineraryConfigRepository: fakeItineraryConfigRepository,
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
);
});
Future<void> loadScreen(WidgetTester tester) async {
await testApp(
tester,
LogoutButton(viewModel: viewModel),
goRouter: goRouter,
);
}
testWidgets('should load widget', (WidgetTester tester) async {
await mockNetworkImages(() async {
await loadScreen(tester);
expect(find.byType(LogoutButton), findsOneWidget);
});
});
testWidgets('should perform logout', (WidgetTester tester) async {
await mockNetworkImages(() async {
await loadScreen(tester);
// Repo should have a key
Compass App: WIP Auth logic refactor (#2394) This PR follows up with the conversation we had via email and cleans up the auth implementation. **New `AuthRepository`** The `AuthTokenRepository`, `AuthLoginComponent` and `AuthLogoutComponent` have been refactored into a single `AuthRepository`. This `AuthRepository` exposes the `isAuthenticated` boolean, which is used by `go_router` to handle redirects. Also, has two methods, `login(email, pass)` and `logout()`, similar to what we had in the components. The implementation uses the new `SharedPreferencesService`, which is a service to access the `shared_preferences` plugin to store the `String` token. **New `AuthApiClient`** The `ApiClient` has been split in two. The `/login` REST call is moved to the `AuthApiClient`, while the other calls that require an auth token remain in the `ApiClient`. **`ApiClient` credentials** The main open question is, what is the best way to pass the auth token to the api client? I've found several alternatives: 1. `ApiClient` has a `token` setter which the `AuthRepository` calls to set the token - Implemented in this PR. - Follows our architecture design. - `AuthRepository` should be the source of truth, but the `ApiClient.token` could be set externally by mistake, so the single source of truth principle gets blurry. 2. `ApiClient` calls to the `AuthRepository` to obtain the token. - This is what the code was doing before. - `AuthRepository` is the source of truth. - But the dependency "Service to Repository" breaks our architecture design principle. 3. `ApiClient` calls to the `SharedPreferencesService` to obtain the token ( - `SharedPreferencesService` is the source of truth, as the `ApiClient` doesn't need the `AuthRepository`. - We see a "Service calling a Service" which maybe breaks our architecture design principle? 4. The `ApiClient` doesn't hold the token at all, intead, each call requires us to pass a token. - Tried to implement it but becomes very complex. - Makes other repositories depend on the `AuthRepository` or the `SharedPreferencesService`. Personal take: I have implemented 1 in this PR because it is the one that follows best the architecture recommendations. However I see some pitfalls, e.g. the `AuthRepository` needs to fetch the token from the `SharedPreferencesService` at least once on app start and pass it to the `ApiClient`. While on the solution 2 that's never a problem, since the `AuthRepository` is being called from the `ApiClient` and so it is always the source of truth for authentication. I am looking forward for your thoughts @ericwindmill ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
expect(fakeAuthRepository.token, 'TOKEN');
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
// Itinerary config should have data
expect(
fakeItineraryConfigRepository.itineraryConfig,
const ItineraryConfig(continent: 'CONTINENT'),
);
// // Perform logout
await tester.tap(find.byType(LogoutButton));
await tester.pumpAndSettle();
// Repo should have no key
Compass App: WIP Auth logic refactor (#2394) This PR follows up with the conversation we had via email and cleans up the auth implementation. **New `AuthRepository`** The `AuthTokenRepository`, `AuthLoginComponent` and `AuthLogoutComponent` have been refactored into a single `AuthRepository`. This `AuthRepository` exposes the `isAuthenticated` boolean, which is used by `go_router` to handle redirects. Also, has two methods, `login(email, pass)` and `logout()`, similar to what we had in the components. The implementation uses the new `SharedPreferencesService`, which is a service to access the `shared_preferences` plugin to store the `String` token. **New `AuthApiClient`** The `ApiClient` has been split in two. The `/login` REST call is moved to the `AuthApiClient`, while the other calls that require an auth token remain in the `ApiClient`. **`ApiClient` credentials** The main open question is, what is the best way to pass the auth token to the api client? I've found several alternatives: 1. `ApiClient` has a `token` setter which the `AuthRepository` calls to set the token - Implemented in this PR. - Follows our architecture design. - `AuthRepository` should be the source of truth, but the `ApiClient.token` could be set externally by mistake, so the single source of truth principle gets blurry. 2. `ApiClient` calls to the `AuthRepository` to obtain the token. - This is what the code was doing before. - `AuthRepository` is the source of truth. - But the dependency "Service to Repository" breaks our architecture design principle. 3. `ApiClient` calls to the `SharedPreferencesService` to obtain the token ( - `SharedPreferencesService` is the source of truth, as the `ApiClient` doesn't need the `AuthRepository`. - We see a "Service calling a Service" which maybe breaks our architecture design principle? 4. The `ApiClient` doesn't hold the token at all, intead, each call requires us to pass a token. - Tried to implement it but becomes very complex. - Makes other repositories depend on the `AuthRepository` or the `SharedPreferencesService`. Personal take: I have implemented 1 in this PR because it is the one that follows best the architecture recommendations. However I see some pitfalls, e.g. the `AuthRepository` needs to fetch the token from the `SharedPreferencesService` at least once on app start and pass it to the `ApiClient`. While on the solution 2 that's never a problem, since the `AuthRepository` is being called from the `ApiClient` and so it is always the source of truth for authentication. I am looking forward for your thoughts @ericwindmill ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
expect(fakeAuthRepository.token, null);
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
3 months ago
// Itinerary config should be cleared
expect(
fakeItineraryConfigRepository.itineraryConfig,
const ItineraryConfig(),
);
});
});
});
}