This commit is contained in:
Shahrad Elahi 2024-04-01 17:02:40 +03:30
parent 626380c1f7
commit d87ba2fc6a
55 changed files with 1264 additions and 585 deletions

View File

@ -1,13 +1,16 @@
#!/usr/bin/env bash
#!/bin/bash
set -e
# This script is used for getting last 100 lines of a screen session
LOG_DIR="/var/vlogs"
LOG_FILE="${LOG_DIR}/${1}"
SCREEN_NAME="$1"
LIMIT="${2:-100}"
SCRIPT=$(basename "${0}")
if [ ! -f "${LOG_FILE}" ]; then
echo "Usage: ${0} <filename>"
if [ -z "$SCREEN_NAME" ]; then
echo "Usage: ${SCRIPT} <screen_name>"
exit 1
fi
cat "${LOG_FILE}"
screen -S "$SCREEN_NAME" -X hardcopy /tmp/screen-hardcopy
tail -n "${LIMIT}" /tmp/screen-hardcopy
rm /tmp/screen-hardcopy

View File

@ -9,6 +9,5 @@ services:
environment:
- WG_HOST=192.168.1.102
- UI_PASSWORD=password
- TOR_SOCKS5_PROXY=host.docker.internal:8080
extra_hosts:
- "host.docker.internal:host-gateway"

View File

@ -101,7 +101,7 @@ awk -F= '!a[$1]++' "${ENV_FILE}" >"/tmp/$(basename "${ENV_FILE}")" &&
generate_tor_config
# Start Tor on the background
screen -L -Logfile /var/vlogs/tor -dmS "tor" tor -f "${TOR_CONFIG}"
screen -dmS "tor" tor -f "${TOR_CONFIG}"
sleep 1
echo -e "\n======================== Versions ========================"

View File

