rework login to wor with authentik

rework env handling a runtime
remove oketbase
This commit is contained in:
2024-05-15 16:00:56 +02:00
parent 206bdd0e55
commit 2eea436641
22 changed files with 1253 additions and 813 deletions

View File

@@ -1,13 +1,14 @@
<script setup lang="ts">
import { RouterView, useRoute } from 'vue-router';
import { useAuthStore } from '@/auth';
import { RouterView } from 'vue-router';
import { Sidebar } from './sidebar';
const route = useRoute();
const authStore = useAuthStore();
</script>
<template>
<template v-if="route.name === 'login'">
<template v-if="!authStore.isLoggedIn">
<RouterView />
</template>
<template v-else>

45
src/auth.ts Normal file
View File

@@ -0,0 +1,45 @@
import log from "loglevel";
import { Log, User, UserManager } from "oidc-client-ts";
import { defineStore } from "pinia";
import { computed, ref } from "vue";
Log.setLogger(log);
export const useAuthStore = defineStore('auth', () => {
const userManager = new UserManager({
authority: import.meta.env.VITE_AUTH_AUTHORITY,
client_id: import.meta.env.VITE_AUTH_CLIENT_ID,
client_secret: import.meta.env.VITE_AUTH_CLIENT_SECRET,
redirect_uri: import.meta.env.VITE_AUTH_REDIRECT_URI,
scope: import.meta.env.VITE_AUTH_SCOPE,
});
const user = ref<User>();
const isLoggedIn = computed(() => !!user.value);
const accessToken = computed(() => user.value?.access_token);
const username = computed(() => user.value?.profile.name ?? "");
const redirect = async () => {
await userManager.signinRedirect();
log.info("Redirecting to login page");
}
const login = async () => {
await userManager.signinCallback();
log.debug("Logged in");
}
const logout = async () => {
await userManager.signoutRedirect();
log.debug("Logged out");
}
userManager.events.addUserLoaded(u => {
user.value = u;
log.info("User loaded", u.profile.name);
});
userManager.events.addAccessTokenExpiring(() => {
log.debug("Access token expiring");
});
return { redirect, login, logout, isLoggedIn, accessToken, username };
});

View File

