mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-11 22:49:00 +00:00
feat: disable/enable users (#437)
Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
@@ -37,6 +37,7 @@ export type AllAppConfig = AppConfig & {
|
||||
ldapAttributeGroupUniqueIdentifier: string;
|
||||
ldapAttributeGroupName: string;
|
||||
ldapAttributeAdminGroup: string;
|
||||
ldapSoftDeleteUsers: boolean;
|
||||
};
|
||||
|
||||
export type AppConfigRawResponse = {
|
||||
|
||||
@@ -13,6 +13,7 @@ export type User = {
|
||||
customClaims: CustomClaim[];
|
||||
locale?: Locale;
|
||||
ldapId?: string;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
export type UserCreate = Omit<User, 'id' | 'customClaims' | 'ldapId' | 'userGroups'>;
|
||||
|
||||
@@ -14,7 +14,10 @@ export function getAxiosErrorMessage(
|
||||
return message;
|
||||
}
|
||||
|
||||
export function axiosErrorToast(e: unknown, defaultMessage: string = m.an_unknown_error_occurred()) {
|
||||
export function axiosErrorToast(
|
||||
e: unknown,
|
||||
defaultMessage: string = m.an_unknown_error_occurred()
|
||||
) {
|
||||
const message = getAxiosErrorMessage(e, defaultMessage);
|
||||
toast.error(message);
|
||||
}
|
||||
@@ -29,7 +32,8 @@ export function getWebauthnErrorMessage(e: unknown) {
|
||||
m.authenticator_does_not_support_resident_keys(),
|
||||
ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED: m.passkey_was_previously_registered(),
|
||||
ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG:
|
||||
m.authenticator_does_not_support_any_of_the_requested_algorithms()
|
||||
m.authenticator_does_not_support_any_of_the_requested_algorithms(),
|
||||
ERROR_USER_DISABLED_MSG: m.user_disabled()
|
||||
};
|
||||
|
||||
let message = m.an_unknown_error_occurred();
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
ldapAttributeGroupMember: appConfig.ldapAttributeGroupMember,
|
||||
ldapAttributeGroupUniqueIdentifier: appConfig.ldapAttributeGroupUniqueIdentifier,
|
||||
ldapAttributeGroupName: appConfig.ldapAttributeGroupName,
|
||||
ldapAttributeAdminGroup: appConfig.ldapAttributeAdminGroup
|
||||
ldapAttributeAdminGroup: appConfig.ldapAttributeAdminGroup,
|
||||
ldapSoftDeleteUsers: appConfig.ldapSoftDeleteUsers || true
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
@@ -63,7 +64,8 @@
|
||||
ldapAttributeGroupMember: z.string(),
|
||||
ldapAttributeGroupUniqueIdentifier: z.string().min(1),
|
||||
ldapAttributeGroupName: z.string().min(1),
|
||||
ldapAttributeAdminGroup: z.string()
|
||||
ldapAttributeAdminGroup: z.string(),
|
||||
ldapSoftDeleteUsers: z.boolean()
|
||||
});
|
||||
|
||||
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
|
||||
@@ -116,7 +118,11 @@
|
||||
placeholder="cn=people,dc=example,dc=com"
|
||||
bind:input={$inputs.ldapBindDn}
|
||||
/>
|
||||
<FormInput label={m.ldap_bind_password()} type="password" bind:input={$inputs.ldapBindPassword} />
|
||||
<FormInput
|
||||
label={m.ldap_bind_password()}
|
||||
type="password"
|
||||
bind:input={$inputs.ldapBindPassword}
|
||||
/>
|
||||
<FormInput
|
||||
label={m.ldap_base_dn()}
|
||||
placeholder="dc=example,dc=com"
|
||||
@@ -140,6 +146,12 @@
|
||||
description={m.this_can_be_useful_for_selfsigned_certificates()}
|
||||
bind:checked={$inputs.ldapSkipCertVerify.value}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="ldap-soft-delete-users"
|
||||
label={m.ldap_soft_delete_users()}
|
||||
description={m.ldap_soft_delete_users_description()}
|
||||
bind:checked={$inputs.ldapSoftDeleteUsers.value}
|
||||
/>
|
||||
</div>
|
||||
<h4 class="mt-10 text-lg font-semibold">{m.attribute_mapping()}</h4>
|
||||
<div class="mt-4 grid grid-cols-1 items-end gap-5 md:grid-cols-2">
|
||||
@@ -203,7 +215,9 @@
|
||||
|
||||
<div class="mt-8 flex flex-wrap justify-end gap-3">
|
||||
{#if ldapEnabled}
|
||||
<Button variant="secondary" onclick={onDisable} disabled={uiConfigDisabled}>{m.disable()}</Button>
|
||||
<Button variant="secondary" onclick={onDisable} disabled={uiConfigDisabled}
|
||||
>{m.disable()}</Button
|
||||
>
|
||||
<Button variant="secondary" onclick={syncLdap} isLoading={ldapSyncing}>{m.sync_now()}</Button>
|
||||
<Button type="submit" disabled={uiConfigDisabled}>{m.save()}</Button>
|
||||
{:else}
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
lastName: existingUser?.lastName || '',
|
||||
email: existingUser?.email || '',
|
||||
username: existingUser?.username || '',
|
||||
isAdmin: existingUser?.isAdmin || false
|
||||
isAdmin: existingUser?.isAdmin || false,
|
||||
disabled: existingUser?.disabled || false
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
@@ -34,12 +35,10 @@
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.regex(
|
||||
/^[a-z0-9_@.-]+$/,
|
||||
m.username_can_only_contain()
|
||||
),
|
||||
.regex(/^[a-z0-9_@.-]+$/, m.username_can_only_contain()),
|
||||
email: z.string().email(),
|
||||
isAdmin: z.boolean()
|
||||
isAdmin: z.boolean(),
|
||||
disabled: z.boolean()
|
||||
});
|
||||
type FormSchema = typeof formSchema;
|
||||
|
||||
@@ -68,6 +67,12 @@
|
||||
description={m.admins_have_full_access_to_the_admin_panel()}
|
||||
bind:checked={$inputs.isAdmin.value}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="user-disabled"
|
||||
label={m.user_disabled()}
|
||||
description={m.disabled_users_cannot_log_in_or_use_services()}
|
||||
bind:checked={$inputs.disabled.value}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-5 flex justify-end">
|
||||
<Button {isLoading} type="submit">{m.save()}</Button>
|
||||
|
||||
@@ -2,20 +2,26 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import AdvancedTable from '$lib/components/advanced-table.svelte';
|
||||
import { openConfirmDialog } from '$lib/components/confirm-dialog/';
|
||||
import OneTimeLinkModal from '$lib/components/one-time-link-modal.svelte';
|
||||
import { Badge } from '$lib/components/ui/badge/index';
|
||||
import { buttonVariants } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { User } from '$lib/types/user.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { LucideLink, LucidePencil, LucideTrash } from 'lucide-svelte';
|
||||
import {
|
||||
LucideLink,
|
||||
LucidePencil,
|
||||
LucideTrash,
|
||||
LucideUserCheck,
|
||||
LucideUserX
|
||||
} from 'lucide-svelte';
|
||||
import Ellipsis from 'lucide-svelte/icons/ellipsis';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import OneTimeLinkModal from '$lib/components/one-time-link-modal.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
users = $bindable(),
|
||||
@@ -28,7 +34,7 @@
|
||||
|
||||
async function deleteUser(user: User) {
|
||||
openConfirmDialog({
|
||||
title: m.delete_firstname_lastname({firstName: user.firstName, lastName: user.lastName}),
|
||||
title: m.delete_firstname_lastname({ firstName: user.firstName, lastName: user.lastName }),
|
||||
message: m.are_you_sure_you_want_to_delete_this_user(),
|
||||
confirm: {
|
||||
label: m.delete(),
|
||||
@@ -45,6 +51,42 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function enableUser(user: User) {
|
||||
await userService
|
||||
.update(user.id, {
|
||||
...user,
|
||||
disabled: false
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(m.user_enabled_successfully());
|
||||
userService.list(requestOptions!).then((updatedUsers) => (users = updatedUsers));
|
||||
})
|
||||
.catch(axiosErrorToast);
|
||||
}
|
||||
|
||||
async function disableUser(user: User) {
|
||||
openConfirmDialog({
|
||||
title: m.disable_firstname_lastname({ firstName: user.firstName, lastName: user.lastName }),
|
||||
message: m.are_you_sure_you_want_to_disable_this_user(),
|
||||
confirm: {
|
||||
label: m.disable(),
|
||||
destructive: true,
|
||||
action: async () => {
|
||||
try {
|
||||
await userService.update(user.id, {
|
||||
...user,
|
||||
disabled: true
|
||||
});
|
||||
users = await userService.list(requestOptions!);
|
||||
toast.success(m.user_disabled_successfully());
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<AdvancedTable
|
||||
@@ -57,7 +99,8 @@
|
||||
{ label: m.email(), sortColumn: 'email' },
|
||||
{ label: m.username(), sortColumn: 'username' },
|
||||
{ label: m.role(), sortColumn: 'isAdmin' },
|
||||
...($appConfigStore.ldapEnabled ? [{ label: m.source()}] : []),
|
||||
{ label: m.status(), sortColumn: 'disabled' },
|
||||
...($appConfigStore.ldapEnabled ? [{ label: m.source() }] : []),
|
||||
{ label: m.actions(), hidden: true }
|
||||
]}
|
||||
>
|
||||
@@ -69,9 +112,15 @@
|
||||
<Table.Cell>
|
||||
<Badge variant="outline">{item.isAdmin ? m.admin() : m.user()}</Badge>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Badge variant={item.disabled ? 'destructive' : 'default'}>
|
||||
{item.disabled ? m.disabled() : m.enabled()}
|
||||
</Badge>
|
||||
</Table.Cell>
|
||||
{#if $appConfigStore.ldapEnabled}
|
||||
<Table.Cell>
|
||||
<Badge variant={item.ldapId ? 'default' : 'outline'}>{item.ldapId ? m.ldap() : m.local()}</Badge
|
||||
<Badge variant={item.ldapId ? 'default' : 'outline'}
|
||||
>{item.ldapId ? m.ldap() : m.local()}</Badge
|
||||
>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
@@ -89,6 +138,17 @@
|
||||
><LucidePencil class="mr-2 h-4 w-4" /> {m.edit()}</DropdownMenu.Item
|
||||
>
|
||||
{#if !item.ldapId || !$appConfigStore.ldapEnabled}
|
||||
{#if item.disabled}
|
||||
<DropdownMenu.Item onclick={() => enableUser(item)}
|
||||
><LucideUserCheck class="mr-2 h-4 w-4" />{m.enable()}</DropdownMenu.Item
|
||||
>
|
||||
{:else}
|
||||
<DropdownMenu.Item onclick={() => disableUser(item)}
|
||||
><LucideUserX class="mr-2 h-4 w-4" />{m.disable()}</DropdownMenu.Item
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if !item.ldapId || (item.ldapId && item.disabled)}
|
||||
<DropdownMenu.Item
|
||||
class="text-red-500 focus:!text-red-700"
|
||||
onclick={() => deleteUser(item)}
|
||||
|
||||
Reference in New Issue
Block a user