mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-03-31 11:46:37 +00:00
feat: add audit log with email notification (#26)
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
import ApplicationConfigurationService from '$lib/services/application-configuration-service';
|
||||
import AppConfigService from '$lib/services/app-config-service';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const applicationConfigurationService = new ApplicationConfigurationService(
|
||||
cookies.get('access_token')
|
||||
);
|
||||
const applicationConfiguration = await applicationConfigurationService.list(true);
|
||||
return { applicationConfiguration };
|
||||
const appConfigService = new AppConfigService(cookies.get('access_token'));
|
||||
const appConfig = await appConfigService.list(true);
|
||||
return { appConfig };
|
||||
};
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import ApplicationConfigurationService from '$lib/services/application-configuration-service';
|
||||
import applicationConfigurationStore from '$lib/stores/application-configuration-store';
|
||||
import type { AllApplicationConfiguration } from '$lib/types/application-configuration';
|
||||
import AppConfigService from '$lib/services/app-config-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import type { AllAppConfig } from '$lib/types/application-configuration';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import ApplicationConfigurationForm from './application-configuration-form.svelte';
|
||||
import AppConfigEmailForm from './forms/app-config-email-form.svelte';
|
||||
import AppConfigGeneralForm from './forms/app-config-general-form.svelte';
|
||||
import UpdateApplicationImages from './update-application-images.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let applicationConfiguration = $state(data.applicationConfiguration);
|
||||
let appConfig = $state(data.appConfig);
|
||||
|
||||
const applicationConfigurationService = new ApplicationConfigurationService();
|
||||
const appConfigService = new AppConfigService();
|
||||
|
||||
async function updateConfiguration(configuration: AllApplicationConfiguration) {
|
||||
await applicationConfigurationService
|
||||
.update(configuration)
|
||||
.then(() => toast.success('Application configuration updated successfully'))
|
||||
.catch(axiosErrorToast);
|
||||
await applicationConfigurationStore.reload();
|
||||
async function updateAppConfig(updatedAppConfig: Partial<AllAppConfig>) {
|
||||
await appConfigService
|
||||
.update({
|
||||
...appConfig,
|
||||
...updatedAppConfig
|
||||
})
|
||||
.catch((e) => {
|
||||
axiosErrorToast(e);
|
||||
throw e;
|
||||
});
|
||||
await appConfigStore.reload();
|
||||
}
|
||||
|
||||
async function updateImages(
|
||||
@@ -26,12 +32,10 @@
|
||||
backgroundImage: File | null,
|
||||
favicon: File | null
|
||||
) {
|
||||
const faviconPromise = favicon
|
||||
? applicationConfigurationService.updateFavicon(favicon)
|
||||
: Promise.resolve();
|
||||
const logoPromise = logo ? applicationConfigurationService.updateLogo(logo) : Promise.resolve();
|
||||
const faviconPromise = favicon ? appConfigService.updateFavicon(favicon) : Promise.resolve();
|
||||
const logoPromise = logo ? appConfigService.updateLogo(logo) : Promise.resolve();
|
||||
const backgroundImagePromise = backgroundImage
|
||||
? applicationConfigurationService.updateBackgroundImage(backgroundImage)
|
||||
? appConfigService.updateBackgroundImage(backgroundImage)
|
||||
: Promise.resolve();
|
||||
|
||||
await Promise.all([logoPromise, backgroundImagePromise, faviconPromise])
|
||||
@@ -49,7 +53,20 @@
|
||||
<Card.Title>General</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<ApplicationConfigurationForm {applicationConfiguration} callback={updateConfiguration} />
|
||||
<AppConfigGeneralForm {appConfig} callback={updateAppConfig} />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Email</Card.Title>
|
||||
<Card.Description>
|
||||
Enable email notifications to alert users when a login is detected from a new device or
|
||||
location.
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<AppConfigEmailForm {appConfig} callback={updateAppConfig} />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import FormInput from '$lib/components/form-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { AllAppConfig } from '$lib/types/application-configuration';
|
||||
import { createForm } from '$lib/utils/form-util';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
let {
|
||||
callback,
|
||||
appConfig
|
||||
}: {
|
||||
appConfig: AllAppConfig;
|
||||
callback: (appConfig: Partial<AllAppConfig>) => Promise<void>;
|
||||
} = $props();
|
||||
|
||||
let isLoading = $state(false);
|
||||
let emailEnabled = $state(appConfig.emailEnabled == 'true');
|
||||
|
||||
const updatedAppConfig = {
|
||||
emailEnabled: emailEnabled.toString(),
|
||||
smtpHost: appConfig.smtpHost,
|
||||
smtpPort: appConfig.smtpPort,
|
||||
smtpUser: appConfig.smtpUser,
|
||||
smtpPassword: appConfig.smtpPassword,
|
||||
smtpFrom: appConfig.smtpFrom
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
smtpHost: z.string().min(1),
|
||||
smtpPort: z.string().min(1),
|
||||
smtpUser: z.string().min(1),
|
||||
smtpPassword: z.string().min(1),
|
||||
smtpFrom: z.string().email()
|
||||
});
|
||||
|
||||
const { inputs, ...form } = createForm< typeof formSchema>(formSchema, updatedAppConfig);
|
||||
|
||||
async function onSubmit() {
|
||||
const data = form.validate();
|
||||
if (!data) return false;
|
||||
isLoading = true;
|
||||
await callback({
|
||||
...data,
|
||||
emailEnabled: 'true'
|
||||
}).finally(() => (isLoading = false));
|
||||
toast.success('Email configuration updated successfully');
|
||||
return true;
|
||||
}
|
||||
|
||||
async function onDisable() {
|
||||
await callback({ emailEnabled: 'false' });
|
||||
emailEnabled = false;
|
||||
toast.success('Email disabled successfully');
|
||||
}
|
||||
|
||||
async function onEnable() {
|
||||
if (await onSubmit()) {
|
||||
emailEnabled = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form onsubmit={onSubmit}>
|
||||
<div class="mt-5 grid grid-cols-2 gap-5">
|
||||
<FormInput label="SMTP Host" bind:input={$inputs.smtpHost} />
|
||||
<FormInput label="SMTP Port" bind:input={$inputs.smtpPort} />
|
||||
<FormInput label="SMTP User" bind:input={$inputs.smtpUser} />
|
||||
<FormInput label="SMTP Password" type="password" bind:input={$inputs.smtpPassword} />
|
||||
<FormInput label="SMTP From" bind:input={$inputs.smtpFrom} />
|
||||
</div>
|
||||
<div class="mt-5 flex justify-end gap-3">
|
||||
{#if emailEnabled}
|
||||
<Button variant="secondary" onclick={onDisable}>Disable</Button>
|
||||
<Button {isLoading} onclick={onSubmit} type="submit">Save</Button>
|
||||
{:else}
|
||||
<Button {isLoading} onclick={onEnable} type="submit">Enable</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,23 +1,24 @@
|
||||
<script lang="ts">
|
||||
import FormInput from '$lib/components/form-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { AllApplicationConfiguration } from '$lib/types/application-configuration';
|
||||
import type { AllAppConfig } from '$lib/types/application-configuration';
|
||||
import { createForm } from '$lib/utils/form-util';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
let {
|
||||
callback,
|
||||
applicationConfiguration
|
||||
appConfig
|
||||
}: {
|
||||
applicationConfiguration: AllApplicationConfiguration;
|
||||
callback: (user: AllApplicationConfiguration) => Promise<void>;
|
||||
appConfig: AllAppConfig;
|
||||
callback: (appConfig: Partial<AllAppConfig>) => Promise<void>;
|
||||
} = $props();
|
||||
|
||||
let isLoading = $state(false);
|
||||
|
||||
const updatedApplicationConfiguration: AllApplicationConfiguration = {
|
||||
appName: applicationConfiguration.appName,
|
||||
sessionDuration: applicationConfiguration.sessionDuration
|
||||
const updatedAppConfig = {
|
||||
appName: appConfig.appName,
|
||||
sessionDuration: appConfig.sessionDuration
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
@@ -32,15 +33,14 @@
|
||||
}
|
||||
)
|
||||
});
|
||||
type FormSchema = typeof formSchema;
|
||||
|
||||
const { inputs, ...form } = createForm<FormSchema>(formSchema, updatedApplicationConfiguration);
|
||||
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
|
||||
async function onSubmit() {
|
||||
const data = form.validate();
|
||||
if (!data) return;
|
||||
isLoading = true;
|
||||
await callback(data);
|
||||
isLoading = false;
|
||||
await callback(data).finally(() => (isLoading = false));
|
||||
toast.success('Application configuration updated successfully');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import OIDCService from '$lib/services/oidc-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import clientSecretStore from '$lib/stores/client-secret-store';
|
||||
import type { OidcClientCreateWithLogo } from '$lib/types/oidc.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { LucideMinus } from 'lucide-svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { slide } from 'svelte/transition';
|
||||
import OIDCClientForm from './oidc-client-form.svelte';
|
||||
import OIDCClientList from './oidc-client-list.svelte';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import clientSecretStore from '$lib/stores/client-secret-store';
|
||||
import { goto } from '$app/navigation';
|
||||
import applicationConfigurationStore from '$lib/stores/application-configuration-store';
|
||||
|
||||
let { data } = $props();
|
||||
let clients = $state(data);
|
||||
@@ -22,7 +22,7 @@
|
||||
async function createOIDCClient(client: OidcClientCreateWithLogo) {
|
||||
try {
|
||||
const createdClient = await oidcService.createClient(client);
|
||||
if(client.logo){
|
||||
if (client.logo) {
|
||||
await oidcService.updateClientLogo(createdClient, client.logo);
|
||||
}
|
||||
const clientSecret = await oidcService.createClientSecret(createdClient.id);
|
||||
@@ -31,7 +31,7 @@
|
||||
toast.success('OIDC client created successfully');
|
||||
return true;
|
||||
} catch (e) {
|
||||
axiosErrorToast(e)
|
||||
axiosErrorToast(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Create OIDC Client</Card.Title>
|
||||
<Card.Description>Add a new OIDC client to {$applicationConfigurationStore.appName}.</Card.Description>
|
||||
<Card.Description>Add a new OIDC client to {$appConfigStore.appName}.</Card.Description>
|
||||
</div>
|
||||
{#if !expandAddClient}
|
||||
<Button on:click={() => (expandAddClient = true)}>Add OIDC Client</Button>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import applicationConfigurationStore from '$lib/stores/application-configuration-store';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import type { Paginated } from '$lib/types/pagination.type';
|
||||
import type { User, UserCreate } from '$lib/types/user.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
@@ -42,9 +42,7 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Create User</Card.Title>
|
||||
<Card.Description
|
||||
>Add a new user to {$applicationConfigurationStore.appName}.</Card.Description
|
||||
>
|
||||
<Card.Description>Add a new user to {$appConfigStore.appName}.</Card.Description>
|
||||
</div>
|
||||
{#if !expandAddUser}
|
||||
<Button on:click={() => (expandAddUser = true)}>Add User</Button>
|
||||
|
||||
Reference in New Issue
Block a user