Browse Source

start adding a better permission handler

pull/1619/head
Bernd Storath 3 months ago
parent
commit
93e42c05c0
  1. 2
      src/package.json
  2. 15
      src/server/database/repositories/sessionConfig/schema.ts
  3. 26
      src/server/database/repositories/sessionConfig/service.ts
  4. 29
      src/server/database/repositories/user/service.ts
  5. 4
      src/server/database/repositories/user/types.ts
  6. 5
      src/server/database/schema.ts
  7. 13
      src/server/database/sqlite.ts
  8. 1
      src/server/utils/Database.ts
  9. 7
      src/server/utils/WireGuard.ts
  10. 101
      src/server/utils/handler.ts
  11. 22
      src/server/utils/session.ts

2
src/package.json

@ -61,4 +61,4 @@
"vue-tsc": "^2.2.0"
},
"packageManager": "[email protected]"
}
}

15
src/server/database/repositories/sessionConfig/schema.ts

@ -0,0 +1,15 @@
import { sql } from 'drizzle-orm';
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core';
export const sessionConfig = sqliteTable('session_config_table', {
// limit to one entry
id: int().primaryKey({ autoIncrement: false }).default(1),
password: text().notNull(),
createdAt: text('created_at')
.notNull()
.default(sql`(CURRENT_TIMESTAMP)`),
updatedAt: text('updated_at')
.notNull()
.default(sql`(CURRENT_TIMESTAMP)`)
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
});

26
src/server/database/repositories/sessionConfig/service.ts

@ -0,0 +1,26 @@
import type { DBType } from '#db/sqlite';
function createPreparedStatement(db: DBType) {
return {
find: db.query.sessionConfig.findFirst().prepare(),
};
}
export class SessionConfigService {
#statements: ReturnType<typeof createPreparedStatement>;
constructor(db: DBType) {
this.#statements = createPreparedStatement(db);
}
/**
* @throws
*/
async get() {
const result = await this.#statements.find.all();
if (!result) {
throw new Error('Session Config not found');
}
return result;
}
}

29
src/server/database/repositories/user/service.ts

@ -0,0 +1,29 @@
import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { user } from './schema';
import type { ID } from '../../schema';
function createPreparedStatement(db: DBType) {
return {
findAll: db.query.user.findMany().prepare(),
findById: db.query.user
.findFirst({ where: eq(user.id, sql.placeholder('id')) })
.prepare(),
};
}
export class UserService {
#statements: ReturnType<typeof createPreparedStatement>;
constructor(db: DBType) {
this.#statements = createPreparedStatement(db);
}
async getAll() {
return this.#statements.findAll.all();
}
async get(id: ID) {
return this.#statements.findById.all({ id });
}
}

4
src/server/database/repositories/user/types.ts

@ -0,0 +1,4 @@
import type { InferSelectModel } from 'drizzle-orm';
import type { user } from './schema';
export type UserType = InferSelectModel<typeof user>;

5
src/server/database/schema.ts

@ -5,5 +5,8 @@ export * from './repositories/hooks/schema';
export * from './repositories/interface/schema';
export * from './repositories/metrics/schema';
export * from './repositories/oneTimeLink/schema';
export * from './repositories/userConfig/schema';
export * from './repositories/sessionConfig/schema';
export * from './repositories/user/schema';
export * from './repositories/userConfig/schema';
export type ID = number;

13
src/server/database/sqlite.ts

@ -4,6 +4,8 @@ import { createClient } from '@libsql/client';
import * as schema from './schema';
import { ClientService } from './repositories/client/service';
import { SessionConfigService } from './repositories/sessionConfig/service';
import { UserService } from './repositories/user/service';
const client = createClient({ url: 'file:/etc/wireguard/wg0.db' });
const db = drizzle({ client, schema });
@ -14,8 +16,14 @@ export async function connect() {
}
class DBService {
clients = new ClientService(db);
constructor(private db: DBType) {}
clients: ClientService;
sessionConfig: SessionConfigService;
users: UserService;
constructor(db: DBType) {
this.clients = new ClientService(db);
this.sessionConfig = new SessionConfigService(db);
this.users = new UserService(db);
}
}
export type DBType = typeof db;
@ -27,6 +35,7 @@ async function migrate() {
await drizzleMigrate(db, {
migrationsFolder: './server/database/migrations',
});
// TODO: data migration
console.log('Migration complete');
} catch (e) {
if (e instanceof Error) {

1
src/server/utils/Database.ts

@ -18,6 +18,7 @@ let provider = nullObject as never as DBServiceType;
connect().then((db) => {
provider = db;
// TODO: start wireguard
});
// TODO: check if old config exists and tell user about migration path

7
src/server/utils/WireGuard.ts

@ -4,10 +4,7 @@ import QRCode from 'qrcode';
import CRC32 from 'crc-32';
import isCidr from 'is-cidr';
import type {
CreateClient,
UpdateClient,
} from '~~/services/database/repositories/client';
import type { UpdateClient } from '~~/services/database/repositories/client';
const DEBUG = debug('WireGuard');
@ -24,7 +21,7 @@ class WireGuard {
* Generates and saves WireGuard config from database as wg0
*/
async #saveWireguardConfig() {
const system = await Database.system.get();
const system = await Database.get();
const clients = await Database.client.findAll();
const result = [];
result.push(wg.generateServerInterface(system));

101
src/server/utils/handler.ts

@ -0,0 +1,101 @@
import type {
EventHandler,
EventHandlerRequest,
EventHandlerResponse,
H3Event,
} from 'h3';
import type { UserType } from '#db/repositories/user/types';
export const definePermissionEventHandler = <
TReq extends EventHandlerRequest,
TRes extends EventHandlerResponse,
>(
handler: EventHandler<TReq, TRes>
) => {
return defineEventHandler(async (event) => {
const user = await getCurrentUser(event);
checkPermissions('', user);
return await handler(event);
});
};
function checkPermissions(permission: unknown, user: UserType) {
console.log({ permission, 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;
}

22
src/server/utils/session.ts

@ -1,10 +1,26 @@
import type { H3Event } from 'h3';
import type { ID } from '#db/schema';
export type WGSession = Partial<{
userId: string;
userId: ID;
}>;
const name = 'wg-easy';
export async function useWGSession(event: H3Event) {
const system = await Database.system.get();
return useSession<WGSession>(event, system.sessionConfig);
const sessionConfig = await Database.sessionConfig.get();
return useSession<WGSession>(event, {
password: sessionConfig.password,
name,
cookie: {},
});
}
export async function getWGSession(event: H3Event) {
const sessionConfig = await Database.sessionConfig.get();
return getSession<WGSession>(event, {
password: sessionConfig.password,
name,
cookie: {},
});
}

Loading…
Cancel
Save