Browse Source

migrate to sqlite

pull/1619/head
Bernd Storath 3 months ago
parent
commit
bc93e95ca0
  1. 2
      src/app/components/ClientCard/Address.vue
  2. 6
      src/app/components/ClientCard/OneTimeLinkBtn.vue
  3. 2
      src/app/components/ui/UserMenu.vue
  4. 6
      src/app/utils/api.ts
  5. 78
      src/i18n/locales/en.json
  6. 7
      src/server/api/admin/hooks.get.ts
  7. 2
      src/server/api/admin/hooks.post.ts
  8. 10
      src/server/api/client/[clientId]/configuration.get.ts
  9. 7
      src/server/api/client/[clientId]/disable.post.ts
  10. 7
      src/server/api/client/[clientId]/enable.post.ts
  11. 6
      src/server/api/client/[clientId]/generateOneTimeLink.post.ts
  12. 7
      src/server/api/client/[clientId]/index.delete.ts
  13. 13
      src/server/api/client/[clientId]/index.get.ts
  14. 15
      src/server/api/client/[clientId]/index.post.ts
  15. 4
      src/server/api/client/[clientId]/qrcode.svg.get.ts
  16. 4
      src/server/api/session.post.ts
  17. 4
      src/server/api/setup/4.post.ts
  18. 4
      src/server/api/setup/5.post.ts
  19. 18
      src/server/database/repositories/client/service.ts
  20. 46
      src/server/database/repositories/client/types.ts
  21. 4
      src/server/database/repositories/general/types.ts
  22. 0
      src/server/database/repositories/metrics/service.ts
  23. 4
      src/server/database/repositories/metrics/types.ts
  24. 2
      src/server/database/repositories/oneTimeLink/schema.ts
  25. 17
      src/server/database/repositories/oneTimeLink/service.ts
  26. 4
      src/server/database/repositories/oneTimeLink/types.ts
  27. 38
      src/server/database/repositories/user/types.ts
  28. 15
      src/server/database/repositories/userConfig/types.ts
  29. 9
      src/server/middleware/auth.ts
  30. 212
      src/server/utils/types.ts
  31. 0
      src/shared/utils/permissions.ts

2
src/app/components/ClientCard/Address.vue

@ -1,6 +1,6 @@
<template> <template>
<span class="inline-block"> <span class="inline-block">
{{ client.address4 }}, {{ client.address6 }} {{ client.ipv4Address }}, {{ client.ipv6Address }}
</span> </span>
</template> </template>

6
src/app/components/ClientCard/OneTimeLinkBtn.vue

@ -27,8 +27,10 @@ defineProps<{ client: LocalClient }>();
const clientsStore = useClientsStore(); const clientsStore = useClientsStore();
function showOneTimeLink(client: LocalClient) { function showOneTimeLink(client: LocalClient) {
api // TODO: improve
.showOneTimeLink({ clientId: client.id }) $fetch(`/api/client/${client.id}/generateOneTimeLink`, {
method: 'post',
})
.catch((err) => alert(err.message || err.toString())) .catch((err) => alert(err.message || err.toString()))
.finally(() => clientsStore.refresh().catch(console.error)); .finally(() => clientsStore.refresh().catch(console.error));
} }

2
src/app/components/ui/UserMenu.vue

@ -37,7 +37,7 @@
Account Account
</NuxtLink> </NuxtLink>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem v-if="authStore.userData?.role === 'ADMIN'"> <DropdownMenuItem v-if="authStore.userData?.role === roles.ADMIN">
<NuxtLink <NuxtLink
to="/admin" to="/admin"
class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"

6
src/app/utils/api.ts

