mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
refactor: use drift db based authentication
This commit is contained in:
parent
a799ca55bc
commit
d18f74fd65
@ -32,7 +32,7 @@ import 'package:spotube/pages/stats/playlists/playlists.dart';
|
||||
import 'package:spotube/pages/stats/stats.dart';
|
||||
import 'package:spotube/pages/stats/streams/streams.dart';
|
||||
import 'package:spotube/pages/track/track.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/components/spotube_page_route.dart';
|
||||
@ -59,11 +59,9 @@ final routerProvider = Provider((ref) {
|
||||
path: "/",
|
||||
name: HomePage.name,
|
||||
redirect: (context, state) async {
|
||||
final authNotifier = ref.read(authenticationProvider.notifier);
|
||||
final json = await authNotifier.box.get(authNotifier.cacheKey);
|
||||
final auth = await ref.read(authenticationProvider.future);
|
||||
|
||||
if (json?["cookie"] == null &&
|
||||
!KVStoreService.doneGettingStarted) {
|
||||
if (auth == null && !KVStoreService.doneGettingStarted) {
|
||||
return "/getting-started";
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/pages/settings/settings.dart';
|
||||
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
class AnonymousFallback extends ConsumerWidget {
|
||||
|
@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/heart_button/use_track_toggle_like.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
|
||||
class HeartButton extends HookConsumerWidget {
|
||||
@ -26,7 +26,7 @@ class HeartButton extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, ref) {
|
||||
final auth = ref.watch(authenticationProvider);
|
||||
|
||||
if (auth == null) return const SizedBox.shrink();
|
||||
if (auth.asData?.value == null) return const SizedBox.shrink();
|
||||
|
||||
return IconButton(
|
||||
tooltip: tooltip,
|
||||
|
@ -20,7 +20,7 @@ import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/blacklist_provider.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/provider/local_tracks/local_tracks_provider.dart';
|
||||
|
@ -9,7 +9,7 @@ import 'package:spotube/components/heart_button/heart_button.dart';
|
||||
import 'package:spotube/components/tracks_view/sections/body/use_is_user_playlist.dart';
|
||||
import 'package:spotube/components/tracks_view/track_view_props.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/history/history.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
|
||||
|
@ -2,7 +2,7 @@ import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
@ -18,7 +18,7 @@ void useEndlessPlayback(WidgetRef ref) {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
if (!endlessPlayback || auth == null) return null;
|
||||
if (!endlessPlayback || auth.asData?.value == null) return null;
|
||||
|
||||
void listener(int index) async {
|
||||
try {
|
||||
|
@ -19,10 +19,11 @@ import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
part 'tables/preferences.dart';
|
||||
part 'tables/source_match.dart';
|
||||
part 'tables/skip_segment.dart';
|
||||
part 'tables/authentication.dart';
|
||||
part 'tables/blacklist.dart';
|
||||
part 'tables/preferences.dart';
|
||||
part 'tables/skip_segment.dart';
|
||||
part 'tables/source_match.dart';
|
||||
|
||||
part 'typeconverters/color.dart';
|
||||
part 'typeconverters/locale.dart';
|
||||
@ -31,10 +32,11 @@ part 'typeconverters/encrypted_text.dart';
|
||||
|
||||
@DriftDatabase(
|
||||
tables: [
|
||||
PreferencesTable,
|
||||
SourceMatchTable,
|
||||
SkipSegmentTable,
|
||||
AuthenticationTable,
|
||||
BlacklistTable,
|
||||
PreferencesTable,
|
||||
SkipSegmentTable,
|
||||
SourceMatchTable,
|
||||
],
|
||||
)
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
|
File diff suppressed because it is too large
Load Diff
8
lib/models/database/tables/authentication.dart
Normal file
8
lib/models/database/tables/authentication.dart
Normal file
@ -0,0 +1,8 @@
|
||||
part of '../database.dart';
|
||||
|
||||
class AuthenticationTable extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get cookie => text().map(EncryptedTextConverter())();
|
||||
TextColumn get accessToken => text().map(EncryptedTextConverter())();
|
||||
DateTimeColumn get expiration => dateTime()();
|
||||
}
|
@ -22,6 +22,11 @@ class DecryptedText {
|
||||
}
|
||||
|
||||
String encrypt() {
|
||||
_encrypter ??= Encrypter(
|
||||
Salsa20(
|
||||
Key.fromUtf8(EncryptedKvStoreService.encryptionKeySync),
|
||||
),
|
||||
);
|
||||
return _encrypter!.encrypt(value, iv: KVStoreService.ivKey).base64;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ class StringListConverter extends TypeConverter<List<String>, String> {
|
||||
|
||||
@override
|
||||
List<String> fromSql(String fromDb) {
|
||||
return fromDb.split(",");
|
||||
return fromDb.split(",").where((e) => e.isNotEmpty).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
|
||||
class TokenLoginForm extends HookConsumerWidget {
|
||||
final void Function()? onDone;
|
||||
@ -52,10 +52,7 @@ class TokenLoginForm extends HookConsumerWidget {
|
||||
final cookieHeader =
|
||||
"sp_dc=${directCodeController.text.trim()}";
|
||||
|
||||
authenticationNotifier.setCredentials(
|
||||
await AuthenticationCredentials.fromCookie(
|
||||
cookieHeader),
|
||||
);
|
||||
await authenticationNotifier.login(cookieHeader);
|
||||
if (context.mounted) {
|
||||
onDone?.call();
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/modules/home/sections/friends/friend_item.dart';
|
||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||
import 'package:spotube/models/spotify_friends.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
|
||||
class HomePageFriendsSection extends HookConsumerWidget {
|
||||
@ -59,7 +59,7 @@ class HomePageFriendsSection extends HookConsumerWidget {
|
||||
|
||||
if (friendsQuery.isLoading ||
|
||||
friendsQuery.asData?.value.friends.isEmpty == true ||
|
||||
auth == null) {
|
||||
auth.asData?.value == null) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox.shrink(),
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
|
||||
class HomeNewReleasesSection extends HookConsumerWidget {
|
||||
@ -18,7 +18,7 @@ class HomeNewReleasesSection extends HookConsumerWidget {
|
||||
|
||||
final albums = ref.watch(userArtistAlbumReleasesProvider);
|
||||
|
||||
if (auth == null ||
|
||||
if (auth.asData?.value == null ||
|
||||
newReleases.isLoading ||
|
||||
newReleases.asData?.value.items.isEmpty == true) {
|
||||
return const SizedBox.shrink();
|
||||
|
@ -46,7 +46,7 @@ class LocalFolderItem extends HookConsumerWidget {
|
||||
...pathSegments.skip(pathSegments.length - 3).toList()
|
||||
..removeLast(),
|
||||
]
|
||||
: pathSegments.take(pathSegments.length - 1).toList();
|
||||
: pathSegments.take(max(pathSegments.length - 1, 0)).toList();
|
||||
|
||||
final trackSnapshot = ref.watch(
|
||||
localTracksProvider.select(
|
||||
|
@ -14,7 +14,7 @@ import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
|
||||
import 'package:spotube/components/waypoint.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
|
||||
class UserAlbums extends HookConsumerWidget {
|
||||
@ -46,7 +46,7 @@ class UserAlbums extends HookConsumerWidget {
|
||||
[];
|
||||
}, [albumsQuery.asData?.value, searchText.value]);
|
||||
|
||||
if (auth == null) {
|
||||
if (auth.asData?.value == null) {
|
||||
return const AnonymousFallback();
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
||||
import 'package:spotube/components/waypoint.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
|
||||
class UserArtists extends HookConsumerWidget {
|
||||
@ -48,7 +48,7 @@ class UserArtists extends HookConsumerWidget {
|
||||
|
||||
final controller = useScrollController();
|
||||
|
||||
if (auth == null) {
|
||||
if (auth.asData?.value == null) {
|
||||
return const AnonymousFallback();
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ import 'package:spotube/components/waypoint.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
@ -75,7 +75,7 @@ class UserPlaylists extends HookConsumerWidget {
|
||||
|
||||
final controller = useScrollController();
|
||||
|
||||
if (auth == null) {
|
||||
if (auth.asData?.value == null) {
|
||||
return const AnonymousFallback();
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ import 'package:spotube/hooks/utils/use_custom_status_bar_color.dart';
|
||||
import 'package:spotube/hooks/utils/use_palette_color.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/pages/lyrics/lyrics.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/server/active_sourced_track.dart';
|
||||
import 'package:spotube/provider/volume_provider.dart';
|
||||
|
@ -13,7 +13,7 @@ import 'package:spotube/extensions/duration.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/sleep_timer_provider.dart';
|
||||
|
||||
|
@ -18,7 +18,7 @@ import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/hooks/utils/use_brightness_value.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
|
@ -20,7 +20,7 @@ import 'package:spotube/hooks/controllers/use_sidebarx_controller.dart';
|
||||
import 'package:spotube/pages/profile/profile.dart';
|
||||
import 'package:spotube/pages/settings/settings.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
@ -269,7 +269,7 @@ class SidebarFooter extends HookConsumerWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (auth != null && data == null)
|
||||
if (auth.asData?.value != null && data == null)
|
||||
const CircularProgressIndicator()
|
||||
else if (data != null)
|
||||
Flexible(
|
||||
|
@ -11,7 +11,7 @@ import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/blacklist_provider.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
|
@ -9,7 +9,7 @@ import 'package:spotube/components/links/hyper_link.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/pages/home/home.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
class LoginTutorial extends ConsumerWidget {
|
||||
@ -18,8 +18,7 @@ class LoginTutorial extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
ref.watch(authenticationProvider);
|
||||
final authenticationNotifier = ref.watch(authenticationProvider.notifier);
|
||||
final auth = ref.watch(authenticationProvider);
|
||||
final key = GlobalKey<State<IntroductionScreen>>();
|
||||
final theme = Theme.of(context);
|
||||
|
||||
@ -53,7 +52,7 @@ class LoginTutorial extends ConsumerWidget {
|
||||
),
|
||||
showBackButton: true,
|
||||
overrideDone: FilledButton(
|
||||
onPressed: authenticationNotifier.isLoggedIn
|
||||
onPressed: auth.asData?.value != null
|
||||
? () {
|
||||
ServiceUtils.pushNamed(context, HomePage.name);
|
||||
}
|
||||
@ -91,7 +90,7 @@ class LoginTutorial extends ConsumerWidget {
|
||||
bodyWidget:
|
||||
Text(context.l10n.step_3_steps, textAlign: TextAlign.left),
|
||||
),
|
||||
if (authenticationNotifier.isLoggedIn)
|
||||
if (auth.asData?.value != null)
|
||||
PageViewModel(
|
||||
decoration: pageDecoration.copyWith(
|
||||
bodyAlignment: Alignment.center,
|
||||
|
@ -17,7 +17,7 @@ import 'package:spotube/hooks/utils/use_custom_status_bar_color.dart';
|
||||
import 'package:spotube/hooks/utils/use_palette_color.dart';
|
||||
import 'package:spotube/pages/lyrics/plain_lyrics.dart';
|
||||
import 'package:spotube/pages/lyrics/synced_lyrics.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
@ -84,7 +84,7 @@ class LyricsPage extends HookConsumerWidget {
|
||||
|
||||
final auth = ref.watch(authenticationProvider);
|
||||
|
||||
if (auth == null) {
|
||||
if (auth.asData?.value == null) {
|
||||
return Scaffold(
|
||||
appBar: !kIsMacOS && !isModal ? const PageWindowTitleBar() : null,
|
||||
body: const AnonymousFallback(),
|
||||
|
@ -14,7 +14,7 @@ import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/hooks/utils/use_force_update.dart';
|
||||
import 'package:spotube/pages/lyrics/plain_lyrics.dart';
|
||||
import 'package:spotube/pages/lyrics/synced_lyrics.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
@ -48,7 +48,7 @@ class MiniLyricsPage extends HookConsumerWidget {
|
||||
|
||||
final auth = ref.watch(authenticationProvider);
|
||||
|
||||
if (auth == null) {
|
||||
if (auth.asData?.value == null) {
|
||||
return const Scaffold(
|
||||
appBar: PageWindowTitleBar(),
|
||||
body: AnonymousFallback(),
|
||||
|
@ -3,7 +3,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
class WebViewLogin extends HookConsumerWidget {
|
||||
@ -53,9 +53,7 @@ class WebViewLogin extends HookConsumerWidget {
|
||||
final cookieHeader =
|
||||
"sp_dc=${cookies.firstWhere((element) => element.name == "sp_dc").value}";
|
||||
|
||||
authenticationNotifier.setCredentials(
|
||||
await AuthenticationCredentials.fromCookie(cookieHeader),
|
||||
);
|
||||
await authenticationNotifier.login(cookieHeader);
|
||||
if (context.mounted) {
|
||||
// ignore: use_build_context_synchronously
|
||||
GoRouter.of(context).go("/");
|
||||
|
@ -20,7 +20,7 @@ import 'package:spotube/pages/search/sections/albums.dart';
|
||||
import 'package:spotube/pages/search/sections/artists.dart';
|
||||
import 'package:spotube/pages/search/sections/playlists.dart';
|
||||
import 'package:spotube/pages/search/sections/tracks.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||
|
||||
@ -37,8 +37,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
final searchTerm = ref.watch(searchTermStateProvider);
|
||||
final controller = useSearchController();
|
||||
|
||||
ref.watch(authenticationProvider);
|
||||
final authenticationNotifier = ref.watch(authenticationProvider.notifier);
|
||||
final auth = ref.watch(authenticationProvider);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
|
||||
final searchTrack = ref.watch(searchProvider(SearchType.track));
|
||||
@ -91,7 +90,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
appBar: kIsDesktop && !kIsMacOS
|
||||
? const PageWindowTitleBar(automaticallyImplyLeading: true)
|
||||
: null,
|
||||
body: !authenticationNotifier.isLoggedIn
|
||||
body: auth.asData?.value == null
|
||||
? const AnonymousFallback()
|
||||
: Column(
|
||||
children: [
|
||||
|
@ -9,7 +9,7 @@ import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/pages/profile/profile.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/scrobbler_provider.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
@ -35,7 +35,7 @@ class SettingsAccountSection extends HookConsumerWidget {
|
||||
return SectionCardWithHeading(
|
||||
heading: context.l10n.account,
|
||||
children: [
|
||||
if (auth != null)
|
||||
if (auth.asData?.value != null)
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.user),
|
||||
title: const Text("User Profile"),
|
||||
@ -53,7 +53,7 @@ class SettingsAccountSection extends HookConsumerWidget {
|
||||
ServiceUtils.pushNamed(context, ProfilePage.name);
|
||||
},
|
||||
),
|
||||
if (auth == null)
|
||||
if (auth.asData?.value == null)
|
||||
LayoutBuilder(builder: (context, constrains) {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
|
@ -4,22 +4,30 @@ import 'dart:io';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart'
|
||||
hide X509Certificate;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/collections/routes.dart';
|
||||
import 'package:spotube/components/dialogs/prompt_dialog.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/provider/database/database.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
class AuthenticationCredentials {
|
||||
String cookie;
|
||||
String accessToken;
|
||||
DateTime expiration;
|
||||
|
||||
extension ExpirationAuthenticationTableData on AuthenticationTableData {
|
||||
bool get isExpired => DateTime.now().isAfter(expiration);
|
||||
|
||||
String? getCookie(String key) => cookie.value
|
||||
.split("; ")
|
||||
.firstWhereOrNull((c) => c.trim().startsWith("$key="))
|
||||
?.trim()
|
||||
.split("=")
|
||||
.last
|
||||
.replaceAll(";", "");
|
||||
}
|
||||
|
||||
class AuthenticationNotifier extends AsyncNotifier<AuthenticationTableData?> {
|
||||
static final Dio dio = () {
|
||||
final dio = Dio();
|
||||
|
||||
@ -32,13 +40,68 @@ class AuthenticationCredentials {
|
||||
return dio;
|
||||
}();
|
||||
|
||||
AuthenticationCredentials({
|
||||
required this.cookie,
|
||||
required this.accessToken,
|
||||
required this.expiration,
|
||||
});
|
||||
@override
|
||||
build() async {
|
||||
final database = ref.watch(databaseProvider);
|
||||
|
||||
static Future<AuthenticationCredentials> fromCookie(String cookie) async {
|
||||
final data = await (database.select(database.authenticationTable)
|
||||
..where((s) => s.id.equals(0)))
|
||||
.getSingleOrNull();
|
||||
|
||||
Timer? refreshTimer;
|
||||
|
||||
ref.listenSelf((prevData, newData) async {
|
||||
if (newData.asData?.value == null) return;
|
||||
|
||||
if (newData.asData!.value!.isExpired) {
|
||||
await refreshCredentials();
|
||||
}
|
||||
|
||||
// set the refresh timer
|
||||
refreshTimer?.cancel();
|
||||
refreshTimer = Timer(
|
||||
newData.asData!.value!.expiration.difference(DateTime.now()),
|
||||
() => refreshCredentials(),
|
||||
);
|
||||
});
|
||||
|
||||
final subscription =
|
||||
database.select(database.authenticationTable).watch().listen(
|
||||
(event) {
|
||||
state = AsyncData(event.isEmpty ? null : event.first);
|
||||
},
|
||||
);
|
||||
|
||||
ref.onDispose(() {
|
||||
subscription.cancel();
|
||||
refreshTimer?.cancel();
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<void> refreshCredentials() async {
|
||||
final database = ref.read(databaseProvider);
|
||||
final refreshedCredentials =
|
||||
await credentialsFromCookie(state.asData!.value!.cookie.value);
|
||||
|
||||
await database
|
||||
.update(database.authenticationTable)
|
||||
.replace(refreshedCredentials);
|
||||
}
|
||||
|
||||
Future<void> login(String cookie) async {
|
||||
final database = ref.read(databaseProvider);
|
||||
final refreshedCredentials = await credentialsFromCookie(cookie);
|
||||
|
||||
await database
|
||||
.into(database.authenticationTable)
|
||||
.insert(refreshedCredentials);
|
||||
}
|
||||
|
||||
Future<AuthenticationTableCompanion> credentialsFromCookie(
|
||||
String cookie,
|
||||
) async {
|
||||
try {
|
||||
final spDc = cookie
|
||||
.split("; ")
|
||||
@ -65,9 +128,10 @@ class AuthenticationCredentials {
|
||||
);
|
||||
}
|
||||
|
||||
return AuthenticationCredentials(
|
||||
cookie: "${res.headers["set-cookie"]?.join(";")}; $spDc",
|
||||
accessToken: body['accessToken'],
|
||||
return AuthenticationTableCompanion.insert(
|
||||
id: const Value(0),
|
||||
cookie: DecryptedText("${res.headers["set-cookie"]?.join(";")}; $spDc"),
|
||||
accessToken: DecryptedText(body['accessToken']),
|
||||
expiration: DateTime.fromMillisecondsSinceEpoch(
|
||||
body['accessTokenExpirationTimestampMs'],
|
||||
),
|
||||
@ -86,102 +150,20 @@ class AuthenticationCredentials {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cookie value
|
||||
String? getCookie(String key) => cookie
|
||||
.split("; ")
|
||||
.firstWhereOrNull((c) => c.trim().startsWith("$key="))
|
||||
?.trim()
|
||||
.split("=")
|
||||
.last
|
||||
.replaceAll(";", "");
|
||||
|
||||
factory AuthenticationCredentials.fromJson(Map<String, dynamic> json) {
|
||||
return AuthenticationCredentials(
|
||||
cookie: json['cookie'] as String,
|
||||
accessToken: json['accessToken'] as String,
|
||||
expiration: DateTime.parse(json['expiration'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'cookie': cookie,
|
||||
'accessToken': accessToken,
|
||||
'expiration': expiration.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
AuthenticationCredentials copyWith({
|
||||
String? cookie,
|
||||
String? accessToken,
|
||||
DateTime? expiration,
|
||||
}) {
|
||||
return AuthenticationCredentials(
|
||||
cookie: cookie ?? this.cookie,
|
||||
accessToken: accessToken ?? this.accessToken,
|
||||
expiration: expiration ?? this.expiration,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticationNotifier
|
||||
extends PersistedStateNotifier<AuthenticationCredentials?> {
|
||||
bool get isLoggedIn => state != null;
|
||||
|
||||
AuthenticationNotifier() : super(null, "authentication", encrypted: true);
|
||||
|
||||
Timer? _refreshTimer;
|
||||
|
||||
@override
|
||||
FutureOr<void> onInit() async {
|
||||
super.onInit();
|
||||
if (isLoggedIn && state!.isExpired) {
|
||||
await refreshCredentials();
|
||||
}
|
||||
|
||||
addListener((state) {
|
||||
_refreshTimer?.cancel();
|
||||
if (isLoggedIn && !state!.isExpired) {
|
||||
_refreshTimer = Timer(
|
||||
state.expiration.difference(DateTime.now()),
|
||||
() => refreshCredentials(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setCredentials(AuthenticationCredentials credentials) {
|
||||
state = credentials;
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
state = null;
|
||||
state = const AsyncData(null);
|
||||
final database = ref.read(databaseProvider);
|
||||
await (database.delete(database.authenticationTable)
|
||||
..where((s) => s.id.equals(0)))
|
||||
.go();
|
||||
if (kIsMobile) {
|
||||
WebStorageManager.instance().deleteAllData();
|
||||
CookieManager.instance().deleteAllCookies();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refreshCredentials() async {
|
||||
if (!isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = await AuthenticationCredentials.fromCookie(state!.cookie);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<AuthenticationCredentials?> fromJson(Map<String, dynamic> json) {
|
||||
return AuthenticationCredentials.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return state?.toJson() ?? {};
|
||||
}
|
||||
}
|
||||
|
||||
final authenticationProvider =
|
||||
StateNotifierProvider<AuthenticationNotifier, AuthenticationCredentials?>(
|
||||
(ref) => AuthenticationNotifier(),
|
||||
AsyncNotifierProvider<AuthenticationNotifier, AuthenticationTableData?>(
|
||||
() => AuthenticationNotifier(),
|
||||
);
|
@ -1,10 +1,10 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/services/custom_spotify_endpoints/spotify_endpoints.dart';
|
||||
|
||||
final customSpotifyEndpointProvider = Provider<CustomSpotifyEndpoints>((ref) {
|
||||
ref.watch(spotifyProvider);
|
||||
final auth = ref.watch(authenticationProvider);
|
||||
return CustomSpotifyEndpoints(auth?.accessToken ?? "");
|
||||
return CustomSpotifyEndpoints(auth.asData?.value?.accessToken.value ?? "");
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
@ -8,7 +8,7 @@ final homeViewProvider = FutureProvider((ref) async {
|
||||
userPreferencesProvider.select((s) => s.market),
|
||||
);
|
||||
final spTCookie = ref.watch(
|
||||
authenticationProvider.select((s) => s?.getCookie("sp_t")),
|
||||
authenticationProvider.select((s) => s.asData?.value?.getCookie("sp_t")),
|
||||
);
|
||||
|
||||
if (spTCookie == null) return null;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/models/spotify/home_feed.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
@ -11,7 +11,7 @@ final homeSectionViewProvider =
|
||||
userPreferencesProvider.select((s) => s.market),
|
||||
);
|
||||
final spTCookie = ref.watch(
|
||||
authenticationProvider.select((s) => s?.getCookie("sp_t")),
|
||||
authenticationProvider.select((s) => s.asData?.value?.getCookie("sp_t")),
|
||||
);
|
||||
|
||||
if (spTCookie == null) return null;
|
||||
|
@ -2,14 +2,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/env.dart';
|
||||
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
|
||||
final spotifyProvider = Provider<SpotifyApi>((ref) {
|
||||
final authState = ref.watch(authenticationProvider);
|
||||
final anonCred = PrimitiveUtils.getRandomElement(Env.spotifySecrets);
|
||||
|
||||
if (authState == null) {
|
||||
if (authState.asData?.value == null) {
|
||||
return SpotifyApi(
|
||||
SpotifyApiCredentials(
|
||||
anonCred["clientId"],
|
||||
@ -18,5 +18,5 @@ final spotifyProvider = Provider<SpotifyApi>((ref) {
|
||||
);
|
||||
}
|
||||
|
||||
return SpotifyApi.withAccessToken(authState.accessToken);
|
||||
return SpotifyApi.withAccessToken(authState.asData!.value!.accessToken.value);
|
||||
});
|
||||
|
@ -134,8 +134,11 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
|
||||
|
||||
void setLocalLibraryLocation(List<String> localLibraryDirs) {
|
||||
//if (localLibraryDir.isEmpty) return;
|
||||
setData(PreferencesTableCompanion(
|
||||
localLibraryLocation: Value(localLibraryDirs)));
|
||||
setData(
|
||||
PreferencesTableCompanion(
|
||||
localLibraryLocation: Value(localLibraryDirs),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void setLayoutMode(LayoutMode mode) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
abstract class EncryptedKvStoreService {
|
||||
static const _storage = FlutterSecureStorage(
|
||||
@ -9,15 +10,21 @@ abstract class EncryptedKvStoreService {
|
||||
),
|
||||
);
|
||||
|
||||
static late final String _encryptionKeySync;
|
||||
static String? _encryptionKeySync;
|
||||
|
||||
static Future<void> initialize() async {
|
||||
_encryptionKeySync = await encryptionKey;
|
||||
}
|
||||
|
||||
static String get encryptionKeySync => _encryptionKeySync;
|
||||
static String get encryptionKeySync => _encryptionKeySync!;
|
||||
|
||||
static bool get isUnsupportedPlatform =>
|
||||
kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak);
|
||||
|
||||
static Future<String> get encryptionKey async {
|
||||
if (isUnsupportedPlatform) {
|
||||
return KVStoreService.encryptionKey;
|
||||
}
|
||||
try {
|
||||
final value = await _storage.read(key: 'encryption');
|
||||
final key = const Uuid().v4();
|
||||
@ -34,10 +41,17 @@ abstract class EncryptedKvStoreService {
|
||||
}
|
||||
|
||||
static Future<void> setEncryptionKey(String key) async {
|
||||
if (isUnsupportedPlatform) {
|
||||
await KVStoreService.setEncryptionKey(key);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await _storage.write(key: 'encryption', value: key);
|
||||
} catch (e) {
|
||||
await KVStoreService.setEncryptionKey(key);
|
||||
} finally {
|
||||
_encryptionKeySync = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user