@@ -1,4 +1,4 @@
import { providePocketBase } from '@/pocketbase';
import { useAuthStore } from "@/auth";
import { createPinia } from 'pinia';
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
@@ -10,22 +10,25 @@ import './style.css';
initLogger();
const app = createApp(App);
const pb = providePocketBase(app);
const pinia = createPinia();
const router = createRouter({
history: createWebHistory(),
routes,
});
app.use(pinia);
const authStore = useAuthStore();
router.beforeEach(async to => {
if (!pb.authStore.isValid && to.name !== 'login') {
return { name: 'login' };
} else if (pb.authStore.isValid && to.name === 'login') {
if (to.name === 'callback') {
await authStore.login();
return { name: 'home' };
} else if (!authStore.isLoggedIn) {
await authStore.redirect();
}
});
app.use(pinia);
app.use(router);
app.mount('#app');

View File

@@ -4,7 +4,7 @@ import { MarketType } from "../type";
import { PriceGetter } from './MarketTypePrice';
export const evepraisalAxiosInstance = axios.create({
baseURL: '/evepraisal/',
baseURL: import.meta.env.VITE_EVEPRAISAL_URL,
headers: {
'accept': 'application/json',
"Content-Type": "application/json"

View File

@@ -4,7 +4,7 @@ import { MarketType } from "../type";
import { PriceGetter } from './MarketTypePrice';
export const fuzzworkAxiosInstance = axios.create({
baseURL: '/fuzzwork/',
baseURL: import.meta.env.VITE_FUZZWORK_URL,
headers: {
'accept': 'application/json',
"Content-Type": "application/json"

View File

@@ -1,8 +1,6 @@
import { MarketOrderHistory, MarketType, MarketTypePrice, getHistory, jitaId } from "@/market";
import { usePocketBase, watchCollection } from "@/pocketbase";
import { defineStore } from "pinia";
import { RecordModel } from "pocketbase";
import { computed, onMounted, ref } from "vue";
import { computed, ref } from "vue";
export type ScanResult = {
type: MarketType;
@@ -12,7 +10,7 @@ export type ScanResult = {
orderCount: number,
}
interface MarketScan extends RecordModel {
interface MarketScan {
owner: string;
types: number[];
};
@@ -20,16 +18,11 @@ interface MarketScan extends RecordModel {
const marketScans = 'marketScans';
export const useMarketScanStore = defineStore(marketScans, () => {
const pb = usePocketBase();
const marketScan = ref<MarketScan>();
const types = computed(() => marketScan.value?.types ?? []);
const setTypes = async (types: number[]) => {
if (marketScan.value?.id) {
marketScan.value = await pb.collection(marketScans).update(marketScan.value.id, { owner: pb.authStore.model!.id, types });
} else {
marketScan.value = await pb.collection(marketScans).create({ owner: pb.authStore.model!.id, types });
}
const setTypes = async (_types: number[]) => {
}
const addType = async (type: number) => {
if (!types.value.includes(type)) {
@@ -41,15 +34,6 @@ export const useMarketScanStore = defineStore(marketScans, () => {
await setTypes(types.value.filter(t => t !== type));
}
}
watchCollection<MarketScan>(marketScans, '*', data => {
if (data.action === 'delete') {
marketScan.value = undefined;
} else if (!marketScan.value || data.record.id === marketScan.value.id) {
marketScan.value = data.record;
}
});
onMounted(async () => marketScan.value = await pb.collection(marketScans).getFirstListItem<MarketScan>('').catch(() => undefined));
return { types, setTypes, addType, removeType };
});

View File

@@ -1,30 +0,0 @@
<script setup lang="ts">
import { usePocketBase } from '@/pocketbase';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const pb = usePocketBase();
const router = useRouter();
const username = ref("");
const password = ref("");
const login = async () => {
await pb.collection('users').authWithPassword(username.value, password.value);
await router.push('/');
}
</script>
<template>
<div class="p-4 mx-auto mt-10 grid justify-center gap-2 w-64">
<div class="grid">
Login:
<input type="text" name="username" v-model="username" @keyup.enter="login" />
</div>
<div class="grid">
Password:
<input type="password" name="password" v-model="password" @keyup.enter="login" />
</div>
<button class="justify-self-end" name="login" @click="login">Login</button>
</div>
</template>

View File

@@ -35,7 +35,7 @@ watch(() => acquiredItemStore.items, async itms => {
<template>
<div class="mt-4">
<template v-if="items.length > 0">
<AcquisitionResultTable :items="items" @buy="(type, price, buy, sell) => buyModal?.open(type, { 'Price': price, 'Buy': buy, 'Sell': sell })" @sell="type => sellModal?.open(type)" />
<AcquisitionResultTable :items="items" @buy="(type, price, buy, sell) => buyModal?.open(type, { 'Price': price, 'Buy': buy, 'Sell': sell })" @sell="type => sellModal?.open(type)" />
<BuyModal ref="buyModal" />
<SellModal ref="sellModal" />
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { MarketType, MarketTypeInput, MarketTypePrice, getHistory, getMarketTypes, jitaId, useApraisalStore } from "@/market";
import { BuyModal } from '@/market/acquisition';
import { ScanResult, ScanResultTable, useMarketScanStore } from '@/market/scan';
import { ScanResult, ScanResultTable, createResult, useMarketScanStore } from '@/market/scan';
import { ref, watch } from 'vue';

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { ClipboardButton } from '@/components';
import { MarketType, MarketTypeInput, getMarketType, useApraisalStore } from "@/market";
import { BuyModal } from '@/market/acquisition';
import { ScanResultTable, createResult, useMarketScanStore } from '@/market/scan';
import { BuyModal } from '@/market/track';
import { BookmarkIcon, BookmarkSlashIcon, ShoppingCartIcon } from '@heroicons/vue/24/outline';
import { computedAsync } from '@vueuse/core/index.cjs';
import log from "loglevel";

View File

@@ -1,27 +0,0 @@
import { usePocketBase } from "@/pocketbase";
import { RecordModel, RecordSubscription } from "pocketbase";
import { Ref, computed, onMounted, ref } from "vue";
export const watchCollection = <T extends RecordModel = RecordModel>(collection: string, topic: string, callback: (data: RecordSubscription<T>) => void) => {
const pb = usePocketBase();
onMounted(async () => await pb.collection(collection).subscribe<T>(topic, callback));
};
export const useCollection = <T extends RecordModel = RecordModel>(collection: string) => {
const pb = usePocketBase();
const list = ref<T[]>([]) as Ref<T[]>;
watchCollection<T>(collection, '*', data => {
if (data.action === 'delete') {
list.value = list.value.filter(i => i.id !== data.record.id);
} else if (data.action === 'update') {
list.value = list.value.map(i => i.id === data.record.id ? data.record : i);
} else if (data.action === 'create') {
list.value = [...list.value, data.record];
}
});
onMounted(async () => list.value = await pb.collection(collection).getFullList<T>().catch(() => [] as T[]));
return computed(() => list.value);
}

View File

@@ -1,2 +0,0 @@
export * from './collection';
export * from './pocketbase';

View File

@@ -1,13 +0,0 @@
import PocketBase from 'pocketbase';
import { App, inject } from 'vue';
const pocketBaseSymbol = Symbol('pocketBase');
export const providePocketBase = (app: App) => {
const pb = new PocketBase('/pocketbase/');
app.provide(pocketBaseSymbol, pb);
return pb;
}
export const usePocketBase = () => inject<PocketBase>(pocketBaseSymbol)!;

View File

@@ -2,7 +2,7 @@ import { RouteRecordRaw } from 'vue-router';
export const routes: RouteRecordRaw[] = [
{ path: '/', name: 'home', component: () => import('@/pages/Index.vue') },
{ path: '/login', name: 'login', component: () => import('@/pages/Login.vue') },
{ path: '/callback', name: 'callback', component: () => import('@/pages/Index.vue') },
{ path: '/reprocess', component: () => import('@/pages/Reprocess.vue') },
{ path: '/market', component: () => import('@/pages/Market.vue'), children: [
{ path: '', redirect: '/market/type' },

View File

@@ -1,3 +1,4 @@
import { useAuthStore } from '@/auth';
import axios, { AxiosInstance } from 'axios';
import log from 'loglevel';
@@ -13,9 +14,9 @@ export const logResource = (a: AxiosInstance) => {
}
export const marbasAxiosInstance = axios.create({
baseURL: '/marbas/',
baseURL: import.meta.env.VITE_MARBAS_URL,
headers: {
'accept': 'application/json',
'Accept': 'application/json',
"Content-Type": "application/json"
},
})
@@ -42,12 +43,21 @@ marbasAxiosInstance.interceptors.response.use(async r => {
}
return r;
})
marbasAxiosInstance.interceptors.request.use(r => {
const accessToken = useAuthStore().accessToken;
if (accessToken) {
r.headers.Authorization = `Bearer ${accessToken}`;
}
return r;
})
export const esiAxiosInstance = axios.create({
baseURL: '/esi/',
baseURL: import.meta.env.VITE_ESI_URL,
headers: {
'accept': 'application/json',
"Content-Type": "application/json"
'Accept': 'application/json',
"Content-Type": "application/json",
"User-Agent": import.meta.env.VITE_ESI_USER_AGENT
},
})
logResource(esiAxiosInstance)

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { usePocketBase } from '@/pocketbase';
import { RouterLink, useRouter } from 'vue-router';
import { useAuthStore } from '@/auth';
import { RouterLink } from 'vue-router';
const links = [
{ name: "Market", path: "/market" },
@@ -8,12 +8,10 @@ const links = [
{ name: "Tools", path: "/tools" }
];
const pb = usePocketBase();
const router = useRouter();
const authStore = useAuthStore();
const logout = async () => {
pb.authStore.clear();
await router.push({ name: 'login' });
await authStore.logout();
}
</script>