@ -18,12 +18,6 @@ class API {
}); });
} }
async showOneTimeLink({ clientId }: { clientId: string }) {
return $fetch(`/api/client/${clientId}/generateOneTimeLink`, {
method: 'post',
});
}
async restoreConfiguration(file: string) { async restoreConfiguration(file: string) {
return $fetch('/api/wireguard/restore', { return $fetch('/api/wireguard/restore', {
method: 'put', method: 'put',

78
src/i18n/locales/en.json

@ -41,36 +41,7 @@
"zod": { "zod": {
"stringMalformed": "String is malformed", "stringMalformed": "String is malformed",
"id": "Client ID must be a valid UUID", "id": "Client ID must be a valid UUID",
"address": "IP Address must be a valid string",
"addressMin": "IP Address must be a be at least 1 Character",
"address4": "IPv4 Address must be a valid string",
"address4Min": "IPv4 Address must be a be at least 1 Character",
"address6": "IPv6 Address must be a valid string",
"address6Min": "IPv6 Address must be a be at least 1 Character",
"allowedIps": "Allowed IPs must be a valid array of strings",
"allowedIpsMin": "Allowed IPs must have at least 1 item",
"serverAllowedIps": "Allowed IPs must be a valid array of strings",
"name": "Name must be a valid string",
"nameMin": "Name must be at least 1 Character",
"mtu": "MTU must be a valid number",
"mtuMin": "MTU must be at least 1280",
"mtuMax": "MTU must be at most 9000",
"persistentKeepalive": "Persistent Keepalive must be a valid number",
"persistentKeepaliveMin": "Persistent Keepalive must be at least 0",
"persistentKeepaliveMax": "Persistent Keepalive must be at most 65535",
"file": "File must be a valid string", "file": "File must be a valid string",
"username": "Username must be a valid string",
"usernameMin": "Username must be at least 8 Characters",
"password": "Password must be a valid string",
"passwordMin": "Password must be at least 12 Characters",
"passwordUppercase": "Password must have at least 1 uppercase letter",
"passwordLowercase": "Password must have at least 1 lowercase letter",
"passwordNumber": "Password must have at least 1 number",
"passwordSpecial": "Password must have at least 1 special character",
"accept": "Please accept the condition",
"remember": "Remember must be a valid boolean",
"expireDate": "expiredDate must be a valid string",
"expireDateMin": "expiredDate must be at least 1 Character",
"otl": "oneTimeLink must be a valid string", "otl": "oneTimeLink must be a valid string",
"otlMin": "oneTimeLink must be at least 1 Character", "otlMin": "oneTimeLink must be at least 1 Character",
"features": "key must be a valid string", "features": "key must be a valid string",
@ -81,16 +52,53 @@
"statBool": "enabled must be a valid boolean", "statBool": "enabled must be a valid boolean",
"statNumber": "chartType must be a valid number", "statNumber": "chartType must be a valid number",
"body": "Body must be a valid object", "body": "Body must be a valid object",
"host": "Host must be a valid string",
"hostMin": "Host must contain at least 1 character",
"port": "Port must be a valid number",
"portMin": "Port must be at least 1",
"portMax": "Port must be at most 65535",
"sessionTimeout": "Session Timeout must be a valid number", "sessionTimeout": "Session Timeout must be a valid number",
"device": "Device must be a valid string", "device": "Device must be a valid string",
"deviceMin": "Device must be at least 1 Character", "deviceMin": "Device must be at least 1 Character",
"hook": "Hook must be a valid string", "hook": "Hook must be a valid string",
"dns": "DNS must be a valid array of strings" "client": {
"id": "Client ID must be a valid number",
"name": "Name must be a valid string",
"nameMin": "Name must be at least 1 Character",
"mtu": "MTU must be a valid number",
"mtuMin": "MTU must be at least 1280",
"mtuMax": "MTU must be at most 9000",
"persistentKeepalive": "Persistent Keepalive must be a valid number",
"persistentKeepaliveMin": "Persistent Keepalive must be at least 0",
"persistentKeepaliveMax": "Persistent Keepalive must be at most 65535",
"expireDate": "expiredDate must be a valid string",
"expireDateMin": "expiredDate must be at least 1 Character",
"address": "IP Address must be a valid string",
"addressMin": "IP Address must be a be at least 1 Character",
"address4": "IPv4 Address must be a valid string",
"address4Min": "IPv4 Address must be a be at least 1 Character",
"address6": "IPv6 Address must be a valid string",
"address6Min": "IPv6 Address must be a be at least 1 Character",
"allowedIps": "Allowed IPs must be a valid array of strings",
"allowedIpsMin": "Allowed IPs must have at least 1 item",
"serverAllowedIps": "Allowed IPs must be a valid array of strings",
"dns": "DNS must be a valid array of strings",
"dnsMin": "DNS must have at least 1 item"
},
"user": {
"username": "Username must be a valid string",
"usernameMin": "Username must be at least 8 Characters",
"password": "Password must be a valid string",
"passwordMin": "Password must be at least 12 Characters",
"passwordUppercase": "Password must have at least 1 uppercase letter",
"passwordLowercase": "Password must have at least 1 lowercase letter",
"passwordNumber": "Password must have at least 1 number",
"passwordSpecial": "Password must have at least 1 special character",
"remember": "Remember must be a valid boolean",
"accept": "Please accept the condition"
},
"userConfig": {
"host": "Host must be a valid string",
"hostMin": "Host must contain at least 1 character",
"port": "Port must be a valid number",
"portMin": "Port must be at least 1",
"portMax": "Port must be at most 65535"
}
}, },
"name": "Name", "name": "Name",
"username": "Username", "username": "Username",

7
src/server/api/admin/hooks.get.ts

@ -1,4 +1,7 @@
export default defineEventHandler(async () => { export default defineEventHandler(async () => {
const system = await Database.system.get(); const hooks = await Database.hooks.get('wg0');
return system.hooks; if (!hooks) {
throw new Error('Hooks not found');
}
return hooks;
}); });

2
src/server/api/admin/hooks.post.ts

@ -3,7 +3,7 @@ export default defineEventHandler(async (event) => {
event, event,
validateZod(hooksUpdateType, event) validateZod(hooksUpdateType, event)
); );
await Database.system.updateHooks(data); await Database.hooks.update(data);
await WireGuard.saveConfig(); await WireGuard.saveConfig();
return { success: true }; return { success: true };
}); });

10
src/server/api/client/[clientId]/configuration.get.ts

@ -1,11 +1,19 @@
import { ClientGetSchema } from '#db/repositories/client/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
actions.CLIENT, actions.CLIENT,
async ({ event }) => { async ({ event }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(clientIdType) validateZod(ClientGetSchema)
); );
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);
if (!client) {
throw createError({
statusCode: 404,
statusMessage: 'Client not found',
});
}
const config = await WireGuard.getClientConfiguration({ clientId }); const config = await WireGuard.getClientConfiguration({ clientId });
const configName = client.name const configName = client.name
.replace(/[^a-zA-Z0-9_=+.-]/g, '-') .replace(/[^a-zA-Z0-9_=+.-]/g, '-')

7
src/server/api/client/[clientId]/disable.post.ts

@ -1,11 +1,14 @@
import { ClientGetSchema } from '#db/repositories/client/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
actions.CLIENT, actions.CLIENT,
async ({ event }) => { async ({ event }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(clientIdType) validateZod(ClientGetSchema)
); );
await WireGuard.disableClient({ clientId }); await Database.clients.toggle(clientId, false);
await WireGuard.saveConfig();
return { success: true }; return { success: true };
} }
); );

