Compare commits

...

4 commits

Author SHA1 Message Date
b82355ea1a Remove all login and auth related code
I am gonna use something like caddy as revers proxy to expose the service.
Caddy offers the functionality to secure resources with basic auth
and promt the browser on usage. I am gona go with it. It's the simplest
solution.

I still need to remove the basic auth code from the backend and craft
a configuration for caddy.
2023-04-29 16:34:58 +02:00
48cc667d26 Duplicate servers views for users 2023-04-29 15:53:53 +02:00
6cde6438d2 Remove unused action from ServerMessagesView
There are no actions for this view.
2023-04-29 15:43:37 +02:00
c096ec17c1 Make actions column in TableComponent optional 2023-04-29 15:42:53 +02:00
14 changed files with 122 additions and 198 deletions

72
ui/package-lock.json generated
View file

@ -9,7 +9,6 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"daisyui": "^2.51.5", "daisyui": "^2.51.5",
"pinia": "^2.0.34",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-router": "^4.1.6" "vue-router": "^4.1.6"
}, },
@ -2151,56 +2150,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/pinia": {
"version": "2.0.34",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.34.tgz",
"integrity": "sha512-cgOoGUiyqX0SSgX8XelK9+Ri4XA2/YyNtgjogwfzIx1g7iZTaZPxm7/bZYMCLU2qHRiHhxG7SuQO0eBacFNc2Q==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.0.tgz",
"integrity": "sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pirates": { "node_modules/pirates": {
"version": "4.0.5", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
@ -2832,7 +2781,7 @@
"version": "4.8.4", "version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"devOptional": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -4534,23 +4483,6 @@
"integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
"dev": true "dev": true
}, },
"pinia": {
"version": "2.0.34",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.34.tgz",
"integrity": "sha512-cgOoGUiyqX0SSgX8XelK9+Ri4XA2/YyNtgjogwfzIx1g7iZTaZPxm7/bZYMCLU2qHRiHhxG7SuQO0eBacFNc2Q==",
"requires": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": "*"
},
"dependencies": {
"vue-demi": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.0.tgz",
"integrity": "sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg==",
"requires": {}
}
}
},
"pirates": { "pirates": {
"version": "4.0.5", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
@ -4973,7 +4905,7 @@
"version": "4.8.4", "version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"devOptional": true "dev": true
}, },
"unbox-primitive": { "unbox-primitive": {
"version": "1.0.2", "version": "1.0.2",

View file

@ -11,7 +11,6 @@
}, },
"dependencies": { "dependencies": {
"daisyui": "^2.51.5", "daisyui": "^2.51.5",
"pinia": "^2.0.34",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-router": "^4.1.6" "vue-router": "^4.1.6"
}, },

View file

