Browse Source

Chore: TODOs (#1695)

* verify setup step

* improve readme

* format todos

* move id

* remove objectMessage

* style array field
pull/1696/head
Bernd Storath 5 months ago
committed by GitHub
parent
commit
3102fa1be9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 51
      README.md
  2. 19
      src/app/components/Form/ArrayField.vue
  3. 2
      src/app/components/Ui/Footer.vue
  4. 2
      src/app/pages/index.vue
  5. 4
      src/i18n/locales/en.json
  6. 2
      src/server/api/session.post.ts
  7. 5
      src/server/api/setup/2.post.ts
  8. 5
      src/server/api/setup/4.post.ts
  9. 2
      src/server/api/setup/migrate.post.ts
  10. 3
      src/server/database/repositories/client/service.ts
  11. 2
      src/server/database/repositories/general/types.ts
  12. 1
      src/server/database/repositories/oneTimeLink/service.ts
  13. 7
      src/server/database/repositories/oneTimeLink/types.ts
  14. 1
      src/server/database/repositories/user/service.ts
  15. 28
      src/server/database/repositories/user/types.ts
  16. 3
      src/server/database/schema.ts
  17. 2
      src/server/middleware/setup.ts
  18. 1
      src/server/utils/WireGuard.ts
  19. 29
      src/server/utils/handler.ts
  20. 1
      src/server/utils/session.ts
  21. 12
      src/server/utils/types.ts

51
README.md

@ -1,13 +1,16 @@
# WireGuard Easy
[![Build & Publish Docker Image to Docker Hub](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml/badge.svg?branch=production)](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml)
[![Build & Publish latest Image](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml/badge.svg?branch=production)](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml)
[![Lint](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml)
![Docker](https://img.shields.io/docker/pulls/weejewel/wg-easy.svg)
[![Sponsor](https://img.shields.io/github/sponsors/weejewel)](https://github.com/sponsors/WeeJeWel)
![GitHub Stars](https://img.shields.io/github/stars/wg-easy/wg-easy)
[![GitHub Stars](https://img.shields.io/github/stars/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/stargazers)
[![License](https://img.shields.io/github/license/wg-easy/wg-easy)](LICENSE)
[![GitHub Release](https://img.shields.io/github/v/release/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/releases/latest)
[![Image Pulls](https://img.shields.io/badge/image_pulls-11M-blue)](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy)
You have found the easiest way to install & manage WireGuard on any Linux host!
<!-- TOOD: update screenshot -->
<p align="center">
<img src="./assets/screenshot.png" width="802" />
</p>
@ -24,10 +27,12 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
- Gravatar support.
- Automatic Light / Dark Mode
- Multilanguage Support
- Traffic Stats (default off)
- One Time Links (default off)
- Client Expiration (default off)
- Prometheus metrics support (default off)
- Traffic Stats
- One Time Links
- Client Expiration
- Prometheus metrics support
- IPv6 support
- CIDR support
## Requirements
@ -41,7 +46,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
We offer multiple Docker image tags to suit your needs. The table below is in a particular order, with the first tag being the most recommended:
| tag | Branch | Example | Description |
| ------------- | ------------------------------------------------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| ------------- | ---------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `15` | latest minor for that major tag | `ghcr.io/wg-easy/wg-easy:15` | latest features for specific major versions, no breaking changes |
| `latest` | latest tag | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | stable as possible get bug fixes quickly when needed, see Releases for more information. |
| `15.0` | latest patch for that minor tag | `ghcr.io/wg-easy/wg-easy:15.0` | latest patches for specific minor version |
@ -65,8 +70,16 @@ And log in again.
### 2. Run WireGuard Easy
<!-- TODO: prioritize docker compose over docker run -->
The easiest way to run WireGuard Easy is with Docker Compose.
Just download [`docker-compose.yml`](docker-compose.yml), make necessary adjustments and
execute `docker compose up -d`.
The Web UI will now be available on `http://0.0.0.0:51821`.
<!-- TOOD: add to docs: Grafana dashboard [21733](https://grafana.com/grafana/dashboards/21733-wireguard/) -->
<!-- TOOD: add to docs
To setup the IPv6 Network, simply run once:
```bash
@ -107,20 +120,24 @@ The Prometheus metrics will now be available on `http://0.0.0.0:51821/api/metric
> 💡 Your configuration files will be saved in `~/.wg-easy`
WireGuard Easy can be launched with Docker Compose as well - just download
[`docker-compose.yml`](docker-compose.yml), make necessary adjustments and
execute `docker compose up -d`.
-->
### 3. Sponsor
Are you enjoying this project? [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻
Are you enjoying this project? Consider donating.
Founder: [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻
Maintainer: [Buy kaaax0815 a coffee!](https://github.com/sponsors/kaaax0815) ☕
<!-- TOOD: add to docs
## Options
These options can be configured by setting environment variables using `-e KEY="VALUE"` in the `docker run` command.
| Env | Default | Example | Description |
| --------- | ----------------- | ------------- | -------------------------------------------- |
| ------ | --------- | ----------- | --------------------------- |
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
@ -151,9 +168,11 @@ was pulled.
For less common or specific edge-case scenarios, please refer to the detailed information provided in the [Wiki](https://github.com/wg-easy/wg-easy/wiki).
-->
## License
This project is licensed under the AGPL-3.0-only License - see the LICENSE file for details
This project is licensed under the AGPL-3.0-only License - see the [LICENSE](LICENSE) file for details
This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security

19
src/app/components/Form/ArrayField.vue

@ -1,9 +1,10 @@
<template>
<div v-if="data?.length === 0">
{{ emptyText || 'No items' }}
{{ emptyText || $t('form.noItems') }}
</div>
<div v-else class="flex flex-col">
<div v-else class="flex flex-col gap-2">
<div v-for="(item, i) in data" :key="i">
<div class="flex flex-row gap-1">
<input
:value="item"
:name="name"
@ -11,15 +12,21 @@
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
@input="update($event, i)"
/>
<input type="button" value="-" @click="del(i)" />
<BaseButton as="input" type="button" value="-" @click="del(i)" />
</div>
</div>
<div class="mt-2">
<BaseButton
as="input"
type="button"
:value="$t('form.add')"
@click="add"
/>
</div>
</div>
<input type="button" value="Add" @click="add" />
</template>
<script lang="ts" setup>
// TODO: style
const data = defineModel<string[]>();
defineProps<{ emptyText?: string[]; name: string }>();

2
src/app/components/Ui/Footer.vue

@ -18,7 +18,7 @@
<a
class="hover:underline"
target="_blank"
href="https://spdx.org/licenses/AGPL-3.0-only.html"
href="https://opensource.org/license/agpl-v3"
>AGPL-3.0-only</a
>
·

2
src/app/pages/index.vue

@ -42,8 +42,6 @@ const intervalId = ref<NodeJS.Timeout | null>(null);
clientsStore.refresh();
onMounted(() => {
// TODO: remove (to avoid console spam)
return;
// TODO?: replace with websocket or similar
intervalId.value = setInterval(() => {
clientsStore

4
src/i18n/locales/en.json

@ -116,7 +116,9 @@
"save": "Save",
"revert": "Revert",
"sectionGeneral": "General",
"sectionAdvanced": "Advanced"
"sectionAdvanced": "Advanced",
"noItems": "No items",
"add": "Add"
},
"admin": {
"general": {

2
src/server/api/session.post.ts

@ -28,7 +28,7 @@ export default defineEventHandler(async (event) => {
userId: user.id,
});
// TODO: create audit log?
// TODO?: create audit log
SERVER_DEBUG(`New Session: ${data.id} for ${user.id} (${user.username})`);

5
src/server/api/setup/2.post.ts

@ -1,14 +1,13 @@
import { UserSetupSchema } from '#db/repositories/user/types';
export default defineSetupEventHandler(async ({ event }) => {
export default defineSetupEventHandler(2, async ({ event }) => {
const { username, password } = await readValidatedBody(
event,
validateZod(UserSetupSchema, event)
);
// TODO: validate setup step
await Database.users.create(username, password);
await Database.general.setSetupStep(3);
return { success: true };
});

5
src/server/api/setup/4.post.ts

@ -1,12 +1,13 @@
import { UserConfigSetupSchema } from '#db/repositories/userConfig/types';
export default defineSetupEventHandler(async ({ event }) => {
export default defineSetupEventHandler(4, async ({ event }) => {
const { host, port } = await readValidatedBody(
event,
validateZod(UserConfigSetupSchema, event)
);
// TODO: validate setup step
await Database.userConfigs.updateHostPort(host, port);
await Database.general.setSetupStep(0);
return { success: true };
});

2
src/server/api/setup/migrate.post.ts

@ -2,7 +2,7 @@ import { parseCidr } from 'cidr-tools';
import { stringifyIp } from 'ip-bigint';
import { z } from 'zod';
export default defineSetupEventHandler(async ({ event }) => {
export default defineSetupEventHandler('migrate', async ({ event }) => {
const { file } = await readValidatedBody(
event,
validateZod(FileSchema, event)

3
src/server/database/repositories/client/service.ts

@ -6,7 +6,6 @@ import type {
ClientCreateType,
UpdateClientType,
} from './types';
import type { ID } from '#db/schema';
import { wgInterface, userConfig } from '#db/schema';
import { parseCidr } from 'cidr-tools';
@ -116,7 +115,7 @@ export class ClientService {
.insert(client)
.values({
name,
// TODO: fix
// TODO: properly assign user id
userId: 1,
expiresAt: parsedExpiresAt,
privateKey,

2
src/server/database/repositories/general/types.ts

@ -11,7 +11,7 @@ const metricsEnabled = z.boolean({ message: t('zod.general.metricsEnabled') });
const metricsPassword = z
.string({ message: t('zod.general.metricsPassword') })
.min(1, { message: t('zod.general.metricsPassword') })
// TODO: validate argon2 regex?
// TODO?: validate argon2 regex
.nullable();
export const GeneralUpdateSchema = z.object({

1
src/server/database/repositories/oneTimeLink/service.ts

@ -1,7 +1,6 @@
import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { oneTimeLink } from './schema';
import type { ID } from '../../schema';
import CRC32 from 'crc-32';
function createPreparedStatement(db: DBType) {

7
src/server/database/repositories/oneTimeLink/types.ts

@ -9,9 +9,6 @@ const oneTimeLinkType = z
.min(1, t('zod.otl'))
.pipe(safeStringRefine);
export const OneTimeLinkGetSchema = z.object(
{
export const OneTimeLinkGetSchema = z.object({
oneTimeLink: oneTimeLinkType,
},
{ message: objectMessage }
);
});

1
src/server/database/repositories/user/service.ts

@ -1,7 +1,6 @@
import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { user } from './schema';
import type { ID } from '../../schema';
function createPreparedStatement(db: DBType) {
return {

28
src/server/database/repositories/user/types.ts

@ -20,22 +20,16 @@ const password = z
const remember = z.boolean({ message: t('zod.user.remember') });
export const UserLoginSchema = z.object(
{
export const UserLoginSchema = z.object({
username: username,
password: password,
remember: remember,
},
{ message: objectMessage }
);
});
export const UserSetupSchema = z.object(
{
export const UserSetupSchema = z.object({
username: username,
password: password,
},
{ message: objectMessage }
);
});
const name = z
.string({ message: t('zod.user.name') })
@ -49,23 +43,17 @@ const email = z
.pipe(safeStringRefine)
.nullable();
export const UserUpdateSchema = z.object(
{
export const UserUpdateSchema = z.object({
name: name,
email: email,
},
{ message: objectMessage }
);
});
export const UserUpdatePasswordSchema = z
.object(
{
.object({
currentPassword: password,
newPassword: password,
confirmPassword: password,
},
{ message: objectMessage }
)
})
.refine((val) => val.newPassword === val.confirmPassword, {
message: t('zod.user.passwordMatch'),
});

3
src/server/database/schema.ts

@ -6,6 +6,3 @@ export * from './repositories/interface/schema';
export * from './repositories/oneTimeLink/schema';
export * from './repositories/user/schema';
export * from './repositories/userConfig/schema';
// TODO: move to types
export type ID = number;

2
src/server/middleware/setup.ts

@ -1,4 +1,4 @@
/* First setup of wg-easy app */
/* First setup of wg-easy */
export default defineEventHandler(async (event) => {
const url = getRequestURL(event);

1
src/server/utils/WireGuard.ts

@ -1,7 +1,6 @@
import fs from 'node:fs/promises';
import debug from 'debug';
import QRCode from 'qrcode';
import type { ID } from '#db/schema';
import type { InterfaceType } from '#db/repositories/interface/types';
const WG_DEBUG = debug('WireGuard');

29
src/server/utils/handler.ts

@ -63,6 +63,17 @@ export const definePermissionEventHandler = <
});
};
// which api route is allowed for each setup step
// 0 is done, 1 is start
// 3 means step 2 is done
const ValidSetupSteps = {
1: [2] as const,
3: [4, 'migrate'] as const,
} as const;
type ValidSteps =
(typeof ValidSetupSteps)[keyof typeof ValidSetupSteps][number];
type SetupHandler<
TReq extends EventHandlerRequest,
TRes extends EventHandlerResponse,
@ -75,6 +86,7 @@ export const defineSetupEventHandler = <
TReq extends EventHandlerRequest,
TRes extends EventHandlerResponse,
>(
step: ValidSteps,
handler: SetupHandler<TReq, TRes>
) => {
return defineEventHandler(async (event) => {
@ -87,6 +99,23 @@ export const defineSetupEventHandler = <
});
}
const validSetupSteps =
ValidSetupSteps[setup.step as keyof typeof ValidSetupSteps];
if (!validSetupSteps) {
throw createError({
statusCode: 500,
statusMessage: 'Invalid setup step',
});
}
if (!validSetupSteps.includes(step as never)) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid step',
});
}
return await handler({ event, setup });
});
};

1
src/server/utils/session.ts

@ -1,5 +1,4 @@
import type { H3Event } from 'h3';
import type { ID } from '#db/schema';
import type { UserType } from '#db/repositories/user/types';
export type WGSession = Partial<{

12
src/server/utils/types.ts

@ -2,6 +2,8 @@ import type { ZodSchema } from 'zod';
import z from 'zod';
import type { H3Event, EventHandlerRequest } from 'h3';
export type ID = number;
/**
* return the string as is
*
@ -9,9 +11,6 @@ import type { H3Event, EventHandlerRequest } from 'h3';
*/
export const t = (v: string) => v;
// TODO: use everywhere or remove
export const objectMessage = t('zod.body');
export const safeStringRefine = z
.string()
.refine(
@ -49,12 +48,9 @@ export const AllowedIpsSchema = z
.array(AddressSchema, { message: t('zod.allowedIps') })
.min(1, { message: t('zod.allowedIps') });
export const FileSchema = z.object(
{
export const FileSchema = z.object({
file: z.string({ message: t('zod.file') }),
},
{ message: objectMessage }
);
});
export const schemaForType =
<T>() =>

Loading…
Cancel
Save