Browse Source

permission matrix, permission handler

pull/1619/head
Bernd Storath 3 months ago
parent
commit
d3a262e42c
  1. 2
      src/server/database/repositories/user/schema.ts
  2. 94
      src/server/middleware/session.ts
  3. 28
      src/server/utils/handler.ts
  4. 31
      src/server/utils/permissions.ts

2
src/server/database/repositories/user/schema.ts

@ -7,7 +7,7 @@ export const user = sqliteTable('users_table', {
password: text().notNull(),
email: text(),
name: text().notNull(),
role: int().notNull(),
role: int().$type<Role>().notNull(),
enabled: int().notNull().default(1),
createdAt: text('created_at')
.notNull()

94
src/server/middleware/session.ts

@ -1,94 +0,0 @@
import type { User } from '~~/services/database/repositories/user';
export default defineEventHandler(async (event) => {
const url = getRequestURL(event);
// If one method of a route is public, every method is public!
// Handle api routes
if (
!url.pathname.startsWith('/api/') ||
url.pathname.startsWith('/api/setup/') ||
url.pathname === '/api/session' ||
url.pathname === '/api/release'
) {
return;
}
const system = await Database.system.get();
const session = await getSession<WGSession>(event, system.sessionConfig);
const authorization = getHeader(event, 'Authorization');
let user: User | undefined = undefined;
if (session.data.userId) {
// Handle if authenticating using Session
user = await Database.user.findById(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 users = await Database.user.findAll();
const foundUser = users.find((v) => v.username === 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: 'Not logged in',
});
}
if (!user.enabled) {
throw createError({
statusCode: 403,
statusMessage: 'Account is disabled',
});
}
if (url.pathname.startsWith('/api/admin')) {
if (user.role !== 'ADMIN') {
throw createError({
statusCode: 403,
statusMessage: 'Missing Permissions',
});
}
}
});

28
src/server/utils/handler.ts

@ -1,29 +1,31 @@
import type {
EventHandler,
EventHandlerRequest,
EventHandlerResponse,
H3Event,
} from 'h3';
import type { EventHandlerRequest, EventHandlerResponse, H3Event } from 'h3';
import type { UserType } from '#db/repositories/user/types';
type PermissionHandler<
TReq extends EventHandlerRequest,
TRes extends EventHandlerResponse,
> = { (params: { event: H3Event<TReq>; user: UserType }): TRes };
export const definePermissionEventHandler = <
TReq extends EventHandlerRequest,
TRes extends EventHandlerResponse,
>(
handler: EventHandler<TReq, TRes>
action: Action,
handler: PermissionHandler<TReq, TRes>
) => {
return defineEventHandler(async (event) => {
const user = await getCurrentUser(event);
checkPermissions('', user);
if (!checkPermissions(action, user)) {
throw createError({
statusCode: 403,
statusMessage: 'Forbidden',
});
}
return await handler(event);
return await handler({ event, user });
});
};
function checkPermissions(permission: unknown, user: UserType) {
console.log({ permission, user });
}
/**
* @throws
*/

31
src/server/utils/permissions.ts

@ -0,0 +1,31 @@
// TODO: will need to be updated when we have more roles and actions
export const actions = {
ADMIN: 'ADMIN',
CLIENT: 'CLIENT',
} as const;
export type Role = number & { readonly __role: unique symbol };
export const roles = {
ADMIN: 1 as Role,
CLIENT: 2 as Role,
} as const;
export type Action = keyof typeof actions;
type MATRIX = {
readonly [key in keyof typeof actions]: readonly (typeof roles)[keyof typeof roles][];
};
export const MATRIX: MATRIX = {
[actions.ADMIN]: [roles.ADMIN] as const,
[actions.CLIENT]: [roles.CLIENT, roles.ADMIN] as const,
} as const;
export const checkPermissions = (action: Action, user: { role: Role }) => {
if (!MATRIX[action].includes(user.role)) {
return false;
}
return true;
};
Loading…
Cancel
Save