Newer
Older
import axios from 'axios'
import { defineStore } from 'pinia'
import { computed, readonly, 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
}
type SessionInitializationState =
| 'OIDC_AUTH'
| 'STEERING_INITIALIZATION'
| 'TANK_INITIALIZATION'
| undefined
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)
const sessionInitializationState = ref<SessionInitializationState>()
const isInitializingSession = computed(() => sessionInitializationState.value !== undefined)
async function loadUser() {
sessionInitializationState.value = 'OIDC_AUTH'
const oidcUser = await getUser()
sessionInitializationState.value = 'STEERING_INITIALIZATION'
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
sessionInitializationState.value = 'TANK_INITIALIZATION'
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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
} finally {
sessionInitializationState.value = undefined
return {
currentUser: shallowReadonly(currentUser),
steeringUser,
isSuperuser,
sessionInitializationState: readonly(sessionInitializationState),
isInitializingSession: readonly(isInitializingSession),
})
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}`,
},
}
},
}