1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-07 20:36:48 +01:00

feat(web): randomize password on reest (#7943)

* feat(web): randomize password on reest

* prettier

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Sam Holton 2024-03-14 17:33:39 -04:00 committed by GitHub
parent ab4b8eca15
commit cda45f9bfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 61 additions and 26 deletions

View file

@ -8,8 +8,9 @@
export let color: Color = 'transparent-gray'; export let color: Color = 'transparent-gray';
export let disabled = false; export let disabled = false;
export let fullwidth = false; export let fullwidth = false;
export let title: string | undefined = undefined;
</script> </script>
<Button size="link" {color} shadow={false} rounded="lg" {disabled} on:click {fullwidth}> <Button {title} size="link" {color} shadow={false} rounded="lg" {disabled} on:click {fullwidth}>
<slot /> <slot />
</Button> </Button>

View file

@ -13,6 +13,7 @@
export let user: UserResponseDto; export let user: UserResponseDto;
export let canResetPassword = true; export let canResetPassword = true;
export let newPassword: string;
let error: string; let error: string;
let success: string; let success: string;
@ -53,12 +54,12 @@
const resetPassword = async () => { const resetPassword = async () => {
try { try {
const defaultPassword = 'password'; newPassword = generatePassword();
await updateUser({ await updateUser({
updateUserDto: { updateUserDto: {
id: user.id, id: user.id,
password: defaultPassword, password: newPassword,
shouldChangePassword: true, shouldChangePassword: true,
}, },
}); });
@ -70,6 +71,23 @@
isShowResetPasswordConfirmation = false; isShowResetPasswordConfirmation = false;
} }
}; };
// TODO move password reset server-side
function generatePassword(length: number = 16) {
let generatedPassword = '';
const characterSet = '0123456789' + 'abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + ',.-{}+!#$%/()=?';
for (let i = 0; i < length; i++) {
let randomNumber = crypto.getRandomValues(new Uint32Array(1))[0];
randomNumber = randomNumber / 2 ** 32;
randomNumber = Math.floor(randomNumber * characterSet.length);
generatedPassword += characterSet[randomNumber];
}
return generatedPassword;
}
</script> </script>
<div <div

View file

@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialoge.svelte'; import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialoge.svelte';
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
import RestoreDialogue from '$lib/components/admin-page/restore-dialoge.svelte'; import RestoreDialogue from '$lib/components/admin-page/restore-dialoge.svelte';
import Button from '$lib/components/elements/buttons/button.svelte'; import Button from '$lib/components/elements/buttons/button.svelte';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
@ -17,8 +19,9 @@
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
import { websocketEvents } from '$lib/stores/websocket'; import { websocketEvents } from '$lib/stores/websocket';
import { asByteUnitString } from '$lib/utils/byte-units'; import { asByteUnitString } from '$lib/utils/byte-units';
import { copyToClipboard } from '$lib/utils';
import { UserStatus, getAllUsers, type UserResponseDto } from '@immich/sdk'; import { UserStatus, getAllUsers, type UserResponseDto } from '@immich/sdk';
import { mdiClose, mdiDeleteRestore, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js'; import { mdiClose, mdiContentCopy, mdiDeleteRestore, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -28,10 +31,11 @@
let allUsers: UserResponseDto[] = []; let allUsers: UserResponseDto[] = [];
let shouldShowEditUserForm = false; let shouldShowEditUserForm = false;
let shouldShowCreateUserForm = false; let shouldShowCreateUserForm = false;
let shouldShowInfoPanel = false; let shouldShowPasswordResetSuccess = false;
let shouldShowDeleteConfirmDialog = false; let shouldShowDeleteConfirmDialog = false;
let shouldShowRestoreDialog = false; let shouldShowRestoreDialog = false;
let selectedUser: UserResponseDto; let selectedUser: UserResponseDto;
let newPassword: string;
const refresh = async () => { const refresh = async () => {
allUsers = await getAllUsers({ isAll: false }); allUsers = await getAllUsers({ isAll: false });
@ -84,7 +88,7 @@
const onEditPasswordSuccess = async () => { const onEditPasswordSuccess = async () => {
await refresh(); await refresh();
shouldShowEditUserForm = false; shouldShowEditUserForm = false;
shouldShowInfoPanel = true; shouldShowPasswordResetSuccess = true;
}; };
const deleteUserHandler = (user: UserResponseDto) => { const deleteUserHandler = (user: UserResponseDto) => {
@ -121,6 +125,7 @@
<FullScreenModal onClose={() => (shouldShowEditUserForm = false)}> <FullScreenModal onClose={() => (shouldShowEditUserForm = false)}>
<EditUserForm <EditUserForm
user={selectedUser} user={selectedUser}
bind:newPassword
canResetPassword={selectedUser?.id !== $user.id} canResetPassword={selectedUser?.id !== $user.id}
on:editSuccess={onEditUserSuccess} on:editSuccess={onEditUserSuccess}
on:resetPasswordSuccess={onEditPasswordSuccess} on:resetPasswordSuccess={onEditPasswordSuccess}
@ -147,29 +152,40 @@
/> />
{/if} {/if}
{#if shouldShowInfoPanel} {#if shouldShowPasswordResetSuccess}
<FullScreenModal onClose={() => (shouldShowInfoPanel = false)}> <FullScreenModal onClose={() => (shouldShowPasswordResetSuccess = false)}>
<div <ConfirmDialogue
class="w-[500px] max-w-[95vw] rounded-3xl bg-immich-bg p-8 text-immich-fg shadow-sm dark:bg-immich-dark-gray dark:text-immich-dark-fg" title="Password Reset Success"
confirmText="Done"
onConfirm={() => (shouldShowPasswordResetSuccess = false)}
onClose={() => (shouldShowPasswordResetSuccess = false)}
hideCancelButton={true}
confirmColor="green"
> >
<h1 class="mb-4 text-2xl font-medium text-immich-primary dark:text-immich-dark-primary"> <svelte:fragment slot="prompt">
Password reset success <div class="flex flex-col gap-4">
</h1> <p>The user's password has been reset:</p>
<p> <div class="flex justify-center gap-2">
The user's password has been reset to the default <code <code
class="rounded-md bg-gray-200 px-2 py-1 font-bold text-immich-primary dark:text-immich-dark-primary dark:bg-gray-700" class="rounded-md bg-gray-200 px-2 py-1 font-bold text-immich-primary dark:text-immich-dark-primary dark:bg-gray-700"
>password</code >
> {newPassword}
<br /> </code>
<br /> <LinkButton on:click={() => copyToClipboard(newPassword)} title="Copy password">
Please inform the user, and they will need to change the password at the next log-on. <div class="flex place-items-center gap-2 text-sm">
</p> <Icon path={mdiContentCopy} size="18" />
</div>
</LinkButton>
</div>
<div class="mt-6 flex w-full"> <p>
<Button fullwidth on:click={() => (shouldShowInfoPanel = false)}>Done</Button> Please provide the temporary password to the user and inform them they will need to change the
</div> password at their next login.
</div> </p>
</div>
</svelte:fragment>
</ConfirmDialogue>
</FullScreenModal> </FullScreenModal>
{/if} {/if}