7
src/server/api/client/[clientId]/enable.post.ts

@ -1,11 +1,14 @@
import { ClientGetSchema } from '#db/repositories/client/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
actions.CLIENT, actions.CLIENT,
async ({ event }) => { async ({ event }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(clientIdType) validateZod(ClientGetSchema)
); );
await WireGuard.enableClient({ clientId }); await Database.clients.toggle(clientId, false);
await WireGuard.saveConfig();
return { success: true }; return { success: true };
} }
); );

6
src/server/api/client/[clientId]/generateOneTimeLink.post.ts

@ -1,11 +1,13 @@
import { ClientGetSchema } from '#db/repositories/client/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
actions.CLIENT, actions.CLIENT,
async ({ event }) => { async ({ event }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(clientIdType) validateZod(ClientGetSchema)
); );
await WireGuard.generateOneTimeLink({ clientId }); await Database.oneTimeLinks.generate(clientId);
return { success: true }; return { success: true };
} }
); );

7
src/server/api/client/[clientId]/index.delete.ts

@ -1,11 +1,14 @@
import { ClientGetSchema } from '#db/repositories/client/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
actions.CLIENT, actions.CLIENT,
async ({ event }) => { async ({ event }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(clientIdType) validateZod(ClientGetSchema)
); );
await WireGuard.deleteClient({ clientId }); await Database.clients.delete(clientId);
await WireGuard.saveConfig();
return { success: true }; return { success: true };
} }
); );

