diff --git a/mobile/android/fastlane/metadata/android/en-US/changelogs/21.txt b/mobile/android/fastlane/metadata/android/en-US/changelogs/21.txt
new file mode 100644
index 0000000000..c09aa043d4
--- /dev/null
+++ b/mobile/android/fastlane/metadata/android/en-US/changelogs/21.txt
@@ -0,0 +1,3 @@
+* Fixed app does not resume back up when reopening a closed app
+* Fixed wrong asset count on the upload page
+* Added mechanism to change the password of new user on the first login (except Admin)
\ No newline at end of file
diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile
index 8fc1ed9f0c..dc18950208 100644
--- a/mobile/ios/fastlane/Fastfile
+++ b/mobile/ios/fastlane/Fastfile
@@ -19,7 +19,7 @@ platform :ios do
   desc "iOS Beta"
   lane :beta do
     increment_version_number(
-      version_number: "1.13.0"
+      version_number: "1.14.0"
     )
     increment_build_number(
       build_number: latest_testflight_build_number + 1,
diff --git a/mobile/lib/modules/home/ui/profile_drawer.dart b/mobile/lib/modules/home/ui/profile_drawer.dart
index 4bfdc8c60f..9c75e52d8a 100644
--- a/mobile/lib/modules/home/ui/profile_drawer.dart
+++ b/mobile/lib/modules/home/ui/profile_drawer.dart
@@ -191,7 +191,7 @@ class ProfileDrawer extends HookConsumerWidget {
                 ),
                 onTap: () async {
                   bool res =
-                      await ref.read(authenticationProvider.notifier).logout();
+                      await ref.watch(authenticationProvider.notifier).logout();
 
                   if (res) {
                     ref.watch(backupProvider.notifier).cancelBackup();
diff --git a/mobile/lib/modules/login/models/authentication_state.model.dart b/mobile/lib/modules/login/models/authentication_state.model.dart
index aae86f6aba..6603021bb8 100644
--- a/mobile/lib/modules/login/models/authentication_state.model.dart
+++ b/mobile/lib/modules/login/models/authentication_state.model.dart
@@ -11,7 +11,7 @@ class AuthenticationState {
   final String firstName;
   final String lastName;
   final bool isAdmin;
-  final bool isFirstLogin;
+  final bool shouldChangePassword;
   final String profileImagePath;
   final DeviceInfoRemote deviceInfo;
 
@@ -24,7 +24,7 @@ class AuthenticationState {
     required this.firstName,
     required this.lastName,
     required this.isAdmin,
-    required this.isFirstLogin,
+    required this.shouldChangePassword,
     required this.profileImagePath,
     required this.deviceInfo,
   });
@@ -38,7 +38,7 @@ class AuthenticationState {
     String? firstName,
     String? lastName,
     bool? isAdmin,
-    bool? isFirstLoggedIn,
+    bool? shouldChangePassword,
     String? profileImagePath,
     DeviceInfoRemote? deviceInfo,
   }) {
@@ -51,17 +51,12 @@ class AuthenticationState {
       firstName: firstName ?? this.firstName,
       lastName: lastName ?? this.lastName,
       isAdmin: isAdmin ?? this.isAdmin,
-      isFirstLogin: isFirstLoggedIn ?? isFirstLogin,
+      shouldChangePassword: shouldChangePassword ?? this.shouldChangePassword,
       profileImagePath: profileImagePath ?? this.profileImagePath,
       deviceInfo: deviceInfo ?? this.deviceInfo,
     );
   }
 
-  @override
-  String toString() {
-    return 'AuthenticationState(deviceId: $deviceId, deviceType: $deviceType, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, firstName: $firstName, lastName: $lastName, isAdmin: $isAdmin, isFirstLoggedIn: $isFirstLogin, profileImagePath: $profileImagePath, deviceInfo: $deviceInfo)';
-  }
-
   Map<String, dynamic> toMap() {
     final result = <String, dynamic>{};
 
@@ -73,7 +68,7 @@ class AuthenticationState {
     result.addAll({'firstName': firstName});
     result.addAll({'lastName': lastName});
     result.addAll({'isAdmin': isAdmin});
-    result.addAll({'isFirstLogin': isFirstLogin});
+    result.addAll({'shouldChangePassword': shouldChangePassword});
     result.addAll({'profileImagePath': profileImagePath});
     result.addAll({'deviceInfo': deviceInfo.toMap()});
 
@@ -90,7 +85,7 @@ class AuthenticationState {
       firstName: map['firstName'] ?? '',
       lastName: map['lastName'] ?? '',
       isAdmin: map['isAdmin'] ?? false,
-      isFirstLogin: map['isFirstLogin'] ?? false,
+      shouldChangePassword: map['shouldChangePassword'] ?? false,
       profileImagePath: map['profileImagePath'] ?? '',
       deviceInfo: DeviceInfoRemote.fromMap(map['deviceInfo']),
     );
@@ -101,6 +96,11 @@ class AuthenticationState {
   factory AuthenticationState.fromJson(String source) =>
       AuthenticationState.fromMap(json.decode(source));
 
+  @override
+  String toString() {
+    return 'AuthenticationState(deviceId: $deviceId, deviceType: $deviceType, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, firstName: $firstName, lastName: $lastName, isAdmin: $isAdmin, shouldChangePassword: $shouldChangePassword, profileImagePath: $profileImagePath, deviceInfo: $deviceInfo)';
+  }
+
   @override
   bool operator ==(Object other) {
     if (identical(this, other)) return true;
@@ -114,7 +114,7 @@ class AuthenticationState {
         other.firstName == firstName &&
         other.lastName == lastName &&
         other.isAdmin == isAdmin &&
-        other.isFirstLogin == isFirstLogin &&
+        other.shouldChangePassword == shouldChangePassword &&
         other.profileImagePath == profileImagePath &&
         other.deviceInfo == deviceInfo;
   }
@@ -129,7 +129,7 @@ class AuthenticationState {
         firstName.hashCode ^
         lastName.hashCode ^
         isAdmin.hashCode ^
-        isFirstLogin.hashCode ^
+        shouldChangePassword.hashCode ^
         profileImagePath.hashCode ^
         deviceInfo.hashCode;
   }
diff --git a/mobile/lib/modules/login/models/login_response.model.dart b/mobile/lib/modules/login/models/login_response.model.dart
index 2ba840e4dd..6e825eff67 100644
--- a/mobile/lib/modules/login/models/login_response.model.dart
+++ b/mobile/lib/modules/login/models/login_response.model.dart
@@ -8,7 +8,7 @@ class LogInReponse {
   final String lastName;
   final String profileImagePath;
   final bool isAdmin;
-  final bool isFirstLogin;
+  final bool shouldChangePassword;
 
   LogInReponse({
     required this.accessToken,
@@ -18,7 +18,7 @@ class LogInReponse {
     required this.lastName,
     required this.profileImagePath,
     required this.isAdmin,
-    required this.isFirstLogin,
+    required this.shouldChangePassword,
   });
 
   LogInReponse copyWith({
@@ -29,7 +29,7 @@ class LogInReponse {
     String? lastName,
     String? profileImagePath,
     bool? isAdmin,
-    bool? isFirstLogin,
+    bool? shouldChangePassword,
   }) {
     return LogInReponse(
       accessToken: accessToken ?? this.accessToken,
@@ -39,7 +39,7 @@ class LogInReponse {
       lastName: lastName ?? this.lastName,
       profileImagePath: profileImagePath ?? this.profileImagePath,
       isAdmin: isAdmin ?? this.isAdmin,
-      isFirstLogin: isFirstLogin ?? this.isFirstLogin,
+      shouldChangePassword: shouldChangePassword ?? this.shouldChangePassword,
     );
   }
 
@@ -53,7 +53,7 @@ class LogInReponse {
     result.addAll({'lastName': lastName});
     result.addAll({'profileImagePath': profileImagePath});
     result.addAll({'isAdmin': isAdmin});
-    result.addAll({'isFirstLogin': isFirstLogin});
+    result.addAll({'shouldChangePassword': shouldChangePassword});
 
     return result;
   }
@@ -67,7 +67,7 @@ class LogInReponse {
       lastName: map['lastName'] ?? '',
       profileImagePath: map['profileImagePath'] ?? '',
       isAdmin: map['isAdmin'] ?? false,
-      isFirstLogin: map['isFirstLogin'] ?? false,
+      shouldChangePassword: map['shouldChangePassword'] ?? false,
     );
   }
 
@@ -78,7 +78,7 @@ class LogInReponse {
 
   @override
   String toString() {
-    return 'LogInReponse(accessToken: $accessToken, userId: $userId, userEmail: $userEmail, firstName: $firstName, lastName: $lastName, profileImagePath: $profileImagePath, isAdmin: $isAdmin, isFirstLogin: $isFirstLogin)';
+    return 'LogInReponse(accessToken: $accessToken, userId: $userId, userEmail: $userEmail, firstName: $firstName, lastName: $lastName, profileImagePath: $profileImagePath, isAdmin: $isAdmin, shouldChangePassword: $shouldChangePassword)';
   }
 
   @override
@@ -93,7 +93,7 @@ class LogInReponse {
         other.lastName == lastName &&
         other.profileImagePath == profileImagePath &&
         other.isAdmin == isAdmin &&
-        other.isFirstLogin == isFirstLogin;
+        other.shouldChangePassword == shouldChangePassword;
   }
 
   @override
@@ -105,6 +105,6 @@ class LogInReponse {
         lastName.hashCode ^
         profileImagePath.hashCode ^
         isAdmin.hashCode ^
-        isFirstLogin.hashCode;
+        shouldChangePassword.hashCode;
   }
 }
diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart
index 014912bd72..0634e5731a 100644
--- a/mobile/lib/modules/login/providers/authentication.provider.dart
+++ b/mobile/lib/modules/login/providers/authentication.provider.dart
@@ -24,7 +24,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
             lastName: '',
             profileImagePath: '',
             isAdmin: false,
-            isFirstLogin: false,
+            shouldChangePassword: false,
             isAuthenticated: false,
             deviceInfo: DeviceInfoRemote(
               id: 0,
@@ -87,7 +87,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
         lastName: payload.lastName,
         profileImagePath: payload.profileImagePath,
         isAdmin: payload.isAdmin,
-        isFirstLoggedIn: payload.isFirstLogin,
+        shouldChangePassword: payload.shouldChangePassword,
       );
 
       if (isSavedLoginInfo) {
@@ -111,8 +111,12 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
     // Register device info
     try {
       Response res = await _networkService.postRequest(
-          url: 'device-info',
-          data: {'deviceId': state.deviceId, 'deviceType': state.deviceType});
+        url: 'device-info',
+        data: {
+          'deviceId': state.deviceId,
+          'deviceType': state.deviceType,
+        },
+      );
 
       DeviceInfoRemote deviceInfo = DeviceInfoRemote.fromJson(res.toString());
       state = state.copyWith(deviceInfo: deviceInfo);
@@ -133,7 +137,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
       firstName: '',
       lastName: '',
       profileImagePath: '',
-      isFirstLogin: false,
+      shouldChangePassword: false,
       isAuthenticated: false,
       isAdmin: false,
       deviceInfo: DeviceInfoRemote(
@@ -163,6 +167,24 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
   updateUserProfileImagePath(String path) {
     state = state.copyWith(profileImagePath: path);
   }
+
+  Future<bool> changePassword(String newPassword) async {
+    Response res = await _networkService.putRequest(
+      url: 'user',
+      data: {
+        'id': state.userId,
+        'password': newPassword,
+        'shouldChangePassword': false,
+      },
+    );
+
+    if (res.statusCode == 200) {
+      state = state.copyWith(shouldChangePassword: false);
+      return true;
+    } else {
+      return false;
+    }
+  }
 }
 
 final authenticationProvider =
diff --git a/mobile/lib/modules/login/ui/change_password_form.dart b/mobile/lib/modules/login/ui/change_password_form.dart
new file mode 100644
index 0000000000..b71b821e63
--- /dev/null
+++ b/mobile/lib/modules/login/ui/change_password_form.dart
@@ -0,0 +1,160 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
+import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/shared/providers/asset.provider.dart';
+import 'package:immich_mobile/shared/providers/websocket.provider.dart';
+
+class ChangePasswordForm extends HookConsumerWidget {
+  const ChangePasswordForm({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final passwordController =
+        useTextEditingController.fromValue(TextEditingValue.empty);
+    final confirmPasswordController =
+        useTextEditingController.fromValue(TextEditingValue.empty);
+    final authState = ref.watch(authenticationProvider);
+
+    return Center(
+      child: ConstrainedBox(
+        constraints: const BoxConstraints(maxWidth: 300),
+        child: SingleChildScrollView(
+          child: Wrap(
+            spacing: 16,
+            runSpacing: 16,
+            alignment: WrapAlignment.start,
+            children: [
+              Text(
+                'Change Password',
+                style: TextStyle(
+                  fontSize: 24,
+                  fontWeight: FontWeight.bold,
+                  color: Theme.of(context).primaryColor,
+                ),
+              ),
+              Padding(
+                padding: const EdgeInsets.symmetric(vertical: 24.0),
+                child: Text(
+                  'Hi ${authState.firstName} ${authState.lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.',
+                  style: TextStyle(
+                    fontSize: 14,
+                    color: Colors.grey[700],
+                    fontWeight: FontWeight.w600,
+                  ),
+                ),
+              ),
+              PasswordInput(controller: passwordController),
+              ConfirmPasswordInput(
+                originalController: passwordController,
+                confirmController: confirmPasswordController,
+              ),
+              Align(
+                alignment: Alignment.center,
+                child: ChangePasswordButton(
+                    passwordController: passwordController),
+              )
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class PasswordInput extends StatelessWidget {
+  final TextEditingController controller;
+
+  const PasswordInput({Key? key, required this.controller}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return TextFormField(
+      obscureText: true,
+      controller: controller,
+      decoration: const InputDecoration(
+        labelText: 'New Password',
+        border: OutlineInputBorder(),
+        hintText: 'New Password',
+      ),
+    );
+  }
+}
+
+class ConfirmPasswordInput extends StatelessWidget {
+  final TextEditingController originalController;
+  final TextEditingController confirmController;
+
+  const ConfirmPasswordInput({
+    Key? key,
+    required this.originalController,
+    required this.confirmController,
+  }) : super(key: key);
+
+  String? _validateInput(String? email) {
+    if (confirmController.value != originalController.value) {
+      return 'Passwords do not match';
+    }
+    return null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return TextFormField(
+      obscureText: true,
+      controller: confirmController,
+      decoration: const InputDecoration(
+        labelText: 'Confirm Password',
+        hintText: 'Re-enter New Password',
+        border: OutlineInputBorder(),
+      ),
+      validator: _validateInput,
+      autovalidateMode: AutovalidateMode.always,
+    );
+  }
+}
+
+class ChangePasswordButton extends ConsumerWidget {
+  final TextEditingController passwordController;
+
+  const ChangePasswordButton({
+    Key? key,
+    required this.passwordController,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return ElevatedButton(
+        style: ElevatedButton.styleFrom(
+          visualDensity: VisualDensity.standard,
+          primary: Theme.of(context).primaryColor,
+          onPrimary: Colors.grey[50],
+          elevation: 2,
+          padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
+        ),
+        onPressed: () async {
+          var isSuccess = await ref
+              .watch(authenticationProvider.notifier)
+              .changePassword(passwordController.value.text);
+
+          if (isSuccess) {
+            bool res =
+                await ref.watch(authenticationProvider.notifier).logout();
+
+            if (res) {
+              ref.watch(backupProvider.notifier).cancelBackup();
+              ref.watch(assetProvider.notifier).clearAllAsset();
+              ref.watch(websocketProvider.notifier).disconnect();
+              AutoRouter.of(context).replace(const LoginRoute());
+            }
+          }
+        },
+        child: const Text(
+          "Change Password",
+          style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+        ));
+  }
+}
diff --git a/mobile/lib/modules/login/ui/login_form.dart b/mobile/lib/modules/login/ui/login_form.dart
index 34260d9e3c..70728e2097 100644
--- a/mobile/lib/modules/login/ui/login_form.dart
+++ b/mobile/lib/modules/login/ui/login_form.dart
@@ -5,6 +5,7 @@ import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
+import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
@@ -20,7 +21,7 @@ class LoginForm extends HookConsumerWidget {
     final passwordController =
         useTextEditingController.fromValue(TextEditingValue.empty);
     final serverEndpointController =
-        useTextEditingController(text: 'http://your-server-ip:2283');
+        useTextEditingController(text: 'http://your-server-ip:2283/api');
     final isSaveLoginInfo = useState<bool>(false);
 
     useEffect(() {
@@ -106,9 +107,18 @@ class ServerEndpointInput extends StatelessWidget {
       : super(key: key);
 
   String? _validateInput(String? url) {
-    if (url == null) return null;
-    if (!url.startsWith(RegExp(r'https?://')))
+    if (url == null) {
+      return null;
+    }
+
+    if (url.isEmpty) {
+      return 'Server endpoint is required';
+    }
+
+    if (!url.startsWith(RegExp(r'https?://'))) {
       return 'Please specify http:// or https://';
+    }
+
     return null;
   }
 
@@ -117,9 +127,10 @@ class ServerEndpointInput extends StatelessWidget {
     return TextFormField(
       controller: controller,
       decoration: const InputDecoration(
-          labelText: 'Server Endpoint URL',
-          border: OutlineInputBorder(),
-          hintText: 'http://your-server-ip:port'),
+        labelText: 'Server Endpoint URL',
+        border: OutlineInputBorder(),
+        hintText: 'http://your-server-ip:port',
+      ),
       validator: _validateInput,
       autovalidateMode: AutovalidateMode.always,
     );
@@ -144,9 +155,10 @@ class EmailInput extends StatelessWidget {
     return TextFormField(
       controller: controller,
       decoration: const InputDecoration(
-          labelText: 'Email',
-          border: OutlineInputBorder(),
-          hintText: 'youremail@email.com'),
+        labelText: 'Email',
+        border: OutlineInputBorder(),
+        hintText: 'youremail@email.com',
+      ),
       validator: _validateInput,
       autovalidateMode: AutovalidateMode.always,
     );
@@ -200,14 +212,19 @@ class LoginButton extends ConsumerWidget {
           ref.watch(assetProvider.notifier).clearAllAsset();
 
           var isAuthenticated = await ref
-              .read(authenticationProvider.notifier)
+              .watch(authenticationProvider.notifier)
               .login(emailController.text, passwordController.text,
                   serverEndpointController.text, isSavedLoginInfo);
 
           if (isAuthenticated) {
             // Resume backup (if enable) then navigate
-            ref.watch(backupProvider.notifier).resumeBackup();
-            AutoRouter.of(context).pushNamed("/tab-controller-page");
+
+            if (ref.watch(authenticationProvider).shouldChangePassword) {
+              AutoRouter.of(context).push(const ChangePasswordRoute());
+            } else {
+              ref.watch(backupProvider.notifier).resumeBackup();
+              AutoRouter.of(context).pushNamed("/tab-controller-page");
+            }
           } else {
             ImmichToast.show(
               context: context,
diff --git a/mobile/lib/modules/login/views/change_password_page.dart b/mobile/lib/modules/login/views/change_password_page.dart
new file mode 100644
index 0000000000..b5c8a6ca0d
--- /dev/null
+++ b/mobile/lib/modules/login/views/change_password_page.dart
@@ -0,0 +1,14 @@
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/login/ui/change_password_form.dart';
+
+class ChangePasswordPage extends HookConsumerWidget {
+  const ChangePasswordPage({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return const Scaffold(
+      body: ChangePasswordForm(),
+    );
+  }
+}
diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart
index b520f49a1d..8100248f03 100644
--- a/mobile/lib/routing/router.dart
+++ b/mobile/lib/routing/router.dart
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
 import 'package:flutter/material.dart';
 import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
 import 'package:immich_mobile/modules/backup/views/backup_album_selection_page.dart';
+import 'package:immich_mobile/modules/login/views/change_password_page.dart';
 import 'package:immich_mobile/modules/login/views/login_page.dart';
 import 'package:immich_mobile/modules/home/views/home_page.dart';
 import 'package:immich_mobile/modules/search/views/search_page.dart';
@@ -30,6 +31,7 @@ part 'router.gr.dart';
   routes: <AutoRoute>[
     AutoRoute(page: SplashScreenPage, initial: true),
     AutoRoute(page: LoginPage),
+    AutoRoute(page: ChangePasswordPage),
     CustomRoute(
       page: TabControllerPage,
       guards: [AuthGuard],
diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart
index 4cf5338186..598e81b77a 100644
--- a/mobile/lib/routing/router.gr.dart
+++ b/mobile/lib/routing/router.gr.dart
@@ -29,6 +29,10 @@ class _$AppRouter extends RootStackRouter {
       return MaterialPageX<dynamic>(
           routeData: routeData, child: const LoginPage());
     },
+    ChangePasswordRoute.name: (routeData) {
+      return MaterialPageX<dynamic>(
+          routeData: routeData, child: const ChangePasswordPage());
+    },
     TabControllerRoute.name: (routeData) {
       return CustomPage<dynamic>(
           routeData: routeData,
@@ -131,6 +135,7 @@ class _$AppRouter extends RootStackRouter {
   List<RouteConfig> get routes => [
         RouteConfig(SplashScreenRoute.name, path: '/'),
         RouteConfig(LoginRoute.name, path: '/login-page'),
+        RouteConfig(ChangePasswordRoute.name, path: '/change-password-page'),
         RouteConfig(TabControllerRoute.name,
             path: '/tab-controller-page',
             guards: [
@@ -192,6 +197,15 @@ class LoginRoute extends PageRouteInfo<void> {
   static const String name = 'LoginRoute';
 }
 
+/// generated route for
+/// [ChangePasswordPage]
+class ChangePasswordRoute extends PageRouteInfo<void> {
+  const ChangePasswordRoute()
+      : super(ChangePasswordRoute.name, path: '/change-password-page');
+
+  static const String name = 'ChangePasswordRoute';
+}
+
 /// generated route for
 /// [TabControllerPage]
 class TabControllerRoute extends PageRouteInfo<void> {
diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml
index 5e7d855a48..63ee71c096 100644
--- a/mobile/pubspec.yaml
+++ b/mobile/pubspec.yaml
@@ -2,7 +2,7 @@ name: immich_mobile
 description: Immich - selfhosted backup media file on mobile phone
 
 publish_to: "none"
-version: 1.13.0+20
+version: 1.14.0+21
 
 environment:
   sdk: ">=2.17.0 <3.0.0"
diff --git a/server/apps/immich/src/api-v1/auth/auth.service.ts b/server/apps/immich/src/api-v1/auth/auth.service.ts
index 675acd2af2..bc9d4f0080 100644
--- a/server/apps/immich/src/api-v1/auth/auth.service.ts
+++ b/server/apps/immich/src/api-v1/auth/auth.service.ts
@@ -30,7 +30,7 @@ export class AuthService {
           'lastName',
           'isAdmin',
           'profileImagePath',
-          'isFirstLoggedIn',
+          'shouldChangePassword',
         ],
       },
     );
@@ -66,7 +66,7 @@ export class AuthService {
       lastName: validatedUser.lastName,
       isAdmin: validatedUser.isAdmin,
       profileImagePath: validatedUser.profileImagePath,
-      isFirstLogin: validatedUser.isFirstLoggedIn,
+      shouldChangePassword: validatedUser.shouldChangePassword,
     };
   }
 
diff --git a/server/apps/immich/src/api-v1/user/dto/create-user.dto.ts b/server/apps/immich/src/api-v1/user/dto/create-user.dto.ts
index e3ef6eb5ff..b8df1d249c 100644
--- a/server/apps/immich/src/api-v1/user/dto/create-user.dto.ts
+++ b/server/apps/immich/src/api-v1/user/dto/create-user.dto.ts
@@ -20,7 +20,7 @@ export class CreateUserDto {
   isAdmin?: boolean;
 
   @IsOptional()
-  isFirstLoggedIn?: boolean;
+  shouldChangePassword?: boolean;
 
   @IsOptional()
   id?: string;
diff --git a/server/apps/immich/src/api-v1/user/user.controller.ts b/server/apps/immich/src/api-v1/user/user.controller.ts
index a81de1301d..3a1f422790 100644
--- a/server/apps/immich/src/api-v1/user/user.controller.ts
+++ b/server/apps/immich/src/api-v1/user/user.controller.ts
@@ -32,6 +32,12 @@ export class UserController {
     return await this.userService.getAllUsers(authUser, isAll);
   }
 
+  @UseGuards(JwtAuthGuard)
+  @Get('me')
+  async getUserInfo(@GetAuthUser() authUser: AuthUserDto) {
+    return await this.userService.getUserInfo(authUser);
+  }
+
   @UseGuards(JwtAuthGuard)
   @UseGuards(AdminRolesGuard)
   @Post()
diff --git a/server/apps/immich/src/api-v1/user/user.service.ts b/server/apps/immich/src/api-v1/user/user.service.ts
index d0ad2d0843..1ab2df7f31 100644
--- a/server/apps/immich/src/api-v1/user/user.service.ts
+++ b/server/apps/immich/src/api-v1/user/user.service.ts
@@ -37,6 +37,10 @@ export class UserService {
     });
   }
 
+  async getUserInfo(authUser: AuthUserDto) {
+    return this.userRepository.findOne({ id: authUser.id });
+  }
+
   async getUserCount(isAdmin: boolean) {
     let users;
 
@@ -89,7 +93,8 @@ export class UserService {
     user.lastName = updateUserDto.lastName || user.lastName;
     user.firstName = updateUserDto.firstName || user.firstName;
     user.profileImagePath = updateUserDto.profileImagePath || user.profileImagePath;
-    user.isFirstLoggedIn = updateUserDto.isFirstLoggedIn || user.isFirstLoggedIn;
+    user.shouldChangePassword =
+      updateUserDto.shouldChangePassword != undefined ? updateUserDto.shouldChangePassword : user.shouldChangePassword;
 
     // If payload includes password - Create new password for user
     if (updateUserDto.password) {
diff --git a/server/apps/immich/src/constants/server_version.constant.ts b/server/apps/immich/src/constants/server_version.constant.ts
index 7de061a8f9..e862cbbcad 100644
--- a/server/apps/immich/src/constants/server_version.constant.ts
+++ b/server/apps/immich/src/constants/server_version.constant.ts
@@ -3,7 +3,7 @@
 
 export const serverVersion = {
   major: 1,
-  minor: 13,
+  minor: 14,
   patch: 0,
-  build: 20,
+  build: 21,
 };
diff --git a/server/apps/immich/test/user.e2e-spec.ts b/server/apps/immich/test/user.e2e-spec.ts
index ab87fedcfb..42f6f7f829 100644
--- a/server/apps/immich/test/user.e2e-spec.ts
+++ b/server/apps/immich/test/user.e2e-spec.ts
@@ -98,7 +98,7 @@ describe('User', () => {
               id: expect.anything(),
               createdAt: expect.anything(),
               isAdmin: false,
-              isFirstLoggedIn: true,
+              shouldChangePassword: true,
               profileImagePath: '',
             },
             {
@@ -108,7 +108,7 @@ describe('User', () => {
               id: expect.anything(),
               createdAt: expect.anything(),
               isAdmin: false,
-              isFirstLoggedIn: true,
+              shouldChangePassword: true,
               profileImagePath: '',
             },
           ]),
diff --git a/server/libs/database/src/entities/user.entity.ts b/server/libs/database/src/entities/user.entity.ts
index c9ae1e2bb0..40f5273471 100644
--- a/server/libs/database/src/entities/user.entity.ts
+++ b/server/libs/database/src/entities/user.entity.ts
@@ -27,7 +27,7 @@ export class UserEntity {
   profileImagePath!: string;
 
   @Column()
-  isFirstLoggedIn!: boolean;
+  shouldChangePassword!: boolean;
 
   @CreateDateColumn()
   createdAt!: string;
diff --git a/server/libs/database/src/migrations/1656338626260-RenameIsFirstLoggedInColumn.ts b/server/libs/database/src/migrations/1656338626260-RenameIsFirstLoggedInColumn.ts
new file mode 100644
index 0000000000..c4e4d7cd63
--- /dev/null
+++ b/server/libs/database/src/migrations/1656338626260-RenameIsFirstLoggedInColumn.ts
@@ -0,0 +1,17 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class RenameIsFirstLoggedInColumn1656338626260 implements MigrationInterface {
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`
+    ALTER TABLE users
+         RENAME COLUMN "isFirstLoggedIn" to "shouldChangePassword";
+    `);
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`
+      ALTER TABLE users
+          RENAME COLUMN "shouldChangePassword" to "isFirstLoggedIn";
+    `);
+  }
+}
diff --git a/web/src/lib/auth-api.ts b/web/src/lib/auth-api.ts
index 318755e836..5b483726f3 100644
--- a/web/src/lib/auth-api.ts
+++ b/web/src/lib/auth-api.ts
@@ -1,74 +1,64 @@
 type AdminRegistrationResult = Promise<{
-  error?: string
-  success?: string
-  user?: {
-    email: string
-  }
-}>
-
-
+	error?: string;
+	success?: string;
+	user?: {
+		email: string;
+	};
+}>;
 
 type LoginResult = Promise<{
-  error?: string
-  success?: string
-  needUpdate?: boolean
-  needSelectAdmin?: boolean
-  user?: {
-    accessToken: string
-    firstName: string
-    lastName: string
-    isAdmin: boolean
-    id: string
-    email: string
-  }
-}>
+	error?: string;
+	success?: string;
+	user?: {
+		accessToken: string;
+		firstName: string;
+		lastName: string;
+		isAdmin: boolean;
+		id: string;
+		email: string;
+		shouldChangePassword: boolean;
+	};
+}>;
 
 type UpdateResult = Promise<{
-  error?: string
-  success?: string,
-  user?: {
-    accessToken: string
-    firstName: string
-    lastName: string
-    isAdmin: boolean
-    id: string
-    email: string
-  }
-}>
-
+	error?: string;
+	success?: string;
+	user?: {
+		accessToken: string;
+		firstName: string;
+		lastName: string;
+		isAdmin: boolean;
+		id: string;
+		email: string;
+	};
+}>;
 
 export async function sendRegistrationForm(form: HTMLFormElement): AdminRegistrationResult {
+	const response = await fetch(form.action, {
+		method: form.method,
+		body: new FormData(form),
+		headers: { accept: 'application/json' },
+	});
 
-  const response = await fetch(form.action, {
-    method: form.method,
-    body: new FormData(form),
-    headers: { accept: 'application/json' },
-  })
-
-  return await response.json()
-
+	return await response.json();
 }
 
-
 export async function sendLoginForm(form: HTMLFormElement): LoginResult {
+	const response = await fetch(form.action, {
+		method: form.method,
+		body: new FormData(form),
+		headers: { accept: 'application/json' },
+	});
 
-  const response = await fetch(form.action, {
-    method: form.method,
-    body: new FormData(form),
-    headers: { accept: 'application/json' },
-  })
-
-  return await response.json()
+	return await response.json();
 }
 
 export async function sendUpdateForm(form: HTMLFormElement): UpdateResult {
+	const response = await fetch(form.action, {
+		method: form.method,
+		body: new FormData(form),
+		headers: { accept: 'application/json' },
+	});
 
-  const response = await fetch(form.action, {
-    method: form.method,
-    body: new FormData(form),
-    headers: { accept: 'application/json' },
-  })
-
-  return await response.json()
+	return await response.json();
 }
-
diff --git a/web/src/lib/components/forms/admin-registration-form.svelte b/web/src/lib/components/forms/admin-registration-form.svelte
index 81e3f3848d..a74234128e 100644
--- a/web/src/lib/components/forms/admin-registration-form.svelte
+++ b/web/src/lib/components/forms/admin-registration-form.svelte
@@ -5,20 +5,36 @@
 	let error: string;
 	let success: string;
 
-	async function registerAdmin(event: SubmitEvent) {
-		error = '';
+	let password: string = '';
+	let confirmPassowrd: string = '';
 
-		const formElement = event.target as HTMLFormElement;
+	let canRegister = false;
 
-		const response = await sendRegistrationForm(formElement);
-
-		if (response.error) {
-			error = JSON.stringify(response.error);
+	$: {
+		if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
+			error = 'Password does not match';
+			canRegister = false;
+		} else {
+			error = '';
+			canRegister = true;
 		}
+	}
+	async function registerAdmin(event: SubmitEvent) {
+		if (canRegister) {
+			error = '';
 
-		if (response.success) {
-			success = response.success;
-			goto('/auth/login');
+			const formElement = event.target as HTMLFormElement;
+
+			const response = await sendRegistrationForm(formElement);
+
+			if (response.error) {
+				error = JSON.stringify(response.error);
+			}
+
+			if (response.success) {
+				success = response.success;
+				goto('/auth/login');
+			}
 		}
 	}
 </script>
@@ -41,21 +57,33 @@
 
 		<div class="m-4 flex flex-col gap-2">
 			<label class="immich-form-label" for="password">Admin Password</label>
-			<input class="immich-form-input" id="password" name="password" type="password" required />
+			<input class="immich-form-input" id="password" name="password" type="password" required bind:value={password} />
 		</div>
 
 		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="password">First Name</label>
+			<label class="immich-form-label" for="confirmPassword">Confirm Admin Password</label>
+			<input
+				class="immich-form-input"
+				id="confirmPassword"
+				name="password"
+				type="password"
+				required
+				bind:value={confirmPassowrd}
+			/>
+		</div>
+
+		<div class="m-4 flex flex-col gap-2">
+			<label class="immich-form-label" for="firstName">First Name</label>
 			<input class="immich-form-input" id="firstName" name="firstName" type="text" required />
 		</div>
 
 		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="password">Last Name</label>
+			<label class="immich-form-label" for="lastName">Last Name</label>
 			<input class="immich-form-input" id="lastName" name="lastName" type="text" required />
 		</div>
 
 		{#if error}
-			<p class="text-red-400">{error}</p>
+			<p class="text-red-400 ml-4">{error}</p>
 		{/if}
 
 		{#if success}
diff --git a/web/src/lib/components/forms/change-password-form.svelte b/web/src/lib/components/forms/change-password-form.svelte
new file mode 100644
index 0000000000..b956db6f2a
--- /dev/null
+++ b/web/src/lib/components/forms/change-password-form.svelte
@@ -0,0 +1,97 @@
+<script lang="ts">
+	import { session } from '$app/stores';
+
+	import { sendRegistrationForm, sendUpdateForm } from '$lib/auth-api';
+	import { createEventDispatcher } from 'svelte';
+	import type { ImmichUser } from '../../models/immich-user';
+
+	export let user: ImmichUser;
+	let error: string;
+	let success: string;
+
+	let password: string = '';
+	let confirmPassowrd: string = '';
+
+	let changeChagePassword = false;
+
+	$: {
+		if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
+			error = 'Password does not match';
+			changeChagePassword = false;
+		} else {
+			error = '';
+			changeChagePassword = true;
+		}
+	}
+	const dispatch = createEventDispatcher();
+
+	async function changePassword(event: SubmitEvent) {
+		if (changeChagePassword) {
+			error = '';
+
+			const formElement = event.target as HTMLFormElement;
+
+			const response = await sendUpdateForm(formElement);
+
+			if (response.error) {
+				error = JSON.stringify(response.error);
+			}
+
+			if (response.success) {
+				success = 'Password has been changed';
+
+				dispatch('success');
+			}
+		}
+	}
+</script>
+
+<div class="border bg-white p-4 shadow-sm w-[500px] rounded-md py-8">
+	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
+		<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
+		<h1 class="text-2xl text-immich-primary font-medium">Chage Password</h1>
+
+		<p class="text-sm border rounded-md p-4 font-mono text-gray-600">
+			Hi {user.firstName}
+			{user.lastName} ({user.email}),
+			<br />
+			<br />
+			This is either the first time you are signing into the system or a request has been made to change your password. Please
+			enter the new password below.
+		</p>
+	</div>
+
+	<form on:submit|preventDefault={changePassword} method="post" autocomplete="off">
+		<div class="m-4 flex flex-col gap-2">
+			<label class="immich-form-label" for="password">New Password</label>
+			<input class="immich-form-input" id="password" name="password" type="password" required bind:value={password} />
+		</div>
+
+		<div class="m-4 flex flex-col gap-2">
+			<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
+			<input
+				class="immich-form-input"
+				id="confirmPassword"
+				name="password"
+				type="password"
+				required
+				bind:value={confirmPassowrd}
+			/>
+		</div>
+
+		{#if error}
+			<p class="text-red-400 ml-4 text-sm">{error}</p>
+		{/if}
+
+		{#if success}
+			<p class="text-immich-primary ml-4 text-sm">{success}</p>
+		{/if}
+		<div class="flex w-full">
+			<button
+				type="submit"
+				class="m-4 p-2 bg-immich-primary hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full"
+				>Change Password</button
+			>
+		</div>
+	</form>
+</div>
diff --git a/web/src/lib/components/forms/create-user-form.svelte b/web/src/lib/components/forms/create-user-form.svelte
index 0d58f18100..bdbe4642d7 100644
--- a/web/src/lib/components/forms/create-user-form.svelte
+++ b/web/src/lib/components/forms/create-user-form.svelte
@@ -5,23 +5,39 @@
 	let error: string;
 	let success: string;
 
+	let password: string = '';
+	let confirmPassowrd: string = '';
+
+	let canCreateUser = false;
+
+	$: {
+		if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
+			error = 'Password does not match';
+			canCreateUser = false;
+		} else {
+			error = '';
+			canCreateUser = true;
+		}
+	}
 	const dispatch = createEventDispatcher();
 
 	async function registerUser(event: SubmitEvent) {
-		error = '';
+		if (canCreateUser) {
+			error = '';
 
-		const formElement = event.target as HTMLFormElement;
+			const formElement = event.target as HTMLFormElement;
 
-		const response = await sendRegistrationForm(formElement);
+			const response = await sendRegistrationForm(formElement);
 
-		if (response.error) {
-			error = JSON.stringify(response.error);
-		}
+			if (response.error) {
+				error = JSON.stringify(response.error);
+			}
 
-		if (response.success) {
-			success = 'New user created';
+			if (response.success) {
+				success = 'New user created';
 
-			dispatch('user-created');
+				dispatch('user-created');
+			}
 		}
 	}
 </script>
@@ -43,25 +59,37 @@
 
 		<div class="m-4 flex flex-col gap-2">
 			<label class="immich-form-label" for="password">Password</label>
-			<input class="immich-form-input" id="password" name="password" type="password" required />
+			<input class="immich-form-input" id="password" name="password" type="password" required bind:value={password} />
 		</div>
 
 		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="password">First Name</label>
+			<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
+			<input
+				class="immich-form-input"
+				id="confirmPassword"
+				name="password"
+				type="password"
+				required
+				bind:value={confirmPassowrd}
+			/>
+		</div>
+
+		<div class="m-4 flex flex-col gap-2">
+			<label class="immich-form-label" for="firstName">First Name</label>
 			<input class="immich-form-input" id="firstName" name="firstName" type="text" required />
 		</div>
 
 		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="password">Last Name</label>
+			<label class="immich-form-label" for="lastName">Last Name</label>
 			<input class="immich-form-input" id="lastName" name="lastName" type="text" required />
 		</div>
 
 		{#if error}
-			<p class="text-red-400">{error}</p>
+			<p class="text-red-400 ml-4 text-sm">{error}</p>
 		{/if}
 
 		{#if success}
-			<p class="text-immich-primary">{success}</p>
+			<p class="text-immich-primary ml-4 text-sm">{success}</p>
 		{/if}
 		<div class="flex w-full">
 			<button
diff --git a/web/src/lib/components/forms/login-form.svelte b/web/src/lib/components/forms/login-form.svelte
index 214bdff42a..96b7c13824 100644
--- a/web/src/lib/components/forms/login-form.svelte
+++ b/web/src/lib/components/forms/login-form.svelte
@@ -18,14 +18,6 @@
 			error = response.error;
 		}
 
-		if (response.needUpdate) {
-			return dispatch('need-update');
-		}
-
-		if (response.needSelectAdmin) {
-			return dispatch('need-select-admin');
-		}
-
 		if (response.success) {
 			$session.user = {
 				accessToken: response.user!.accessToken,
@@ -36,6 +28,10 @@
 				email: response.user!.email,
 			};
 
+			if (!response.user?.isAdmin && response.user?.shouldChangePassword) {
+				return dispatch('first-login');
+			}
+
 			return dispatch('success');
 		}
 	}
diff --git a/web/src/lib/components/forms/select-admin-form.svelte b/web/src/lib/components/forms/select-admin-form.svelte
deleted file mode 100644
index e09038aa83..0000000000
--- a/web/src/lib/components/forms/select-admin-form.svelte
+++ /dev/null
@@ -1,93 +0,0 @@
-<script lang="ts">
-	import { session } from '$app/stores';
-
-	import { createEventDispatcher, onMount } from 'svelte';
-	import { fade } from 'svelte/transition';
-	import type { ImmichUser } from '../../models/immich-user';
-	import Check from 'svelte-material-icons/Check.svelte';
-
-	let error: string = '';
-	let allUsers: Array<ImmichUser> = [];
-	let selectedUserId: string;
-	const dispatch = createEventDispatcher();
-
-	onMount(async () => {
-		const res = await fetch('/auth/login/api/get-users', { method: 'GET' });
-		const data = await res.json();
-		allUsers = data.allUsers;
-	});
-
-	const assignAdmin = async () => {
-		const res = await fetch('/auth/login/api/select-admin', {
-			method: 'POST',
-			body: JSON.stringify({
-				id: selectedUserId,
-				isAdmin: true,
-			}),
-		});
-
-		if (res.status === 200) {
-			const data = await res.json();
-
-			$session.user = {
-				accessToken: '',
-				firstName: data.userInfo.firstName,
-				lastName: data.userInfo.lastName,
-				isAdmin: data.userInfo.isAdmin,
-				id: data.userInfo.id,
-				email: data.userInfo.email,
-			};
-
-			dispatch('success');
-		} else {
-			error = JSON.stringify(await res.json());
-		}
-	};
-</script>
-
-<div class="border bg-white p-4 shadow-sm w-[500px] rounded-md py-8">
-	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
-		<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
-		<h1 class="text-2xl text-immich-primary font-medium">Select Admin</h1>
-		<p class="text-sm border rounded-md p-4 font-mono text-gray-600">
-			There are multiple users on the server, and none have been selected to be the admin. Please assign one as the
-			admin, who will be responsible for administrative tasks
-		</p>
-	</div>
-
-	<div class="text-xs m-4">USERS ON SERVER, CLICK TO SELECT ONE</div>
-	<div class="overflow-y-auto rounded-md max-h-[300px] block border mx-4 px-4 py-2">
-		{#each allUsers as user, i}
-			<div
-				class="p-4 flex justify-between place-items-center my-4 rounded-md hover:cursor-pointer shadow-sm bg-gray-50 hover:bg-gray-100"
-				on:click={() => (selectedUserId = user.id)}
-			>
-				<p class="test-sm text-slate-600">{i + 1} | {user.email}</p>
-
-				<!-- Icon -->
-				{#if selectedUserId == user.id}
-					<div
-						in:fade={{ duration: 100 }}
-						class="border rounded-full border-gray-300 bg-immich-primary w-8 h-8 flex place-items-center place-content-center"
-					>
-						<Check color="white" size="24" />
-					</div>
-				{:else}
-					<div in:fade={{ duration: 100 }} class="border rounded-full border-gray-300 w-8 h-8" />
-				{/if}
-			</div>
-		{/each}
-	</div>
-
-	{#if error}
-		<div class="text-xs m-4 text-red-400">Error: {error}</div>
-	{/if}
-
-	<div class="flex w-full">
-		<button
-			type="submit"
-			class="m-4 p-2 bg-immich-primary hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full font-semibold"
-			on:click={assignAdmin}>Assign as Admin</button
-		>
-	</div>
-</div>
diff --git a/web/src/lib/components/forms/update-form.svelte b/web/src/lib/components/forms/update-form.svelte
deleted file mode 100644
index 07bb5d3e27..0000000000
--- a/web/src/lib/components/forms/update-form.svelte
+++ /dev/null
@@ -1,68 +0,0 @@
-<script lang="ts">
-	import { goto } from '$app/navigation';
-	import { session } from '$app/stores';
-	import { sendUpdateForm } from '$lib/auth-api';
-	import { createEventDispatcher } from 'svelte';
-
-	let error: string;
-	const dispatch = createEventDispatcher();
-
-	async function updateInfo(event: SubmitEvent) {
-		error = '';
-
-		const formElement = event.target as HTMLFormElement;
-
-		const response = await sendUpdateForm(formElement);
-
-		if (response.error) {
-			error = response.error;
-		}
-
-		if (response.success) {
-			$session.user = {
-				accessToken: response.user!.accessToken,
-				firstName: response.user!.firstName,
-				lastName: response.user!.lastName,
-				isAdmin: response.user!.isAdmin,
-				id: response.user!.id,
-				email: response.user!.email,
-			};
-
-			dispatch('success');
-		}
-	}
-</script>
-
-<div class="border bg-white p-4 shadow-sm w-[500px] rounded-md py-8">
-	<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
-		<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
-		<h1 class="text-2xl text-immich-primary font-medium">Update User Info</h1>
-		<p class="text-sm border rounded-md p-4 font-mono text-gray-600">
-			Your account doesn't have information about your name, please update to continue the login process.
-		</p>
-	</div>
-
-	<form on:submit|preventDefault={updateInfo} method="post" action="/auth/login/update" autocomplete="off">
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="firstName">First name</label>
-			<input class="immich-form-input" id="firstName" name="firstName" type="text" required />
-		</div>
-
-		<div class="m-4 flex flex-col gap-2">
-			<label class="immich-form-label" for="lastName">Last name</label>
-			<input class="immich-form-input" id="lastName" name="lastName" type="text" required />
-		</div>
-
-		{#if error}
-			<p class="text-red-400 pl-4">{error}</p>
-		{/if}
-
-		<div class="flex w-full">
-			<button
-				type="submit"
-				class="m-4 p-2 bg-immich-primary hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full font-semibold"
-				>Update</button
-			>
-		</div>
-	</form>
-</div>
diff --git a/web/src/lib/models/immich-user.ts b/web/src/lib/models/immich-user.ts
index 7eb9e59dc4..c0bf781640 100644
--- a/web/src/lib/models/immich-user.ts
+++ b/web/src/lib/models/immich-user.ts
@@ -1,7 +1,9 @@
 export type ImmichUser = {
-  id: string,
-  email: string,
-  firstName: string,
-  lastName: string,
-  isAdmin: boolean,
-}
+	id: string;
+	email: string;
+	firstName: string;
+	lastName: string;
+	isAdmin: boolean;
+	profileImagePath: string;
+	shouldChangePassword: boolean;
+};
diff --git a/web/src/routes/auth/change-password/index.svelte b/web/src/routes/auth/change-password/index.svelte
new file mode 100644
index 0000000000..4ee49ec2bb
--- /dev/null
+++ b/web/src/routes/auth/change-password/index.svelte
@@ -0,0 +1,75 @@
+<script context="module" lang="ts">
+	export const prerender = false;
+
+	import type { Load } from '@sveltejs/kit';
+	import type { ImmichUser } from '$lib/models/immich-user';
+
+	export const load: Load = async ({ session }) => {
+		if (!session.user) {
+			return {
+				status: 302,
+				redirect: '/auth/login',
+			};
+		}
+
+		try {
+			const res = await fetch(serverEndpoint + '/user/me', {
+				method: 'GET',
+				headers: {
+					Authorization: 'Bearer ' + session.user.accessToken,
+				},
+			});
+
+			const userInfo: ImmichUser = await res.json();
+
+			if (userInfo.shouldChangePassword) {
+				return {
+					status: 200,
+					props: {
+						user: userInfo,
+					},
+				};
+			} else {
+				return {
+					status: 302,
+					redirect: '/photos',
+				};
+			}
+		} catch (e) {
+			console.log('ERROR Getting user info', e);
+			return {
+				status: 302,
+				redirect: '/photos',
+			};
+		}
+	};
+</script>
+
+<script lang="ts">
+	import { goto } from '$app/navigation';
+	import { session } from '$app/stores';
+	import { onMount } from 'svelte';
+	import { fade } from 'svelte/transition';
+	import ChangePasswordForm from '../../../lib/components/forms/change-password-form.svelte';
+	import { serverEndpoint } from '../../../lib/constants';
+
+	export let user: ImmichUser;
+
+	const onSuccessHandler = async () => {
+		const res = await fetch('/auth/logout', { method: 'POST' });
+
+		if (res.status == 200 && res.statusText == 'OK') {
+			goto('/auth/login');
+		}
+	};
+</script>
+
+<svelte:head>
+	<title>Immich - Change Password</title>
+</svelte:head>
+
+<section class="h-screen w-screen flex place-items-center place-content-center">
+	<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
+		<ChangePasswordForm {user} on:success={onSuccessHandler} />
+	</div>
+</section>
diff --git a/web/src/routes/auth/change-password/index.ts b/web/src/routes/auth/change-password/index.ts
new file mode 100644
index 0000000000..4abbb4bee4
--- /dev/null
+++ b/web/src/routes/auth/change-password/index.ts
@@ -0,0 +1,39 @@
+import type { RequestHandler } from '@sveltejs/kit';
+import { serverEndpoint } from '$lib/constants';
+
+export const post: RequestHandler = async ({ request, locals }) => {
+	const form = await request.formData();
+
+	const password = form.get('password');
+
+	const payload = {
+		id: locals.user?.id,
+		password,
+		shouldChangePassword: false,
+	};
+
+	const res = await fetch(`${serverEndpoint}/user`, {
+		method: 'PUT',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${locals.user?.accessToken}`,
+		},
+		body: JSON.stringify(payload),
+	});
+
+	if (res.status === 200) {
+		return {
+			status: 200,
+			body: {
+				success: 'Succesfully change password',
+			},
+		};
+	} else {
+		return {
+			status: 400,
+			body: {
+				error: await res.json(),
+			},
+		};
+	}
+};
diff --git a/web/src/routes/auth/login/index.svelte b/web/src/routes/auth/login/index.svelte
index 4304af7446..0e00cd31aa 100644
--- a/web/src/routes/auth/login/index.svelte
+++ b/web/src/routes/auth/login/index.svelte
@@ -3,25 +3,10 @@
 	import { fade } from 'svelte/transition';
 
 	import LoginForm from '$lib/components/forms/login-form.svelte';
-	import UpdateForm from '../../../lib/components/forms/update-form.svelte';
-	import SelectAdminForm from '../../../lib/components/forms/select-admin-form.svelte';
-
-	let shouldShowUpdateForm = false;
-	let shouldShowSelectAdminForm = false;
 
 	const onLoginSuccess = async () => {
 		goto('/photos');
 	};
-
-	const onNeedUpdate = () => {
-		shouldShowUpdateForm = true;
-		shouldShowSelectAdminForm = false;
-	};
-
-	const onNeedSelectAdmin = () => {
-		shouldShowUpdateForm = false;
-		shouldShowSelectAdminForm = true;
-	};
 </script>
 
 <svelte:head>
@@ -29,21 +14,7 @@
 </svelte:head>
 
 <section class="h-screen w-screen flex place-items-center place-content-center">
-	{#if !shouldShowUpdateForm && !shouldShowSelectAdminForm}
-		<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
-			<LoginForm on:success={onLoginSuccess} on:need-update={onNeedUpdate} on:need-select-admin={onNeedSelectAdmin} />
-		</div>
-	{/if}
-
-	{#if shouldShowUpdateForm}
-		<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
-			<UpdateForm on:success={onLoginSuccess} />
-		</div>
-	{/if}
-
-	{#if shouldShowSelectAdminForm}
-		<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
-			<SelectAdminForm on:success={onLoginSuccess} />
-		</div>
-	{/if}
+	<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
+		<LoginForm on:success={onLoginSuccess} on:first-login={() => goto('/auth/change-password')} />
+	</div>
 </section>
diff --git a/web/src/routes/auth/login/index.ts b/web/src/routes/auth/login/index.ts
index 965cb14b4c..64db005540 100644
--- a/web/src/routes/auth/login/index.ts
+++ b/web/src/routes/auth/login/index.ts
@@ -1,229 +1,81 @@
 import type { RequestHandler } from '@sveltejs/kit';
 import { serverEndpoint } from '$lib/constants';
-import * as cookie from 'cookie'
+import * as cookie from 'cookie';
 import { getRequest, putRequest } from '$lib/api';
 
-type LoggedInUser = {
-  accessToken: string;
-  userId: string;
-  userEmail: string;
-  firstName: string;
-  lastName: string;
-  isAdmin: boolean;
-}
+type AuthUser = {
+	accessToken: string;
+	userId: string;
+	userEmail: string;
+	firstName: string;
+	lastName: string;
+	isAdmin: boolean;
+	shouldChangePassword: boolean;
+};
 
 export const post: RequestHandler = async ({ request }) => {
-  const form = await request.formData();
+	const form = await request.formData();
 
-  const email = form.get('email')
-  const password = form.get('password')
+	const email = form.get('email');
+	const password = form.get('password');
 
-  const payload = {
-    email,
-    password,
-  }
+	const payload = {
+		email,
+		password,
+	};
 
-  const res = await fetch(`${serverEndpoint}/auth/login`, {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json'
-    },
-    body: JSON.stringify(payload),
-  })
+	const res = await fetch(`${serverEndpoint}/auth/login`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+		},
+		body: JSON.stringify(payload),
+	});
 
-  if (res.status === 201) {
-    // Login success
-    const loggedInUser = await res.json() as LoggedInUser;
+	if (res.status === 201) {
+		// Login success
+		const authUser = (await res.json()) as AuthUser;
 
-    /**
-     * Support legacy users with two scenario
-     * 
-     * Scenario 1 - If one user exists on the server - make the user admin and ask for name.
-     * Scenario 2 - After assigned as admin, scenario 1 user not complete update form with names
-     * Scenario 3 - If two users exists on the server and no admin - ask to choose which one will be made admin
-     */
-
-
-    // check how many user on the server
-    const { userCount } = await getRequest('user/count', '');
-    const { userCount: adminUserCount } = await getRequest('user/count?isAdmin=true', '')
-    /**
-     * Scenario 1 handler
-     */
-    if (userCount == 1 && !loggedInUser.isAdmin) {
-
-      const updatedUser = await putRequest('user', {
-        id: loggedInUser.userId,
-        isAdmin: true
-      }, loggedInUser.accessToken)
-
-
-      /**
-      * Scenario 2 handler for current admin user
-      */
-      let bodyResponse = { success: true, needUpdate: false }
-
-      if (loggedInUser.firstName == "" || loggedInUser.lastName == "") {
-        bodyResponse = { success: false, needUpdate: true }
-      }
-
-
-      return {
-        status: 200,
-        body: {
-          ...bodyResponse,
-          user: {
-            id: updatedUser.userId,
-            accessToken: loggedInUser.accessToken,
-            firstName: updatedUser.firstName,
-            lastName: updatedUser.lastName,
-            isAdmin: updatedUser.isAdmin,
-            email: updatedUser.email,
-          },
-        },
-        headers: {
-          'Set-Cookie': cookie.serialize('session', JSON.stringify(
-            {
-              id: updatedUser.userId,
-              accessToken: loggedInUser.accessToken,
-              firstName: updatedUser.firstName,
-              lastName: updatedUser.lastName,
-              isAdmin: updatedUser.isAdmin,
-              email: updatedUser.email,
-            }), {
-            path: '/',
-            httpOnly: true,
-            sameSite: 'strict',
-            maxAge: 60 * 60 * 24 * 30,
-          })
-        }
-      }
-    }
-
-
-    /**
-    * Scenario 3 handler
-    */
-    if (userCount >= 2 && adminUserCount == 0) {
-      return {
-        status: 200,
-        body: {
-          needSelectAdmin: true,
-          user: {
-            id: loggedInUser.userId,
-            accessToken: loggedInUser.accessToken,
-            firstName: loggedInUser.firstName,
-            lastName: loggedInUser.lastName,
-            isAdmin: loggedInUser.isAdmin,
-            email: loggedInUser.userEmail
-          },
-          success: 'success'
-        },
-        headers: {
-          'Set-Cookie': cookie.serialize('session', JSON.stringify(
-            {
-              id: loggedInUser.userId,
-              accessToken: loggedInUser.accessToken,
-              firstName: loggedInUser.firstName,
-              lastName: loggedInUser.lastName,
-              isAdmin: loggedInUser.isAdmin,
-              email: loggedInUser.userEmail
-            }), {
-            path: '/',
-            httpOnly: true,
-            sameSite: 'strict',
-            maxAge: 60 * 60 * 24 * 30,
-          })
-        }
-      }
-    }
-
-    /**
-    * Scenario 2 handler
-    */
-    if (loggedInUser.firstName == "" || loggedInUser.lastName == "") {
-      return {
-        status: 200,
-        body: {
-          needUpdate: true,
-          user: {
-            id: loggedInUser.userId,
-            accessToken: loggedInUser.accessToken,
-            firstName: loggedInUser.firstName,
-            lastName: loggedInUser.lastName,
-            isAdmin: loggedInUser.isAdmin,
-            email: loggedInUser.userEmail
-          },
-        },
-        headers: {
-          'Set-Cookie': cookie.serialize('session', JSON.stringify(
-            {
-              id: loggedInUser.userId,
-              accessToken: loggedInUser.accessToken,
-              firstName: loggedInUser.firstName,
-              lastName: loggedInUser.lastName,
-              isAdmin: loggedInUser.isAdmin,
-              email: loggedInUser.userEmail
-            }), {
-            path: '/',
-            httpOnly: true,
-            sameSite: 'strict',
-            maxAge: 60 * 60 * 24 * 30,
-          })
-        }
-      }
-    }
-
-
-
-    return {
-      status: 200,
-      body: {
-        user: {
-          id: loggedInUser.userId,
-          accessToken: loggedInUser.accessToken,
-          firstName: loggedInUser.firstName,
-          lastName: loggedInUser.lastName,
-          isAdmin: loggedInUser.isAdmin,
-          email: loggedInUser.userEmail
-        },
-        success: 'success'
-      },
-      headers: {
-        'Set-Cookie': cookie.serialize('session', JSON.stringify(
-          {
-            id: loggedInUser.userId,
-            accessToken: loggedInUser.accessToken,
-            firstName: loggedInUser.firstName,
-            lastName: loggedInUser.lastName,
-            isAdmin: loggedInUser.isAdmin,
-            email: loggedInUser.userEmail,
-          }), {
-          // send cookie for every page
-          path: '/',
-
-          // server side only cookie so you can't use `document.cookie`
-          httpOnly: true,
-
-          // only requests from same site can send cookies
-          // and serves to protect from CSRF
-          // https://developer.mozilla.org/en-US/docs/Glossary/CSRF
-          sameSite: 'strict',
-
-          // set cookie to expire after a month
-          maxAge: 60 * 60 * 24 * 30,
-        })
-      }
-    }
-
-  } else {
-    return {
-      status: 400,
-      body: {
-        error: 'Incorrect email or password'
-      }
-    }
-  }
-
-
-}
\ No newline at end of file
+		return {
+			status: 200,
+			body: {
+				user: {
+					id: authUser.userId,
+					accessToken: authUser.accessToken,
+					firstName: authUser.firstName,
+					lastName: authUser.lastName,
+					isAdmin: authUser.isAdmin,
+					email: authUser.userEmail,
+					shouldChangePassword: authUser.shouldChangePassword,
+				},
+				success: 'success',
+			},
+			headers: {
+				'Set-Cookie': cookie.serialize(
+					'session',
+					JSON.stringify({
+						id: authUser.userId,
+						accessToken: authUser.accessToken,
+						firstName: authUser.firstName,
+						lastName: authUser.lastName,
+						isAdmin: authUser.isAdmin,
+						email: authUser.userEmail,
+					}),
+					{
+						path: '/',
+						httpOnly: true,
+						sameSite: 'strict',
+						maxAge: 60 * 60 * 24 * 30,
+					},
+				),
+			},
+		};
+	} else {
+		return {
+			status: 400,
+			body: {
+				error: 'Incorrect email or password',
+			},
+		};
+	}
+};
diff --git a/web/src/routes/index.svelte b/web/src/routes/index.svelte
index d173d3c4d9..25fd6b01b3 100644
--- a/web/src/routes/index.svelte
+++ b/web/src/routes/index.svelte
@@ -35,7 +35,6 @@
 <script lang="ts">
 	import { serverEndpoint } from '$lib/constants';
 	import { goto } from '$app/navigation';
-	import { onMount } from 'svelte';
 
 	export let isAdminUserExist: boolean;