I Think I've Successfully Re-Added Auth

This commit is contained in:
Annika Merris 2024-02-28 08:51:16 -05:00
parent 5c0d3c2fd7
commit d914b7cbd6
18 changed files with 269 additions and 93 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -14,11 +14,12 @@
"format": "prettier --write src/"
},
"dependencies": {
"@zitadel/vue": "^1.0.0",
"axios": "^1.6.5",
"axios-retry": "^4.0.0",
"oidc-client-ts": "^3.0.1",
"pinia": "^2.1.7",
"vue": "^3.3.11",
"vue-oidc-client": "^1.0.0-alpha.5",
"vue-router": "^4.2.5",
"vuetify": "^3.4.10"
},

View file

@ -1,3 +1,6 @@
{
"apiBaseURL": "http://localhost:3000"
"apiBaseURL": "http://coder.local.merr.is:3000",
"oidcAuthority": "https://auth.joes.moosenet.work",
"oidcClientID": "255988227184328707@isekai:_slow_life_calculator",
"oidcProjectID": "255987963094106115"
}

View file

@ -1,25 +1,13 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
import GlobalHeader from '@/components/GlobalHeader.vue'
import { storeToRefs } from 'pinia'
import { usePowerItems } from './stores/powerItems'
import { computed } from 'vue'
const { blessingPowerItems, fellowPowerItems, intimacyPowerItems } = storeToRefs(usePowerItems())
const isLoaded = computed(
() =>
!blessingPowerItems.value.keys().next().done &&
!fellowPowerItems.value.keys().next().done &&
!intimacyPowerItems.value.keys().next().done
)
</script>
<template>
<v-app>
<GlobalHeader />
<v-main class="d-flex">
<RouterView v-if="isLoaded" />
<RouterView />
</v-main>
<v-footer app>
<div>Copyright Annika Merris 2024</div>

View file

@ -3,7 +3,7 @@ import { usePowerItems } from '@/stores/powerItems'
import type { DataTableHeader } from '@/types/DataTableHeader'
import type { PowerItem } from '@/types/PowerItem'
import type { Ref } from 'vue'
import { computed, ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { useDisplay } from 'vuetify'
import type { VDataTable } from 'vuetify/components'
@ -128,11 +128,19 @@ const getColor = computed(() => (rarity: number): string => {
})
const allSelected = computed(() => events.value.length === selectedEvents.value.length)
const partialSelected = computed(() => selectedEvents.value.length > 0)
toggle()
const isLoading = computed((): boolean => props.items.keys()?.next().done ?? false)
watch(isLoading, (newLoading) => {
if (!newLoading) {
toggle()
}
})
if (!isLoading.value) {
toggle()
}
</script>
<template>
<v-card fluid>
<v-card fluid :loading="isLoading">
<v-card-title>Special Items</v-card-title>
<v-card-subtitle>Items from events</v-card-subtitle>
<v-card-item>

View file

@ -78,10 +78,11 @@ const getColor = computed(() => (rarity: number): string => {
return 'green'
}
})
const isLoading = computed((): boolean => props.items.keys()?.next().done ?? false)
</script>
<template>
<v-card>
<v-card :loading="isLoading">
<v-card-title>Standard Items</v-card-title>
<v-card-subtitle>Items that exist all the time</v-card-subtitle>
<v-card-item>

View file

@ -1,11 +1,12 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed } from 'vue'
export interface Props {
standardTotal: number
minimumTotal: number
maximumTotal: number
averageTotal: number
itemType: string
standardTotal: number
minimumTotal: number
maximumTotal: number
averageTotal: number
}
const props = defineProps<Props>()
@ -17,17 +18,17 @@ const aveIncrease = computed(() => props.standardTotal + props.averageTotal)
<template>
<v-card>
<v-card-title>Overall Blessing Power Increase</v-card-title>
<v-card-title>Overall {{ itemType }} Power Increase</v-card-title>
<v-card-item>
<v-list density="compact">
<v-list-item>
<v-list-item-title>Minimum: {{ minIncrease }}</v-list-item-title>
<v-list-item-title>Minimum: {{ minIncrease }}</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title>Maximum: {{ maxIncrease }}</v-list-item-title>
<v-list-item-title>Maximum: {{ maxIncrease }}</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title>Mean: {{ aveIncrease }}</v-list-item-title>
<v-list-item-title>Mean: {{ aveIncrease }}</v-list-item-title>
</v-list-item>
</v-list>
</v-card-item>

