From e5fb6ff3a6d37c7d4c07bec2ff3b2c519db9427d Mon Sep 17 00:00:00 2001 From: Bernd Storath <32197462+kaaax0815@users.noreply.github.com> Date: Fri, 7 Mar 2025 09:16:24 +0100 Subject: [PATCH] Fix: OneTimeLinks (#1719) * fix otls * one otl per client * revert some code * revert some more code, add comments * adjust migration --- .../database/migrations/0000_short_skin.sql | 5 ++-- .../migrations/meta/0000_snapshot.json | 17 ++++--------- .../migrations/meta/0001_snapshot.json | 19 +++++---------- .../database/migrations/meta/_journal.json | 4 ++-- .../database/repositories/client/schema.ts | 2 +- .../database/repositories/hooks/schema.ts | 1 + .../repositories/oneTimeLink/schema.ts | 13 ++++++---- .../repositories/oneTimeLink/service.ts | 15 +++++++++--- .../repositories/userConfig/schema.ts | 1 + src/server/routes/cnf/[oneTimeLink].ts | 24 ++++++++++++------- src/server/utils/WireGuard.ts | 7 +++--- 11 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/server/database/migrations/0000_short_skin.sql b/src/server/database/migrations/0000_short_skin.sql index a0cb0a3b..dfe5cd17 100644 --- a/src/server/database/migrations/0000_short_skin.sql +++ b/src/server/database/migrations/0000_short_skin.sql @@ -64,13 +64,12 @@ CREATE TABLE `interfaces_table` ( --> statement-breakpoint CREATE UNIQUE INDEX `interfaces_table_port_unique` ON `interfaces_table` (`port`);--> statement-breakpoint CREATE TABLE `one_time_links_table` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `id` integer PRIMARY KEY NOT NULL, `one_time_link` text NOT NULL, `expires_at` text NOT NULL, - `client_id` integer NOT NULL, `created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, `updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, - FOREIGN KEY (`client_id`) REFERENCES `clients_table`(`id`) ON UPDATE cascade ON DELETE cascade + FOREIGN KEY (`id`) REFERENCES `clients_table`(`id`) ON UPDATE cascade ON DELETE cascade ); --> statement-breakpoint CREATE UNIQUE INDEX `one_time_links_table_one_time_link_unique` ON `one_time_links_table` (`one_time_link`);--> statement-breakpoint diff --git a/src/server/database/migrations/meta/0000_snapshot.json b/src/server/database/migrations/meta/0000_snapshot.json index b7574079..7d3b105e 100644 --- a/src/server/database/migrations/meta/0000_snapshot.json +++ b/src/server/database/migrations/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "2cabecf8-93d5-4d32-81b7-2e4369c1cb29", + "id": "383501e4-f8de-4413-847f-a9082f6dc398", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "clients_table": { @@ -452,7 +452,7 @@ "type": "integer", "primaryKey": true, "notNull": true, - "autoincrement": true + "autoincrement": false }, "one_time_link": { "name": "one_time_link", @@ -468,13 +468,6 @@ "notNull": true, "autoincrement": false }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, "created_at": { "name": "created_at", "type": "text", @@ -502,12 +495,12 @@ } }, "foreignKeys": { - "one_time_links_table_client_id_clients_table_id_fk": { - "name": "one_time_links_table_client_id_clients_table_id_fk", + "one_time_links_table_id_clients_table_id_fk": { + "name": "one_time_links_table_id_clients_table_id_fk", "tableFrom": "one_time_links_table", "tableTo": "clients_table", "columnsFrom": [ - "client_id" + "id" ], "columnsTo": [ "id" diff --git a/src/server/database/migrations/meta/0001_snapshot.json b/src/server/database/migrations/meta/0001_snapshot.json index 432a5f10..53f320d5 100644 --- a/src/server/database/migrations/meta/0001_snapshot.json +++ b/src/server/database/migrations/meta/0001_snapshot.json @@ -1,6 +1,6 @@ { - "id": "9476c20a-509b-4cd7-b58b-a042600bafb1", - "prevId": "2cabecf8-93d5-4d32-81b7-2e4369c1cb29", + "id": "bf316694-e2ce-4e29-bd66-ce6c0a9d3c90", + "prevId": "383501e4-f8de-4413-847f-a9082f6dc398", "version": "6", "dialect": "sqlite", "tables": { @@ -452,7 +452,7 @@ "type": "integer", "primaryKey": true, "notNull": true, - "autoincrement": true + "autoincrement": false }, "one_time_link": { "name": "one_time_link", @@ -468,13 +468,6 @@ "notNull": true, "autoincrement": false }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, "created_at": { "name": "created_at", "type": "text", @@ -502,11 +495,11 @@ } }, "foreignKeys": { - "one_time_links_table_client_id_clients_table_id_fk": { - "name": "one_time_links_table_client_id_clients_table_id_fk", + "one_time_links_table_id_clients_table_id_fk": { + "name": "one_time_links_table_id_clients_table_id_fk", "tableFrom": "one_time_links_table", "columnsFrom": [ - "client_id" + "id" ], "tableTo": "clients_table", "columnsTo": [ diff --git a/src/server/database/migrations/meta/_journal.json b/src/server/database/migrations/meta/_journal.json index 6a81e90c..e8612099 100644 --- a/src/server/database/migrations/meta/_journal.json +++ b/src/server/database/migrations/meta/_journal.json @@ -5,14 +5,14 @@ { "idx": 0, "version": "6", - "when": 1741331552405, + "when": 1741335144499, "tag": "0000_short_skin", "breakpoints": true }, { "idx": 1, "version": "6", - "when": 1741331579259, + "when": 1741335153054, "tag": "0001_classy_the_stranger", "breakpoints": true } diff --git a/src/server/database/repositories/client/schema.ts b/src/server/database/repositories/client/schema.ts index f1dd4163..08b2c764 100644 --- a/src/server/database/repositories/client/schema.ts +++ b/src/server/database/repositories/client/schema.ts @@ -42,7 +42,7 @@ export const client = sqliteTable('clients_table', { export const clientsRelations = relations(client, ({ one }) => ({ oneTimeLink: one(oneTimeLink, { fields: [client.id], - references: [oneTimeLink.clientId], + references: [oneTimeLink.id], }), user: one(user, { fields: [client.userId], diff --git a/src/server/database/repositories/hooks/schema.ts b/src/server/database/repositories/hooks/schema.ts index dcd28264..001eecf0 100644 --- a/src/server/database/repositories/hooks/schema.ts +++ b/src/server/database/repositories/hooks/schema.ts @@ -4,6 +4,7 @@ import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { wgInterface } from '../../schema'; export const hooks = sqliteTable('hooks_table', { + /** same as `wgInterface.name` */ id: text() .primaryKey() .references(() => wgInterface.name, { diff --git a/src/server/database/repositories/oneTimeLink/schema.ts b/src/server/database/repositories/oneTimeLink/schema.ts index 9eab8bce..3d0f4caf 100644 --- a/src/server/database/repositories/oneTimeLink/schema.ts +++ b/src/server/database/repositories/oneTimeLink/schema.ts @@ -4,12 +4,15 @@ import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { client } from '../../schema'; export const oneTimeLink = sqliteTable('one_time_links_table', { - id: int().primaryKey({ autoIncrement: true }), + /** same as `client.id` */ + id: int() + .primaryKey() + .references(() => client.id, { + onDelete: 'cascade', + onUpdate: 'cascade', + }), oneTimeLink: text('one_time_link').notNull().unique(), expiresAt: text('expires_at').notNull(), - clientId: int('client_id') - .notNull() - .references(() => client.id, { onDelete: 'cascade', onUpdate: 'cascade' }), createdAt: text('created_at') .notNull() .default(sql`(CURRENT_TIMESTAMP)`), @@ -21,7 +24,7 @@ export const oneTimeLink = sqliteTable('one_time_links_table', { export const oneTimeLinksRelations = relations(oneTimeLink, ({ one }) => ({ client: one(client, { - fields: [oneTimeLink.clientId], + fields: [oneTimeLink.id], references: [client.id], }), })); diff --git a/src/server/database/repositories/oneTimeLink/service.ts b/src/server/database/repositories/oneTimeLink/service.ts index f8af5d4d..dd562934 100644 --- a/src/server/database/repositories/oneTimeLink/service.ts +++ b/src/server/database/repositories/oneTimeLink/service.ts @@ -12,7 +12,7 @@ function createPreparedStatement(db: DBType) { create: db .insert(oneTimeLink) .values({ - clientId: sql.placeholder('id'), + id: sql.placeholder('id'), oneTimeLink: sql.placeholder('oneTimeLink'), expiresAt: sql.placeholder('expiresAt'), }) @@ -20,7 +20,12 @@ function createPreparedStatement(db: DBType) { erase: db .update(oneTimeLink) .set({ expiresAt: sql.placeholder('expiresAt') as never as string }) - .where(eq(oneTimeLink.clientId, sql.placeholder('id'))) + .where(eq(oneTimeLink.id, sql.placeholder('id'))) + .prepare(), + findByOneTimeLink: db.query.oneTimeLink + .findFirst({ + where: eq(oneTimeLink.oneTimeLink, sql.placeholder('oneTimeLink')), + }) .prepare(), }; } @@ -36,6 +41,10 @@ export class OneTimeLinkService { return this.#statements.delete.execute({ id }); } + getByOtl(oneTimeLink: string) { + return this.#statements.findByOneTimeLink.execute({ oneTimeLink }); + } + generate(id: ID) { const key = `${id}-${Math.floor(Math.random() * 1000)}`; const oneTimeLink = Math.abs(CRC32.str(key)).toString(16); @@ -45,7 +54,7 @@ export class OneTimeLinkService { } erase(id: ID) { - const expiresAt = Date.now() + 10 * 1000; + const expiresAt = new Date(Date.now() + 10 * 1000).toISOString(); return this.#statements.erase.execute({ id, expiresAt }); } } diff --git a/src/server/database/repositories/userConfig/schema.ts b/src/server/database/repositories/userConfig/schema.ts index 0c6430f0..94f12753 100644 --- a/src/server/database/repositories/userConfig/schema.ts +++ b/src/server/database/repositories/userConfig/schema.ts @@ -5,6 +5,7 @@ import { wgInterface } from '../../schema'; // default* means clients store it themselves export const userConfig = sqliteTable('user_configs_table', { + /** same as `wgInterface.name` */ id: text() .primaryKey() .references(() => wgInterface.name, { diff --git a/src/server/routes/cnf/[oneTimeLink].ts b/src/server/routes/cnf/[oneTimeLink].ts index 966ae1e5..3d048eb3 100644 --- a/src/server/routes/cnf/[oneTimeLink].ts +++ b/src/server/routes/cnf/[oneTimeLink].ts @@ -5,20 +5,28 @@ export default defineEventHandler(async (event) => { event, validateZod(OneTimeLinkGetSchema, event) ); - const clients = await WireGuard.getAllClients(); - // TODO: filter on the database level - const client = clients.find( - (client) => client.oneTimeLink?.oneTimeLink === oneTimeLink - ); + + const otl = await Database.oneTimeLinks.getByOtl(oneTimeLink); + if (!otl) { + throw createError({ + statusCode: 404, + statusMessage: 'Invalid One Time Link', + }); + } + + const client = await Database.clients.get(otl.id); if (!client) { throw createError({ statusCode: 404, statusMessage: 'Invalid One Time Link', }); } - const clientId = client.id; - const config = await WireGuard.getClientConfiguration({ clientId }); - await Database.oneTimeLinks.erase(clientId); + + const config = await WireGuard.getClientConfiguration({ + clientId: client.id, + }); + await Database.oneTimeLinks.erase(otl.id); + setHeader( event, 'Content-Disposition', diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index 7be7ee90..1fa931b0 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -212,8 +212,8 @@ class WireGuard { client.oneTimeLink !== null && new Date() > new Date(client.oneTimeLink.expiresAt) ) { - WG_DEBUG(`Client ${client.id} One Time Link expired.`); - await Database.oneTimeLinks.delete(client.oneTimeLink.id); + WG_DEBUG(`OneTimeLink for Client ${client.id} expired.`); + await Database.oneTimeLinks.delete(client.id); } } @@ -222,11 +222,10 @@ class WireGuard { } if (OLD_ENV.PASSWORD || OLD_ENV.PASSWORD_HASH) { - // TODO: change url before release throw new Error( ` You are using an invalid Configuration for wg-easy -Please follow the instructions on https://wg-easy.github.io/wg-easy/ to migrate +Please follow the instructions on https://wg-easy.github.io/wg-easy/latest/advanced/migrate/from-14-to-15/ to migrate ` ); }