From 089dbdbd7e76d8b6929518f24a29905882e880d4 Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:38:54 +0200 Subject: [PATCH] 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 --- .../modules/home/ui/home_page_app_bar.dart | 25 ++-------- .../profile_drawer/profile_drawer_header.dart | 25 ++-------- .../modules/home/ui/user_circle_avatar.dart | 44 ++++++++++++++++++ mobile/openapi/doc/ServerInfoApi.md | Bin 4334 -> 5037 bytes mobile/openapi/doc/UserApi.md | Bin 14757 -> 16163 bytes .../src/controllers/server-info.controller.ts | 1 + .../immich/src/controllers/user.controller.ts | 4 +- server/immich-openapi-specs.json | 24 ++++++++++ web/src/api/open-api/api.ts | 18 +++++++ 9 files changed, 96 insertions(+), 45 deletions(-) create mode 100644 mobile/lib/modules/home/ui/user_circle_avatar.dart diff --git a/mobile/lib/modules/home/ui/home_page_app_bar.dart b/mobile/lib/modules/home/ui/home_page_app_bar.dart index aa33f89818..94032e5b3c 100644 --- a/mobile/lib/modules/home/ui/home_page_app_bar.dart +++ b/mobile/lib/modules/home/ui/home_page_app_bar.dart @@ -1,8 +1,7 @@ -import 'dart:math'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.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/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/shared/models/server_info_state.model.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/ui/transparent_image.dart'; class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget { @override @@ -46,29 +43,13 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget { }, ); } else { - final String? endpoint = Store.get(StoreKey.serverEndpoint); - var dummy = Random().nextInt(1024); return InkWell( onTap: () { Scaffold.of(context).openDrawer(); }, - child: CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, + child: const UserCircleAvatar( radius: 18, - child: ClipRRect( - 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), - ), - ), + size: 33, ), ); } diff --git a/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart b/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart index 0a3e8680aa..da8a2c6546 100644 --- a/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart +++ b/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart @@ -1,15 +1,12 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.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/ui/user_circle_avatar.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/immich_loading_indicator.dart'; -import 'package:immich_mobile/shared/ui/transparent_image.dart'; class ProfileDrawerHeader extends HookConsumerWidget { const ProfileDrawerHeader({ @@ -18,31 +15,15 @@ class ProfileDrawerHeader extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final String endpoint = Store.get(StoreKey.serverEndpoint); AuthenticationState authState = ref.watch(authenticationProvider); final uploadProfileImageStatus = ref.watch(uploadProfileImageProvider).status; - var dummy = Random().nextInt(1024); final isDarkMode = Theme.of(context).brightness == Brightness.dark; buildUserProfileImage() { - var userImage = CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, + var userImage = const UserCircleAvatar( radius: 35, - child: ClipRRect( - 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), - ), - ), + size: 66, ); if (authState.profileImagePath.isEmpty) { diff --git a/mobile/lib/modules/home/ui/user_circle_avatar.dart b/mobile/lib/modules/home/ui/user_circle_avatar.dart new file mode 100644 index 0000000000..441aab5cda --- /dev/null +++ b/mobile/lib/modules/home/ui/user_circle_avatar.dart @@ -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), + ), + ), + ); + } +} diff --git a/mobile/openapi/doc/ServerInfoApi.md b/mobile/openapi/doc/ServerInfoApi.md index df57bc9a79459d43e2f540c40399e3cd5f9bc80c..077b34ac2b3889350280082db2b63f2798d6816a 100644 GIT binary patch delta 27 jcmaE-xK@3`WTwp_B?ZOTE6lLIVrC+|^wHJL{(7b5yrEe0;SxmHs{0091S6Gi|4 delta 48 ycmZ2nx3qYJkn-d_ef7z|wF5S*s`)c+4l^`loP0u~03^EkxAsbi$mSn<+5!OA?-GUp diff --git a/server/apps/immich/src/controllers/server-info.controller.ts b/server/apps/immich/src/controllers/server-info.controller.ts index 370673651f..18d364ad66 100644 --- a/server/apps/immich/src/controllers/server-info.controller.ts +++ b/server/apps/immich/src/controllers/server-info.controller.ts @@ -14,6 +14,7 @@ import { Authenticated } from '../decorators/authenticated.decorator'; export class ServerInfoController { constructor(private service: ServerInfoService) {} + @Authenticated() @Get() getServerInfo(): Promise { return this.service.getInfo(); diff --git a/server/apps/immich/src/controllers/user.controller.ts b/server/apps/immich/src/controllers/user.controller.ts index e18fc8dbb4..dc749ecaef 100644 --- a/server/apps/immich/src/controllers/user.controller.ts +++ b/server/apps/immich/src/controllers/user.controller.ts @@ -44,6 +44,7 @@ export class UserController { return this.service.getAllUsers(authUser, isAll); } + @Authenticated() @Get('/info/:userId') getUserById(@Param('userId') userId: string): Promise { return this.service.getUserById(userId); @@ -87,8 +88,8 @@ export class UserController { return this.service.updateUser(authUser, updateUserDto); } - @UseInterceptors(FileInterceptor('file', profileImageUploadOption)) @Authenticated() + @UseInterceptors(FileInterceptor('file', profileImageUploadOption)) @ApiConsumes('multipart/form-data') @ApiBody({ description: 'A new avatar for the user', @@ -102,6 +103,7 @@ export class UserController { return this.service.createProfileImage(authUser, fileInfo); } + @Authenticated() @Get('/profile-image/:userId') @Header('Cache-Control', 'max-age=600') async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise { diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 65ca13d05b..fe357c2756 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -943,6 +943,14 @@ }, "tags": [ "Server Info" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + } ] } }, @@ -1482,6 +1490,14 @@ }, "tags": [ "User" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + } ] } }, @@ -1694,6 +1710,14 @@ }, "tags": [ "User" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + } ] } }, diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 1d633b0a8f..19546ee548 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -7043,6 +7043,12 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + // authentication cookie required + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -8586,6 +8592,12 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + // authentication cookie required + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -8619,6 +8631,12 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + // authentication cookie required + setSearchParams(localVarUrlObj, localVarQueryParameter);