Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib/actions/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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;
});
Comment on lines +18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace onMount with $effect for reactive state synchronization.

The onMount hook only executes once per component instance. If the project prop changes (e.g., when navigating between projects in the same component instance), the local state won't update to reflect the new project's values, leading to stale UI state.

🔎 Recommended fix using $effect
-    onMount(() => {
+    $effect(() => {
         authSessionAlerts = project?.authSessionAlerts ?? false;
         sessionInvalidation = project?.authInvalidateSessions ?? false;
     });

Remove the onMount import:

-    import { onMount } from 'svelte';
🤖 Prompt for AI Agents
In
src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte
around lines 18–21, the onMount usage only runs once and causes stale local
state when the project prop changes; remove the onMount import and replace the
onMount block with a reactive statement so authSessionAlerts and
sessionInvalidation are updated whenever project changes (e.g., use a $effect or
Svelte reactive statement that sets authSessionAlerts =
project?.authSessionAlerts ?? false and 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Error tracking doesn't distinguish between API call failures.

Line 54 always tracks Submit.AuthSessionAlertsUpdate, even if the updateSessionInvalidation call failed. Consider tracking both events or the specific operation that failed.

🔎 Suggested approach

If you parallelize the API calls as suggested above, you could track errors individually:

         try {
-            await sdk.forConsole.projects.updateSessionAlerts({
-                projectId: project.$id,
-                alerts: authSessionAlerts
-            });
-            await sdk.forConsole.projects.updateSessionInvalidation({
-                projectId: project.$id,
-                enabled: sessionInvalidation
-            });
+            await Promise.all([
+                sdk.forConsole.projects.updateSessionAlerts({
+                    projectId: project.$id,
+                    alerts: authSessionAlerts
+                }),
+                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);
+            // Track both events since we can't distinguish which failed
+            trackError(error, Submit.AuthSessionAlertsUpdate);
+            trackError(error, Submit.AuthInvalidateSession);
         }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte
around line 54, the error tracking call always logs
Submit.AuthSessionAlertsUpdate even when updateSessionInvalidation fails; update
the error handling to track the specific failed operation (e.g.,
Submit.AuthSessionAlertsUpdate vs Submit.AuthSessionInvalidationUpdate) and
include the error details; if you parallelize the API calls, attach per-call
.catch handlers or inspect which promise rejected and call trackError with the
corresponding event name and error payload so each failure is logged distinctly.

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

This file was deleted.

Loading