View file

@ -2,17 +2,17 @@ import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// Vuetify
import 'vuetify/styles'
import '@mdi/font/css/materialdesignicons.css'
import { createVuetify } from 'vuetify'
import axios from 'axios'
import zitadelAuth from '@/services/zitadelAuth'
import { apiBaseURL } from './types/ConfigSymbols'
import App from './App.vue'
import { apiBaseURL, oidc } from './types/ConfigSymbols'
import { configureOidc } from './services/authentikAuth'
import router from './router'
import { getConfig } from './services/siteConfig'
import type { Config } from './types/Config'
const vuetify = createVuetify({
icons: {
@ -23,32 +23,30 @@ const vuetify = createVuetify({
}
})
// Stuff to get OIDC auth working through Zitadel
declare module 'vue' {
interface ComponentCustomProperties {
$zitadel: typeof zitadelAuth
const conf: Config = await getConfig().then((c: Config | null): Config => {
if (c === null) {
throw new Error("config was null")
}
return c
}).catch((reason) => {
console.log(reason)
throw new Error(reason);
})
const authentikAuth = await configureOidc()
authentikAuth.startup().then((ok: boolean) => {
if (ok) {
const app = createApp(App)
const pinia = createPinia()
app.use(router)
app.use(vuetify)
app.use(pinia)
app.provide(oidc, authentikAuth)
app.provide(apiBaseURL, conf.apiBaseURL)
app.mount('#app')
}
}
zitadelAuth.oidcAuth.startup().then(() => {
const app = createApp(App)
const pinia = createPinia()
app.use(router)
app.use(vuetify)
app.use(pinia)
app.config.globalProperties.$zitadel = zitadelAuth
// Fetch my config
axios
.get('/config.json?noCache=' + Date.now())
.then((resp) => {
app.provide(apiBaseURL, resp.data.apiBaseURL)
app.mount('#app')
})
.catch((err) => {
console.log(err)
})
})

View file

@ -1,6 +1,12 @@
import zitadelAuth from '@/services/zitadelAuth'
import { configureOidc } from '@/services/authentikAuth'
import { getConfig } from '@/services/siteConfig'
import type { Config } from '@/types/Config'
import type { OidcAuth } from 'vue-oidc-client/vue3'
import { createRouter, createWebHistory } from 'vue-router'
const oidcAuth: OidcAuth = await configureOidc().then((value: OidcAuth): OidcAuth => value)
const config: Config = await getConfig().then((value: Config | null): Config => value !== null ? value : {} as Config)
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
@ -34,7 +40,7 @@ const router = createRouter({
path: '/login',
name: 'login',
meta: {
authName: zitadelAuth.oidcAuth.authName
authName: oidcAuth.authName
},
component: () => import('@/views/Login.vue')
},
@ -42,14 +48,25 @@ const router = createRouter({
path: '/admin',
name: 'admin',
meta: {
authName: zitadelAuth.oidcAuth.authName
authName: oidcAuth.authName
},
component: () =>
zitadelAuth.hasRole('admin') ? import('@/views/Admin.vue') : import('@/views/NoAccess.vue')
hasRole('admin') ? import('@/views/Admin.vue') : import('@/views/NoAccess.vue')
}
]
})
zitadelAuth.oidcAuth.useRouter(router)
const hasRole = (role: string) => {
if (config.oidcProjectID === undefined) {
throw new Error('Config was not loaded')
}
const roles = oidcAuth.userProfile[`urn:zitadel:iam:org:project:${config.oidcProjectID}:roles`] as Array<any>
if (!roles) {
return false
}
return roles.find(r => r[role])
}
oidcAuth!.useRouter(router)
export default router

View file

@ -0,0 +1,80 @@
import type { User } from 'oidc-client'
import { createOidcAuth, SignInType, type OidcAuth, LogLevel } from 'vue-oidc-client/vue3'
import { getConfig } from './siteConfig'
import type { Config } from '@/types/Config'
const loco = window.location
const appRootUrl = `${loco.protocol}//${loco.host}${import.meta.env.BASE_URL}`
let authObj = null as OidcAuth | null
export async function configureOidc() {
if (authObj) return Promise.resolve(authObj)
const appConfig: Config = await getConfig().then((c) => {
if (c === null) {
throw new Error("config was null")
}
return c
}).catch((reason) => {
console.log(reason)
throw new Error(reason);
})
const config = {
authority: appConfig.oidcAuthority,
client_id: appConfig.oidcClientID,
response_type: 'code',
scope: 'openid profile email api offline_access'
}
authObj = createOidcAuth(
'main',
SignInType.Window,
appRootUrl,
{
...config,
// test use
prompt: 'login'
},
console,
LogLevel.Debug
)
// handle events
authObj.events.addAccessTokenExpiring(function () {
// eslint-disable-next-line no-console
console.log('access token expiring')
})
authObj.events.addAccessTokenExpired(function () {
// eslint-disable-next-line no-console
console.log('access token expired')
})
authObj.events.addSilentRenewError(function (err: Error) {
// eslint-disable-next-line no-console
console.error('silent renew error', err)
})
authObj.events.addUserLoaded(function (user: User) {
// eslint-disable-next-line no-console
console.log('user loaded', user)
})
authObj.events.addUserUnloaded(function () {
// eslint-disable-next-line no-console
console.log('user unloaded')
})
authObj.events.addUserSignedOut(function () {
// eslint-disable-next-line no-console
console.log('user signed out')
})
authObj.events.addUserSessionChanged(function () {
// eslint-disable-next-line no-console
console.log('user session changed')
})
return authObj
}

View file

@ -0,0 +1,18 @@
import type { Config } from '@/types/Config'
import axios from 'axios'
let configObject = null as Config | null
export async function getConfig() {
if (configObject) return Promise.resolve(configObject)
configObject = await axios.get('/config.json?noCache=' + Date.now()).then((resp) => {
return {
apiBaseURL: resp.data.apiBaseURL,
oidcAuthority: resp.data.oidcAuthority,
oidcClientID: resp.data.oidcClientID,
oidcProjectID: resp.data.oidcProjectID
} as Config
})
return configObject
}

View file

@ -3,8 +3,7 @@ import axios, { type AxiosRequestConfig } from 'axios'
import { defineStore } from 'pinia'
import { computed, ref, toRaw } from 'vue'
import axiosRetry from 'axios-retry'
import { getCurrentInstance } from 'vue'
import { apiBaseURL, apiBaseURL as apiBaseURLKey } from '@/types/ConfigSymbols'
import { apiBaseURL as apiBaseURLKey } from '@/types/ConfigSymbols'
import { inject } from 'vue'
const BLESSING = 1
@ -21,21 +20,26 @@ const noCacheConfig: AxiosRequestConfig = {
}
export const usePowerItems = defineStore('powerItems', () => {
//#region privateVariables
const BLESSING_POWER_ITEM_STORAGE = 'BLESSING_POWER_ITEM_STORAGE'
const FELLOW_POWER_ITEM_STORAGE = 'FELLOW_POWER_ITEM_STORAGE'
const INTIMACY_POWER_ITEM_STORAGE = 'INTIMACY_POWER_ITEM_STORAGE'
const apiBaseURL = inject(apiBaseURLKey)!
//#endregion
//#region state
const blessingPowerItems = ref(new Map<string, PowerItem>())
const fellowPowerItems = ref(new Map<string, PowerItem>())
const intimacyPowerItems = ref(new Map<string, PowerItem>())
const isLoadComplete = ref(false)
const apiBaseURL = inject(apiBaseURLKey)!
//#endregion
axiosRetry(axios, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay
})
async function fetchPowerItems() {
//#region loaders
async function fetchBlessingItems() {
axios
.get(apiBaseURL + '/powerItems/byType/' + BLESSING + '/asMap', noCacheConfig)
.then((resp) => {
@ -52,6 +56,8 @@ export const usePowerItems = defineStore('powerItems', () => {
.catch((err) => {
console.log(err)
})
}
async function fetchFellowItems() {
axios
.get(apiBaseURL + '/powerItems/byType/' + FELLOW + '/asMap', noCacheConfig)
.then((resp) => {
@ -68,6 +74,8 @@ export const usePowerItems = defineStore('powerItems', () => {
.catch((err) => {
console.log(err)
})
}
async function fetchIntimacyItems() {
axios
.get(apiBaseURL + '/powerItems/byType/' + INTIMACY + '/asMap', noCacheConfig)
.then((resp) => {
@ -84,9 +92,10 @@ export const usePowerItems = defineStore('powerItems', () => {
.catch((err) => {
console.log(err)
})
isLoadComplete.value = true
}
//#endregion
//#region setters
function updateOwned(key: string, newOwned: number) {
let cur = blessingPowerItems.value.get(key)
if (cur !== undefined) {
@ -118,16 +127,6 @@ export const usePowerItems = defineStore('powerItems', () => {
}
}
function mapToObj(map: Map<string, PowerItem>) {
return Array.from(map).reduce((obj, [key, value]) => {
// Doing weird magic to work with maps is infuriating, and I haven't found a better solution for this.
// So ignore that error TypeScript, I (don't) know what I'm doing!
// @ts-ignore: noImplicitAny
obj[key] = value
return obj
}, {})
}
function addPowerItem(newItem: PowerItem): Promise<PowerItem | null> {
const resultPromise: Promise<PowerItem | null> = new Promise((resolve, reject) => {
axios
@ -140,7 +139,9 @@ export const usePowerItems = defineStore('powerItems', () => {
return resultPromise
}
//#endregion
//#region computed
const totalBlessingPower = computed(() =>
[...blessingPowerItems.value.values()].reduce(
(accumulator: number, currentValue: PowerItem) => {
@ -323,15 +324,29 @@ export const usePowerItems = defineStore('powerItems', () => {
0
)
)
//#endregion
fetchPowerItems()
//#region helpers
function mapToObj(map: Map<string, PowerItem>) {
return Array.from(map).reduce((obj, [key, value]) => {
// Doing weird magic to work with maps is infuriating, and I haven't found a better solution for this.
// So ignore that error TypeScript, I (don't) know what I'm doing!
// @ts-ignore: noImplicitAny
obj[key] = value
return obj
}, {})
}
//#endregion
return {
blessingPowerItems,
fellowPowerItems,
intimacyPowerItems,
isLoadComplete,
fetchPowerItems,
fetchBlessingItems,
fetchFellowItems,
fetchIntimacyItems,
updateOwned,
totalBlessingPower,
standardBlessingItems,

6
src/types/Config.ts Normal file
View file

@ -0,0 +1,6 @@
export type Config = {
apiBaseURL: string,
oidcAuthority: string,
oidcClientID: string,
oidcProjectID: string,
}

View file

@ -1,7 +1,12 @@
import type { InjectionKey } from "vue";
import type { OidcAuth } from "vue-oidc-client/vue3";
const apiBaseURL = Symbol() as InjectionKey<string>
const oidcProjectID = Symbol() as InjectionKey<string>
const oidc = Symbol() as InjectionKey<OidcAuth>
export {
apiBaseURL
apiBaseURL,
oidcProjectID,
oidc
}

View file

@ -4,6 +4,7 @@ import StandardItemsCard from '@/components/StandardItemsCard.vue'
import SummaryCard from '@/components/SummaryCard.vue'
import { storeToRefs } from 'pinia'
import { usePowerItems } from '@/stores/powerItems'
import { onBeforeMount } from 'vue'
const {
standardBlessingItems,
@ -13,6 +14,15 @@ const {
specialBlessingItemsMaxTotal,
specialBlessingItemsAveTotal
} = storeToRefs(usePowerItems())
onBeforeMount(() => {
if (standardBlessingItems.value.keys().next().done) {
usePowerItems().fetchBlessingItems()
}
if (specialBlessingItems.value.keys().next().done) {
usePowerItems().fetchBlessingItems()
}
})
</script>
<template>
@ -34,6 +44,7 @@ const {
/>
<SummaryCard
class="ma-2 align-self-start"
item-type="Blessing"
:standard-total="standardBlessingItemTotal"
:minimum-total="specialBlessingItemsMinTotal"
:maximum-total="specialBlessingItemsMaxTotal"

View file

@ -4,6 +4,7 @@ import StandardItemsCard from '@/components/StandardItemsCard.vue'
import SummaryCard from '@/components/SummaryCard.vue'
import { storeToRefs } from 'pinia'
import { usePowerItems } from '@/stores/powerItems'
import { onBeforeMount } from 'vue'
const {
standardFellowItems,
@ -13,6 +14,15 @@ const {
specialFellowItemsMaxTotal,
specialFellowItemsAveTotal
} = storeToRefs(usePowerItems())
onBeforeMount(() => {
if (standardFellowItems.value.keys().next().done) {
usePowerItems().fetchFellowItems()
}
if (specialFellowItems.value.keys().next().done) {
usePowerItems().fetchFellowItems()
}
})
</script>
<template>
@ -33,6 +43,7 @@ const {
:average-total="specialFellowItemsAveTotal"
/>
<SummaryCard
item-type="Fellow"
class="ma-2 align-self-start"
:standard-total="standardFellowItemTotal"
:minimum-total="specialFellowItemsMinTotal"

View file

@ -4,6 +4,7 @@ import StandardItemsCard from '@/components/StandardItemsCard.vue'
import SummaryCard from '@/components/SummaryCard.vue'
import { storeToRefs } from 'pinia'
import { usePowerItems } from '@/stores/powerItems'
import { onBeforeMount } from 'vue'
const {
standardIntimacyItems,
@ -13,6 +14,15 @@ const {
specialIntimacyItemsMaxTotal,
specialIntimacyItemsAveTotal
} = storeToRefs(usePowerItems())
onBeforeMount(() => {
if (standardIntimacyItems.value.keys().next().done) {
usePowerItems().fetchIntimacyItems()
}
if (specialIntimacyItems.value.keys().next().done) {
usePowerItems().fetchIntimacyItems()
}
})
</script>
<template>
@ -33,6 +43,7 @@ const {
:average-total="specialIntimacyItemsAveTotal"
/>
<SummaryCard
item-type="Intimacy"
class="ma-2 align-self-start"
:standard-total="standardIntimacyItemTotal"
:minimum-total="specialIntimacyItemsMinTotal"

View file

@ -28,14 +28,15 @@
}
</style>
<script lang="ts">
import { getCurrentInstance } from 'vue';
import { oidc } from '@/types/ConfigSymbols';
import { inject } from 'vue';
import { ref } from 'vue';
import { defineComponent } from 'vue'
export default defineComponent({
computed: {
user() {
return this.$zitadel.oidcAuth.userProfile
return inject(oidc)?.userProfile
},
claims() {
if (this.user) {
@ -51,11 +52,12 @@ export default defineComponent({
</script>
<script setup lang="ts">
const zitadel = ref(getCurrentInstance()?.appContext.config.globalProperties.$zitadel!)
const user = ref(zitadel.value.oidcAuth.userProfile)
const zitadel = ref(inject(oidc))
const user = ref(zitadel.value?.userProfile)
const signout = function() {
if (user.value) {
zitadel.value.oidcAuth.signOut()
zitadel.value?.signOut()
}
}
console.log(zitadel.value)
</script>