import axios from 'axios' import { defineStore } from 'pinia' import { computed, ref, shallowReadonly, watch } from 'vue' import { components as steeringTypes } from '../steering-types' import { createSteeringURL, createTankURL } from '@/api' import { getUser, oidcManager } from '@/oidc' import { APICreate, APIListUnpaginated, APIRemove, APIRetrieve, APIUpdate, createExtendableAPI, } from '@rokoli/bnb/drf' const log = globalThis.console export type CurrentUser = { name: string email: string oidcAccessToken: string tankSessionToken: string } type Session = { allshows?: boolean privileged?: boolean publicshows?: string[] readonly?: boolean shows?: string[] username?: string } type NewSessionResponse = { session?: Session token?: string } export type SteeringUser = Required<steeringTypes['schemas']['User']> export const useUserStore = defineStore('steeringUser', () => { const { api, base } = createExtendableAPI<SteeringUser>(createSteeringURL.prefix('users'), { getRequestDefaults() { const authStore = useAuthStore() return authStore.currentUser ? { headers: { Authorization: `Bearer ${authStore.currentUser.oidcAccessToken}` } } : {} }, }) const listOperations = APIListUnpaginated(api) useOnAuthBehaviour(() => void listOperations.list()) return { ...base, ...listOperations, ...APIRetrieve(api), ...APICreate(api), ...APIUpdate(api), ...APIRemove(api), } }) async function createTankSession(accessToken: string): Promise<NewSessionResponse> { const res = await fetch(`${import.meta.env.VUE_APP_TANK}/auth/session`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ backend: 'oidc', arguments: { access_token: accessToken, token_type: 'Bearer', }, }), }) if (res.ok) { return await res.json() } else { throw new Error('Could not initialize Tank session.') } } export function useOnAuthBehaviour(behaviour: (user?: SteeringUser) => unknown | Promise<unknown>) { const authStore = useAuthStore() return watch( () => authStore.steeringUser, async (user) => { if (user) await behaviour(user) }, { immediate: true }, ) } export const useAuthStore = defineStore('auth', () => { const currentUser = ref<CurrentUser>() const _steeringUser = ref<SteeringUser>() const steeringUser = computed(() => (currentUser.value ? _steeringUser.value : undefined)) const isSuperuser = computed(() => steeringUser.value?.isSuperuser === true) async function loadUser() { const oidcUser = await getUser() const userStore = useUserStore() const users = await userStore.list({ requestInit: { headers: { Authorization: `Bearer ${oidcUser.access_token}` }, }, }) const user = users.find((user: SteeringUser) => user.username === oidcUser.profile?.username) // now that we have a valid token, we can create a session with tank let tankSessionToken: string try { tankSessionToken = (await createTankSession(oidcUser.access_token))?.token ?? '' log.debug('Tank session token:', tankSessionToken) } catch (e) { log.error('Could not create tank session.', e) return } if (!tankSessionToken) { log.error('Tank session creation was successful, but no token has been assigned.') return } _steeringUser.value = user currentUser.value = { name: oidcUser.profile.nickname ?? '', email: oidcUser.profile.email ?? '', oidcAccessToken: oidcUser.access_token, tankSessionToken, } } async function init() { log.debug('Initializing oidc client') oidcManager.events.addUserSignedOut(async () => { log.debug('User has signed out. Resetting auth store.') currentUser.value = undefined }) oidcManager.events.addAccessTokenExpiring(async () => { log.debug('User token expiration imminent. Starting silent access token renewal.') try { const user = await oidcManager.signinSilent() if (currentUser.value) { log.debug('Successfully renewed user access token.') currentUser.value.oidcAccessToken = user.access_token } } catch (e) { log.error('Silent OIDC access token renewal has failed.', e) } }) oidcManager.events.addAccessTokenExpired(() => { log.debug('OIDC token has expired. Logging out...') currentUser.value = undefined }) try { await loadUser() } catch (e) { log.debug('Could not load user data.') currentUser.value = undefined _steeringUser.value = undefined } } return { currentUser: shallowReadonly(currentUser), steeringUser, isSuperuser, init, } }) axios.interceptors.request.use((config) => { const url = config?.url const authStore = useAuthStore() if (!url || !authStore.currentUser) return config if (url.startsWith(createSteeringURL())) { config.headers.set('Authorization', `Bearer ${authStore.currentUser.oidcAccessToken}`) } if (url.startsWith(createTankURL())) { config.headers.set('Authorization', `Bearer ${authStore.currentUser.tankSessionToken}`) } return config }) export const steeringAuthInit: { getRequestDefaults: () => RequestInit } = { getRequestDefaults() { const authStore = useAuthStore() return { headers: { Authorization: `Bearer ${authStore.currentUser?.oidcAccessToken}`, }, } }, } export const tankAuthInit: { getRequestDefaults: () => RequestInit } = { getRequestDefaults() { const authStore = useAuthStore() return { headers: { Authorization: `Bearer ${authStore.currentUser?.tankSessionToken}`, }, } }, }