1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-28 09:33:27 +02:00

feat(server): require auth for more endpoints (#2092)

* feat(server): require auth for more endpoints

* dev: add authorization header to profile image on mobile

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Michel Heusschen 2023-03-27 16:38:54 +02:00 committed by GitHub
parent 4e526dfaae
commit 089dbdbd7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 96 additions and 45 deletions

View File

@ -1,8 +1,7 @@
import 'dart:math';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/ui/user_circle_avatar.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
@ -10,9 +9,7 @@ import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
import 'package:immich_mobile/shared/models/server_info_state.model.dart'; import 'package:immich_mobile/shared/models/server_info_state.model.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/ui/transparent_image.dart';
class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget { class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
@override @override
@ -46,29 +43,13 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
}, },
); );
} else { } else {
final String? endpoint = Store.get(StoreKey.serverEndpoint);
var dummy = Random().nextInt(1024);
return InkWell( return InkWell(
onTap: () { onTap: () {
Scaffold.of(context).openDrawer(); Scaffold.of(context).openDrawer();
}, },
child: CircleAvatar( child: const UserCircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
radius: 18, radius: 18,
child: ClipRRect( size: 33,
borderRadius: BorderRadius.circular(50),
child: FadeInImage.memoryNetwork(
fit: BoxFit.cover,
placeholder: kTransparentImage,
width: 33,
height: 33,
image:
'$endpoint/user/profile-image/${authState.userId}?d=${dummy++}',
fadeInDuration: const Duration(milliseconds: 200),
imageErrorBuilder: (context, error, stackTrace) =>
Image.memory(kTransparentImage),
),
),
), ),
); );
} }

View File

@ -1,15 +1,12 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart'; import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart';
import 'package:immich_mobile/modules/home/ui/user_circle_avatar.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/transparent_image.dart';
class ProfileDrawerHeader extends HookConsumerWidget { class ProfileDrawerHeader extends HookConsumerWidget {
const ProfileDrawerHeader({ const ProfileDrawerHeader({
@ -18,31 +15,15 @@ class ProfileDrawerHeader extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final String endpoint = Store.get(StoreKey.serverEndpoint);
AuthenticationState authState = ref.watch(authenticationProvider); AuthenticationState authState = ref.watch(authenticationProvider);
final uploadProfileImageStatus = final uploadProfileImageStatus =
ref.watch(uploadProfileImageProvider).status; ref.watch(uploadProfileImageProvider).status;
var dummy = Random().nextInt(1024);
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
buildUserProfileImage() { buildUserProfileImage() {
var userImage = CircleAvatar( var userImage = const UserCircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
radius: 35, radius: 35,
child: ClipRRect( size: 66,
borderRadius: BorderRadius.circular(50),
child: FadeInImage.memoryNetwork(
fit: BoxFit.cover,
placeholder: kTransparentImage,
width: 66,
height: 66,
image:
'$endpoint/user/profile-image/${authState.userId}?d=${dummy++}',
fadeInDuration: const Duration(milliseconds: 200),
imageErrorBuilder: (context, error, stackTrace) =>
Image.memory(kTransparentImage),
),
),
); );
if (authState.profileImagePath.isEmpty) { if (authState.profileImagePath.isEmpty) {

View File

@ -0,0 +1,44 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/transparent_image.dart';
class UserCircleAvatar extends ConsumerWidget {
final double radius;
final double size;
const UserCircleAvatar({super.key, required this.radius, required this.size});
@override
Widget build(BuildContext context, WidgetRef ref) {
AuthenticationState authState = ref.watch(authenticationProvider);
var profileImageUrl =
'${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${authState.userId}?d=${Random().nextInt(1024)}';
return CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
radius: radius,
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: FadeInImage(
fit: BoxFit.cover,
placeholder: MemoryImage(kTransparentImage),
width: size,
height: size,
image: NetworkImage(
profileImageUrl,
headers: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"
},
),
fadeInDuration: const Duration(milliseconds: 200),
imageErrorBuilder: (context, error, stackTrace) =>
Image.memory(kTransparentImage),
),
),
);
}
}

Binary file not shown.

Binary file not shown.

View File

@ -14,6 +14,7 @@ import { Authenticated } from '../decorators/authenticated.decorator';
export class ServerInfoController { export class ServerInfoController {
constructor(private service: ServerInfoService) {} constructor(private service: ServerInfoService) {}
@Authenticated()
@Get() @Get()
getServerInfo(): Promise<ServerInfoResponseDto> { getServerInfo(): Promise<ServerInfoResponseDto> {
return this.service.getInfo(); return this.service.getInfo();

View File

@ -44,6 +44,7 @@ export class UserController {
return this.service.getAllUsers(authUser, isAll); return this.service.getAllUsers(authUser, isAll);
} }
@Authenticated()
@Get('/info/:userId') @Get('/info/:userId')
getUserById(@Param('userId') userId: string): Promise<UserResponseDto> { getUserById(@Param('userId') userId: string): Promise<UserResponseDto> {
return this.service.getUserById(userId); return this.service.getUserById(userId);
@ -87,8 +88,8 @@ export class UserController {
return this.service.updateUser(authUser, updateUserDto); return this.service.updateUser(authUser, updateUserDto);
} }
@UseInterceptors(FileInterceptor('file', profileImageUploadOption))
@Authenticated() @Authenticated()
@UseInterceptors(FileInterceptor('file', profileImageUploadOption))
@ApiConsumes('multipart/form-data') @ApiConsumes('multipart/form-data')
@ApiBody({ @ApiBody({
description: 'A new avatar for the user', description: 'A new avatar for the user',
@ -102,6 +103,7 @@ export class UserController {
return this.service.createProfileImage(authUser, fileInfo); return this.service.createProfileImage(authUser, fileInfo);
} }
@Authenticated()
@Get('/profile-image/:userId') @Get('/profile-image/:userId')
@Header('Cache-Control', 'max-age=600') @Header('Cache-Control', 'max-age=600')
async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise<any> { async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise<any> {

View File

@ -943,6 +943,14 @@
}, },
"tags": [ "tags": [
"Server Info" "Server Info"
],
"security": [
{
"bearer": []
},
{
"cookie": []
}
] ]
} }
}, },
@ -1482,6 +1490,14 @@
}, },
"tags": [ "tags": [
"User" "User"
],
"security": [
{
"bearer": []
},
{
"cookie": []
}
] ]
} }
}, },
@ -1694,6 +1710,14 @@
}, },
"tags": [ "tags": [
"User" "User"
],
"security": [
{
"bearer": []
},
{
"cookie": []
}
] ]
} }
}, },

View File

@ -7043,6 +7043,12 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur
const localVarHeaderParameter = {} as any; const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any; const localVarQueryParameter = {} as any;
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
// authentication cookie required
setSearchParams(localVarUrlObj, localVarQueryParameter); setSearchParams(localVarUrlObj, localVarQueryParameter);
@ -8586,6 +8592,12 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
const localVarHeaderParameter = {} as any; const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any; const localVarQueryParameter = {} as any;
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
// authentication cookie required
setSearchParams(localVarUrlObj, localVarQueryParameter); setSearchParams(localVarUrlObj, localVarQueryParameter);
@ -8619,6 +8631,12 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
const localVarHeaderParameter = {} as any; const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any; const localVarQueryParameter = {} as any;
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
// authentication cookie required
setSearchParams(localVarUrlObj, localVarQueryParameter); setSearchParams(localVarUrlObj, localVarQueryParameter);