feat: add audit log with email notification (#26)

This commit is contained in:
Elias Schneider
2024-09-09 10:29:41 +02:00
committed by GitHub
parent 4010ee27d6
commit 9121239dd7
51 changed files with 944 additions and 163 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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