1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-04 02:46:47 +01:00

feat(mobile): Add support for Basic Authentication (#6840)

This commit is contained in:
rovo89 2024-02-04 21:35:13 +01:00 committed by GitHub
parent b4c211cad1
commit 5061c35c8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 33 additions and 33 deletions

View file

@ -49,7 +49,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
type: ThumbnailFormat.WEBP, type: ThumbnailFormat.WEBP,
), ),
httpHeaders: { httpHeaders: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", "x-immich-user-token": Store.get(StoreKey.accessToken),
}, },
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.WEBP), cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.WEBP),
errorWidget: (context, url, error) => errorWidget: (context, url, error) =>

View file

@ -78,8 +78,7 @@ class GalleryViewerPage extends HookConsumerWidget {
final isPlayingMotionVideo = useState(false); final isPlayingMotionVideo = useState(false);
final isPlayingVideo = useState(false); final isPlayingVideo = useState(false);
Offset? localPosition; Offset? localPosition;
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}'; final header = {"x-immich-user-token": Store.get(StoreKey.accessToken)};
final header = {"Authorization": authToken};
final currentIndex = useState(initialIndex); final currentIndex = useState(initialIndex);
final currentAsset = loadAsset(currentIndex.value); final currentAsset = loadAsset(currentIndex.value);
final isTrashEnabled = final isTrashEnabled =
@ -524,8 +523,7 @@ class GalleryViewerPage extends HookConsumerWidget {
imageUrl: imageUrl:
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/$assetId', '${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/$assetId',
httpHeaders: { httpHeaders: {
"Authorization": "x-immich-user-token": Store.get(StoreKey.accessToken),
"Bearer ${Store.get(StoreKey.accessToken)}",
}, },
errorWidget: (context, url, error) => errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined), const Icon(Icons.image_not_supported_outlined),

View file

@ -68,7 +68,7 @@ class VideoViewerPage extends HookConsumerWidget {
children: [ children: [
VideoPlayer( VideoPlayer(
url: videoUrl, url: videoUrl,
jwtToken: Store.get(StoreKey.accessToken), accessToken: Store.get(StoreKey.accessToken),
isMotionVideo: isMotionVideo, isMotionVideo: isMotionVideo,
onVideoEnded: onVideoEnded, onVideoEnded: onVideoEnded,
onPaused: onPaused, onPaused: onPaused,
@ -99,7 +99,7 @@ final _fileFamily =
class VideoPlayer extends StatefulWidget { class VideoPlayer extends StatefulWidget {
final String? url; final String? url;
final String? jwtToken; final String? accessToken;
final File? file; final File? file;
final bool isMotionVideo; final bool isMotionVideo;
final VoidCallback onVideoEnded; final VoidCallback onVideoEnded;
@ -114,7 +114,7 @@ class VideoPlayer extends StatefulWidget {
const VideoPlayer({ const VideoPlayer({
super.key, super.key,
this.url, this.url,
this.jwtToken, this.accessToken,
this.file, this.file,
required this.onVideoEnded, required this.onVideoEnded,
required this.isMotionVideo, required this.isMotionVideo,
@ -160,7 +160,7 @@ class _VideoPlayerState extends State<VideoPlayer> {
videoPlayerController = widget.file == null videoPlayerController = widget.file == null
? VideoPlayerController.networkUrl( ? VideoPlayerController.networkUrl(
Uri.parse(widget.url!), Uri.parse(widget.url!),
httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"}, httpHeaders: {"x-immich-user-token": widget.accessToken ?? ""},
) )
: VideoPlayerController.file(widget.file!); : VideoPlayerController.file(widget.file!);

View file

@ -302,8 +302,7 @@ class BackupService {
onProgress: ((bytes, totalBytes) => onProgress: ((bytes, totalBytes) =>
uploadProgressCb(bytes, totalBytes)), uploadProgressCb(bytes, totalBytes)),
); );
req.headers["Authorization"] = req.headers["x-immich-user-token"] = Store.get(StoreKey.accessToken);
"Bearer ${Store.get(StoreKey.accessToken)}";
req.headers["Transfer-Encoding"] = "chunked"; req.headers["Transfer-Encoding"] = "chunked";
req.fields['deviceAssetId'] = entity.id; req.fields['deviceAssetId'] = entity.id;

View file

@ -89,8 +89,7 @@ class _AssetMarkerIcon extends StatelessWidget {
imageUrl, imageUrl,
cacheKey: cacheKey, cacheKey: cacheKey,
headers: { headers: {
"Authorization": "x-immich-user-token": Store.get(StoreKey.accessToken),
"Bearer ${Store.get(StoreKey.accessToken)}",
}, },
errorListener: (_) => errorListener: (_) =>
const Icon(Icons.image_not_supported_outlined), const Icon(Icons.image_not_supported_outlined),

View file

@ -27,7 +27,7 @@ class MemoryCard extends StatelessWidget {
super.key, super.key,
}); });
String get authToken => 'Bearer ${Store.get(StoreKey.accessToken)}'; String get accessToken => Store.get(StoreKey.accessToken);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -55,7 +55,7 @@ class MemoryCard extends StatelessWidget {
cacheKey: getThumbnailCacheKey( cacheKey: getThumbnailCacheKey(
asset, asset,
), ),
headers: {"Authorization": authToken}, headers: {"x-immich-user-token": accessToken},
), ),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),

View file

@ -51,7 +51,7 @@ class CuratedPeopleRow extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final person = content[index]; final person = content[index];
final headers = { final headers = {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", "x-immich-user-token": Store.get(StoreKey.accessToken),
}; };
return Padding( return Padding(
padding: const EdgeInsets.only(right: 18.0), padding: const EdgeInsets.only(right: 18.0),

View file

@ -46,8 +46,7 @@ class ThumbnailWithInfo extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
imageUrl: imageUrl!, imageUrl: imageUrl!,
httpHeaders: { httpHeaders: {
"Authorization": "x-immich-user-token": Store.get(StoreKey.accessToken),
"Bearer ${Store.get(StoreKey.accessToken)}",
}, },
errorWidget: (context, url, error) => errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined), const Icon(Icons.image_not_supported_outlined),

View file

@ -123,8 +123,7 @@ class PersonResultPage extends HookConsumerWidget {
backgroundImage: NetworkImage( backgroundImage: NetworkImage(
getFaceThumbnailUrl(personId), getFaceThumbnailUrl(personId),
headers: { headers: {
"Authorization": "x-immich-user-token": Store.get(StoreKey.accessToken),
"Bearer ${Store.get(StoreKey.accessToken)}",
}, },
), ),
), ),

View file

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -106,6 +108,10 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
final accessToken = Store.get(StoreKey.accessToken); final accessToken = Store.get(StoreKey.accessToken);
try { try {
final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint)); 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"); debugPrint("Attempting to connect to websocket");
// Configure socket transports must be specified // Configure socket transports must be specified
@ -118,7 +124,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
.enableForceNew() .enableForceNew()
.enableForceNewConnection() .enableForceNewConnection()
.enableAutoConnect() .enableAutoConnect()
.setExtraHeaders({"Authorization": "Bearer $accessToken"}) .setExtraHeaders(headers)
.build(), .build(),
); );

View file

@ -31,12 +31,12 @@ class ApiService {
setEndpoint(endpoint); setEndpoint(endpoint);
} }
} }
String? _authToken; String? _accessToken;
setEndpoint(String endpoint) { setEndpoint(String endpoint) {
_apiClient = ApiClient(basePath: endpoint); _apiClient = ApiClient(basePath: endpoint);
if (_authToken != null) { if (_accessToken != null) {
setAccessToken(_authToken!); setAccessToken(_accessToken!);
} }
userApi = UserApi(_apiClient); userApi = UserApi(_apiClient);
authenticationApi = AuthenticationApi(_apiClient); authenticationApi = AuthenticationApi(_apiClient);
@ -134,8 +134,8 @@ class ApiService {
} }
setAccessToken(String accessToken) { setAccessToken(String accessToken) {
_authToken = accessToken; _accessToken = accessToken;
_apiClient.addDefaultHeader('Authorization', 'Bearer $accessToken'); _apiClient.addDefaultHeader('x-immich-user-token', accessToken);
} }
ApiClient get apiClient => _apiClient; ApiClient get apiClient => _apiClient;

View file

@ -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); final String thumbnailRequestUrl = getThumbnailUrl(asset, type: type);
return CachedNetworkImage( return CachedNetworkImage(
imageUrl: thumbnailRequestUrl, imageUrl: thumbnailRequestUrl,
httpHeaders: {"Authorization": "Bearer $token"}, httpHeaders: {"x-immich-user-token": accessToken ?? ""},
cacheKey: getThumbnailCacheKey(asset, type: type), cacheKey: getThumbnailCacheKey(asset, type: type),
width: width, width: width,
height: height, height: height,
@ -177,7 +177,7 @@ class ImmichImage extends StatelessWidget {
getThumbnailUrlForRemoteId(assetId, type: type), getThumbnailUrlForRemoteId(assetId, type: type),
cacheKey: getThumbnailCacheKeyForRemoteId(assetId, type: type), cacheKey: getThumbnailCacheKeyForRemoteId(assetId, type: type),
headers: { headers: {
"Authorization": 'Bearer ${Store.get(StoreKey.accessToken)}', "x-immich-user-token": Store.get(StoreKey.accessToken),
}, },
); );
@ -195,10 +195,10 @@ class ImmichImage extends StatelessWidget {
context, context,
); );
} else { } 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 // Precache the remote image since we are not using local images
return precacheImage( return precacheImage(
remoteThumbnailProvider(asset, type, {"Authorization": authToken}), remoteThumbnailProvider(asset, type, {"x-immich-user-token": accessToken}),
context, context,
); );
} }

View file

@ -13,7 +13,7 @@ Widget userAvatar(BuildContext context, User u, {double? radius}) {
backgroundColor: context.primaryColor.withAlpha(50), backgroundColor: context.primaryColor.withAlpha(50),
foregroundImage: CachedNetworkImageProvider( foregroundImage: CachedNetworkImageProvider(
url, url,
headers: {"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"}, headers: {"x-immich-user-token": Store.get(StoreKey.accessToken)},
cacheKey: "user-${u.id}-profile", cacheKey: "user-${u.id}-profile",
), ),
// silence errors if user has no profile image, use initials as fallback // silence errors if user has no profile image, use initials as fallback

View file

@ -51,7 +51,7 @@ class UserCircleAvatar extends ConsumerWidget {
placeholder: (_, __) => Image.memory(kTransparentImage), placeholder: (_, __) => Image.memory(kTransparentImage),
imageUrl: profileImageUrl, imageUrl: profileImageUrl,
httpHeaders: { httpHeaders: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", "x-immich-user-token": Store.get(StoreKey.accessToken),
}, },
fadeInDuration: const Duration(milliseconds: 300), fadeInDuration: const Duration(milliseconds: 300),
errorWidget: (context, error, stackTrace) => textIcon, errorWidget: (context, error, stackTrace) => textIcon,