Browse Source

one otl per client

pull/1719/head
Bernd Storath 5 months ago
parent
commit
b2bdb32f2b
  1. 60
      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

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

@ -1,17 +1,13 @@
<template> <template>
<div v-if="modifiedOtls.length > 0" class="text-xs text-gray-400"> <div v-if="props.client.oneTimeLink" class="text-xs text-gray-400">
<div v-for="link in modifiedOtls" :key="link.oneTimeLink"> <a :href="'./cnf/' + props.client.oneTimeLink.oneTimeLink">{{ path }}</a>
<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 modifiedOtls = ref< const path = ref('Loading...');
(LocalClient['oneTimeLinks'][number] & { path?: string })[]
>(props.client.oneTimeLinks);
const timer = ref<NodeJS.Timeout | null>(null); const timer = ref<NodeJS.Timeout | null>(null);
@ -19,32 +15,32 @@ const { localeProperties } = useI18n();
onMounted(() => { onMounted(() => {
timer.value = setIntervalImmediately(() => { timer.value = setIntervalImmediately(() => {
for (const link of modifiedOtls.value) { if (!props.client.oneTimeLink) {
const timeLeft = new Date(link.expiresAt).getTime() - Date.now(); return;
if (timeLeft <= 0) {
link.path = `${document.location.protocol}//${document.location.host}/cnf/${link.oneTimeLink} (00:00)`;
continue;
}
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);
link.path = `${document.location.protocol}//${document.location.host}/cnf/${link.oneTimeLink} (${formatter.format(date)})`;
} }
const timeLeft =
new Date(props.client.oneTimeLink.expiresAt).getTime() - Date.now();
if (timeLeft <= 0) {
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (00:00)`;
return;
}
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,8 +39,11 @@ export const client = sqliteTable('clients_table', {
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), .$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
}); });
export const clientsRelations = relations(client, ({ one, many }) => ({ export const clientsRelations = relations(client, ({ one }) => ({
oneTimeLinks: many(oneTimeLink), oneTimeLink: one(oneTimeLink, {
fields: [client.id],
references: [oneTimeLink.id],
}),
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: {
oneTimeLinks: true, oneTimeLink: 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: { oneTimeLinks: true }, with: { oneTimeLink: true },
}) })
.prepare(), .prepare(),
toggle: db 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'; 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()
.references(() => client.id, {
onDelete: 'cascade',
onUpdate: 'cascade',
}),
oneTimeLink: text('one_time_link').notNull().unique(), oneTimeLink: text('one_time_link').notNull().unique(),
expiresAt: text('expires_at').notNull(), expiresAt: text('expires_at').notNull(),
clientId: int('client_id')
.notNull()
.references(() => client.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
createdAt: text('created_at') createdAt: text('created_at')
.notNull() .notNull()
.default(sql`(CURRENT_TIMESTAMP)`), .default(sql`(CURRENT_TIMESTAMP)`),
@ -21,7 +23,7 @@ export const oneTimeLink = sqliteTable('one_time_links_table', {
export const oneTimeLinksRelations = relations(oneTimeLink, ({ one }) => ({ export const oneTimeLinksRelations = relations(oneTimeLink, ({ one }) => ({
client: one(client, { client: one(client, {
fields: [oneTimeLink.clientId], fields: [oneTimeLink.id],
references: [client.id], references: [client.id],
}), }),
})); }));

8
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('clientId'), id: sql.placeholder('id'),
oneTimeLink: sql.placeholder('oneTimeLink'), oneTimeLink: sql.placeholder('oneTimeLink'),
expiresAt: sql.placeholder('expiresAt'), expiresAt: sql.placeholder('expiresAt'),
}) })
@ -45,13 +45,13 @@ export class OneTimeLinkService {
return this.#statements.findByOneTimeLink.execute({ oneTimeLink }); return this.#statements.findByOneTimeLink.execute({ oneTimeLink });
} }
generate(clientId: ID) { generate(id: ID) {
const key = `${clientId}-${Math.floor(Math.random() * 1000)}`; const key = `${id}-${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({ return this.#statements.create.execute({
clientId, id,
oneTimeLink, oneTimeLink,
expiresAt, 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) { if (!client) {
throw createError({ throw createError({
statusCode: 404, statusCode: 404,

10
src/server/utils/WireGuard.ts

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

Loading…
Cancel
Save