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:
parent
4e526dfaae
commit
089dbdbd7e
@ -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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
44
mobile/lib/modules/home/ui/user_circle_avatar.dart
Normal file
44
mobile/lib/modules/home/ui/user_circle_avatar.dart
Normal 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
BIN
mobile/openapi/doc/ServerInfoApi.md
generated
BIN
mobile/openapi/doc/ServerInfoApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/UserApi.md
generated
BIN
mobile/openapi/doc/UserApi.md
generated
Binary file not shown.
@ -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();
|
||||||
|
@ -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> {
|
||||||
|
@ -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": []
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
18
web/src/api/open-api/api.ts
generated
18
web/src/api/open-api/api.ts
generated
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user