Skip to content
Snippets Groups Projects
auth.ts 4.98 KiB
Newer Older
import axios from 'axios'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { components as steeringTypes } from '../steering-types'
import { createUnpaginatedAPIStore, createSteeringURL, createTankURL } from '@/api'
import { getUser, oidcManager } from '@/oidc'

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 = steeringTypes['schemas']['User']

export const useUserStore = createUnpaginatedAPIStore<SteeringUser>(
  createSteeringURL.prefix('users'),
  {
    getRequestDefaults() {
      const authStore = useAuthStore()
      return authStore.currentUser
        ? { headers: { Authorization: `Bearer ${authStore.currentUser.oidcAccessToken}` } }
        : {}
    },
  },
)

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 const useAuthStore = defineStore('auth', () => {
  const currentUser = ref<CurrentUser>()
  const _steeringUser = ref<steeringTypes['schemas']['User']>()
  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, 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}`,
      },
    }
  },
}