Browse Source

one otl per client

pull/1719/head
Bernd Storath 5 months ago
parent
commit
b2bdb32f2b
  1. 32
      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. 12
      src/server/database/repositories/oneTimeLink/schema.ts
  5. 8
      src/server/database/repositories/oneTimeLink/service.ts
  6. 2
      src/server/routes/cnf/[oneTimeLink].ts
  7. 10
      src/server/utils/WireGuard.ts

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

@ -1,17 +1,13 @@
<template>
<div v-if="modifiedOtls.length > 0" class="text-xs text-gray-400">
<div v-for="link in modifiedOtls" :key="link.oneTimeLink">
<a :href="'./cnf/' + link.oneTimeLink">{{ link.path ?? 'Loading' }}</a>
</div>
<div v-if="props.client.oneTimeLink" class="text-xs text-gray-400">
<a :href="'./cnf/' + props.client.oneTimeLink.oneTimeLink">{{ path }}</a>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{ client: LocalClient }>();
const modifiedOtls = ref<
(LocalClient['oneTimeLinks'][number] & { path?: string })[]
>(props.client.oneTimeLinks);
const path = ref('Loading...');
const timer = ref<NodeJS.Timeout | null>(null);
@ -19,22 +15,23 @@ const { localeProperties } = useI18n();
onMounted(() => {
timer.value = setIntervalImmediately(() => {
for (const link of modifiedOtls.value) {
const timeLeft = new Date(link.expiresAt).getTime() - Date.now();
if (!props.client.oneTimeLink) {
return;
}
const timeLeft =
new Date(props.client.oneTimeLink.expiresAt).getTime() - Date.now();
if (timeLeft <= 0) {
link.path = `${document.location.protocol}//${document.location.host}/cnf/${link.oneTimeLink} (00:00)`;
continue;
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (00:00)`;
return;
}
const formatter = new Intl.DateTimeFormat(
localeProperties.value.language,
{
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);
@ -43,8 +40,7 @@ onMounted(() => {
date.setMinutes(minutes);
date.setSeconds(seconds);
link.path = `${document.location.protocol}//${document.location.host}/cnf/${link.oneTimeLink} (${formatter.format(date)})`;
}
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (${formatter.format(date)})`;
}, 1000);
});

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

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

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

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

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

@ -4,12 +4,14 @@ 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 }),
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 +23,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],
}),
}));

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

@ -12,7 +12,7 @@ function createPreparedStatement(db: DBType) {
create: db
.insert(oneTimeLink)
.values({
clientId: sql.placeholder('clientId'),
id: sql.placeholder('id'),
oneTimeLink: sql.placeholder('oneTimeLink'),
expiresAt: sql.placeholder('expiresAt'),
})
@ -45,13 +45,13 @@ export class OneTimeLinkService {
return this.#statements.findByOneTimeLink.execute({ oneTimeLink });
}
generate(clientId: ID) {
const key = `${clientId}-${Math.floor(Math.random() * 1000)}`;
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({
clientId,
id,
oneTimeLink,
expiresAt,
});

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

@ -14,7 +14,7 @@ export default defineEventHandler(async (event) => {
});
}
const client = await Database.clients.get(otl.clientId);
const client = await Database.clients.get(otl.id);
if (!client) {
throw createError({
statusCode: 404,

10
src/server/utils/WireGuard.ts

@ -208,12 +208,10 @@ class WireGuard {
}
// One Time Link Feature
for (const client of clients) {
for (const oneTimeLink of client.oneTimeLinks) {
if (new Date() > new Date(oneTimeLink.expiresAt)) {
WG_DEBUG(
`OneTimeLink ${oneTimeLink.id} for Client ${client.id} expired.`
);
await Database.oneTimeLinks.delete(oneTimeLink.id);
if (client.oneTimeLink !== null) {
if (new Date() > new Date(client.oneTimeLink.expiresAt)) {
WG_DEBUG(`OneTimeLink for Client ${client.id} expired.`);
await Database.oneTimeLinks.delete(client.id);
}
}
}

Loading…
Cancel
Save