13
src/server/api/client/[clientId]/index.get.ts

@ -1,10 +1,19 @@
import { ClientGetSchema } from '~~/server/database/repositories/client/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
actions.CLIENT, actions.CLIENT,
async ({ event }) => { async ({ event }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(clientIdType) validateZod(ClientGetSchema)
); );
return WireGuard.getClient({ clientId }); const result = await Database.clients.get(clientId);
if (!result) {
throw createError({
statusCode: 404,
statusMessage: 'Client not found',
});
}
return result;
} }
); );

15
src/server/api/client/[clientId]/index.post.ts

@ -1,20 +1,23 @@
import {
ClientGetSchema,
ClientUpdateSchema,
} from '#db/repositories/client/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
actions.CLIENT, actions.CLIENT,
async ({ event }) => { async ({ event }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(clientIdType) validateZod(ClientGetSchema)
); );
const data = await readValidatedBody( const data = await readValidatedBody(
event, event,
validateZod(clientUpdateType, event) validateZod(ClientUpdateSchema, event)
); );
await WireGuard.updateClient({ await Database.clients.update(clientId, data);
clientId, await WireGuard.saveConfig();
client: data,
});
return { success: true }; return { success: true };
} }

4
src/server/api/client/[clientId]/qrcode.svg.get.ts

@ -1,9 +1,11 @@
import { ClientGetSchema } from '#db/repositories/client/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
actions.CLIENT, actions.CLIENT,
async ({ event }) => { async ({ event }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(clientIdType) validateZod(ClientGetSchema)
); );
const svg = await WireGuard.getClientQRCodeSVG({ clientId }); const svg = await WireGuard.getClientQRCodeSVG({ clientId });
setHeader(event, 'Content-Type', 'image/svg+xml'); setHeader(event, 'Content-Type', 'image/svg+xml');

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

@ -1,7 +1,9 @@
import { UserLoginSchema } from '#db/repositories/user/types';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const { username, password, remember } = await readValidatedBody( const { username, password, remember } = await readValidatedBody(
event, event,
validateZod(credentialsType, event) validateZod(UserLoginSchema, event)
); );
const user = await Database.users.getByUsername(username); const user = await Database.users.getByUsername(username);

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

@ -1,3 +1,5 @@
import { UserSetupType } from '#db/repositories/user/types';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const { done } = await Database.general.getSetupStep(); const { done } = await Database.general.getSetupStep();
if (done) { if (done) {
@ -9,7 +11,7 @@ export default defineEventHandler(async (event) => {
const { username, password } = await readValidatedBody( const { username, password } = await readValidatedBody(
event, event,
validateZod(passwordSetupType, event) validateZod(UserSetupType, event)
); );
await Database.users.create(username, password); await Database.users.create(username, password);

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

@ -1,3 +1,5 @@
import { UserConfigSetupType } from '#db/repositories/userConfig/types';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const { done } = await Database.general.getSetupStep(); const { done } = await Database.general.getSetupStep();
if (done) { if (done) {
@ -9,7 +11,7 @@ export default defineEventHandler(async (event) => {
const { host, port } = await readValidatedBody( const { host, port } = await readValidatedBody(
event, event,
validateZod(hostPortType, event) validateZod(UserConfigSetupType, event)
); );
await Database.userConfigs.updateHostPort('wg0', host, port); await Database.userConfigs.updateHostPort('wg0', host, port);
await Database.general.setSetupStep(0); await Database.general.setSetupStep(0);

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

@ -1,7 +1,7 @@
import type { DBType } from '#db/sqlite'; import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm'; import { eq, sql } from 'drizzle-orm';
import { client } from './schema'; import { client } from './schema';
import type { ClientCreateType } from './types'; import type { ClientCreateType, UpdateClientType } from './types';
import type { ID } from '../../schema'; import type { ID } from '../../schema';
import { wgInterface, userConfig } from '../../schema'; import { wgInterface, userConfig } from '../../schema';
import { parseCidr } from 'cidr-tools'; import { parseCidr } from 'cidr-tools';
@ -23,6 +23,10 @@ function createPreparedStatement(db: DBType) {
.set({ enabled: sql.placeholder('enabled') as never as boolean }) .set({ enabled: sql.placeholder('enabled') as never as boolean })
.where(eq(client.id, sql.placeholder('id'))) .where(eq(client.id, sql.placeholder('id')))
.prepare(), .prepare(),
delete: db
.delete(client)
.where(eq(client.id, sql.placeholder('id')))
.prepare(),
}; };
} }
@ -44,7 +48,7 @@ export class ClientService {
})); }));
} }
async get(id: ID) { get(id: ID) {
return this.#statements.findById.execute({ id }); return this.#statements.findById.execute({ id });
} }
@ -110,7 +114,15 @@ export class ClientService {
}); });
} }
async toggle(id: ID, enabled: boolean) { toggle(id: ID, enabled: boolean) {
return this.#statements.toggle.execute({ id, enabled }); return this.#statements.toggle.execute({ id, enabled });
} }
delete(id: ID) {
return this.#statements.delete.execute({ id });
}
update(id: ID, data: UpdateClientType) {
return this.#db.update(client).set(data).where(eq(client.id, id)).prepare();
}
} }

