+
{
+ currentPath = ''
+ getFolder(currentPath)
+ }
+ "
+ >
ระบบทรัพยากรบุคคล
จัดเก็บข้อมูลผลการประเมิน
@@ -41,4 +59,8 @@ const { loader } = storeToRefs(loaderStore)
.q-layout {
background: var(--q-secondary);
}
+
+.pointer {
+ cursor: pointer;
+}
diff --git a/Services/client/tests-examples/demo-todo-app.spec.ts b/Services/client/tests-examples/demo-todo-app.spec.ts
new file mode 100644
index 0000000..2fd6016
--- /dev/null
+++ b/Services/client/tests-examples/demo-todo-app.spec.ts
@@ -0,0 +1,437 @@
+import { test, expect, type Page } from '@playwright/test';
+
+test.beforeEach(async ({ page }) => {
+ await page.goto('https://demo.playwright.dev/todomvc');
+});
+
+const TODO_ITEMS = [
+ 'buy some cheese',
+ 'feed the cat',
+ 'book a doctors appointment'
+];
+
+test.describe('New Todo', () => {
+ test('should allow me to add todo items', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // Create 1st todo.
+ await newTodo.fill(TODO_ITEMS[0]);
+ await newTodo.press('Enter');
+
+ // Make sure the list only has one todo item.
+ await expect(page.getByTestId('todo-title')).toHaveText([
+ TODO_ITEMS[0]
+ ]);
+
+ // Create 2nd todo.
+ await newTodo.fill(TODO_ITEMS[1]);
+ await newTodo.press('Enter');
+
+ // Make sure the list now has two todo items.
+ await expect(page.getByTestId('todo-title')).toHaveText([
+ TODO_ITEMS[0],
+ TODO_ITEMS[1]
+ ]);
+
+ await checkNumberOfTodosInLocalStorage(page, 2);
+ });
+
+ test('should clear text input field when an item is added', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // Create one todo item.
+ await newTodo.fill(TODO_ITEMS[0]);
+ await newTodo.press('Enter');
+
+ // Check that input is empty.
+ await expect(newTodo).toBeEmpty();
+ await checkNumberOfTodosInLocalStorage(page, 1);
+ });
+
+ test('should append new items to the bottom of the list', async ({ page }) => {
+ // Create 3 items.
+ await createDefaultTodos(page);
+
+ // create a todo count locator
+ const todoCount = page.getByTestId('todo-count')
+
+ // Check test using different methods.
+ await expect(page.getByText('3 items left')).toBeVisible();
+ await expect(todoCount).toHaveText('3 items left');
+ await expect(todoCount).toContainText('3');
+ await expect(todoCount).toHaveText(/3/);
+
+ // Check all items in one call.
+ await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS);
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+});
+
+test.describe('Mark all as completed', () => {
+ test.beforeEach(async ({ page }) => {
+ await createDefaultTodos(page);
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+
+ test.afterEach(async ({ page }) => {
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+
+ test('should allow me to mark all items as completed', async ({ page }) => {
+ // Complete all todos.
+ await page.getByLabel('Mark all as complete').check();
+
+ // Ensure all todos have 'completed' class.
+ await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']);
+ await checkNumberOfCompletedTodosInLocalStorage(page, 3);
+ });
+
+ test('should allow me to clear the complete state of all items', async ({ page }) => {
+ const toggleAll = page.getByLabel('Mark all as complete');
+ // Check and then immediately uncheck.
+ await toggleAll.check();
+ await toggleAll.uncheck();
+
+ // Should be no completed classes.
+ await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']);
+ });
+
+ test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => {
+ const toggleAll = page.getByLabel('Mark all as complete');
+ await toggleAll.check();
+ await expect(toggleAll).toBeChecked();
+ await checkNumberOfCompletedTodosInLocalStorage(page, 3);
+
+ // Uncheck first todo.
+ const firstTodo = page.getByTestId('todo-item').nth(0);
+ await firstTodo.getByRole('checkbox').uncheck();
+
+ // Reuse toggleAll locator and make sure its not checked.
+ await expect(toggleAll).not.toBeChecked();
+
+ await firstTodo.getByRole('checkbox').check();
+ await checkNumberOfCompletedTodosInLocalStorage(page, 3);
+
+ // Assert the toggle all is checked again.
+ await expect(toggleAll).toBeChecked();
+ });
+});
+
+test.describe('Item', () => {
+
+ test('should allow me to mark items as complete', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // Create two items.
+ for (const item of TODO_ITEMS.slice(0, 2)) {
+ await newTodo.fill(item);
+ await newTodo.press('Enter');
+ }
+
+ // Check first item.
+ const firstTodo = page.getByTestId('todo-item').nth(0);
+ await firstTodo.getByRole('checkbox').check();
+ await expect(firstTodo).toHaveClass('completed');
+
+ // Check second item.
+ const secondTodo = page.getByTestId('todo-item').nth(1);
+ await expect(secondTodo).not.toHaveClass('completed');
+ await secondTodo.getByRole('checkbox').check();
+
+ // Assert completed class.
+ await expect(firstTodo).toHaveClass('completed');
+ await expect(secondTodo).toHaveClass('completed');
+ });
+
+ test('should allow me to un-mark items as complete', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // Create two items.
+ for (const item of TODO_ITEMS.slice(0, 2)) {
+ await newTodo.fill(item);
+ await newTodo.press('Enter');
+ }
+
+ const firstTodo = page.getByTestId('todo-item').nth(0);
+ const secondTodo = page.getByTestId('todo-item').nth(1);
+ const firstTodoCheckbox = firstTodo.getByRole('checkbox');
+
+ await firstTodoCheckbox.check();
+ await expect(firstTodo).toHaveClass('completed');
+ await expect(secondTodo).not.toHaveClass('completed');
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+
+ await firstTodoCheckbox.uncheck();
+ await expect(firstTodo).not.toHaveClass('completed');
+ await expect(secondTodo).not.toHaveClass('completed');
+ await checkNumberOfCompletedTodosInLocalStorage(page, 0);
+ });
+
+ test('should allow me to edit an item', async ({ page }) => {
+ await createDefaultTodos(page);
+
+ const todoItems = page.getByTestId('todo-item');
+ const secondTodo = todoItems.nth(1);
+ await secondTodo.dblclick();
+ await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]);
+ await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
+ await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter');
+
+ // Explicitly assert the new text value.
+ await expect(todoItems).toHaveText([
+ TODO_ITEMS[0],
+ 'buy some sausages',
+ TODO_ITEMS[2]
+ ]);
+ await checkTodosInLocalStorage(page, 'buy some sausages');
+ });
+});
+
+test.describe('Editing', () => {
+ test.beforeEach(async ({ page }) => {
+ await createDefaultTodos(page);
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+
+ test('should hide other controls when editing', async ({ page }) => {
+ const todoItem = page.getByTestId('todo-item').nth(1);
+ await todoItem.dblclick();
+ await expect(todoItem.getByRole('checkbox')).not.toBeVisible();
+ await expect(todoItem.locator('label', {
+ hasText: TODO_ITEMS[1],
+ })).not.toBeVisible();
+ await checkNumberOfTodosInLocalStorage(page, 3);
+ });
+
+ test('should save edits on blur', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).dblclick();
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur');
+
+ await expect(todoItems).toHaveText([
+ TODO_ITEMS[0],
+ 'buy some sausages',
+ TODO_ITEMS[2],
+ ]);
+ await checkTodosInLocalStorage(page, 'buy some sausages');
+ });
+
+ test('should trim entered text', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).dblclick();
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages ');
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter');
+
+ await expect(todoItems).toHaveText([
+ TODO_ITEMS[0],
+ 'buy some sausages',
+ TODO_ITEMS[2],
+ ]);
+ await checkTodosInLocalStorage(page, 'buy some sausages');
+ });
+
+ test('should remove the item if an empty text string was entered', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).dblclick();
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('');
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter');
+
+ await expect(todoItems).toHaveText([
+ TODO_ITEMS[0],
+ TODO_ITEMS[2],
+ ]);
+ });
+
+ test('should cancel edits on escape', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).dblclick();
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
+ await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape');
+ await expect(todoItems).toHaveText(TODO_ITEMS);
+ });
+});
+
+test.describe('Counter', () => {
+ test('should display the current number of todo items', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ // create a todo count locator
+ const todoCount = page.getByTestId('todo-count')
+
+ await newTodo.fill(TODO_ITEMS[0]);
+ await newTodo.press('Enter');
+
+ await expect(todoCount).toContainText('1');
+
+ await newTodo.fill(TODO_ITEMS[1]);
+ await newTodo.press('Enter');
+ await expect(todoCount).toContainText('2');
+
+ await checkNumberOfTodosInLocalStorage(page, 2);
+ });
+});
+
+test.describe('Clear completed button', () => {
+ test.beforeEach(async ({ page }) => {
+ await createDefaultTodos(page);
+ });
+
+ test('should display the correct text', async ({ page }) => {
+ await page.locator('.todo-list li .toggle').first().check();
+ await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible();
+ });
+
+ test('should remove completed items when clicked', async ({ page }) => {
+ const todoItems = page.getByTestId('todo-item');
+ await todoItems.nth(1).getByRole('checkbox').check();
+ await page.getByRole('button', { name: 'Clear completed' }).click();
+ await expect(todoItems).toHaveCount(2);
+ await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
+ });
+
+ test('should be hidden when there are no items that are completed', async ({ page }) => {
+ await page.locator('.todo-list li .toggle').first().check();
+ await page.getByRole('button', { name: 'Clear completed' }).click();
+ await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden();
+ });
+});
+
+test.describe('Persistence', () => {
+ test('should persist its data', async ({ page }) => {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ for (const item of TODO_ITEMS.slice(0, 2)) {
+ await newTodo.fill(item);
+ await newTodo.press('Enter');
+ }
+
+ const todoItems = page.getByTestId('todo-item');
+ const firstTodoCheck = todoItems.nth(0).getByRole('checkbox');
+ await firstTodoCheck.check();
+ await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
+ await expect(firstTodoCheck).toBeChecked();
+ await expect(todoItems).toHaveClass(['completed', '']);
+
+ // Ensure there is 1 completed item.
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+
+ // Now reload.
+ await page.reload();
+ await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
+ await expect(firstTodoCheck).toBeChecked();
+ await expect(todoItems).toHaveClass(['completed', '']);
+ });
+});
+
+test.describe('Routing', () => {
+ test.beforeEach(async ({ page }) => {
+ await createDefaultTodos(page);
+ // make sure the app had a chance to save updated todos in storage
+ // before navigating to a new view, otherwise the items can get lost :(
+ // in some frameworks like Durandal
+ await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
+ });
+
+ test('should allow me to display active items', async ({ page }) => {
+ const todoItem = page.getByTestId('todo-item');
+ await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
+
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+ await page.getByRole('link', { name: 'Active' }).click();
+ await expect(todoItem).toHaveCount(2);
+ await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
+ });
+
+ test('should respect the back button', async ({ page }) => {
+ const todoItem = page.getByTestId('todo-item');
+ await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
+
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+
+ await test.step('Showing all items', async () => {
+ await page.getByRole('link', { name: 'All' }).click();
+ await expect(todoItem).toHaveCount(3);
+ });
+
+ await test.step('Showing active items', async () => {
+ await page.getByRole('link', { name: 'Active' }).click();
+ });
+
+ await test.step('Showing completed items', async () => {
+ await page.getByRole('link', { name: 'Completed' }).click();
+ });
+
+ await expect(todoItem).toHaveCount(1);
+ await page.goBack();
+ await expect(todoItem).toHaveCount(2);
+ await page.goBack();
+ await expect(todoItem).toHaveCount(3);
+ });
+
+ test('should allow me to display completed items', async ({ page }) => {
+ await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+ await page.getByRole('link', { name: 'Completed' }).click();
+ await expect(page.getByTestId('todo-item')).toHaveCount(1);
+ });
+
+ test('should allow me to display all items', async ({ page }) => {
+ await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
+ await checkNumberOfCompletedTodosInLocalStorage(page, 1);
+ await page.getByRole('link', { name: 'Active' }).click();
+ await page.getByRole('link', { name: 'Completed' }).click();
+ await page.getByRole('link', { name: 'All' }).click();
+ await expect(page.getByTestId('todo-item')).toHaveCount(3);
+ });
+
+ test('should highlight the currently applied filter', async ({ page }) => {
+ await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected');
+
+ //create locators for active and completed links
+ const activeLink = page.getByRole('link', { name: 'Active' });
+ const completedLink = page.getByRole('link', { name: 'Completed' });
+ await activeLink.click();
+
+ // Page change - active items.
+ await expect(activeLink).toHaveClass('selected');
+ await completedLink.click();
+
+ // Page change - completed items.
+ await expect(completedLink).toHaveClass('selected');
+ });
+});
+
+async function createDefaultTodos(page: Page) {
+ // create a new todo locator
+ const newTodo = page.getByPlaceholder('What needs to be done?');
+
+ for (const item of TODO_ITEMS) {
+ await newTodo.fill(item);
+ await newTodo.press('Enter');
+ }
+}
+
+async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
+ return await page.waitForFunction(e => {
+ return JSON.parse(localStorage['react-todos']).length === e;
+ }, expected);
+}
+
+async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) {
+ return await page.waitForFunction(e => {
+ return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e;
+ }, expected);
+}
+
+async function checkTodosInLocalStorage(page: Page, title: string) {
+ return await page.waitForFunction(t => {
+ return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t);
+ }, title);
+}
diff --git a/Services/client/tests/example.spec.ts b/Services/client/tests/example.spec.ts
new file mode 100644
index 0000000..54a906a
--- /dev/null
+++ b/Services/client/tests/example.spec.ts
@@ -0,0 +1,18 @@
+import { test, expect } from '@playwright/test';
+
+test('has title', async ({ page }) => {
+ await page.goto('https://playwright.dev/');
+
+ // Expect a title "to contain" a substring.
+ await expect(page).toHaveTitle(/Playwright/);
+});
+
+test('get started link', async ({ page }) => {
+ await page.goto('https://playwright.dev/');
+
+ // Click the get started link.
+ await page.getByRole('link', { name: 'Get started' }).click();
+
+ // Expects page to have a heading with the name of Installation.
+ await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
+});
diff --git a/Services/client/tsconfig.config.json b/Services/client/tsconfig.config.json
index 216b43e..1332f8c 100644
--- a/Services/client/tsconfig.config.json
+++ b/Services/client/tsconfig.config.json
@@ -1,11 +1,6 @@
{
"extends": ["@tsconfig/node18/tsconfig.json", "@vue/tsconfig/tsconfig.json"],
- "include": [
- "vite.config.*",
- "vitest.config.*",
- "cypress.config.*",
- "playwright.config.*"
- ],
+ "include": ["vite.config.*", "vitest.config.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"ignoreDeprecations": "5.0",
diff --git a/Services/client/typedoc.json b/Services/client/typedoc.json
index 3eb93e0..2c27c43 100644
--- a/Services/client/typedoc.json
+++ b/Services/client/typedoc.json
@@ -1,5 +1,5 @@
{
- "plugin": ["typedoc-plugin-vue"],
- "entryPoints": ["src/main.ts","src/**/*.ts"],
- "out": "fe-typedoc"
+ "plugin": ["typedoc-plugin-vue"],
+ "entryPoints": ["src/main.ts", "src/**/*.ts"],
+ "out": "fe-typedoc"
}
diff --git a/Services/server/README.md b/Services/server/README.md
index a73b9a9..40013c5 100644
--- a/Services/server/README.md
+++ b/Services/server/README.md
@@ -13,6 +13,12 @@ Enterprise Document Management (EDM) ส่วน backend
- Attachment Processor สำหรับดึงข้อความเพื่อทำ Index สำหรับ Elasticsearch
- RabbitMQ ใช้ Bucket Notification ของ MiniO ส่งแจ้งเตือนเมื่อมีเอกสารใหม่จะต้องทำ Index ทำงานเป็น คิวเพื่อทำให้รับโหลดเอกสารได้มากยิ่งขึ้น เอกสารที่ทำผ่าน MiniO ก็จะถูกทำ Index โดยที่ Application ไม่ต้องเขียนการจัดการพิเศษ
+# การเรียกใช้ผ่าน Service Account
+จำเป็นต้องตั้งค่า Keycloak เพื่อให้สามารถขอ Token โดยใช้ Secret Key ที่ได้รับ ผ่านทาง `https://edm-id.frappet.synology.me/realms/EDM/protocol/openid-connect/token` ดังนี้
+
+
+# สิทธิในจัดการ
+การจัดการสามารถตั้งค่าได้โดยใช้ ENV ที่มีชื่อว่า MANAGEMENT_ROLE ให้ตรงกับที่ตั้งค่าใน Keycloak อีกที
## Note
- ELK (Elasticsearch+Kibana) ใช้แบบ unsecure อยู่หลัง firewall น่าจะเพียงพอ อาจจะ[ปรับเพิ่มเรื่องความปลอดภัย](https://vorapoap.medium.com/%E0%B8%95%E0%B8%AD%E0%B8%99%E0%B8%97%E0%B8%B5%E0%B9%88-3-%E0%B8%95%E0%B8%B4%E0%B8%94%E0%B8%95%E0%B8%B1%E0%B9%89%E0%B8%87-security-%E0%B9%83%E0%B8%AB%E0%B9%89%E0%B8%81%E0%B8%B1%E0%B8%9A-elasticsearch-aa26a71b87ff)ในอนาคน
diff --git a/Services/server/src/controllers/cabinetController.ts b/Services/server/src/controllers/cabinetController.ts
index 0f22ebb..463cb43 100644
--- a/Services/server/src/controllers/cabinetController.ts
+++ b/Services/server/src/controllers/cabinetController.ts
@@ -64,7 +64,7 @@ export class CabinetController extends Controller {
@Post("/")
@Tags("ตู้เอกสาร")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์")
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
public async createCabinet(
@@ -94,7 +94,7 @@ export class CabinetController extends Controller {
*/
@Put("/{cabinetName}")
@Tags("ตู้เอกสาร")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async editCabinet(
@@ -163,7 +163,7 @@ export class CabinetController extends Controller {
*/
@Delete("/{cabinetName}")
@Tags("ตู้เอกสาร")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาด ไม่สามารถลบไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteCabinet(@Path() cabinetName: string) {
diff --git a/Services/server/src/controllers/drawerController.ts b/Services/server/src/controllers/drawerController.ts
index 804e942..9d3a505 100644
--- a/Services/server/src/controllers/drawerController.ts
+++ b/Services/server/src/controllers/drawerController.ts
@@ -71,7 +71,7 @@ export class DrawerController extends Controller {
*/
@Post("/")
@Tags("ลิ้นชัก")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบลิ้นชัก")
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์")
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
@@ -110,7 +110,7 @@ export class DrawerController extends Controller {
*/
@Put("/{drawerName}")
@Tags("ลิ้นชัก")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async editDrawer(
@@ -181,7 +181,7 @@ export class DrawerController extends Controller {
*/
@Delete("/{drawerName}")
@Tags("ลิ้นชัก")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteDrawer(@Path() cabinetName: string, @Path() drawerName: string) {
await new Promise((resolve, reject) => {
diff --git a/Services/server/src/controllers/fileController.ts b/Services/server/src/controllers/fileController.ts
index 5d7c6d9..6baec35 100644
--- a/Services/server/src/controllers/fileController.ts
+++ b/Services/server/src/controllers/fileController.ts
@@ -93,7 +93,7 @@ export class FileController extends Controller {
*/
@Post("/")
@Tags("ไฟล์")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(
HttpStatusCode.NOT_FOUND,
"ตำแหน่งที่ระบุไม่พบ กรุณาเตรียมตำแหน่งที่ต้องการก่อนดำเนินการ",
@@ -180,7 +180,7 @@ export class FileController extends Controller {
const metadata: Partial = {
pathname,
path: basePath,
- fileName: replaceIllegalChars( body.file ),
+ fileName: replaceIllegalChars(body.file),
fileSize: 0,
fileType: "",
title: body.title ?? "",
@@ -218,7 +218,7 @@ export class FileController extends Controller {
*/
@Patch("/{fileName}")
@Tags("ไฟล์")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างแฟ้ม")
@Response(HttpStatusCode.NO_CONTENT, "สำเร็จ")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@@ -270,9 +270,11 @@ export class FileController extends Controller {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์");
}
+ const { file, ...metadata } = body;
+
// assume user will probably replace file by re-upload but maybe just rename
- if (body.file) {
- const destination = `${basePath}${replaceIllegalChars(body.file)}`;
+ if (file) {
+ const destination = `${basePath}${replaceIllegalChars(file)}`;
const source = `/${DEFAULT_BUCKET}/${basePath}${fileName}`;
const copy = await minioClient.copyObject(DEFAULT_BUCKET!, destination, source, copyCond);
@@ -291,9 +293,10 @@ export class FileController extends Controller {
index,
id,
doc: {
+ ...metadata,
pathname: destination,
path: basePath,
- fileName: replaceIllegalChars(body.file),
+ fileName: replaceIllegalChars(file),
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
@@ -318,7 +321,7 @@ export class FileController extends Controller {
index,
id,
doc: {
- ...body,
+ ...metadata,
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
@@ -345,7 +348,7 @@ export class FileController extends Controller {
*/
@Delete("/{fileName}")
@Tags("ไฟล์")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async deleteFile(
@Path() cabinetName: string,
diff --git a/Services/server/src/controllers/folderController.ts b/Services/server/src/controllers/folderController.ts
index 553661d..9a2e663 100644
--- a/Services/server/src/controllers/folderController.ts
+++ b/Services/server/src/controllers/folderController.ts
@@ -75,7 +75,7 @@ export class FolderController extends Controller {
*/
@Post("/")
@Tags("แฟ้ม")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างแฟ้ม")
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์")
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
@@ -116,7 +116,7 @@ export class FolderController extends Controller {
*/
@Put("/{folderName}")
@Tags("แฟ้ม")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async editFolder(
@@ -189,7 +189,7 @@ export class FolderController extends Controller {
*/
@Delete("/{folderName}")
@Tags("แฟ้ม")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteFolder(
@Path() cabinetName: string,
diff --git a/Services/server/src/controllers/subFolderController.ts b/Services/server/src/controllers/subFolderController.ts
index 08dab6d..9199a30 100644
--- a/Services/server/src/controllers/subFolderController.ts
+++ b/Services/server/src/controllers/subFolderController.ts
@@ -79,7 +79,7 @@ export class SubFolderController extends Controller {
*/
@Post("/")
@Tags("แฟ้มย่อย")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบของแฟ้ม")
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์")
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
@@ -116,7 +116,7 @@ export class SubFolderController extends Controller {
*/
@Put("/{subFolderName}")
@Tags("แฟ้มย่อย")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้")
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async editFolder(
@@ -193,7 +193,7 @@ export class SubFolderController extends Controller {
*/
@Delete("/{subFolderName}")
@Tags("แฟ้มย่อย")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
public async deleteFolder(
@Path() cabinetName: string,
diff --git a/Services/server/src/controllers/subFolderFileController.ts b/Services/server/src/controllers/subFolderFileController.ts
index 2bdc672..717a87e 100644
--- a/Services/server/src/controllers/subFolderFileController.ts
+++ b/Services/server/src/controllers/subFolderFileController.ts
@@ -98,7 +98,7 @@ export class SubFolderFileController extends Controller {
*/
@Post("/")
@Tags("ไฟล์")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(
HttpStatusCode.NOT_FOUND,
"ตำแหน่งที่ระบุไม่พบ กรุณาเตรียมตำแหน่งที่ต้องการก่อนดำเนินการ",
@@ -225,7 +225,7 @@ export class SubFolderFileController extends Controller {
*/
@Patch("/{fileName}")
@Tags("ไฟล์")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@Response(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างแฟ้ม")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async updateFile(
@@ -277,9 +277,11 @@ export class SubFolderFileController extends Controller {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์");
}
+ const { file, ...metadata } = body;
+
// assume user will probably replace file by re-upload but maybe just rename
- if (body.file) {
- const destination = `${basePath}${replaceIllegalChars(body.file)}`;
+ if (file) {
+ const destination = `${basePath}${replaceIllegalChars(file)}`;
const source = `/${DEFAULT_BUCKET}/${basePath}${fileName}`;
const copy = await minioClient.copyObject(DEFAULT_BUCKET!, destination, source, copyCond);
@@ -298,8 +300,10 @@ export class SubFolderFileController extends Controller {
index,
id,
doc: {
+ ...metadata,
pathname: destination,
- fileName: replaceIllegalChars(body.file),
+ path: basePath,
+ fileName: replaceIllegalChars(file),
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
@@ -324,7 +328,7 @@ export class SubFolderFileController extends Controller {
index,
id,
doc: {
- ...body,
+ ...metadata,
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
@@ -352,7 +356,7 @@ export class SubFolderFileController extends Controller {
*/
@Delete("/{fileName}")
@Tags("ไฟล์")
- @Security("bearerAuth", ["admin"])
+ @Security("bearerAuth", ["admin", "management-role"])
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
public async deleteFile(
@Path() cabinetName: string,
@@ -405,7 +409,7 @@ export class SubFolderFileController extends Controller {
...rest,
download: await minioClient.presignedGetObject(
DEFAULT_BUCKET!,
- `${cabinetName}/${drawerName}/${folderName}/${fileName}`,
+ `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`,
),
};
}
diff --git a/Services/server/src/routes.ts b/Services/server/src/routes.ts
index 9fa706a..a056828 100644
--- a/Services/server/src/routes.ts
+++ b/Services/server/src/routes.ts
@@ -101,7 +101,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(CabinetController)),
...(fetchMiddlewares(CabinetController.prototype.createCabinet)),
@@ -128,7 +128,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/cabinet/:cabinetName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(CabinetController)),
...(fetchMiddlewares(CabinetController.prototype.editCabinet)),
@@ -155,7 +155,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(CabinetController)),
...(fetchMiddlewares(CabinetController.prototype.deleteCabinet)),
@@ -207,7 +207,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(DrawerController)),
...(fetchMiddlewares(DrawerController.prototype.createDrawer)),
@@ -235,7 +235,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/cabinet/:cabinetName/drawer/:drawerName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(DrawerController)),
...(fetchMiddlewares(DrawerController.prototype.editDrawer)),
@@ -263,7 +263,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(DrawerController)),
...(fetchMiddlewares(DrawerController.prototype.deleteDrawer)),
@@ -318,7 +318,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/file',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(FileController)),
...(fetchMiddlewares(FileController.prototype.uploadFile)),
@@ -348,7 +348,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.patch('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/file/:fileName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(FileController)),
...(fetchMiddlewares(FileController.prototype.updateFile)),
@@ -379,7 +379,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/file/:fileName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(FileController)),
...(fetchMiddlewares(FileController.prototype.deleteFile)),
@@ -464,7 +464,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer/:drawerName/folder',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(FolderController)),
...(fetchMiddlewares(FolderController.prototype.createFolder)),
@@ -493,7 +493,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(FolderController)),
...(fetchMiddlewares(FolderController.prototype.editFolder)),
@@ -522,7 +522,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(FolderController)),
...(fetchMiddlewares(FolderController.prototype.deleteFolder)),
@@ -604,7 +604,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(SubFolderController)),
...(fetchMiddlewares(SubFolderController.prototype.createFolder)),
@@ -634,7 +634,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(SubFolderController)),
...(fetchMiddlewares(SubFolderController.prototype.editFolder)),
@@ -664,7 +664,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(SubFolderController)),
...(fetchMiddlewares(SubFolderController.prototype.deleteFolder)),
@@ -722,7 +722,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/file',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(SubFolderFileController)),
...(fetchMiddlewares(SubFolderFileController.prototype.uploadFile)),
@@ -753,7 +753,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.patch('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/file/:fileName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(SubFolderFileController)),
...(fetchMiddlewares(SubFolderFileController.prototype.updateFile)),
@@ -785,7 +785,7 @@ export function RegisterRoutes(app: Router) {
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder/:subFolderName/file/:fileName',
- authenticateMiddleware([{"bearerAuth":["admin"]}]),
+ authenticateMiddleware([{"bearerAuth":["admin","management-role"]}]),
...(fetchMiddlewares(SubFolderFileController)),
...(fetchMiddlewares(SubFolderFileController.prototype.deleteFile)),
diff --git a/Services/server/src/swagger.json b/Services/server/src/swagger.json
index da8bf1b..37ab26f 100644
--- a/Services/server/src/swagger.json
+++ b/Services/server/src/swagger.json
@@ -261,7 +261,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -304,7 +305,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -355,7 +357,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -450,7 +453,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -503,7 +507,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -560,7 +565,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -783,7 +789,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -906,7 +913,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -1007,7 +1015,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -1286,7 +1295,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -1348,7 +1358,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -1414,7 +1425,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -1584,7 +1596,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -1654,7 +1667,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -1729,7 +1743,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -1979,7 +1994,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -2108,7 +2124,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
@@ -2218,7 +2235,8 @@
"security": [
{
"bearerAuth": [
- "admin"
+ "admin",
+ "management-role"
]
}
],
diff --git a/Services/server/src/utils/auth.ts b/Services/server/src/utils/auth.ts
index 2b601fa..b7ae6cd 100644
--- a/Services/server/src/utils/auth.ts
+++ b/Services/server/src/utils/auth.ts
@@ -1,12 +1,19 @@
import * as express from "express";
-import { createVerifier } from "fast-jwt";
+import { createDecoder, createVerifier } from "fast-jwt";
import HttpError from "../interfaces/http-error";
import HttpStatusCode from "../interfaces/http-status";
+import { JwtPayload } from "jsonwebtoken";
if (!process.env.PUBLIC_KEY && !process.env.REALM_URL) {
throw new Error("Require public key or realm url.");
}
+if (process.env.PUBLIC_KEY && process.env.REALM_URL && !process.env.PREFERRED_AUTH) {
+ throw new Error("Preferred auth type must be specified if public key and realm url is provided.");
+}
+if (!process.env.MANAGEMENT_ROLE) {
+ throw new Error("Management role env is required.");
+}
const jwtVerify = createVerifier({
key: async () => {
@@ -14,6 +21,8 @@ const jwtVerify = createVerifier({
},
});
+const jwtDecode = createDecoder();
+
export async function expressAuthentication(
request: express.Request,
securityName: string,
@@ -29,19 +38,47 @@ export async function expressAuthentication(
if (!token) throw new HttpError(HttpStatusCode.UNAUTHORIZED, "No token provided.");
- const payload = await jwtVerify(token).catch((_) => null);
+ let payload: JwtPayload = {};
- if (!payload) {
- throw new HttpError(HttpStatusCode.UNAUTHORIZED, "Invalid token provided.");
+ switch (process.env.PREFERRED_AUTH) {
+ case "online":
+ payload = await verifyOnline(token);
+ break;
+ case "offline":
+ payload = await verifyOffline(token);
+ break;
+ default:
+ if (process.env.REALM_URL) payload = await verifyOnline(token);
+ if (process.env.PUBLIC_KEY) payload = await verifyOffline(token);
+ break;
}
if (
scopes &&
scopes.length > 0 &&
- scopes.some((v) => !payload.resource_access[payload.azp].roles.includes(v))
+ scopes
+ .map((v) => (v === "management-role" ? process.env.MANAGEMENT_ROLE : v))
+ .every((v) => !payload.resource_access[payload.azp].roles.includes(v))
) {
throw new HttpError(HttpStatusCode.FORBIDDEN, "You are not allowed to perform this action.");
}
return payload;
}
+
+async function verifyOffline(token: string) {
+ const payload = await jwtVerify(token).catch((_) => null);
+ if (!payload) throw new HttpError(HttpStatusCode.UNAUTHORIZED, "Invalid token provided.");
+ return payload;
+}
+
+async function verifyOnline(token: string) {
+ const res = await fetch(`${process.env.REALM_URL}/protocol/openid-connect/userinfo`, {
+ headers: { authorization: `Bearer ${token}` },
+ }).catch((e) => console.error(e));
+
+ if (!res) throw new Error("Cannot connect to auth service.");
+ if (!res.ok) throw new HttpError(HttpStatusCode.UNAUTHORIZED, "Invalid token provided.");
+
+ return await jwtDecode(token);
+}