Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): randomize password on reest #7943

Merged
merged 3 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion web/src/lib/components/elements/buttons/link-button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
export let color: Color = 'transparent-gray';
export let disabled = false;
export let fullwidth = false;
export let title: string | undefined = undefined;
</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 />
</Button>
22 changes: 20 additions & 2 deletions web/src/lib/components/forms/edit-user-form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

export let user: UserResponseDto;
export let canResetPassword = true;
export let newPassword: string;

let error: string;
let success: string;
Expand Down Expand Up @@ -53,12 +54,12 @@

const resetPassword = async () => {
try {
const defaultPassword = 'password';
newPassword = generatePassword();

await updateUser({
updateUserDto: {
id: user.id,
password: defaultPassword,
password: newPassword,
shouldChangePassword: true,
},
});
Expand All @@ -70,6 +71,23 @@
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;
jrasm91 marked this conversation as resolved.
Show resolved Hide resolved
}
</script>

<div
Expand Down
66 changes: 41 additions & 25 deletions web/src/routes/admin/user-management/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script lang="ts">
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 LinkButton from '$lib/components/elements/buttons/link-button.svelte';
import RestoreDialogue from '$lib/components/admin-page/restore-dialoge.svelte';
import Button from '$lib/components/elements/buttons/button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
Expand All @@ -17,8 +19,9 @@
import { user } from '$lib/stores/user.store';
import { websocketEvents } from '$lib/stores/websocket';
import { asByteUnitString } from '$lib/utils/byte-units';
import { copyToClipboard } from '$lib/utils';
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 { onMount } from 'svelte';
import type { PageData } from './$types';
Expand All @@ -28,10 +31,11 @@
let allUsers: UserResponseDto[] = [];
let shouldShowEditUserForm = false;
let shouldShowCreateUserForm = false;
let shouldShowInfoPanel = false;
let shouldShowPasswordResetSuccess = false;
let shouldShowDeleteConfirmDialog = false;
let shouldShowRestoreDialog = false;
let selectedUser: UserResponseDto;
let newPassword: string;

const refresh = async () => {
allUsers = await getAllUsers({ isAll: false });
Expand Down Expand Up @@ -84,7 +88,7 @@
const onEditPasswordSuccess = async () => {
await refresh();
shouldShowEditUserForm = false;
shouldShowInfoPanel = true;
shouldShowPasswordResetSuccess = true;
};

const deleteUserHandler = (user: UserResponseDto) => {
Expand Down Expand Up @@ -121,6 +125,7 @@
<FullScreenModal onClose={() => (shouldShowEditUserForm = false)}>
<EditUserForm
user={selectedUser}
bind:newPassword
canResetPassword={selectedUser?.id !== $user.id}
on:editSuccess={onEditUserSuccess}
on:resetPasswordSuccess={onEditPasswordSuccess}
Expand All @@ -147,29 +152,40 @@
/>
{/if}

{#if shouldShowInfoPanel}
<FullScreenModal onClose={() => (shouldShowInfoPanel = false)}>
<div
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"
{#if shouldShowPasswordResetSuccess}
<FullScreenModal onClose={() => (shouldShowPasswordResetSuccess = false)}>
<ConfirmDialogue
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">
Password reset success
</h1>

<p>
The user's password has been reset to the default <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"
>password</code
>
<br />
<br />
Please inform the user, and they will need to change the password at the next log-on.
</p>

<div class="mt-6 flex w-full">
<Button fullwidth on:click={() => (shouldShowInfoPanel = false)}>Done</Button>
</div>
</div>
<svelte:fragment slot="prompt">
<div class="flex flex-col gap-4">
<p>The user's password has been reset:</p>

<div class="flex justify-center gap-2">
<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"
>
{newPassword}
</code>
<LinkButton on:click={() => copyToClipboard(newPassword)} title="Copy password">
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiContentCopy} size="18" />
</div>
</LinkButton>
</div>

<p>
Please provide the temporary password to the user and inform them they will need to change the
password at their next login.
</p>
</div>
</svelte:fragment>
</ConfirmDialogue>
</FullScreenModal>
{/if}

Expand Down