46
src/server/database/repositories/client/types.ts

@ -25,52 +25,54 @@ export type UpdateClientType = Omit<
>; >;
const name = zod const name = zod
.string({ message: 'zod.name' }) .string({ message: 'zod.client.name' })
.min(1, 'zod.nameMin') .min(1, 'zod.client.nameMin')
.pipe(safeStringRefine); .pipe(safeStringRefine);
const expiresAt = zod const expiresAt = zod
.string({ message: 'zod.expireDate' }) .string({ message: 'zod.client.expireDate' })
.min(1, 'zod.expireDateMin') .min(1, 'zod.client.expireDateMin')
.pipe(safeStringRefine) .pipe(safeStringRefine)
.nullable(); .nullable();
const address = zod const address = zod
.string({ message: 'zod.address' }) .string({ message: 'zod.client.address' })
.min(1, { message: 'zod.addressMin' }) .min(1, { message: 'zod.client.addressMin' })
.pipe(safeStringRefine); .pipe(safeStringRefine);
const address4 = zod const address4 = zod
.string({ message: 'zod.address4' }) .string({ message: 'zod.client.address4' })
.min(1, { message: 'zod.address4Min' }) .min(1, { message: 'zod.client.address4Min' })
.pipe(safeStringRefine); .pipe(safeStringRefine);
const address6 = zod const address6 = zod
.string({ message: 'zod.address6' }) .string({ message: 'zod.client.address6' })
.min(1, { message: 'zod.address6Min' }) .min(1, { message: 'zod.client.address6Min' })
.pipe(safeStringRefine); .pipe(safeStringRefine);
const allowedIps = zod const allowedIps = zod
.array(address, { message: 'zod.allowedIps' }) .array(address, { message: 'zod.client.allowedIps' })
.min(1, { message: 'zod.allowedIpsMin' }); .min(1, { message: 'zod.client.allowedIpsMin' });
const serverAllowedIps = zod.array(address, { const serverAllowedIps = zod.array(address, {
message: 'zod.serverAllowedIps', message: 'zod.serverAllowedIps',
}); });
const mtu = zod const mtu = zod
.number({ message: 'zod.mtu' }) .number({ message: 'zod.client.mtu' })
.min(1280, { message: 'zod.mtuMin' }) .min(1280, { message: 'zod.client.mtuMin' })
.max(9000, { message: 'zod.mtuMax' }); .max(9000, { message: 'zod.client.mtuMax' });
const persistentKeepalive = zod const persistentKeepalive = zod
.number({ message: 'zod.persistentKeepalive' }) .number({ message: 'zod.client.persistentKeepalive' })
.min(0, 'zod.persistentKeepaliveMin') .min(0, 'zod.client.persistentKeepaliveMin')
.max(65535, 'zod.persistentKeepaliveMax'); .max(65535, 'zod.client.persistentKeepaliveMax');
const enabled = zod.boolean({ message: 'zod.enabled' }); const enabled = zod.boolean({ message: 'zod.enabled' });
const dns = zod.array(address, { message: 'zod.dns' }).min(1, 'zod.dnsMin'); const dns = zod
.array(address, { message: 'zod.client.dns' })
.min(1, 'zod.client.dnsMin');
export const ClientCreateSchema = zod.object({ export const ClientCreateSchema = zod.object({
name: name, name: name,
@ -93,3 +95,9 @@ export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
dns: dns, dns: dns,
}) })
); );
const clientId = zod.number({ message: 'zod.client.id' });
export const ClientGetSchema = zod.object({
clientId: clientId,
});

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

