Merge pull request #2747 from appwrite/feat-SER-1037-Group-security-cards

feat: New Grouped security cards
This commit is contained in:
Harsh Mahajan
2026-01-04 18:53:17 +05:30
committed by GitHub
9 changed files with 261 additions and 320 deletions
+1 -1
View File
@@ -268,7 +268,7 @@ export enum Submit {
AuthSessionAlertsUpdate = 'submit_auth_session_alerts_update',
AuthMembershipPrivacyUpdate = 'submit_auth_membership_privacy_update',
AuthMockNumbersUpdate = 'submit_auth_mock_numbers_update',
AuthInvalidateSesssion = 'submit_auth_invalidate_session',
AuthInvalidateSession = 'submit_auth_invalidate_session',
SessionsLengthUpdate = 'submit_sessions_length_update',
SessionsLimitUpdate = 'submit_sessions_limit_update',
SessionDelete = 'submit_session_delete',
@@ -1,26 +1,23 @@
<script lang="ts">
import { Container } from '$lib/layout';
import type { PageProps } from './$types';
import UpdateMockNumbers from './updateMockNumbers.svelte';
import UpdatePasswordDictionary from './updatePasswordDictionary.svelte';
import UpdatePasswordHistory from './updatePasswordHistory.svelte';
import UpdatePersonalDataCheck from './updatePersonalDataCheck.svelte';
import UpdateSessionAlerts from './updateSessionAlerts.svelte';
import UpdateSessionLength from './updateSessionLength.svelte';
import UpdateSessionsLimit from './updateSessionsLimit.svelte';
import UpdateMembershipPrivacy from './updateMembershipPrivacy.svelte';
import UpdateUsersLimit from './updateUsersLimit.svelte';
import UpdateSessionInvalidation from './updateSessionInvalidation.svelte';
import UpdateSessionLength from './updateSessionLength.svelte';
import UpdateSessionsLimit from './updateSessionsLimit.svelte';
import PasswordPolicies from './passwordPolicies.svelte';
import SessionSecurity from './sessionSecurity.svelte';
let { data }: PageProps = $props();
</script>
<Container>
<UpdateUsersLimit />
<UpdateSessionLength />
<UpdateSessionsLimit />
<UpdatePasswordHistory />
<UpdatePasswordDictionary />
<UpdatePersonalDataCheck />
<UpdateSessionAlerts />
<UpdateSessionInvalidation />
<PasswordPolicies project={data.project} />
<SessionSecurity project={data.project} />
<UpdateMockNumbers />
<UpdateMembershipPrivacy />
</Container>
@@ -0,0 +1,159 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputNumber, InputSwitch } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Typography, Link, Layout } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';
import { onMount } from 'svelte';
let {
project
}: {
project: Models.Project;
} = $props();
let lastValidLimit = $state(5);
let passwordHistory = $state(5);
let passwordDictionary = $state(false);
let passwordHistoryEnabled = $state(false);
let authPersonalDataCheck = $state(false);
onMount(() => {
// update initial states here in onMount.
const historyValue = project.authPasswordHistory;
if (historyValue && historyValue > 0) {
passwordHistory = historyValue;
lastValidLimit = historyValue;
}
passwordHistoryEnabled = (historyValue ?? 0) !== 0;
passwordDictionary = project.authPasswordDictionary ?? false;
authPersonalDataCheck = project.authPersonalDataCheck ?? false;
});
$effect(() => {
// restore last valid limit when enabling
if (passwordHistoryEnabled && passwordHistory < 1) {
passwordHistory = lastValidLimit;
}
});
const hasChanges = $derived.by(() => {
const dictChanged = passwordDictionary !== (project.authPasswordDictionary ?? false);
const dataCheckChanged = authPersonalDataCheck !== (project.authPersonalDataCheck ?? false);
const historyChanged =
passwordHistoryEnabled !== ((project.authPasswordHistory ?? 0) !== 0);
const limitChanged =
passwordHistoryEnabled &&
Number(passwordHistory) !== (project.authPasswordHistory ?? 0);
return historyChanged || dictChanged || dataCheckChanged || limitChanged;
});
async function updatePasswordPolicies() {
try {
const projectSdk = sdk.forConsole.projects;
await projectSdk.updateAuthPasswordHistory({
projectId: project.$id,
limit: passwordHistoryEnabled ? passwordHistory : 0
});
await projectSdk.updateAuthPasswordDictionary({
projectId: project.$id,
enabled: passwordDictionary
});
await projectSdk.updatePersonalDataCheck({
projectId: project.$id,
enabled: authPersonalDataCheck
});
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message: 'Updated password policies.'
});
trackEvent(Submit.AuthPasswordHistoryUpdate);
trackEvent(Submit.AuthPasswordDictionaryUpdate);
trackEvent(Submit.AuthPersonalDataCheckUpdate);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthPasswordHistoryUpdate);
}
}
</script>
<Form onSubmit={updatePasswordPolicies}>
<CardGrid gap="xxl">
<svelte:fragment slot="title">Password policies</svelte:fragment>
<svelte:fragment slot="aside">
<InputSwitch
bind:value={passwordHistoryEnabled}
id="passwordHistoryEnabled"
label="Password history">
<svelte:fragment slot="description">
<Layout.Stack gap="m">
<Typography.Text>
Enabling this option prevents users from reusing recent passwords by
comparing the new password with their password history.
</Typography.Text>
{#if passwordHistoryEnabled}
<InputNumber
required
max={20}
min={1}
autofocus
label="Limit"
id="password-history"
bind:value={passwordHistory}
helper="Maximum 20 passwords." />
{/if}
</Layout.Stack>
</svelte:fragment>
</InputSwitch>
<InputSwitch
bind:value={passwordDictionary}
id="passwordDictionary"
label="Password dictionary">
<svelte:fragment slot="description">
<Typography.Text>
Enabling this option prevents users from setting insecure passwords by
comparing the user's password with the <Link.Anchor
target="_blank"
rel="noopener noreferrer"
class="link"
href="https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt"
>10k most commonly used passwords.</Link.Anchor>
</Typography.Text>
</svelte:fragment>
</InputSwitch>
<InputSwitch
bind:value={authPersonalDataCheck}
id="personalDataCheck"
label="Disallow personal data">
<svelte:fragment slot="description">
<Typography.Text>
Do not allow passwords that contain any part of the user's personal data.
This includes the user's <Typography.Code>name</Typography.Code>, <Typography.Code
>email</Typography.Code
>, or <Typography.Code>phone</Typography.Code>.
</Typography.Text>
</svelte:fragment>
</InputSwitch>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={!hasChanges} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -0,0 +1,92 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputSwitch } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Typography } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';
import { onMount } from 'svelte';
let { project }: { project: Models.Project } = $props();
let authSessionAlerts = $state(false);
let sessionInvalidation = $state(false);
onMount(() => {
authSessionAlerts = project?.authSessionAlerts ?? false;
sessionInvalidation = project?.authInvalidateSessions ?? false;
});
const hasChanges = $derived.by(() => {
const alertsChanged = authSessionAlerts !== (project?.authSessionAlerts ?? false);
const invalidationChanged =
sessionInvalidation !== (project?.authInvalidateSessions ?? false);
return alertsChanged || invalidationChanged;
});
async function updateSessionSecurity() {
try {
await sdk.forConsole.projects.updateSessionAlerts({
projectId: project.$id,
alerts: authSessionAlerts
});
await sdk.forConsole.projects.updateSessionInvalidation({
projectId: project.$id,
enabled: sessionInvalidation
});
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message: 'Updated session security settings.'
});
trackEvent(Submit.AuthSessionAlertsUpdate);
trackEvent(Submit.AuthInvalidateSession);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthSessionAlertsUpdate);
}
}
</script>
<Form onSubmit={updateSessionSecurity}>
<CardGrid gap="xxl">
<svelte:fragment slot="title">Session security</svelte:fragment>
<svelte:fragment slot="aside">
<InputSwitch
bind:value={authSessionAlerts}
id="authSessionAlerts"
label="Session alerts">
<svelte:fragment slot="description">
<Typography.Text>
Enabling this option will send an email to the users when a new session is
created.
</Typography.Text>
</svelte:fragment>
</InputSwitch>
<InputSwitch
bind:value={sessionInvalidation}
id="invalidateSessions"
label="Invalidate sessions">
<svelte:fragment slot="description">
<Typography.Text>
Enabling this option will clear all existing sessions when the user changes
their password.
</Typography.Text>
</svelte:fragment>
</InputSwitch>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={!hasChanges} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -1,61 +0,0 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputSwitch } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Typography, Link } from '@appwrite.io/pink-svelte';
import { project } from '../../store';
let passwordDictionary = $project?.authPasswordDictionary ?? false;
async function updatePasswordDictionary() {
try {
await sdk.forConsole.projects.updateAuthPasswordDictionary({
projectId: $project.$id,
enabled: passwordDictionary
});
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message: 'Updated password dictionary check.'
});
trackEvent(Submit.AuthPasswordDictionaryUpdate);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthPasswordDictionaryUpdate);
}
}
</script>
<Form onSubmit={updatePasswordDictionary}>
<CardGrid>
<svelte:fragment slot="title">Password dictionary</svelte:fragment>
<svelte:fragment slot="aside">
<InputSwitch
bind:value={passwordDictionary}
id="passwordDictionary"
label="Password dictionary" />
<Typography.Text>
Enabling this option prevent users from setting insecure passwords by comparing the
user's password with the <Link.Anchor
target="_blank"
rel="noopener noreferrer"
class="link"
href="https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt"
>10k most commonly used passwords.</Link.Anchor>
</Typography.Text>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={passwordDictionary === $project?.authPasswordDictionary} submit>
Update
</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -1,80 +0,0 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputNumber, InputSwitch } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Typography } from '@appwrite.io/pink-svelte';
import { project } from '../../store';
import { tick } from 'svelte';
let passwordHistory = $project?.authPasswordHistory < 1 ? 5 : $project?.authPasswordHistory;
let passwordHistoryEnabled = ($project?.authPasswordHistory ?? 0) !== 0;
let initialPasswordHistoryEnabled = passwordHistoryEnabled;
async function updatePasswordHistoryLimit() {
try {
await sdk.forConsole.projects.updateAuthPasswordHistory({
projectId: $project.$id,
limit: passwordHistoryEnabled ? passwordHistory : 0
});
await invalidate(Dependencies.PROJECT);
initialPasswordHistoryEnabled = passwordHistoryEnabled;
addNotification({
type: 'success',
message: 'Updated password history limit.'
});
trackEvent(Submit.AuthPasswordHistoryUpdate);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthPasswordHistoryUpdate);
}
}
let maxPasswordInputField: InputNumber | null = null;
$: if (passwordHistoryEnabled && maxPasswordInputField) {
tick().then(() => {
maxPasswordInputField.addInputFocus();
});
}
</script>
<Form onSubmit={updatePasswordHistoryLimit}>
<CardGrid>
<svelte:fragment slot="title">Password history</svelte:fragment>
Set the maximum number of passwords saved per user.
<svelte:fragment slot="aside">
<InputSwitch
bind:value={passwordHistoryEnabled}
id="passwordHistoryEnabled"
label="Password history" />
<Typography.Text>
Enabling this option prevents users from reusing recent passwords by comparing the
new password with their password history.
</Typography.Text>
<InputNumber
required
max={20}
min={1}
id="password-history"
label="Limit"
disabled={!passwordHistoryEnabled}
bind:value={passwordHistory}
helper="Maximum 20 passwords." />
</svelte:fragment>
<svelte:fragment slot="actions">
<Button
disabled={(passwordHistory === $project?.authPasswordHistory ||
$project?.authPasswordHistory === 0) &&
initialPasswordHistoryEnabled === passwordHistoryEnabled}
submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -1,56 +0,0 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputSwitch } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Typography } from '@appwrite.io/pink-svelte';
import { project } from '../../store';
let authPersonalDataCheck = $project?.authPersonalDataCheck ?? false;
async function updatePersonalDataCheck() {
try {
await sdk.forConsole.projects.updatePersonalDataCheck({
projectId: $project.$id,
enabled: authPersonalDataCheck
});
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message: 'Toggled personal data checks for passwords'
});
trackEvent(Submit.AuthPersonalDataCheckUpdate);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthPersonalDataCheckUpdate);
}
}
</script>
<Form onSubmit={updatePersonalDataCheck}>
<CardGrid>
<svelte:fragment slot="title">Personal data</svelte:fragment>
<svelte:fragment slot="aside">
<InputSwitch
bind:value={authPersonalDataCheck}
id="personalDataCheck"
label="Disallow personal data" />
<Typography.Text>
Do not allow passwords that contain any part of the user's personal data. This
includes the user's <Typography.Code>name</Typography.Code>, <Typography.Code
>email</Typography.Code
>, or <Typography.Code>phone</Typography.Code>.
</Typography.Text>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={authPersonalDataCheck === $project?.authPersonalDataCheck} submit
>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -1,54 +0,0 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputSwitch } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Typography } from '@appwrite.io/pink-svelte';
import { project } from '../../store';
let authSessionAlerts = $project?.authSessionAlerts ?? false;
async function updateSessionAlerts() {
try {
await sdk.forConsole.projects.updateSessionAlerts({
projectId: $project.$id,
alerts: authSessionAlerts
});
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message: 'Updated session alerts.'
});
trackEvent(Submit.AuthSessionAlertsUpdate);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthSessionAlertsUpdate);
}
}
</script>
<Form onSubmit={updateSessionAlerts}>
<CardGrid>
<svelte:fragment slot="title">Session alerts</svelte:fragment>
<svelte:fragment slot="aside">
<InputSwitch
bind:value={authSessionAlerts}
id="authSessionAlerts"
label="Session alerts" />
<Typography.Text>
Enabling this option will send an email to the users when a new session is created.
</Typography.Text>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={authSessionAlerts === $project?.authSessionAlerts} submit>
Update
</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -1,56 +0,0 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputSwitch } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Typography } from '@appwrite.io/pink-svelte';
import { project } from '../../store';
let sessionInvalidation = $project?.authInvalidateSessions ?? false;
async function updateSessionInvalidation() {
try {
await sdk.forConsole.projects.updateSessionInvalidation({
projectId: $project.$id,
enabled: sessionInvalidation
});
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message: 'Updated session invalidation check.'
});
trackEvent(Submit.AuthInvalidateSesssion);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthInvalidateSesssion);
}
}
</script>
<Form onSubmit={updateSessionInvalidation}>
<CardGrid>
<svelte:fragment slot="title">Invalidate sessions</svelte:fragment>
<svelte:fragment slot="aside">
<InputSwitch
bind:value={sessionInvalidation}
id="invalidateSessions"
label="Invalidate sessions" />
<Typography.Text>
Enabling this option will clear all existing sessions when the user changes their
password.
</Typography.Text>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={sessionInvalidation === $project?.authInvalidateSessions} submit>
Update
</Button>
</svelte:fragment>
</CardGrid>
</Form>