Browse Source

fix otls

pull/1719/head
Bernd Storath 5 months ago
parent
commit
251f2a1c00
  1. 61
      src/app/components/ClientCard/OneTimeLink.vue
  2. 7
      src/server/database/repositories/client/schema.ts
  3. 4
      src/server/database/repositories/client/service.ts
  4. 25
      src/server/database/repositories/oneTimeLink/service.ts
  5. 24
      src/server/routes/cnf/[oneTimeLink].ts
  6. 16
      src/server/utils/WireGuard.ts

61
src/app/components/ClientCard/OneTimeLink.vue

@ -1,45 +1,50 @@
<template> <template>
<div v-if="client.oneTimeLink !== null" class="text-xs text-gray-400"> <div v-if="modifiedOtls.length > 0" class="text-xs text-gray-400">
<a :href="'./cnf/' + client.oneTimeLink.oneTimeLink">{{ path }}</a> <div v-for="link in modifiedOtls" :key="link.oneTimeLink">
<a :href="'./cnf/' + link.oneTimeLink">{{ link.path ?? 'Loading' }}</a>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ client: LocalClient }>(); const props = defineProps<{ client: LocalClient }>();
const path = ref('Loading...'); const modifiedOtls = ref<
(LocalClient['oneTimeLinks'][number] & { path?: string })[]
>(props.client.oneTimeLinks);
const timer = ref<NodeJS.Timeout | null>(null); const timer = ref<NodeJS.Timeout | null>(null);
const { localeProperties } = useI18n(); const { localeProperties } = useI18n();
onMounted(() => { onMounted(() => {
timer.value = setIntervalImmediately(() => { timer.value = setIntervalImmediately(() => {
if (props.client.oneTimeLink === null) { for (const link of modifiedOtls.value) {
return; const timeLeft = new Date(link.expiresAt).getTime() - Date.now();
}
if (timeLeft <= 0) {
const timeLeft = link.path = `${document.location.protocol}//${document.location.host}/cnf/${link.oneTimeLink} (00:00)`;
new Date(props.client.oneTimeLink.expiresAt).getTime() - Date.now(); continue;
}
if (timeLeft <= 0) {
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (00:00)`; const formatter = new Intl.DateTimeFormat(
return; localeProperties.value.language,
{
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23',
}
);
const minutes = Math.floor(timeLeft / 60000);
const seconds = Math.floor((timeLeft % 60000) / 1000);
const date = new Date(0);
date.setMinutes(minutes);
date.setSeconds(seconds);
link.path = `${document.location.protocol}//${document.location.host}/cnf/${link.oneTimeLink} (${formatter.format(date)})`;
} }
const formatter = new Intl.DateTimeFormat(localeProperties.value.language, {
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23',
});
const minutes = Math.floor(timeLeft / 60000);
const seconds = Math.floor((timeLeft % 60000) / 1000);
const date = new Date(0);
date.setMinutes(minutes);
date.setSeconds(seconds);
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (${formatter.format(date)})`;
}, 1000); }, 1000);
}); });

7
src/server/database/repositories/client/schema.ts

@ -39,11 +39,8 @@ export const client = sqliteTable('clients_table', {
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), .$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
}); });
export const clientsRelations = relations(client, ({ one }) => ({ export const clientsRelations = relations(client, ({ one, many }) => ({
oneTimeLink: one(oneTimeLink, { oneTimeLinks: many(oneTimeLink),
fields: [client.id],
references: [oneTimeLink.clientId],
}),
user: one(user, { user: one(user, {
fields: [client.userId], fields: [client.userId],
references: [user.id], references: [user.id],

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

@ -14,7 +14,7 @@ function createPreparedStatement(db: DBType) {
findAll: db.query.client findAll: db.query.client
.findMany({ .findMany({
with: { with: {
oneTimeLink: true, oneTimeLinks: true,
}, },
}) })
.prepare(), .prepare(),
@ -24,7 +24,7 @@ function createPreparedStatement(db: DBType) {
findByUserId: db.query.client findByUserId: db.query.client
.findMany({ .findMany({
where: eq(client.userId, sql.placeholder('userId')), where: eq(client.userId, sql.placeholder('userId')),
with: { oneTimeLink: true }, with: { oneTimeLinks: true },
}) })
.prepare(), .prepare(),
toggle: db toggle: db

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

@ -12,7 +12,7 @@ function createPreparedStatement(db: DBType) {
create: db create: db
.insert(oneTimeLink) .insert(oneTimeLink)
.values({ .values({
clientId: sql.placeholder('id'), clientId: sql.placeholder('clientId'),
oneTimeLink: sql.placeholder('oneTimeLink'), oneTimeLink: sql.placeholder('oneTimeLink'),
expiresAt: sql.placeholder('expiresAt'), expiresAt: sql.placeholder('expiresAt'),
}) })
@ -20,7 +20,12 @@ function createPreparedStatement(db: DBType) {
erase: db erase: db
.update(oneTimeLink) .update(oneTimeLink)
.set({ expiresAt: sql.placeholder('expiresAt') as never as string }) .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(), .prepare(),
}; };
} }
@ -36,16 +41,24 @@ export class OneTimeLinkService {
return this.#statements.delete.execute({ id }); return this.#statements.delete.execute({ id });
} }
generate(id: ID) { getByOtl(oneTimeLink: string) {
const key = `${id}-${Math.floor(Math.random() * 1000)}`; return this.#statements.findByOneTimeLink.execute({ oneTimeLink });
}
generate(clientId: ID) {
const key = `${clientId}-${Math.floor(Math.random() * 1000)}`;
const oneTimeLink = Math.abs(CRC32.str(key)).toString(16); const oneTimeLink = Math.abs(CRC32.str(key)).toString(16);
const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString(); const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString();
return this.#statements.create.execute({ id, oneTimeLink, expiresAt }); return this.#statements.create.execute({
clientId,
oneTimeLink,
expiresAt,
});
} }
erase(id: ID) { 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 }); return this.#statements.erase.execute({ id, expiresAt });
} }
} }

24
src/server/routes/cnf/[oneTimeLink].ts

@ -5,20 +5,28 @@ export default defineEventHandler(async (event) => {
event, event,
validateZod(OneTimeLinkGetSchema, event) validateZod(OneTimeLinkGetSchema, event)
); );
const clients = await WireGuard.getAllClients();
// TODO: filter on the database level const otl = await Database.oneTimeLinks.getByOtl(oneTimeLink);
const client = clients.find( if (!otl) {
(client) => client.oneTimeLink?.oneTimeLink === oneTimeLink throw createError({
); statusCode: 404,
statusMessage: 'Invalid One Time Link',
});
}
const client = await Database.clients.get(otl.clientId);
if (!client) { if (!client) {
throw createError({ throw createError({
statusCode: 404, statusCode: 404,
statusMessage: 'Invalid One Time Link', statusMessage: 'Invalid One Time Link',
}); });
} }
const clientId = client.id;
const config = await WireGuard.getClientConfiguration({ clientId }); const config = await WireGuard.getClientConfiguration({
await Database.oneTimeLinks.erase(clientId); clientId: client.id,
});
await Database.oneTimeLinks.erase(otl.id);
setHeader( setHeader(
event, event,
'Content-Disposition', 'Content-Disposition',

16
src/server/utils/WireGuard.ts

@ -208,12 +208,13 @@ class WireGuard {
} }
// One Time Link Feature // One Time Link Feature
for (const client of clients) { for (const client of clients) {
if ( for (const oneTimeLink of client.oneTimeLinks) {
client.oneTimeLink !== null && if (new Date() > new Date(oneTimeLink.expiresAt)) {
new Date() > new Date(client.oneTimeLink.expiresAt) WG_DEBUG(
) { `OneTimeLink ${oneTimeLink.id} for Client ${client.id} expired.`
WG_DEBUG(`Client ${client.id} One Time Link expired.`); );
await Database.oneTimeLinks.delete(client.oneTimeLink.id); await Database.oneTimeLinks.delete(oneTimeLink.id);
}
} }
} }
@ -222,11 +223,10 @@ class WireGuard {
} }
if (OLD_ENV.PASSWORD || OLD_ENV.PASSWORD_HASH) { if (OLD_ENV.PASSWORD || OLD_ENV.PASSWORD_HASH) {
// TODO: change url before release
throw new Error( throw new Error(
` `
You are using an invalid Configuration for wg-easy 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
` `
); );
} }

Loading…
Cancel
Save