@ -0,0 +1,4 @@
import type { InferSelectModel } from 'drizzle-orm';
import type { general } from './schema';
export type GeneralType = InferSelectModel<typeof general>;

0
src/server/database/repositories/metrics/service.ts

4
src/server/database/repositories/metrics/types.ts

@ -0,0 +1,4 @@
import type { InferSelectModel } from 'drizzle-orm';
import type { prometheus } from './schema';
export type PrometheusType = InferSelectModel<typeof prometheus>;

2
src/server/database/repositories/oneTimeLink/schema.ts

@ -5,7 +5,7 @@ import { client } from '../../schema';
export const oneTimeLink = sqliteTable('one_time_links_table', { export const oneTimeLink = sqliteTable('one_time_links_table', {
id: int().primaryKey({ autoIncrement: true }), id: int().primaryKey({ autoIncrement: true }),
oneTimeLink: text('one_time_link').notNull(), oneTimeLink: text('one_time_link').notNull().unique(),
expiresAt: text('expires_at').notNull(), expiresAt: text('expires_at').notNull(),
clientId: int() clientId: int()
.notNull() .notNull()

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

@ -2,6 +2,7 @@ import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm'; import { eq, sql } from 'drizzle-orm';
import { oneTimeLink } from './schema'; import { oneTimeLink } from './schema';
import type { ID } from '../../schema'; import type { ID } from '../../schema';
import CRC32 from 'crc-32';
function createPreparedStatement(db: DBType) { function createPreparedStatement(db: DBType) {
return { return {
@ -9,6 +10,14 @@ function createPreparedStatement(db: DBType) {
.delete(oneTimeLink) .delete(oneTimeLink)
.where(eq(oneTimeLink.id, sql.placeholder('id'))) .where(eq(oneTimeLink.id, sql.placeholder('id')))
.prepare(), .prepare(),
create: db
.insert(oneTimeLink)
.values({
clientId: sql.placeholder('id'),
oneTimeLink: sql.placeholder('oneTimeLink'),
expiresAt: sql.placeholder('expiresAt'),
})
.prepare(),
}; };
} }
@ -22,4 +31,12 @@ export class OneTimeLinkService {
delete(id: ID) { delete(id: ID) {
return this.#statements.delete.execute({ id }); return this.#statements.delete.execute({ id });
} }
generate(id: ID) {
const key = `${id}-${Math.floor(Math.random() * 1000)}`;
const oneTimeLink = Math.abs(CRC32.str(key)).toString(16);
const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString();
return this.#statements.create.execute({ id, oneTimeLink, expiresAt });
}
} }

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

@ -0,0 +1,4 @@
import type { InferSelectModel } from 'drizzle-orm';
import type { oneTimeLink } from './schema';
export type OneTimeLinkType = InferSelectModel<typeof oneTimeLink>;

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

@ -2,3 +2,41 @@ import type { InferSelectModel } from 'drizzle-orm';
import type { user } from './schema'; import type { user } from './schema';
export type UserType = InferSelectModel<typeof user>; export type UserType = InferSelectModel<typeof user>;
const username = zod
.string({ message: 'zod.user.username' })
.min(8, 'zod.user.usernameMin')
.pipe(safeStringRefine);
const password = zod
.string({ message: 'zod.user.password' })
.min(12, 'zod.user.passwordMin')
.regex(/[A-Z]/, 'zod.user.passwordUppercase')
.regex(/[a-z]/, 'zod.user.passwordLowercase')
.regex(/\d/, 'zod.user.passwordNumber')
.regex(/[!@#$%^&*(),.?":{}|<>]/, 'zod.user.passwordSpecial')
.pipe(safeStringRefine);
const remember = zod.boolean({ message: 'zod.user.remember' });
export const UserLoginSchema = zod.object(
{
username: username,
password: password,
remember: remember,
},
{ message: objectMessage }
);
const accept = zod.boolean().refine((val) => val === true, {
message: 'zod.user.accept',
});
export const UserSetupType = zod.object(
{
username: username,
password: password,
accept: accept,
},
{ message: objectMessage }
);

15
src/server/database/repositories/userConfig/types.ts

@ -2,3 +2,18 @@ import type { InferSelectModel } from 'drizzle-orm';
import type { userConfig } from './schema'; import type { userConfig } from './schema';
export type UserConfigType = InferSelectModel<typeof userConfig>; export type UserConfigType = InferSelectModel<typeof userConfig>;
const host = zod
.string({ message: 'zod.userConfig.host' })
.min(1, 'zod.userConfig.hostMin')
.pipe(safeStringRefine);
const port = zod
.number({ message: 'zod.userConfig.port' })
.min(1, 'zod.userConfig.portMin')
.max(65535, 'zod.userConfig.portMax');
export const UserConfigSetupType = zod.object({
host: host,
port: port,
});

9
src/server/middleware/auth.ts

@ -1,5 +1,6 @@
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
/*const url = getRequestURL(event); // TODO: improve, wrapper or smth
const url = getRequestURL(event);
const session = await useWGSession(event); const session = await useWGSession(event);
// Api handled by session, Setup handled with setup middleware // Api handled by session, Setup handled with setup middleware
@ -21,15 +22,15 @@ export default defineEventHandler(async (event) => {
} }
if (url.pathname.startsWith('/admin')) { if (url.pathname.startsWith('/admin')) {
const user = await Database.user.findById(session.data.userId); const user = await Database.users.get(session.data.userId);
if (!user) { if (!user) {
return sendRedirect(event, '/login', 302); return sendRedirect(event, '/login', 302);
} }
if (user.role !== 'ADMIN') { if (user.role !== roles.ADMIN) {
throw createError({ throw createError({
statusCode: 403, statusCode: 403,
statusMessage: 'Not allowed to access Admin Panel', statusMessage: 'Not allowed to access Admin Panel',
}); });
} }
}*/ }
}); });

212
src/server/utils/types.ts

@ -1,10 +1,10 @@
import type { ZodSchema, ZodTypeDef } from 'zod'; import type { ZodSchema } from 'zod';
import z from 'zod'; import z from 'zod';
import type { H3Event, EventHandlerRequest } from 'h3'; import type { H3Event, EventHandlerRequest } from 'h3';
export { default as zod } from 'zod'; export { default as zod } from 'zod';
const objectMessage = 'zod.body'; export const objectMessage = 'zod.body';
export const safeStringRefine = z export const safeStringRefine = z
.string() .string()
@ -13,210 +13,8 @@ export const safeStringRefine = z
{ message: 'zod.stringMalformed' } { message: 'zod.stringMalformed' }
); );
const host = z
.string({ message: 'zod.host' })
.min(1, 'zod.hostMin')
.pipe(safeStringRefine);
const port = z
.number({ message: 'zod.port' })
.min(1, 'zod.portMin')
.max(65535, 'zod.portMax');
export const hostPortType = z.object({
host: host,
port: port,
});
const id = z.string().uuid('zod.id').pipe(safeStringRefine);
export const clientIdType = z.object(
{
clientId: id,
},
{ message: objectMessage }
);
const oneTimeLink = z
.string({ message: 'zod.otl' })
.min(1, 'zod.otlMin')
.pipe(safeStringRefine);
export const oneTimeLinkType = z.object(
{
oneTimeLink: oneTimeLink,
},
{ message: objectMessage }
);
const name = z
.string({ message: 'zod.name' })
.min(1, 'zod.nameMin')
.pipe(safeStringRefine);
const expireDate = z
.string({ message: 'zod.expireDate' })
.min(1, 'zod.expireDateMin')
.pipe(safeStringRefine)
.nullable();
export const createType = z.object(
{
name: name,
expireDate: expireDate,
},
{ message: objectMessage }
);
const file = z.string({ message: 'zod.file' }).pipe(safeStringRefine);
const file_ = z.instanceof(File, { message: 'zod.file' });
export const fileType = z.object(
{
file: file,
},
{ message: objectMessage }
);
export const fileType_ = z.object(
{
file: file_,
},
{ message: objectMessage }
);
const username = z
.string({ message: 'zod.username' })
.min(8, 'zod.usernameMin')
.pipe(safeStringRefine);
const password = z
.string({ message: 'zod.password' })
.min(12, 'zod.passwordMin')
.regex(/[A-Z]/, 'zod.passwordUppercase')
.regex(/[a-z]/, 'zod.passwordLowercase')
.regex(/\d/, 'zod.passwordNumber')
.regex(/[!@#$%^&*(),.?":{}|<>]/, 'zod.passwordSpecial')
.pipe(safeStringRefine);
const remember = z.boolean({ message: 'zod.remember' });
export const credentialsType = z.object(
{
username: username,
password: password,
remember: remember,
},
{ message: objectMessage }
);
export const passwordType = z.object(
{
username: username,
password: password,
},
{ message: objectMessage }
);
const accept = z.boolean().refine((val) => val === true, {
message: 'zod.accept',
});
export const passwordSetupType = z.object(
{
username: username,
password: password,
accept: accept,
},
{ message: objectMessage }
);
const address = z
.string({ message: 'zod.address' })
.min(1, { message: 'zod.addressMin' })
.pipe(safeStringRefine);
const address4 = z
.string({ message: 'zod.address4' })
.min(1, { message: 'zod.address4Min' })
.pipe(safeStringRefine);
const address6 = z
.string({ message: 'zod.address6' })
.min(1, { message: 'zod.address6Min' })
.pipe(safeStringRefine);
const allowedIps = z
.array(address, { message: 'zod.allowedIps' })
.min(1, { message: 'zod.allowedIpsMin' });
const mtu = z
.number({ message: 'zod.mtu' })
.min(1280, { message: 'zod.mtuMin' })
.max(9000, { message: 'zod.mtuMax' });
const persistentKeepalive = z
.number({ message: 'zod.persistentKeepalive' })
.min(0, 'zod.persistentKeepaliveMin')
.max(65535, 'zod.persistentKeepaliveMax');
export const clientUpdateType = z.object({
name: name,
enabled: z.boolean(),
expiresAt: expireDate,
address4: address4,
address6: address6,
allowedIps: allowedIps,
serverAllowedIPs: z.array(address, { message: 'zod.serverAllowedIPs' }),
mtu: mtu,
persistentKeepalive: persistentKeepalive,
});
export const generalUpdateType = z.object({
sessionTimeout: z.number({ message: 'zod.sessionTimeout' }),
});
const device = z
.string({ message: 'zod.device' })
.min(1, 'zod.deviceMin')
.pipe(safeStringRefine);
export const interfaceUpdateType = z.object({
mtu: mtu,
port: port,
device: device,
});
export const userConfigUpdateType = z.object({
host: host,
port: port,
allowedIps: allowedIps,
defaultDns: z.array(address, { message: 'zod.dns' }),
mtu: mtu,
persistentKeepalive: persistentKeepalive,
});
const hook = z.string({ message: 'zod.hook' }).pipe(safeStringRefine);
export const hooksUpdateType = z.object({
PreUp: hook,
PostUp: hook,
PreDown: hook,
PostDown: hook,
});
export const cidrUpdateType = z.object({
address4: address,
address6: address,
});
// from https://github.com/airjp73/rvf/blob/7e7c35d98015ea5ecff5affaf89f78296e84e8b9/packages/zod-form-data/src/helpers.ts#L117
type FormDataLikeInput = {
[Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
entries(): IterableIterator<[string, FormDataEntryValue]>;
};
export function validateZod<T>( export function validateZod<T>(
schema: ZodSchema<T> | ZodSchema<T, ZodTypeDef, FormData | FormDataLikeInput>, schema: ZodSchema<T>,
event?: H3Event<EventHandlerRequest> event?: H3Event<EventHandlerRequest>
) { ) {
return async (data: unknown) => { return async (data: unknown) => {
@ -247,7 +45,3 @@ export function validateZod<T>(
} }
}; };
} }
export type DeepWriteable<T> = {
-readonly [P in keyof T]: DeepWriteable<T[P]>;
};

0
src/server/utils/permissions.ts → src/shared/utils/permissions.ts

Loading…
Cancel
Save