From 5061c35c8d0668eba58a8aeb31bc6323d1747828 Mon Sep 17 00:00:00 2001 From: rovo89 Date: Sun, 4 Feb 2024 21:35:13 +0100 Subject: [PATCH] feat(mobile): Add support for Basic Authentication (#6840) --- .../lib/modules/album/ui/album_thumbnail_listtile.dart | 2 +- .../lib/modules/asset_viewer/views/gallery_viewer.dart | 6 ++---- .../modules/asset_viewer/views/video_viewer_page.dart | 8 ++++---- mobile/lib/modules/backup/services/backup.service.dart | 3 +-- .../map/widgets/positioned_asset_marker_icon.dart | 3 +-- mobile/lib/modules/memories/ui/memory_card.dart | 4 ++-- mobile/lib/modules/search/ui/curated_people_row.dart | 2 +- mobile/lib/modules/search/ui/thumbnail_with_info.dart | 3 +-- .../lib/modules/search/views/person_result_page.dart | 3 +-- mobile/lib/shared/providers/websocket.provider.dart | 8 +++++++- mobile/lib/shared/services/api.service.dart | 10 +++++----- mobile/lib/shared/ui/immich_image.dart | 10 +++++----- mobile/lib/shared/ui/user_avatar.dart | 2 +- mobile/lib/shared/ui/user_circle_avatar.dart | 2 +- 14 files changed, 33 insertions(+), 33 deletions(-) diff --git a/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart b/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart index 107d08b1fb..f2f465f29e 100644 --- a/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart +++ b/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart @@ -49,7 +49,7 @@ class AlbumThumbnailListTile extends StatelessWidget { type: ThumbnailFormat.WEBP, ), httpHeaders: { - "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", + "x-immich-user-token": Store.get(StoreKey.accessToken), }, cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.WEBP), errorWidget: (context, url, error) => diff --git a/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart b/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart index ac6bd1fc85..1903a7f19c 100644 --- a/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart +++ b/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart @@ -78,8 +78,7 @@ class GalleryViewerPage extends HookConsumerWidget { final isPlayingMotionVideo = useState(false); final isPlayingVideo = useState(false); Offset? localPosition; - final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}'; - final header = {"Authorization": authToken}; + final header = {"x-immich-user-token": Store.get(StoreKey.accessToken)}; final currentIndex = useState(initialIndex); final currentAsset = loadAsset(currentIndex.value); final isTrashEnabled = @@ -524,8 +523,7 @@ class GalleryViewerPage extends HookConsumerWidget { imageUrl: '${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/$assetId', httpHeaders: { - "Authorization": - "Bearer ${Store.get(StoreKey.accessToken)}", + "x-immich-user-token": Store.get(StoreKey.accessToken), }, errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined), diff --git a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart index 1df312095a..8257f28578 100644 --- a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart +++ b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart @@ -68,7 +68,7 @@ class VideoViewerPage extends HookConsumerWidget { children: [ VideoPlayer( url: videoUrl, - jwtToken: Store.get(StoreKey.accessToken), + accessToken: Store.get(StoreKey.accessToken), isMotionVideo: isMotionVideo, onVideoEnded: onVideoEnded, onPaused: onPaused, @@ -99,7 +99,7 @@ final _fileFamily = class VideoPlayer extends StatefulWidget { final String? url; - final String? jwtToken; + final String? accessToken; final File? file; final bool isMotionVideo; final VoidCallback onVideoEnded; @@ -114,7 +114,7 @@ class VideoPlayer extends StatefulWidget { const VideoPlayer({ super.key, this.url, - this.jwtToken, + this.accessToken, this.file, required this.onVideoEnded, required this.isMotionVideo, @@ -160,7 +160,7 @@ class _VideoPlayerState extends State { videoPlayerController = widget.file == null ? VideoPlayerController.networkUrl( Uri.parse(widget.url!), - httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"}, + httpHeaders: {"x-immich-user-token": widget.accessToken ?? ""}, ) : VideoPlayerController.file(widget.file!); diff --git a/mobile/lib/modules/backup/services/backup.service.dart b/mobile/lib/modules/backup/services/backup.service.dart index fd92222ef5..48d6f71cf9 100644 --- a/mobile/lib/modules/backup/services/backup.service.dart +++ b/mobile/lib/modules/backup/services/backup.service.dart @@ -302,8 +302,7 @@ class BackupService { onProgress: ((bytes, totalBytes) => uploadProgressCb(bytes, totalBytes)), ); - req.headers["Authorization"] = - "Bearer ${Store.get(StoreKey.accessToken)}"; + req.headers["x-immich-user-token"] = Store.get(StoreKey.accessToken); req.headers["Transfer-Encoding"] = "chunked"; req.fields['deviceAssetId'] = entity.id; diff --git a/mobile/lib/modules/map/widgets/positioned_asset_marker_icon.dart b/mobile/lib/modules/map/widgets/positioned_asset_marker_icon.dart index cec5114ee1..e7cd6f6227 100644 --- a/mobile/lib/modules/map/widgets/positioned_asset_marker_icon.dart +++ b/mobile/lib/modules/map/widgets/positioned_asset_marker_icon.dart @@ -89,8 +89,7 @@ class _AssetMarkerIcon extends StatelessWidget { imageUrl, cacheKey: cacheKey, headers: { - "Authorization": - "Bearer ${Store.get(StoreKey.accessToken)}", + "x-immich-user-token": Store.get(StoreKey.accessToken), }, errorListener: (_) => const Icon(Icons.image_not_supported_outlined), diff --git a/mobile/lib/modules/memories/ui/memory_card.dart b/mobile/lib/modules/memories/ui/memory_card.dart index d9ccaed39f..883c5b3866 100644 --- a/mobile/lib/modules/memories/ui/memory_card.dart +++ b/mobile/lib/modules/memories/ui/memory_card.dart @@ -27,7 +27,7 @@ class MemoryCard extends StatelessWidget { super.key, }); - String get authToken => 'Bearer ${Store.get(StoreKey.accessToken)}'; + String get accessToken => Store.get(StoreKey.accessToken); @override Widget build(BuildContext context) { @@ -55,7 +55,7 @@ class MemoryCard extends StatelessWidget { cacheKey: getThumbnailCacheKey( asset, ), - headers: {"Authorization": authToken}, + headers: {"x-immich-user-token": accessToken}, ), fit: BoxFit.cover, ), diff --git a/mobile/lib/modules/search/ui/curated_people_row.dart b/mobile/lib/modules/search/ui/curated_people_row.dart index e838c59e12..aa3403f2a1 100644 --- a/mobile/lib/modules/search/ui/curated_people_row.dart +++ b/mobile/lib/modules/search/ui/curated_people_row.dart @@ -51,7 +51,7 @@ class CuratedPeopleRow extends StatelessWidget { itemBuilder: (context, index) { final person = content[index]; final headers = { - "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", + "x-immich-user-token": Store.get(StoreKey.accessToken), }; return Padding( padding: const EdgeInsets.only(right: 18.0), diff --git a/mobile/lib/modules/search/ui/thumbnail_with_info.dart b/mobile/lib/modules/search/ui/thumbnail_with_info.dart index e9be93ad49..6d447526ce 100644 --- a/mobile/lib/modules/search/ui/thumbnail_with_info.dart +++ b/mobile/lib/modules/search/ui/thumbnail_with_info.dart @@ -46,8 +46,7 @@ class ThumbnailWithInfo extends StatelessWidget { fit: BoxFit.cover, imageUrl: imageUrl!, httpHeaders: { - "Authorization": - "Bearer ${Store.get(StoreKey.accessToken)}", + "x-immich-user-token": Store.get(StoreKey.accessToken), }, errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined), diff --git a/mobile/lib/modules/search/views/person_result_page.dart b/mobile/lib/modules/search/views/person_result_page.dart index 8e09f47c34..0b7eeea51d 100644 --- a/mobile/lib/modules/search/views/person_result_page.dart +++ b/mobile/lib/modules/search/views/person_result_page.dart @@ -123,8 +123,7 @@ class PersonResultPage extends HookConsumerWidget { backgroundImage: NetworkImage( getFaceThumbnailUrl(personId), headers: { - "Authorization": - "Bearer ${Store.get(StoreKey.accessToken)}", + "x-immich-user-token": Store.get(StoreKey.accessToken), }, ), ), diff --git a/mobile/lib/shared/providers/websocket.provider.dart b/mobile/lib/shared/providers/websocket.provider.dart index c78777da5a..6b12916d27 100644 --- a/mobile/lib/shared/providers/websocket.provider.dart +++ b/mobile/lib/shared/providers/websocket.provider.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -106,6 +108,10 @@ class WebsocketNotifier extends StateNotifier { final accessToken = Store.get(StoreKey.accessToken); try { final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint)); + final headers = {"x-immich-user-token": accessToken}; + if (endpoint.userInfo.isNotEmpty) { + headers["Authorization"] = "Basic ${base64.encode(utf8.encode(endpoint.userInfo))}"; + } debugPrint("Attempting to connect to websocket"); // Configure socket transports must be specified @@ -118,7 +124,7 @@ class WebsocketNotifier extends StateNotifier { .enableForceNew() .enableForceNewConnection() .enableAutoConnect() - .setExtraHeaders({"Authorization": "Bearer $accessToken"}) + .setExtraHeaders(headers) .build(), ); diff --git a/mobile/lib/shared/services/api.service.dart b/mobile/lib/shared/services/api.service.dart index 656de826cf..2df1ec857f 100644 --- a/mobile/lib/shared/services/api.service.dart +++ b/mobile/lib/shared/services/api.service.dart @@ -31,12 +31,12 @@ class ApiService { setEndpoint(endpoint); } } - String? _authToken; + String? _accessToken; setEndpoint(String endpoint) { _apiClient = ApiClient(basePath: endpoint); - if (_authToken != null) { - setAccessToken(_authToken!); + if (_accessToken != null) { + setAccessToken(_accessToken!); } userApi = UserApi(_apiClient); authenticationApi = AuthenticationApi(_apiClient); @@ -134,8 +134,8 @@ class ApiService { } setAccessToken(String accessToken) { - _authToken = accessToken; - _apiClient.addDefaultHeader('Authorization', 'Bearer $accessToken'); + _accessToken = accessToken; + _apiClient.addDefaultHeader('x-immich-user-token', accessToken); } ApiClient get apiClient => _apiClient; diff --git a/mobile/lib/shared/ui/immich_image.dart b/mobile/lib/shared/ui/immich_image.dart index 575a841f11..18f5147e83 100644 --- a/mobile/lib/shared/ui/immich_image.dart +++ b/mobile/lib/shared/ui/immich_image.dart @@ -95,11 +95,11 @@ class ImmichImage extends StatelessWidget { }, ); } - final String? token = Store.get(StoreKey.accessToken); + final String? accessToken = Store.get(StoreKey.accessToken); final String thumbnailRequestUrl = getThumbnailUrl(asset, type: type); return CachedNetworkImage( imageUrl: thumbnailRequestUrl, - httpHeaders: {"Authorization": "Bearer $token"}, + httpHeaders: {"x-immich-user-token": accessToken ?? ""}, cacheKey: getThumbnailCacheKey(asset, type: type), width: width, height: height, @@ -177,7 +177,7 @@ class ImmichImage extends StatelessWidget { getThumbnailUrlForRemoteId(assetId, type: type), cacheKey: getThumbnailCacheKeyForRemoteId(assetId, type: type), headers: { - "Authorization": 'Bearer ${Store.get(StoreKey.accessToken)}', + "x-immich-user-token": Store.get(StoreKey.accessToken), }, ); @@ -195,10 +195,10 @@ class ImmichImage extends StatelessWidget { context, ); } else { - final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}'; + final accessToken = Store.get(StoreKey.accessToken); // Precache the remote image since we are not using local images return precacheImage( - remoteThumbnailProvider(asset, type, {"Authorization": authToken}), + remoteThumbnailProvider(asset, type, {"x-immich-user-token": accessToken}), context, ); } diff --git a/mobile/lib/shared/ui/user_avatar.dart b/mobile/lib/shared/ui/user_avatar.dart index 95f76de43f..68ed2edbdc 100644 --- a/mobile/lib/shared/ui/user_avatar.dart +++ b/mobile/lib/shared/ui/user_avatar.dart @@ -13,7 +13,7 @@ Widget userAvatar(BuildContext context, User u, {double? radius}) { backgroundColor: context.primaryColor.withAlpha(50), foregroundImage: CachedNetworkImageProvider( url, - headers: {"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"}, + headers: {"x-immich-user-token": Store.get(StoreKey.accessToken)}, cacheKey: "user-${u.id}-profile", ), // silence errors if user has no profile image, use initials as fallback diff --git a/mobile/lib/shared/ui/user_circle_avatar.dart b/mobile/lib/shared/ui/user_circle_avatar.dart index 3dc0e65c1b..103d8970e3 100644 --- a/mobile/lib/shared/ui/user_circle_avatar.dart +++ b/mobile/lib/shared/ui/user_circle_avatar.dart @@ -51,7 +51,7 @@ class UserCircleAvatar extends ConsumerWidget { placeholder: (_, __) => Image.memory(kTransparentImage), imageUrl: profileImageUrl, httpHeaders: { - "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", + "x-immich-user-token": Store.get(StoreKey.accessToken), }, fadeInDuration: const Duration(milliseconds: 300), errorWidget: (context, error, stackTrace) => textIcon,