mirror of https://github.com/wg-easy/wg-easy
11 changed files with 213 additions and 12 deletions
@ -61,4 +61,4 @@ |
|||
"vue-tsc": "^2.2.0" |
|||
}, |
|||
"packageManager": "[email protected]" |
|||
} |
|||
} |
|||
|
@ -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)`), |
|||
}); |
@ -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; |
|||
} |
|||
} |
@ -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 }); |
|||
} |
|||
} |
@ -0,0 +1,4 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { user } from './schema'; |
|||
|
|||
export type UserType = InferSelectModel<typeof user>; |
@ -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; |
|||
} |
@ -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…
Reference in new issue