diff --git a/src/server/api/client/[clientId]/configuration.get.ts b/src/server/api/client/[clientId]/configuration.get.ts index 2a404f08..97349f5a 100644 --- a/src/server/api/client/[clientId]/configuration.get.ts +++ b/src/server/api/client/[clientId]/configuration.get.ts @@ -1,20 +1,23 @@ -export default defineEventHandler(async (event) => { - const { clientId } = await getValidatedRouterParams( - event, - validateZod(clientIdType) - ); - const client = await WireGuard.getClient({ clientId }); - const config = await WireGuard.getClientConfiguration({ clientId }); - const configName = client.name - .replace(/[^a-zA-Z0-9_=+.-]/g, '-') - .replace(/(-{2,}|-$)/g, '-') - .replace(/-$/, '') - .substring(0, 32); - setHeader( - event, - 'Content-Disposition', - `attachment; filename="${configName || clientId}.conf"` - ); - setHeader(event, 'Content-Type', 'text/plain'); - return config; -}); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + const client = await Database.clients.get(clientId); + const config = await WireGuard.getClientConfiguration({ clientId }); + const configName = client.name + .replace(/[^a-zA-Z0-9_=+.-]/g, '-') + .replace(/(-{2,}|-$)/g, '-') + .replace(/-$/, '') + .substring(0, 32); + setHeader( + event, + 'Content-Disposition', + `attachment; filename="${configName || clientId}.conf"` + ); + setHeader(event, 'Content-Type', 'text/plain'); + return config; + } +); diff --git a/src/server/api/client/[clientId]/disable.post.ts b/src/server/api/client/[clientId]/disable.post.ts index 14d1bc68..aa6a506f 100644 --- a/src/server/api/client/[clientId]/disable.post.ts +++ b/src/server/api/client/[clientId]/disable.post.ts @@ -1,8 +1,11 @@ -export default defineEventHandler(async (event) => { - const { clientId } = await getValidatedRouterParams( - event, - validateZod(clientIdType) - ); - await WireGuard.disableClient({ clientId }); - return { success: true }; -}); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + await WireGuard.disableClient({ clientId }); + return { success: true }; + } +); diff --git a/src/server/api/client/[clientId]/enable.post.ts b/src/server/api/client/[clientId]/enable.post.ts index 1064a872..e1e8788c 100644 --- a/src/server/api/client/[clientId]/enable.post.ts +++ b/src/server/api/client/[clientId]/enable.post.ts @@ -1,8 +1,11 @@ -export default defineEventHandler(async (event) => { - const { clientId } = await getValidatedRouterParams( - event, - validateZod(clientIdType) - ); - await WireGuard.enableClient({ clientId }); - return { success: true }; -}); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + await WireGuard.enableClient({ clientId }); + return { success: true }; + } +); diff --git a/src/server/api/client/[clientId]/generateOneTimeLink.post.ts b/src/server/api/client/[clientId]/generateOneTimeLink.post.ts index dfd861d1..0b1f82f1 100644 --- a/src/server/api/client/[clientId]/generateOneTimeLink.post.ts +++ b/src/server/api/client/[clientId]/generateOneTimeLink.post.ts @@ -1,8 +1,11 @@ -export default defineEventHandler(async (event) => { - const { clientId } = await getValidatedRouterParams( - event, - validateZod(clientIdType) - ); - await WireGuard.generateOneTimeLink({ clientId }); - return { success: true }; -}); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + await WireGuard.generateOneTimeLink({ clientId }); + return { success: true }; + } +); diff --git a/src/server/api/client/[clientId]/index.delete.ts b/src/server/api/client/[clientId]/index.delete.ts index 68a1d678..edd100e7 100644 --- a/src/server/api/client/[clientId]/index.delete.ts +++ b/src/server/api/client/[clientId]/index.delete.ts @@ -1,8 +1,11 @@ -export default defineEventHandler(async (event) => { - const { clientId } = await getValidatedRouterParams( - event, - validateZod(clientIdType) - ); - await WireGuard.deleteClient({ clientId }); - return { success: true }; -}); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + await WireGuard.deleteClient({ clientId }); + return { success: true }; + } +); diff --git a/src/server/api/client/[clientId]/index.get.ts b/src/server/api/client/[clientId]/index.get.ts index e5a2ca8d..6d5b1b49 100644 --- a/src/server/api/client/[clientId]/index.get.ts +++ b/src/server/api/client/[clientId]/index.get.ts @@ -1,7 +1,10 @@ -export default defineEventHandler(async (event) => { - const { clientId } = await getValidatedRouterParams( - event, - validateZod(clientIdType) - ); - return WireGuard.getClient({ clientId }); -}); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + return WireGuard.getClient({ clientId }); + } +); diff --git a/src/server/api/client/[clientId]/index.post.ts b/src/server/api/client/[clientId]/index.post.ts index 764afdc0..bb18bd1a 100644 --- a/src/server/api/client/[clientId]/index.post.ts +++ b/src/server/api/client/[clientId]/index.post.ts @@ -1,18 +1,21 @@ -export default defineEventHandler(async (event) => { - const { clientId } = await getValidatedRouterParams( - event, - validateZod(clientIdType) - ); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); - const data = await readValidatedBody( - event, - validateZod(clientUpdateType, event) - ); + const data = await readValidatedBody( + event, + validateZod(clientUpdateType, event) + ); - await WireGuard.updateClient({ - clientId, - client: data, - }); + await WireGuard.updateClient({ + clientId, + client: data, + }); - return { success: true }; -}); + return { success: true }; + } +); diff --git a/src/server/api/client/[clientId]/qrcode.svg.get.ts b/src/server/api/client/[clientId]/qrcode.svg.get.ts index dc21fb84..494b36a0 100644 --- a/src/server/api/client/[clientId]/qrcode.svg.get.ts +++ b/src/server/api/client/[clientId]/qrcode.svg.get.ts @@ -1,9 +1,12 @@ -export default defineEventHandler(async (event) => { - const { clientId } = await getValidatedRouterParams( - event, - validateZod(clientIdType) - ); - const svg = await WireGuard.getClientQRCodeSVG({ clientId }); - setHeader(event, 'Content-Type', 'image/svg+xml'); - return svg; -}); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + const svg = await WireGuard.getClientQRCodeSVG({ clientId }); + setHeader(event, 'Content-Type', 'image/svg+xml'); + return svg; + } +); diff --git a/src/server/api/client/index.get.ts b/src/server/api/client/index.get.ts index adffc9b3..9608b72b 100644 --- a/src/server/api/client/index.get.ts +++ b/src/server/api/client/index.get.ts @@ -1,3 +1,3 @@ -export default defineEventHandler(() => { +export default definePermissionEventHandler(actions.CLIENT, () => { return WireGuard.getClients(); }); diff --git a/src/server/api/client/index.post.ts b/src/server/api/client/index.post.ts index fc8e0fe7..aa461746 100644 --- a/src/server/api/client/index.post.ts +++ b/src/server/api/client/index.post.ts @@ -1,11 +1,14 @@ import { ClientCreateSchema } from '#db/repositories/client/types'; -export default defineEventHandler(async (event) => { - const { name, expiresAt } = await readValidatedBody( - event, - validateZod(ClientCreateSchema) - ); - await Database.clients.create({ name, expiresAt }); - await WireGuard.saveConfig(); - return { success: true }; -}); +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event }) => { + const { name, expiresAt } = await readValidatedBody( + event, + validateZod(ClientCreateSchema) + ); + await Database.clients.create({ name, expiresAt }); + await WireGuard.saveConfig(); + return { success: true }; + } +); diff --git a/src/server/api/session.get.ts b/src/server/api/session.get.ts index dee40f88..6623c86f 100644 --- a/src/server/api/session.get.ts +++ b/src/server/api/session.get.ts @@ -7,7 +7,7 @@ export default defineEventHandler(async (event) => { statusMessage: 'Not logged in', }); } - const user = await Database.user.findById(session.data.userId); + const user = await Database.users.get(session.data.userId); if (!user) { throw createError({ statusCode: 404, diff --git a/src/server/api/session.post.ts b/src/server/api/session.post.ts index b4e7e9f8..6e6d5be0 100644 --- a/src/server/api/session.post.ts +++ b/src/server/api/session.post.ts @@ -4,7 +4,7 @@ export default defineEventHandler(async (event) => { validateZod(credentialsType, event) ); - const users = await Database.user.findAll(); + const users = await Database.users.getAll(); const user = users.find((user) => user.username == username); if (!user) throw createError({ @@ -21,18 +21,7 @@ export default defineEventHandler(async (event) => { }); } - const system = await Database.system.get(); - - const conf = { ...system.sessionConfig }; - - if (remember) { - conf.cookie = { - ...(system.sessionConfig.cookie ?? {}), - maxAge: system.general.sessionTimeout, - }; - } - - const session = await useSession(event, conf); + const session = await useWGSession(event, remember); const data = await session.update({ userId: user.id, diff --git a/src/server/api/wireguard/backup.get.ts b/src/server/api/wireguard/backup.get.ts index 97d21ae8..9132c0e9 100644 --- a/src/server/api/wireguard/backup.get.ts +++ b/src/server/api/wireguard/backup.get.ts @@ -1,6 +1,9 @@ -export default defineEventHandler(async (event) => { - const config = await WireGuard.backupConfiguration(); - setHeader(event, 'Content-Disposition', 'attachment; filename="wg0.json"'); - setHeader(event, 'Content-Type', 'text/json'); - return config; -}); +export default definePermissionEventHandler( + actions.ADMIN, + async ({ event }) => { + const config = await WireGuard.backupConfiguration(); + setHeader(event, 'Content-Disposition', 'attachment; filename="wg0.json"'); + setHeader(event, 'Content-Type', 'text/json'); + return config; + } +); diff --git a/src/server/api/wireguard/restore.put.ts b/src/server/api/wireguard/restore.put.ts index 0d902c4e..3b246d00 100644 --- a/src/server/api/wireguard/restore.put.ts +++ b/src/server/api/wireguard/restore.put.ts @@ -1,5 +1,8 @@ -export default defineEventHandler(async (event) => { - const { file } = await readValidatedBody(event, validateZod(fileType)); - await WireGuard.restoreConfiguration(file); - return { success: true }; -}); +export default definePermissionEventHandler( + actions.ADMIN, + async ({ event }) => { + const { file } = await readValidatedBody(event, validateZod(fileType)); + await WireGuard.restoreConfiguration(file); + return { success: true }; + } +); diff --git a/src/server/database/repositories/general/schema.ts b/src/server/database/repositories/general/schema.ts deleted file mode 100644 index 947339a4..00000000 --- a/src/server/database/repositories/general/schema.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { sql } from 'drizzle-orm'; -import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; - -export const general = sqliteTable('general_table', { - // limit to one entry - id: int().primaryKey({ autoIncrement: false }).default(1), - //test: text().notNull(), - sessionTimeout: int('session_timeout').notNull(), - createdAt: text('created_at') - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - updatedAt: text('updated_at') - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`) - .$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), -}); diff --git a/src/server/database/repositories/sessionConfig/schema.ts b/src/server/database/repositories/sessionConfig/schema.ts index 3ed97f2c..e369a200 100644 --- a/src/server/database/repositories/sessionConfig/schema.ts +++ b/src/server/database/repositories/sessionConfig/schema.ts @@ -5,6 +5,7 @@ export const sessionConfig = sqliteTable('session_config_table', { // limit to one entry id: int().primaryKey({ autoIncrement: false }).default(1), password: text().notNull(), + timeout: int().notNull(), createdAt: text('created_at') .notNull() .default(sql`(CURRENT_TIMESTAMP)`), diff --git a/src/server/database/repositories/user/schema.ts b/src/server/database/repositories/user/schema.ts index 7a2dd4b7..a2fcfaad 100644 --- a/src/server/database/repositories/user/schema.ts +++ b/src/server/database/repositories/user/schema.ts @@ -3,7 +3,7 @@ import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; export const user = sqliteTable('users_table', { id: int().primaryKey({ autoIncrement: true }), - username: text().notNull(), + username: text().notNull().unique(), password: text().notNull(), email: text(), name: text().notNull(), diff --git a/src/server/database/repositories/user/service.ts b/src/server/database/repositories/user/service.ts index 56b8a510..2d08d781 100644 --- a/src/server/database/repositories/user/service.ts +++ b/src/server/database/repositories/user/service.ts @@ -9,6 +9,11 @@ function createPreparedStatement(db: DBType) { findById: db.query.user .findFirst({ where: eq(user.id, sql.placeholder('id')) }) .prepare(), + findByUsername: db.query.user + .findFirst({ + where: eq(user.username, sql.placeholder('username')), + }) + .prepare(), }; } @@ -26,4 +31,8 @@ export class UserService { async get(id: ID) { return this.#statements.findById.all({ id }); } + + async getByUsername(username: string) { + return this.#statements.findByUsername.all({ username }); + } } diff --git a/src/server/utils/handler.ts b/src/server/utils/handler.ts index 8cb50adb..738acc4a 100644 --- a/src/server/utils/handler.ts +++ b/src/server/utils/handler.ts @@ -25,79 +25,3 @@ export const definePermissionEventHandler = < return await handler({ event, user }); }); }; - -/** - * @throws - */ -async function getCurrentUser(event: H3Event) { - const session = await getWGSession(event); - - const authorization = getHeader(event, 'Authorization'); - - let user: UserType | undefined = undefined; - if (session.data.userId) { - // Handle if authenticating using Session - user = await Database.users.get(session.data.userId); - } else if (authorization) { - // Handle if authenticating using Header - const [method, value] = authorization.split(' '); - // Support Basic Authentication - // TODO: support personal access token or similar - if (method !== 'Basic' || !value) { - throw createError({ - statusCode: 401, - statusMessage: 'Session failed', - }); - } - - const basicValue = Buffer.from(value, 'base64').toString('utf-8'); - - // Split by first ":" - const index = basicValue.indexOf(':'); - const userId = basicValue.substring(0, index); - const password = basicValue.substring(index + 1); - - if (!userId || !password) { - throw createError({ - statusCode: 401, - statusMessage: 'Session failed', - }); - } - - const foundUser = await Database.users.get(Number.parseInt(userId)); - - if (!foundUser) { - throw createError({ - statusCode: 401, - statusMessage: 'Session failed', - }); - } - - const userHashPassword = foundUser.password; - const passwordValid = await isPasswordValid(password, userHashPassword); - - if (!passwordValid) { - throw createError({ - statusCode: 401, - statusMessage: 'Incorrect Password', - }); - } - user = foundUser; - } - - if (!user) { - throw createError({ - statusCode: 401, - statusMessage: 'Session failed. User not found', - }); - } - - if (!user.enabled) { - throw createError({ - statusCode: 403, - statusMessage: 'User is disabled', - }); - } - - return user; -} diff --git a/src/server/utils/session.ts b/src/server/utils/session.ts index 9ee72343..4eae34dc 100644 --- a/src/server/utils/session.ts +++ b/src/server/utils/session.ts @@ -1,5 +1,6 @@ import type { H3Event } from 'h3'; import type { ID } from '#db/schema'; +import type { UserType } from '#db/repositories/user/types'; export type WGSession = Partial<{ userId: ID; @@ -7,12 +8,12 @@ export type WGSession = Partial<{ const name = 'wg-easy'; -export async function useWGSession(event: H3Event) { +export async function useWGSession(event: H3Event, rememberMe = false) { const sessionConfig = await Database.sessionConfig.get(); return useSession(event, { password: sessionConfig.password, name, - cookie: {}, + cookie: { maxAge: rememberMe ? sessionConfig.timeout : undefined }, }); } @@ -24,3 +25,79 @@ export async function getWGSession(event: H3Event) { cookie: {}, }); } + +/** + * @throws + */ +export async function getCurrentUser(event: H3Event) { + const session = await getWGSession(event); + + const authorization = getHeader(event, 'Authorization'); + + let user: UserType | undefined = undefined; + if (session.data.userId) { + // Handle if authenticating using Session + user = await Database.users.get(session.data.userId); + } else if (authorization) { + // Handle if authenticating using Header + const [method, value] = authorization.split(' '); + // Support Basic Authentication + // TODO: support personal access token or similar + if (method !== 'Basic' || !value) { + throw createError({ + statusCode: 401, + statusMessage: 'Session failed', + }); + } + + const basicValue = Buffer.from(value, 'base64').toString('utf-8'); + + // Split by first ":" + const index = basicValue.indexOf(':'); + const username = basicValue.substring(0, index); + const password = basicValue.substring(index + 1); + + if (!username || !password) { + throw createError({ + statusCode: 401, + statusMessage: 'Session failed', + }); + } + + const foundUser = await Database.users.getByUsername(username); + + if (!foundUser) { + throw createError({ + statusCode: 401, + statusMessage: 'Session failed', + }); + } + + const userHashPassword = foundUser.password; + const passwordValid = await isPasswordValid(password, userHashPassword); + + if (!passwordValid) { + throw createError({ + statusCode: 401, + statusMessage: 'Incorrect Password', + }); + } + user = foundUser; + } + + if (!user) { + throw createError({ + statusCode: 401, + statusMessage: 'Session failed. User not found', + }); + } + + if (!user.enabled) { + throw createError({ + statusCode: 403, + statusMessage: 'User is disabled', + }); + } + + return user; +}