@ -12,11 +12,6 @@ import { RouterView } from 'vue-router'
</div> </div>
<div class="flex-none"> <div class="flex-none">
<ul class="menu menu-horizontal px-1"> <ul class="menu menu-horizontal px-1">
<li>
<a>
<RouterLink to="/login">Login</RouterLink>
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -4,7 +4,7 @@ type ActionFunction = (index: number) => void
const { tableHeader, tableRows, actions } = defineProps<{ const { tableHeader, tableRows, actions } = defineProps<{
tableHeader: string[], tableHeader: string[],
tableRows: any[][], tableRows: any[][],
actions: { actions?: {
content: string, content: string,
class: string, class: string,
event: ActionFunction event: ActionFunction
@ -20,7 +20,7 @@ const { tableHeader, tableRows, actions } = defineProps<{
<template v-for="header in tableHeader"> <template v-for="header in tableHeader">
<th>{{ header }}</th> <th>{{ header }}</th>
</template> </template>
<th>Action</th> <th v-if="actions != null && actions.length > 0">Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -30,7 +30,7 @@ const { tableHeader, tableRows, actions } = defineProps<{
<template v-for="data in row"> <template v-for="data in row">
<th>{{ data }}</th> <th>{{ data }}</th>
</template> </template>
<th> <th v-if="actions != null && actions.length > 0">
<template v-for="action in actions"> <template v-for="action in actions">
<button :class="action.class" @click="action.event(index)"> <button :class="action.class" @click="action.event(index)">
{{ action.content }} {{ action.content }}

View file

@ -1,55 +0,0 @@
import { useAuthStore } from "../stores/auth.store"
export const fetchWrapper = {
get: request('GET'),
post: request('POST'),
put: request('PUT'),
delete: request('DELETE')
};
function request(method) {
return (url, body) => {
const requestOptions = {
method,
headers: authHeader(url)
};
if (body) {
requestOptions.headers['Content-Type'] = 'application/json';
requestOptions.body = JSON.stringify(body);
}
return fetch(url, requestOptions).then(handleResponse);
}
}
// helper functions
function authHeader(url) {
// return auth header with basic auth credentials if user is logged in and request is to the api url
const { user } = useAuthStore();
const isLoggedIn = !!user?.authdata;
const isApiUrl = url.startsWith(import.meta.env.VITE_API_URL);
if (isLoggedIn && isApiUrl) {
return { Authorization: `Basic ${user.authdata}` };
} else {
return {};
}
}
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
const { user, logout } = useAuthStore();
if ([401, 403].includes(response.status) && user) {
// auto logout if 401 Unauthorized or 403 Forbidden response returned from api
logout();
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}

View file

@ -1,11 +1,15 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue' import HomeView from '@/views/HomeView.vue'
import LoginView from '@/views/LoginView.vue'
import AllowedServersView from '@/views/servers/AllowedServersView.vue' import AllowedServersView from '@/views/servers/AllowedServersView.vue'
import IndexServersView from '@/views/servers/IndexServersView.vue' import IndexServersView from '@/views/servers/IndexServersView.vue'
import ServerConfigsView from '@/views/servers/ServerConfigsView.vue' import ServerConfigsView from '@/views/servers/ServerConfigsView.vue'
import ServersMessagesView from '@/views/servers/ServersMessagesView.vue' import ServersMessagesView from '@/views/servers/ServersMessagesView.vue'
import ServerMessagesView from '@/views/servers/ServerMessagesView.vue' import ServerMessagesView from '@/views/servers/ServerMessagesView.vue'
import AllowedUsersView from '@/views/users/AllowedUsersView.vue'
import IndexUsersView from '@/views/users/IndexUsersView.vue'
import UserConfigsView from '@/views/users/UserConfigsView.vue'
import UsersMessagesView from '@/views/users/UsersMessagesView.vue'
import UserMessagesView from '@/views/users/UserMessagesView.vue'
const routerConfig = createRouter({ const routerConfig = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -15,11 +19,6 @@ const routerConfig = createRouter({
name: 'home', name: 'home',
component: HomeView component: HomeView
}, },
{
path: '/login',
name: 'login',
component: LoginView
},
{ {
path: '/servers', path: '/servers',
name: 'servers', name: 'servers',
@ -44,6 +43,31 @@ const routerConfig = createRouter({
path: '/servers/messages/:serverId(\\d+)', path: '/servers/messages/:serverId(\\d+)',
name: 'serverMessages', name: 'serverMessages',
component: ServerMessagesView component: ServerMessagesView
},
{
path: '/users',
name: 'users',
component: IndexUsersView
},
{
path: '/users/allowed',
name: 'allowedUsers',
component: AllowedUsersView
},
{
path: '/users/configs',
name: 'userConfigs',
component: UserConfigsView
},
{
path: '/users/messages',
name: 'usersMessages',
component: UsersMessagesView
},
{
path: '/users/messages/:userId(\\d+)',
name: 'userMessages',
component: UserMessagesView
} }
] ]
}) })

View file

@ -1,34 +0,0 @@
import { defineStore } from 'pinia';
import { fetchWrapper } from '../helpers/fetch-wrapper.js';
const baseUrl = `${import.meta.env.VITE_API_URL}/users`;
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({
// initialize state from local storage to enable user to stay logged in
user: JSON.parse(localStorage.getItem('user')),
returnUrl: null
}),
actions: {
async login(username, password, router) {
const user = await fetchWrapper.post(`${baseUrl}/authenticate`, { username, password });
// update pinia state with user object + basic auth data
user.authdata = window.btoa(username + ':' + password);
this.user = user;
// store user details and basic auth data in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
// redirect to previous url or default to home page
router.push(this.returnUrl || '/');
},
logout() {
this.user = null;
localStorage.removeItem('user');
router.push('/login');
}
}
});

View file

@ -1,21 +0,0 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth.store';
function onSubmit(values, { setErrors }) {
const authStore = useAuthStore();
const { username, password } = values;
return authStore.login(username, password, this.$router)
.catch(error => setErrors({ apiError: error }));
}
</script>
<template>
<div class="flex justify-center m-4">
<div class="flex flex-col space-y-2">
<input type="text" placeholder="Username" class="input input-bordered w-full max-w-xs" />
<input type="password" placeholder="Username" class="input input-bordered w-full max-w-xs" />
<button class="btn btn-wide btn-outline">Login</button>
</div>
</div>
</template>

View file

@ -6,13 +6,11 @@ const router = useRoute()
const { serverId } = router.params const { serverId } = router.params
const tableHeader = ["ID", "Server ID", "User ID", "Tokens", "Date"] const tableHeader = ["ID", "Server ID", "User ID", "Tokens", "Date"]
const tableRows = [["1", "name", "comment", "2", Date.now()]] const tableRows = [["1", "name", "comment", "2", Date.now()]]
const actions = [{ class: "btn btn-sm btn-info m-1 no-animation", content: "View", event: (_: number) => { } }]
</script> </script>
<template> <template>
<div class="flex justify-between m-5"> <div class="flex justify-between m-5">
<h1 class="text-xl normal-case">Server Messages</h1> <h1 class="text-xl normal-case">Server Messages</h1>
<h1 class="text-xl normal-case">Server ID: {{ serverId }}</h1> <h1 class="text-xl normal-case">Server ID: {{ serverId }}</h1>
</div> </div>
<TableComponent :tableHeader="tableHeader" :tableRows="tableRows" :actions="actions" /> <TableComponent :tableHeader="tableHeader" :tableRows="tableRows" />
</template> </template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import TableComponent from '@/components/TableComponent.vue';
const tableHeader = ["ID", "User", "Comment", "Date"]
const tableRows = [["1", "name", "comment", Date.now()]]
const actions = [{ class: "btn btn-sm btn-info m-1", content: "Edit", event: (_: number) => { } }, { class: "btn btn-sm btn-error m-1", content: "Delete", event: (_: number) => { } }]
</script>
<template>
<div class="flex justify-between m-5">
<h1 class="text-xl normal-case">Allowed Users</h1>
<button class="btn btn-circle btn-success">Add</button>
</div>
<TableComponent :tableHeader="tableHeader" :tableRows="tableRows" :actions="actions" />
</template>

View file

@ -0,0 +1,18 @@
<script setup lang="ts">
</script>
<template>
<h1 class="text-xl normal-case m-5">Server overview</h1>
<div class="flex justify-center m-4">
<div class="flex flex-col space-y-2">
<RouterLink to="/users/allowed">
<button class="btn btn-wide btn-outline">Allowed</button>
</RouterLink>
<RouterLink to="/users/configs">
<button class="btn btn-wide btn-outline">Configs</button>
</RouterLink>
<RouterLink to="/users/messages">
<button class="btn btn-wide btn-outline">User with Messages</button>
</RouterLink>
</div>
</div>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import TableComponent from '@/components/TableComponent.vue';
const tableHeader = ["ID", "Server ID", "System Message", "Rate limit"]
const tableRows = [["1", "name", "comment", "1"]]
const actions = [{ class: "btn btn-sm btn-info m-1", content: "Edit", event: (_: number) => { } }, { class: "btn btn-sm btn-error m-1", content: "Delete", event: (_: number) => { } }]
</script>
<template>
<div class="flex justify-between m-5">
<h1 class="text-xl normal-case">User Configs</h1>
<button class="btn btn-circle btn-success">Add</button>
</div>
<TableComponent :tableHeader="tableHeader" :tableRows="tableRows" :actions="actions" />
</template>

View file

@ -0,0 +1,16 @@
<script setup lang="ts">
import TableComponent from '@/components/TableComponent.vue';
import { useRoute } from 'vue-router';
const router = useRoute()
const { userId } = router.params
const tableHeader = ["ID", "Server ID", "User ID", "Tokens", "Date"]
const tableRows = [["1", "name", "comment", "2", Date.now()]]
</script>
<template>
<div class="flex justify-between m-5">
<h1 class="text-xl normal-case">User Messages</h1>
<h1 class="text-xl normal-case">User ID: {{ userId }}</h1>
</div>
<TableComponent :tableHeader="tableHeader" :tableRows="tableRows" />
</template>

View file

@ -0,0 +1,24 @@
<script setup lang="ts">
import TableComponent from '@/components/TableComponent.vue';
import { useRouter } from 'vue-router';
import { provide } from 'vue';
const router = useRouter()
const tableHeader = ["ID", "Server ID"]
const tableRows = [["1", "1234567890"]]
const view = (index: number) => {
const row = tableRows[index]
const id = row[0]
router.push("/servers/messages/" + id)
}
const actions = [{ class: "btn btn-sm btn-info m-1", content: "View", event: view }]
</script>
<template>
<div class="flex justify-between m-5">
<h1 class="text-xl normal-case">Users with Messages</h1>
</div>
<TableComponent :tableHeader="tableHeader" :tableRows="tableRows" :actions="actions" />
</template>