1
0
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:
Kyle Mendell
2025-04-18 10:38:50 -05:00
committed by GitHub
parent 56a8b5d0c0
commit c843a60131
24 changed files with 245 additions and 56 deletions

View File

@@ -37,6 +37,7 @@ export type AllAppConfig = AppConfig & {
ldapAttributeGroupUniqueIdentifier: string;
ldapAttributeGroupName: string;
ldapAttributeAdminGroup: string;
ldapSoftDeleteUsers: boolean;
};
export type AppConfigRawResponse = {

View File

@@ -13,6 +13,7 @@ export type User = {
customClaims: CustomClaim[];
locale?: Locale;
ldapId?: string;
disabled: boolean;
};
export type UserCreate = Omit<User, 'id' | 'customClaims' | 'ldapId' | 'userGroups'>;

View File

@@ -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();

View File

@@ -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}

View File

@@ -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>

View File

@@ -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)}