@ -15,47 +15,47 @@
},
"packageManager": "pnpm@8.15.0",
"engines": {
"node": ">=20.9.0"
"node": ">=20.0.0"
},
"devDependencies": {
"@sveltejs/adapter-node": "^4.0.1",
"@sveltejs/kit": "^2.5.0",
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/kit": "^2.5.5",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/chai": "^4.3.11",
"@types/jsonwebtoken": "^9.0.5",
"@types/chai": "^4.3.14",
"@types/jsonwebtoken": "^9.0.6",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.17",
"@types/node": "^20.12.2",
"@types/qrcode": "^1.5.5",
"autoprefixer": "^10.4.17",
"autoprefixer": "^10.4.19",
"chai": "^5.1.0",
"mocha": "^10.3.0",
"postcss": "^8.4.35",
"mocha": "^10.4.0",
"postcss": "^8.4.38",
"postcss-load-config": "^5.0.3",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.1",
"svelte": "^4.2.10",
"svelte-check": "^3.6.4",
"prettier-plugin-svelte": "^3.2.2",
"svelte": "^4.2.12",
"svelte-check": "^3.6.8",
"svelte-preprocess": "^5.1.3",
"sveltekit-superforms": "^1.13.4",
"tailwindcss": "^3.4.1",
"sveltekit-superforms": "^2.12.2",
"tailwindcss": "^3.4.3",
"tslib": "^2.6.2",
"tsx": "^4.7.1",
"typescript": "^5.3.3",
"vite": "^5.1.2",
"typescript": "^5.4.3",
"vite": "^5.2.7",
"zod": "^3.22.4"
},
"dependencies": {
"@litehex/storage-box": "^0.2.1",
"@litehex/storage-box": "^0.2.2-canary.0",
"@t3-oss/env-core": "0.7.3",
"bits-ui": "^0.18.0",
"clsx": "^2.1.0",
"deepmerge": "^4.3.1",
"dotenv": "^16.4.4",
"execa": "^8.0.1",
"formsnap": "^0.4.4",
"formsnap": "^1.0.0",
"jsonwebtoken": "^9.0.2",
"lucide-svelte": "^0.330.0",
"node-netkit": "0.1.0-canary.1",
"node-netkit": "0.1.0-canary.2",
"pino": "^8.18.0",
"pino-pretty": "^10.3.1",
"pretty-bytes": "^6.1.1",

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,21 @@
import type { Handle } from '@sveltejs/kit';
import { type Handle, redirect } from '@sveltejs/kit';
import { verifyToken } from '$lib/auth';
import { AUTH_COOKIE } from '$lib/constants';
import 'dotenv/config';
export const handle: Handle = async ({ event, resolve }) => {
if (!AUTH_EXCEPTION.includes(event.url.pathname)) {
const token = event.cookies.get('authorization');
const token = event.cookies.get(AUTH_COOKIE);
const token_valid = await verifyToken(token ?? '');
const redirect = new Response(null, { status: 302, headers: { location: '/login' } });
const is_login_page = event.url.pathname === '/login';
if (!token_valid && !is_login_page) {
return redirect;
// return redirect;
throw redirect(303, '/login');
}
if (token_valid && is_login_page) {
return new Response(null, { status: 302, headers: { location: '/' } });
throw redirect(303, '/');
}
}

View File

@ -1,10 +1,6 @@
import jwt from 'jsonwebtoken';
import { randomUUID } from 'node:crypto';
import { sha256 } from '$lib/hash';
import { client } from '$lib/storage';
import 'dotenv/config';
export const AUTH_SECRET = process.env.AUTH_SECRET || sha256(randomUUID());
import { env } from '$lib/env';
export async function generateToken(): Promise<string> {
const now = Math.floor(Date.now() / 1000);
@ -15,7 +11,7 @@ export async function generateToken(): Promise<string> {
iat: now,
exp: now + oneHour,
},
AUTH_SECRET,
env.AUTH_SECRET,
);
client.setex(token, '1', oneHour);
return token;
@ -23,7 +19,7 @@ export async function generateToken(): Promise<string> {
export async function verifyToken(token: string): Promise<boolean> {
try {
const decode = jwt.verify(token, AUTH_SECRET);
const decode = jwt.verify(token, env.AUTH_SECRET);
if (!decode) return false;
const exists = client.exists(token);

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { cn } from '$lib/utils.js';
import { cn } from '$lib/utils';
export let showInHover: boolean = false;
export let rootClass: string | undefined = undefined;

View File

@ -12,7 +12,9 @@
<div class="w-full min-h-screen flex justify-center px-2 md:px-6 py-2">
<div class={cn('w-full mx-auto max-w-3xl space-y-3.5', rootClass)}>
<PageHeader {showLogout} />
<main class={cn('py-2', className)}>
<main
class={cn('flex flex-col items-center justify-between py-3 px-2 [&>*]:w-full', className)}
>
<slot />
</main>
<PageFooter />

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { Button as ButtonPrimitive } from 'bits-ui';
import { cn } from '$lib/utils';
import { buttonVariants, type Props, type Events } from '.';
import { type Events, type Props, buttonVariants } from '.';
type $$Props = Props;
type $$Events = Events;

View File

@ -1,9 +1,9 @@
import Root from './button.svelte';
import { tv, type VariantProps } from 'tailwind-variants';
import { type VariantProps, tv } from 'tailwind-variants';
import type { Button as ButtonPrimitive } from 'bits-ui';
import Root from './button.svelte';
const buttonVariants = tv({
base: 'inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
base: 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { cn } from '$lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils';
type $$Props = HTMLAttributes<HTMLDivElement>;

View File

@ -1,7 +1,7 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import type { HeadingLevel } from './index.js';
import { cn } from '$lib/utils';
import type { HeadingLevel } from '.';
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
tag?: HeadingLevel;

View File

@ -1,6 +1,7 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
import { Check, Minus } from 'lucide-svelte';
import Check from 'lucide-svelte/icons/check';
import Minus from 'lucide-svelte/icons/minus';
import { cn } from '$lib/utils';
type $$Props = CheckboxPrimitive.Props;
@ -13,7 +14,7 @@
<CheckboxPrimitive.Root
class={cn(
'box-content peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50',
'peer box-content h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:opacity-50',
className,
)}
bind:checked
@ -21,7 +22,7 @@
on:click
>
<CheckboxPrimitive.Indicator
class={cn('flex items-center justify-center text-current h-4 w-4')}
class={cn('flex h-4 w-4 items-center justify-center text-current')}
let:isChecked
let:isIndeterminate
>

View File

@ -1,10 +1,10 @@
<script lang="ts">
import * as Button from '$lib/components/ui/button';
import * as Button from '$lib/components/ui/button/index.js';
type $$Props = Button.Props;
type $$Events = Button.Events;
</script>
<Button.Root type="submit" {...$$restProps} on:click on:keydown>
<Button.Root type="submit" on:click on:keydown {...$$restProps}>
<slot />
</Button.Root>

View File

@ -1,13 +1,17 @@
<script lang="ts">
import { Form as FormPrimitive } from 'formsnap';
import { cn } from '$lib/utils';
import * as FormPrimitive from 'formsnap';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils';
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<FormPrimitive.Description class={cn('text-sm text-muted-foreground', className)} {...$$restProps}>
<slot />
<FormPrimitive.Description
class={cn('text-sm text-muted-foreground', className)}
{...$$restProps}
let:descriptionAttrs
>
<slot {descriptionAttrs} />
</FormPrimitive.Description>

View File

@ -0,0 +1,26 @@
<script lang="ts" context="module">
import type { FormPathLeaves, SuperForm } from 'sveltekit-superforms';
type T = Record<string, unknown>;
type U = FormPathLeaves<T>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
import type { HTMLAttributes } from 'svelte/elements';
import * as FormPrimitive from 'formsnap';
import { cn } from '$lib/utils';
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<FormPrimitive.ElementField {form} {name} let:constraints let:errors let:tainted let:value>
<div class={cn('space-y-2', className)}>
<slot {constraints} {errors} {tainted} {value} />
</div>
</FormPrimitive.ElementField>

View File

@ -0,0 +1,26 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { cn } from '$lib/utils';
type $$Props = FormPrimitive.FieldErrorsProps & {
errorClasses?: string | undefined | null;
};
let className: $$Props['class'] = undefined;
export { className as class };
export let errorClasses: $$Props['class'] = undefined;
</script>
<FormPrimitive.FieldErrors
class={cn('text-sm font-medium text-destructive', className)}
{...$$restProps}
let:errors
let:fieldErrorsAttrs
let:errorAttrs
>
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
{#each errors as error}
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
{/each}
</slot>
</FormPrimitive.FieldErrors>

View File

@ -0,0 +1,26 @@
<script lang="ts" context="module">
import type { FormPath, SuperForm } from 'sveltekit-superforms';
type T = Record<string, unknown>;
type U = FormPath<T>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import type { HTMLAttributes } from 'svelte/elements';
import * as FormPrimitive from 'formsnap';
import { cn } from '$lib/utils';
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<FormPrimitive.Field {form} {name} let:constraints let:errors let:tainted let:value>
<div class={cn('space-y-2', className)}>
<slot {constraints} {errors} {tainted} {value} />
</div>
</FormPrimitive.Field>

View File

@ -0,0 +1,31 @@
<script lang="ts" context="module">
import type { FormPath, SuperForm } from 'sveltekit-superforms';
type T = Record<string, unknown>;
type U = FormPath<T>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from 'formsnap';
import { cn } from '$lib/utils';
type $$Props = FormPrimitive.FieldsetProps<T, U>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<FormPrimitive.Fieldset
{form}
{name}
let:constraints
let:errors
let:tainted
let:value
class={cn('space-y-2', className)}
>
<slot {constraints} {errors} {tainted} {value} />
</FormPrimitive.Fieldset>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import type { Label as LabelPrimitive } from 'bits-ui';
import { getFormField } from 'formsnap';
import { getFormControl } from 'formsnap';
import { cn } from '$lib/utils';
import { Label } from '$lib/components/ui/label';
@ -9,9 +9,9 @@
let className: $$Props['class'] = undefined;
export { className as class };
const { errors, ids } = getFormField();
const { labelAttrs } = getFormControl();
</script>
<Label for={$ids.input} class={cn($errors && 'text-destructive', className)} {...$$restProps}>
<slot />
<Label {...$labelAttrs} class={cn('data-[fs-error]:text-destructive', className)} {...$$restProps}>
<slot {labelAttrs} />
</Label>

View File

@ -0,0 +1,17 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { cn } from '$lib/utils';
type $$Props = FormPrimitive.LegendProps;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<FormPrimitive.Legend
{...$$restProps}
class={cn('text-sm font-medium leading-none data-[fs-error]:text-destructive', className)}
let:legendAttrs
>
<slot {legendAttrs} />
</FormPrimitive.Legend>

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { getFormField } from 'formsnap';
import type { HTMLTextareaAttributes } from 'svelte/elements';
import type { TextareaGetFormField } from '.';
import type { TextareaGetFormField } from './index';
import { Textarea, type TextareaEvents } from '$lib/components/ui/textarea';
type $$Props = HTMLTextareaAttributes;

View File

@ -1,82 +1,33 @@
import { Form as FormPrimitive, getFormField } from 'formsnap';
import * as RadioGroupComp from '$lib/components/ui/radio-group';
import * as SelectComp from '$lib/components/ui/select';
import type { Writable } from 'svelte/store';
import Item from './form-item.svelte';
import Input from './form-input.svelte';
import Textarea from './form-textarea.svelte';
import * as FormPrimitive from 'formsnap';
import Description from './form-description.svelte';
import Label from './form-label.svelte';
import Validation from './form-validation.svelte';
import Checkbox from './form-checkbox.svelte';
import Switch from './form-switch.svelte';
import NativeSelect from './form-native-select.svelte';
import RadioGroup from './form-radio-group.svelte';
import Select from './form-select.svelte';
import SelectTrigger from './form-select-trigger.svelte';
import FieldErrors from './form-field-errors.svelte';
import Field from './form-field.svelte';
import Fieldset from './form-fieldset.svelte';
import Legend from './form-legend.svelte';
import ElementField from './form-element-field.svelte';
import Button from './form-button.svelte';
const Root = FormPrimitive.Root;
const Field = FormPrimitive.Field;
const Control = FormPrimitive.Control;
const RadioItem = RadioGroupComp.Item;
const NativeRadio = FormPrimitive.Radio;
const SelectContent = SelectComp.Content;
const SelectLabel = SelectComp.Label;
const SelectGroup = SelectComp.Group;
const SelectItem = SelectComp.Item;
const SelectSeparator = SelectComp.Separator;
export type TextareaGetFormField = Omit<ReturnType<typeof getFormField>, 'value'> & {
value: Writable<string>;
};
export {
Root,
Field,
Control,
Item,
Input,
Label,
Button,
Switch,
Select,
Checkbox,
Textarea,
Validation,
RadioGroup,
RadioItem,
FieldErrors,
Description,
SelectContent,
SelectLabel,
SelectGroup,
SelectItem,
SelectSeparator,
SelectTrigger,
NativeSelect,
NativeRadio,
Fieldset,
Legend,
ElementField,
//
Root as Form,
Field as FormField,
Control as FormControl,
Item as FormItem,
Input as FormInput,
Textarea as FormTextarea,
Description as FormDescription,
Label as FormLabel,
Validation as FormValidation,
NativeSelect as FormNativeSelect,
NativeRadio as FormNativeRadio,
Checkbox as FormCheckbox,
Switch as FormSwitch,
RadioGroup as FormRadioGroup,
RadioItem as FormRadioItem,
Select as FormSelect,
SelectContent as FormSelectContent,
SelectLabel as FormSelectLabel,
SelectGroup as FormSelectGroup,
SelectItem as FormSelectItem,
SelectSeparator as FormSelectSeparator,
SelectTrigger as FormSelectTrigger,
FieldErrors as FormFieldErrors,
Fieldset as FormFieldset,
Legend as FormLegend,
ElementField as FormElementField,
Button as FormButton,
};

View File

@ -1,15 +1,16 @@
import { Select as SelectPrimitive } from 'bits-ui';
import Root from './select.svelte';
import Label from './select-label.svelte';
import Item from './select-item.svelte';
import Content from './select-content.svelte';
import Trigger from './select-trigger.svelte';
import Separator from './select-separator.svelte';
const Root = SelectPrimitive.Root;
const Group = SelectPrimitive.Group;
const Input = SelectPrimitive.Input;
const Value = SelectPrimitive.Value;
export {
Root,
Group,

View File

@ -1,10 +1,12 @@
<script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui';
import { cn, flyAndScale } from '$lib/utils';
import { scale } from 'svelte/transition';
import { cn, flyAndScale } from '$lib/utils';
type $$Props = SelectPrimitive.ContentProps;
type $$Events = SelectPrimitive.ContentEvents;
export let sideOffset: $$Props['sideOffset'] = 4;
export let inTransition: $$Props['inTransition'] = flyAndScale;
export let inTransitionConfig: $$Props['inTransitionConfig'] = undefined;
export let outTransition: $$Props['outTransition'] = scale;
@ -23,6 +25,7 @@
{inTransitionConfig}
{outTransition}
{outTransitionConfig}
{sideOffset}
class={cn(
'relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md outline-none',
className,

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { cn } from '$lib/utils';
import Check from 'lucide-svelte/icons/check';
import { Select as SelectPrimitive } from 'bits-ui';
import { Check } from 'lucide-svelte';
import { cn } from '$lib/utils';
type $$Props = SelectPrimitive.ItemProps;
type $$Events = SelectPrimitive.ItemEvents;
@ -18,7 +18,7 @@
{disabled}
{label}
class={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
className,
)}
{...$$restProps}
@ -34,5 +34,7 @@
<Check class="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<slot />
<slot>
{label || value}
</slot>
</SelectPrimitive.Item>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui';
import { ChevronDown } from 'lucide-svelte';
import ChevronDown from 'lucide-svelte/icons/chevron-down';
import { cn } from '$lib/utils';
type $$Props = SelectPrimitive.TriggerProps;
@ -12,7 +12,7 @@
<SelectPrimitive.Trigger
class={cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className,
)}
{...$$restProps}

View File

@ -1,12 +0,0 @@
<script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui';
type $$Props = SelectPrimitive.Props;
export let selected: $$Props['selected'] = undefined;
export let open: $$Props['open'] = undefined;
</script>
<SelectPrimitive.Root bind:selected bind:open {...$$restProps}>
<slot />
</SelectPrimitive.Root>

View File

@ -3,6 +3,7 @@
import { cn } from '$lib/utils';
type $$Props = SwitchPrimitive.Props;
type $$Events = SwitchPrimitive.Events;
let className: $$Props['class'] = undefined;
export let checked: $$Props['checked'] = undefined;
@ -16,6 +17,8 @@
className,
)}
{...$$restProps}
on:click
on:keydown
>
<SwitchPrimitive.Thumb
class={cn(

View File

@ -11,7 +11,7 @@
<textarea
class={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
bind:value

View File

@ -1,5 +1,5 @@
export const WG_PATH = '/etc/wireguard';
export const IPV4_REGEX = new RegExp(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
export const WG_SEVER_PATH = `WG::SERVERS`;
export const AUTH_COOKIE = 'authorization';

View File

@ -1,9 +1,15 @@
import { createEnv } from '@t3-oss/env-core';
import { z } from 'zod';
import { sha256 } from '$lib/hash';
import { randomUUID } from 'node:crypto';
import 'dotenv/config';
export const env = createEnv({
runtimeEnv: process.env,
server: {
STORAGE_PATH: z.string().default('/data/storage.pack'),
AUTH_SECRET: z.string().default(sha256(randomUUID())),
HASHED_PASSWORD: z.string(),
ORIGIN: z.string().optional(),
},
});

View File

@ -1,32 +0,0 @@
import fs from 'fs';
import path from 'path';
export default class FileManager {
static readDirectoryFiles(dir: string): string[] {
const files_: string[] = [];
const files = fs.readdirSync(dir);
for (const i in files) {
const name = dir + '/' + files[i];
if (!fs.statSync(name).isDirectory()) {
files_.push(path.resolve(process.cwd(), name));
}
}
return files_;
}
static readFile(filePath: string): string {
if (!fs.existsSync(filePath)) {
throw new Error('file not found');
}
return fs.readFileSync(filePath, { encoding: 'utf8' });
}
static writeFile(filePath: string, content: string, forced: boolean = false): void {
const dir_ = filePath.split('/');
const dir = dir_.slice(0, dir_.length - 1).join('/');
if (!fs.existsSync(dir) && forced) {
fs.mkdirSync(dir, { mode: 0o744 });
}
fs.writeFileSync(filePath, content, { encoding: 'utf8' });
}
}

View File

@ -1,4 +1,4 @@
import { accessSync, promises } from 'fs';
import { accessSync, promises } from 'node:fs';
export function fsAccess(path: string): boolean {
try {

View File

@ -5,7 +5,7 @@ export const SERVICES = <const>{
tor: {
name: 'Tor',
command: {
start: 'screen -L -Logfile /var/vlogs/tor -dmS "tor" tor -f /etc/tor/torrc',
start: 'screen -dmS "tor" tor -f /etc/tor/torrc',
stop: 'pkill tor',
logs: 'logs tor',
},

View File

@ -1,6 +1,6 @@
import { z } from 'zod';
import { IPV4_REGEX } from '$lib/constants';
import { NameSchema, TorSchema } from '$lib/wireguard/schema';
import { IPV4_REGEX } from 'node-netkit/ip';
export const WgKeySchema = z.object({
privateKey: z.string(),

View File

@ -2,19 +2,6 @@ import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { cubicOut } from 'svelte/easing';
import type { TransitionConfig } from 'svelte/transition';
import { IPV4_REGEX } from '$lib/constants';
export function isValidIPv4(str: string): boolean {
return IPV4_REGEX.test(str);
}
export function isBetween(v: any, n1: number, n2: number): boolean {
if (Number.isNaN(v)) {
return false;
}
const n = Number(v);
return n1 <= n && n >= n2;
}
export function isJson(data: any): boolean {
if (typeof data !== 'string') {
@ -28,27 +15,10 @@ export function isJson(data: any): boolean {
}
}
export function isObject(obj: object) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
export async function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Private IP Address Identifier in Regular Expression
*
* 127. 0.0.0 127.255.255.255 127.0.0.0 /8
* 10. 0.0.0 10.255.255.255 10.0.0.0 /8
* 172. 16.0.0 172. 31.255.255 172.16.0.0 /12
* 192.168.0.0 192.168.255.255 192.168.0.0 /16
*/
export function isPrivateIP(ip: string) {
const ipRegex = /^(127\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.)/;
return ipRegex.test(ip);
}
export function dynaJoin(lines: (string | 0 | null | undefined)[]): string[] {
return lines.filter((d) => typeof d === 'string') as string[];
}

14
web/src/lib/utils/ip.ts Normal file
View File

@ -0,0 +1,14 @@
export const IPV4_REGEX = new RegExp(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
/**
* Private IP Address Identifier in Regular Expression
*
* 127. 0.0.0 127.255.255.255 127.0.0.0 /8
* 10. 0.0.0 10.255.255.255 10.0.0.0 /8
* 172. 16.0.0 172. 31.255.255 172.16.0.0 /12
* 192.168.0.0 192.168.255.255 192.168.0.0 /16
*/
export function isPrivateIP(ip: string) {
const ipRegex = /^(127\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.)/;
return ipRegex.test(ip);
}

View File

@ -0,0 +1,7 @@
export function isBetween(v: any, n1: number, n2: number): boolean {
if (Number.isNaN(v)) {
return false;
}
const n = Number(v);
return n1 <= n && n >= n2;
}

View File

@ -0,0 +1,3 @@
export function isObject(obj: object) {
return Object.prototype.toString.call(obj) === '[object Object]';
}

View File

@ -1,4 +1,4 @@
import fs from 'fs';
import fs from 'node:fs';
import path from 'path';
import deepmerge from 'deepmerge';
import type { Peer, WgKey, WgServer } from '$lib/typings';
@ -192,7 +192,7 @@ export class WGServer {
static async getFreePeerIp(serverId: string): Promise<string | undefined> {
const server = await findServer(serverId);
if (!server) {
logger.error('WGSerevr: GetFreePeerIP: no server found');
logger.error('WGServer: GetFreePeerIP: no server found');
return undefined;
}

View File

@ -1,6 +1,6 @@
import { z } from 'zod';
import { isBetween, isPrivateIP } from '$lib/utils';
import { IPV4_REGEX } from '$lib/constants';
import { isBetween } from '$lib/utils/number';
import { IPV4_REGEX, isPrivateIP } from '$lib/utils/ip';
export const NameSchema = z
.string()

View File

@ -1,4 +1,4 @@
import { type Actions, error } from '@sveltejs/kit';
import { type Actions, error, fail } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import {
findServer,
@ -8,15 +8,16 @@ import {
isPortReserved,
WGServer,
} from '$lib/wireguard';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { CreateServerSchema } from './schema';
import { setError, superValidate } from 'sveltekit-superforms';
import { createServerSchema } from './schema';
import { NameSchema } from '$lib/wireguard/schema';
import logger from '$lib/logger';
import { zod } from 'sveltekit-superforms/adapters';
export const load: PageServerLoad = async () => {
return {
servers: getServers(),
form: superValidate(CreateServerSchema),
form: await superValidate(zod(createServerSchema)),
};
};
@ -43,9 +44,11 @@ export const actions: Actions = {
return { ok: true };
},
create: async (event) => {
const form = await superValidate(event, CreateServerSchema);
const form = await superValidate(event, zod(createServerSchema));
if (!form.valid) {
return setError(form, 'Bad Request');
return fail(400, {
form,
});
}
const { name, address, tor = false, port, dns, mtu = '1350' } = form.data;

View File

@ -1,13 +1,13 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import BasePage from '$lib/components/page/BasePage.svelte';
import type { PageData } from './$types';
import CreateServerDialog from './CreateServerDialog.svelte';
import Server from './Server.svelte';
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
import fetchAction from '$lib/fetch-action';
import { Empty } from '$lib/components/empty';
import Service from './Service.svelte';
import { Empty } from '$lib/components/empty';
import Server from './Server.svelte';
import fetchAction from '$lib/fetch-action';
import CreateServerDialog from './CreateServerDialog.svelte';
import { Button } from '$lib/components/ui/button';
export let data: PageData;
@ -34,7 +34,7 @@
<div class={'flex items-center justify-between py-3 px-2'}>
<h2 class={'font-bold text-xl'}>Hello there 👋</h2>
<CreateServerDialog let:builder>
<CreateServerDialog data={data.form} let:builder>
<Button builders={[builder]}>
<i class="fas fa-plus mr-2"></i>
Create Server
@ -42,7 +42,7 @@
</CreateServerDialog>
</div>
<div class="space-y-3.5">
<div class="w-full space-y-3.5">
<Card>
{#if data.servers?.length < 1}
<Empty description={'No server!'} />
@ -72,5 +72,5 @@
</Service>
</CardContent>
</Card>
</div>
</BasePage>
</div></BasePage
>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { CreateServerSchema, type CreateServerSchemaType } from './schema';
import type { SuperValidated } from 'sveltekit-superforms';
import { createServerSchema, type CreateServerSchemaType } from './schema';
import { type Infer, superForm, type SuperValidated } from 'sveltekit-superforms';
import {
Dialog,
DialogContent,
@ -10,16 +10,13 @@
DialogTrigger,
} from '$lib/components/ui/dialog';
import {
Form,
FormButton,
FormControl,
FormDescription,
FormField,
FormInput,
FormFieldErrors,
FormLabel,
FormSwitch,
FormValidation,
} from '$lib/components/ui/form';
import { FormItem } from '$lib/components/ui/form/index.js';
import { cn } from '$lib/utils';
import {
Collapsible,
@ -28,11 +25,38 @@
} from '$lib/components/ui/collapsible';
import { Button } from '$lib/components/ui/button';
import toast from 'svelte-french-toast';
import { Input } from '$lib/components/ui/input';
import { zodClient } from 'sveltekit-superforms/adapters';
import { Switch } from '$lib/components/ui/switch';
let loading: boolean = false;
let dialogOpen = false;
let form: SuperValidated<CreateServerSchemaType>;
export let data: SuperValidated<Infer<CreateServerSchemaType>>;
const form = superForm(data, {
dataType: 'json',
validators: zodClient(createServerSchema),
onSubmit: () => {
loading = true;
},
onError: (e) => {
loading = false;
console.error('Client-side: FormError:', e);
},
onResult: ({ result }) => {
loading = false;
if (result.type === 'success') {
dialogOpen = false;
toast.success('Server created successfully!');
} else {
console.error('Server-failure: Result:', result);
toast.error('Server failed to create.');
}
},
});
const { form: formData, enhance } = form;
</script>
<Dialog bind:open={dialogOpen}>
@ -43,98 +67,82 @@
<DialogHeader>
<DialogTitle>Create Server</DialogTitle>
</DialogHeader>
<Form
{form}
schema={CreateServerSchema}
class="space-y-3.5"
action="?/create"
method={'POST'}
let:config
options={{
onSubmit: () => {
loading = true;
},
onError: (e) => {
loading = false;
console.error('Client-side: FormError:', e);
},
onResult: ({ result }) => {
loading = false;
if (result.type === 'success') {
dialogOpen = false;
toast.success('Server created successfully!');
} else {
console.error('Server-failure: Result:', result);
toast.error('Server failed to create.');
}
},
}}
>
<FormField {config} name={'name'}>
<FormItem>
<form method="POST" action="?/create" use:enhance>
<FormField {form} name={'name'}>
<FormControl let:attrs>
<FormLabel>Name</FormLabel>
<FormInput placeholder={'e.g. CuteHub'} type={'text'} />
<FormValidation />
</FormItem>
<Input
{...attrs}
bind:value={$formData.name}
placeholder={'e.g. CuteHub'}
type={'text'}
/>
<FormFieldErrors />
</FormControl>
</FormField>
<FormField {config} name={'address'}>
<FormItem>
<FormField {form} name={'address'}>
<FormControl let:attrs>
<FormLabel>Address</FormLabel>
<FormInput placeholder={'e.g. 10.8.0.1'} type={'text'} />
<FormDescription>This is the Private IP Address of the server.</FormDescription>
<FormValidation />
</FormItem>
<Input
{...attrs}
bind:value={$formData.address}
placeholder={'e.g. 10.8.0.1'}
type={'text'}
/>
</FormControl>
<FormDescription>This is the Private IP Address of the server.</FormDescription>
<FormFieldErrors />
</FormField>
<FormField {config} name={'port'}>
<FormItem>
<FormField {form} name={'port'}>
<FormControl let:attrs>
<FormLabel>Port</FormLabel>
<FormInput placeholder={'e.g. 51820'} type={'text'} />
<FormDescription
>This is the port that the WireGuard server will listen on.</FormDescription
>
<FormValidation />
</FormItem>
<Input {...attrs} bind:value={$formData.port} placeholder={'e.g. 51820'} type={'text'} />
</FormControl>
<FormDescription>This is the port that the WireGuard server will listen on.</FormDescription
>
<FormFieldErrors />
</FormField>
<Collapsible>
<CollapsibleTrigger asChild let:builder>
<Button builders={[builder]} variant="ghost" size="sm" class="mb-4 -mr-2">
<i class="far fa-cog mr-2"></i>
<i
class={cn('far fa-cog mr-2', builder['data-state'] === 'open' ? 'animate-spin' : '')}
/>
<span>Advanced Options</span>
</Button>
</CollapsibleTrigger>
<CollapsibleContent class="space-y-6">
<FormField {config} name={'tor'}>
<FormItem class="flex items-center justify-between">
<div class="space-y-0.5">
<FormLabel>Use Tor</FormLabel>
<FormDescription>This will route all outgoing traffic through Tor.</FormDescription>
</div>
<FormSwitch />
</FormItem>
<FormField {form} name={'tor'}>
<FormControl let:attrs>
<Switch {...attrs} bind:checked={$formData.tor} />
<FormLabel>Use Tor</FormLabel>
</FormControl>
<FormDescription>This will route all outgoing traffic through Tor.</FormDescription>
</FormField>
<FormField {config} name={'dns'}>
<FormItem>
<FormField {form} name={'dns'}>
<FormControl let:attrs>
<FormLabel>DNS</FormLabel>
<FormInput placeholder={'e.g. 1.1.1.1'} type={'text'} />
<FormDescription
>Optional. This is the DNS server that will be pushed to clients.</FormDescription
>
<FormValidation />
</FormItem>
<Input placeholder={'e.g. 1.1.1.1'} type={'text'} />
</FormControl>
<FormDescription
>Optional. This is the DNS server that will be pushed to clients.</FormDescription
>
<FormFieldErrors />
</FormField>
<FormField {config} name={'mtu'}>
<FormItem>
<FormField {form} name={'mtu'}>
<FormControl let:attrs>
<FormLabel>MTU</FormLabel>
<FormInput placeholder={'1350'} type={'text'} />
<FormDescription>Optional. Recommended to leave this blank.</FormDescription>
<FormValidation />
</FormItem>
<Input {...attrs} bind:value={$formData.mtu} placeholder={'1350'} type={'text'} />
</FormControl>
<FormDescription>Optional. Recommended to leave this blank.</FormDescription>
<FormFieldErrors />
</FormField>
</CollapsibleContent>
</Collapsible>
@ -146,6 +154,6 @@
Create
</FormButton>
</DialogFooter>
</Form>
</form>
</DialogContent>
</Dialog>

View File

@ -2,13 +2,14 @@ import { type Actions, error, redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { findServer, generateWgKey, WGServer } from '$lib/wireguard';
import { NameSchema } from '$lib/wireguard/schema';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { CreatePeerSchema } from './schema';
import { setError, superValidate } from 'sveltekit-superforms';
import { createPeerSchema } from './schema';
import logger from '$lib/logger';
import { zod } from 'sveltekit-superforms/adapters';
export const load: PageServerLoad = async ({ params }) => {
const { serverId } = params;
const exists = await WGServer.exists(serverId ?? '');
const exists = WGServer.exists(serverId ?? '');
if (!exists) {
logger.warn(`Server not found. Redirecting to home page. ServerId: ${serverId}`);
@ -31,6 +32,7 @@ export const load: PageServerLoad = async ({ params }) => {
return {
server,
usage,
form: await superValidate(zod(createPeerSchema)),
};
};
@ -153,7 +155,7 @@ export const actions: Actions = {
return { ok: true };
},
create: async (event) => {
const form = await superValidate(event, CreatePeerSchema);
const form = await superValidate(event, zod(createPeerSchema));
if (!form.valid) {
logger.warn('CreatePeer: Bad Request: failed to validate form');
return setError(form, 'Bad Request');

View File

@ -15,6 +15,7 @@
import { onDestroy } from 'svelte';
export let data: PageData;
let dialogOpen = false;
const handleRename = async (peerId: string, name: string) => {
const resp = await fetchAction({
@ -75,7 +76,9 @@
// revalidate every 2 seconds
const interval = setInterval(() => {
invalidateAll();
if (!dialogOpen) {
invalidateAll();
}
}, 2000);
onDestroy(() => {
@ -206,8 +209,9 @@
<Empty description={'No Clients!'} />
</CardContent>
{/if}
<CardFooter>
<CreatePeerDialog let:builder>
<CreatePeerDialog data={data.form} let:builder bind:open={dialogOpen}>
<Button size="sm" builders={[builder]}>Add Client</Button>
</CreatePeerDialog>
</CardFooter>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { CreatePeerSchema, type CreatePeerSchemaType } from './schema';
import type { SuperValidated } from 'sveltekit-superforms';
import { createPeerSchema, type CreatePeerSchemaType } from './schema';
import { type Infer, superForm, type SuperValidated } from 'sveltekit-superforms';
import {
Dialog,
DialogContent,
@ -10,31 +10,54 @@
DialogTrigger,
} from '$lib/components/ui/dialog';
import {
Form,
FormButton,
FormControl,
FormField,
FormInput,
FormItem,
FormFieldErrors,
FormLabel,
FormValidation,
} from '$lib/components/ui/form';
import { cn } from '$lib/utils';
import { invalidateAll } from '$app/navigation';
import toast from 'svelte-french-toast';
import { zodClient } from 'sveltekit-superforms/adapters';
import { Input } from '$lib/components/ui/input';
let loading: boolean = false;
let dialogOpen = false;
export let open = false;
let form: SuperValidated<CreatePeerSchemaType>;
export let data: SuperValidated<Infer<CreatePeerSchemaType>>;
const form = superForm(data, {
dataType: 'json',
validators: zodClient(createPeerSchema),
onSubmit: () => {
loading = true;
},
onError: (e) => {
loading = false;
console.error('Client-side: FormError:', e);
},
onResult: ({ result }) => {
if (result.type === 'success') {
handleSuccess();
} else {
toast.error('Failed to create peer');
console.error('Server-failure: Result:', result);
}
loading = false;
},
});
const { form: formData, enhance } = form;
const handleSuccess = async () => {
await invalidateAll();
toast.success('Peer created!');
dialogOpen = false;
open = false;
};
</script>
<Dialog bind:open={dialogOpen}>
<Dialog bind:open>
<DialogTrigger asChild let:builder>
<slot {builder} />
</DialogTrigger>
@ -42,38 +65,18 @@
<DialogHeader>
<DialogTitle>Create Peer</DialogTitle>
</DialogHeader>
<Form
{form}
schema={CreatePeerSchema}
class="space-y-3.5"
action="?/create"
method={'POST'}
let:config
options={{
onSubmit: () => {
loading = true;
},
onError: (e) => {
loading = false;
console.error('Client-side: FormError:', e);
},
onResult: ({ result }) => {
if (result.type === 'success') {
handleSuccess();
} else {
toast.error('Failed to create peer');
console.error('Server-failure: Result:', result);
}
loading = false;
},
}}
>
<FormField {config} name={'name'}>
<FormItem>
<form class="space-y-3.5" method="POST" action="?/create" use:enhance>
<FormField {form} name={'name'}>
<FormControl let:attrs>
<FormLabel>Name</FormLabel>
<FormInput placeholder={'e.g. Unicorn'} type={'text'} />
<FormValidation />
</FormItem>
<Input
{...attrs}
bind:value={$formData.name}
placeholder={'e.g. Unicorn'}
type={'text'}
/>
</FormControl>
<FormFieldErrors />
</FormField>
<DialogFooter>
@ -83,6 +86,6 @@
Create
</FormButton>
</DialogFooter>
</Form>
</form>
</DialogContent>
</Dialog>

View File

@ -1,8 +1,8 @@
import { NameSchema } from '$lib/wireguard/schema';
import { z } from 'zod';
export const CreatePeerSchema = z.object({
export const createPeerSchema = z.object({
name: NameSchema,
});
export type CreatePeerSchemaType = typeof CreatePeerSchema;
export type CreatePeerSchemaType = typeof createPeerSchema;

View File

@ -1,6 +1,6 @@
import type { RequestHandler } from '@sveltejs/kit';
import logger from '$lib/logger';
import { execaCommand } from 'execa';
import { execa } from 'execa';
import 'dotenv/config';
export const GET: RequestHandler = async () => {

View File

@ -1,30 +1,30 @@
import type { Actions } from '@sveltejs/kit';
import { fail } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { setError, superValidate } from 'sveltekit-superforms';
import { formSchema } from './schema';
import { generateToken } from '$lib/auth';
import logger from '$lib/logger';
import dotenv from 'dotenv';
import { zod } from 'sveltekit-superforms/adapters';
import { env } from '$lib/env';
import { AUTH_COOKIE } from '$lib/constants';
export const load: PageServerLoad = () => {
export const load: PageServerLoad = async () => {
return {
form: superValidate(formSchema),
form: await superValidate(zod(formSchema)),
};
};
export const actions: Actions = {
default: async (event) => {
const { cookies } = event;
const form = await superValidate(event, formSchema);
const form = await superValidate(event, zod(formSchema));
if (!form.valid) {
return fail(400, { ok: false, message: 'Bad Request', form });
}
dotenv.config();
const { HASHED_PASSWORD } = process.env;
const { HASHED_PASSWORD } = env;
if (HASHED_PASSWORD && HASHED_PASSWORD !== '') {
const { password } = form.data;
@ -34,18 +34,14 @@ export const actions: Actions = {
if (hashed !== receivedHashed) {
return setError(form, 'password', 'Incorrect password.');
}
}
if (!HASHED_PASSWORD || HASHED_PASSWORD === '') {
} else {
logger.warn('No password is set!');
}
const token = await generateToken();
const { ORIGIN } = process.env;
const secure = ORIGIN?.startsWith('https://') ?? false;
cookies.set('authorization', token, {
const secure = env.ORIGIN?.startsWith('https://') ?? false;
cookies.set(AUTH_COOKIE, token, {
secure,
httpOnly: true,
path: '/',

View File

@ -1,26 +1,23 @@
<script lang="ts">
import { formSchema, type FormSchema } from './schema';
import type { SuperValidated } from 'sveltekit-superforms';
import { type Infer, superForm, type SuperValidated } from 'sveltekit-superforms';
import { Card, CardContent } from '$lib/components/ui/card';
import {
Form,
FormButton,
FormControl,
FormField,
FormInput,
FormItem,
FormFieldErrors,
FormLabel,
FormValidation,
} from '$lib/components/ui/form';
import { zodClient } from 'sveltekit-superforms/adapters';
import { goto } from '$app/navigation';
import type { FormOptions } from 'formsnap';
import { Input } from '$lib/components/ui/input';
export let form: SuperValidated<FormSchema>;
export let data: SuperValidated<Infer<FormSchema>>;
const options: FormOptions<typeof formSchema> = {
validators: formSchema,
warnings: {
noValidationAndConstraints: false,
},
const form = superForm(data, {
dataType: 'json',
validators: zodClient(formSchema),
onResult: ({ result }) => {
if (result.type === 'success') {
goto('/');
@ -28,29 +25,29 @@
console.error('Server-failure: Validation failed');
}
},
};
});
const { form: formData, enhance } = form;
</script>
<Card>
<CardContent>
<Form {form} {options} schema={formSchema} let:config let:enhance asChild>
<form method="POST" class="pt-4 space-y-8" use:enhance>
<div class="w-full flex items-center justify-center">
<div class="w-16 aspect-square flex items-center justify-center rounded-full bg-gray-200">
<i class="fas fa-user text-primary text-2xl" />
</div>
<form method="POST" class="pt-4 space-y-8" use:enhance>
<div class="w-full flex items-center justify-center">
<div class="w-16 aspect-square flex items-center justify-center rounded-full bg-gray-200">
<i class="fas fa-user text-primary text-2xl" />
</div>
</div>
<FormField {config} name="password">
<FormItem>
<FormLabel>Password</FormLabel>
<FormInput type="password" autocomplete="off" />
<FormValidation />
</FormItem>
</FormField>
<FormField {form} name="password">
<FormControl let:attrs>
<FormLabel>Password</FormLabel>
<Input {...attrs} bind:value={$formData.password} type="password" autocomplete="off" />
</FormControl>
<FormFieldErrors />
</FormField>
<FormButton class="w-full">Sign In</FormButton>
</form>
</Form>
<FormButton class="w-full">Sign In</FormButton>
</form>
</CardContent>
</Card>

View File

@ -1,13 +1,14 @@
import type { PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import { revokeToken } from '$lib/auth';
import { AUTH_COOKIE } from '$lib/constants';
export const load: PageServerLoad = async ({ cookies }) => {
if (!!cookies.get('authorization')) {
const token = cookies.get('authorization')!;
await revokeToken(token).catch(() => {});
const authToken = cookies.get(AUTH_COOKIE);
if (!!authToken) {
await revokeToken(authToken).catch(() => {});
}
cookies.delete('authorization', {
cookies.delete(AUTH_COOKIE, {
path: '/',
});
throw redirect(302, '/login');

View File

@ -8,7 +8,7 @@ import {
TorSchema,
} from '$lib/wireguard/schema';
export const CreateServerSchema = z.object({
export const createServerSchema = z.object({
name: NameSchema,
address: AddressSchema,
port: PortSchema,
@ -17,4 +17,4 @@ export const CreateServerSchema = z.object({
mtu: MtuSchema,
});
export type CreateServerSchemaType = typeof CreateServerSchema;
export type CreateServerSchemaType = typeof createServerSchema;