Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion VueApp/src/CAHFS/pages/CAHFSAuth.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
<template>
<ContentBlock content-block-name="cahfs-login"></ContentBlock>
<div>
<ContentBlock content-block-name="cahfs-login"></ContentBlock>

<q-card
flat
bordered
class="bg-ucdavis-blue-10 q-mt-md"
>
<q-card-section class="row items-start no-wrap">
<q-icon
name="login"
color="primary"
size="sm"
class="q-mr-md"
/>
<div>
<h2 class="text-subtitle1 text-weight-bold text-primary q-mt-none q-mb-xs">Members sign-in</h2>
<div class="text-body2">
Sign in with your UC Davis account to open web reports and section pages.
</div>
<q-btn
:href="loginHref"
type="a"
color="primary"
no-caps
label="Sign in"
class="q-mt-md"
/>
</div>
</q-card-section>
</q-card>
</div>
</template>

<script setup lang="ts">
Expand All @@ -8,12 +39,14 @@ import { inject, onMounted } from "vue"
import { useRouter } from "vue-router"
import { useFetch } from "@/composables/ViperFetch"
import { useUserStore } from "@/store/UserStore"
import { getLoginUrl } from "@/composables/RequireLogin"
import ContentBlock from "@/CMS/components/ContentBlock.vue"

const router = useRouter()
const baseUrl = inject<string>("apiURL")
const userStore = useUserStore()
const $q = useQuasar()
const loginHref = getLoginUrl()

async function checkAuth() {
$q.loading.show({
Expand Down
4 changes: 3 additions & 1 deletion VueApp/src/CTS/pages/CtsHome.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ async function initPage() {
const r = await get(baseUrl + "loggedInUser")
if (!r.success || !r.result.userId) {
window.location.href =
import.meta.env.VITE_VIPER_HOME + "login?ReturnUrl=" + import.meta.env.VITE_VIPER_HOME + "CTS/"
import.meta.env.VITE_VIPER_HOME +
"welcome?ReturnUrl=" +
encodeURIComponent(import.meta.env.VITE_VIPER_HOME + "CTS/" + window.location.search)
} else {
userStore.loadUser(r.result)
}
Expand Down
Binary file removed VueApp/src/assets/UCDSVMLogo.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added VueApp/src/assets/logo-vetmed-stacked-lockup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added VueApp/src/assets/rod-of-asclepius-white.avif
Binary file not shown.
Binary file added VueApp/src/assets/rod-of-asclepius-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion VueApp/src/components/GenericError.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<a
:href="loginUrl"
target="_blank"
rel="noopener noreferrer"
>
Click to log in, and then try your action again.
<q-icon name="launch"></q-icon>
Expand All @@ -34,11 +35,12 @@
import { computed, ref, watch } from "vue"
import { useErrorStore } from "@/store/ErrorStore"
import { storeToRefs } from "pinia"
import { getLoginUrl } from "@/composables/RequireLogin"

const errorStore = useErrorStore()
const { errorMessage, status } = storeToRefs(errorStore)
const showErrorMessage = computed(() => errorMessage.value !== null && errorMessage.value.length > 0)
const loginUrl = import.meta.env.VITE_VIPER_HOME + "login?ReturnUrl=" + window.location.pathname
const loginUrl = getLoginUrl()

const showError = ref(false)
const showLogin = ref(false)
Expand Down
23 changes: 14 additions & 9 deletions VueApp/src/composables/RequireLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ const ALLOWED_INTERNAL_PREFIXES = ["/", "/2/", "/vue/"]
* Builds a login URL with a validated return path.
* Falls back to home if the path fails validation.
*/
function buildLoginUrl(returnPath: string): string {
// The endpoint param selects the destination: "welcome" is the passive splash (auth-challenge /
// guard redirects); "login" goes straight to CAS for explicit "Log in" buttons, so a deliberate
// click isn't met with another sign-in screen.
function buildLoginUrl(returnPath: string, endpoint: "welcome" | "login" = "welcome"): string {
const viperHome = import.meta.env.VITE_VIPER_HOME ?? "/"
const applicationBase = viperHome.length === 1 ? "" : viperHome.slice(0, -1)
const fallbackPath = `${applicationBase}/`

if (isValidInternalPath(returnPath)) {
return `${viperHome}login?ReturnUrl=${encodeURIComponent(returnPath)}`
return `${viperHome}${endpoint}?ReturnUrl=${encodeURIComponent(returnPath)}`
}
return `${viperHome}login?ReturnUrl=${encodeURIComponent(fallbackPath)}`
return `${viperHome}${endpoint}?ReturnUrl=${encodeURIComponent(fallbackPath)}`
}

/**
Expand All @@ -42,11 +45,11 @@ function getCurrentPath(): string {
function getLoginUrl(): ComputedRef<string> {
const route = useRoute()
return computed(() => {
// Use route.fullPath length check to create reactive dependency while using the value
if (route.fullPath.length >= 0) {
return buildLoginUrl(getCurrentPath())
}
return buildLoginUrl(getCurrentPath())
// Reference route.fullPath so the URL recomputes after navigation. Optional chaining
// keeps this safe outside a router context (e.g. unit tests). The value itself comes
// from getCurrentPath, which includes the app base prefix that route.fullPath omits.
const _routeDep = route?.fullPath
return buildLoginUrl(getCurrentPath(), "login")
})
}

Expand All @@ -56,10 +59,12 @@ function isValidInternalPath(path: string): boolean {
return false
}

// Reject absolute URLs, path traversal, and encoded bypasses
// Reject absolute URLs, path traversal, backslashes, and encoded bypasses. Backslashes are
// rejected because some browsers treat "/\" or "/\\" as protocol-relative (external) redirects.
if (
ABSOLUTE_URL_REGEX.test(path) ||
path.includes("../") ||
path.includes("\\") ||
ENCODED_SLASH_REGEX.test(path) ||
ENCODED_DOT_REGEX.test(path)
) {
Expand Down
73 changes: 32 additions & 41 deletions VueApp/src/layouts/ViperLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,35 @@
height-hint="98"
class="no-print"
>
<div
v-show="false"
id="headerPlaceholder"
>
<div
id="topPlaceholder"
class="row items-center"
>
<a
class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--rectangle q-btn--actionable q-focusable q-hoverable q-btn--no-uppercase q-btn--dense gt-sm text-white"
tabindex="0"
:href="viperHome"
<q-toolbar class="items-end">
<div class="viper-brand">
<div
class="viper-brand__mark"
aria-hidden="true"
>
<span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<i
class="q-icon notranslate material-icons"
aria-hidden="true"
role="img"
>home</i
>
<span class="mainLayoutViper">VIPER 2.0</span>
<span
v-if="environment == 'DEVELOPMENT'"
class="mainLayoutViperMode"
>Development</span
>
<span
v-if="environment == 'TEST'"
class="mainLayoutViperMode"
>Test</span
>
</span>
</a>
<picture>
<source
:srcset="rodMarkAvif"
type="image/avif"
/>
<img
:src="rodMark"
alt=""
/>
</picture>
</div>
<picture>
<source
:srcset="schoolLockupAvif"
type="image/avif"
/>
<img
class="viper-brand__name"
:src="schoolLockup"
alt="UC Davis Weill School of Veterinary Medicine"
/>
</picture>
</div>
</div>
<q-toolbar class="items-end">
<img
src="@/assets/UCDSVMLogo.png"
height="50"
alt="UC Davis School of Veterinary Medicine Logo"
/>

<!--Mini navigation for small screens-->
<q-btn
Expand Down Expand Up @@ -145,7 +132,7 @@
<router-view></router-view>
</div>
<div
v-show="!userStore.isLoggedIn"
v-show="!userStore.isLoggedIn && !showViewWhenNotLoggedIn"
class="q-pa-xl flex flex-center"
>
<q-card
Expand Down Expand Up @@ -194,6 +181,10 @@

<script setup lang="ts">
import { ref } from "vue"
import rodMark from "@/assets/rod-of-asclepius-white.png"
import rodMarkAvif from "@/assets/rod-of-asclepius-white.avif"
import schoolLockup from "@/assets/logo-vetmed-stacked-lockup.png"
import schoolLockupAvif from "@/assets/logo-vetmed-stacked-lockup.avif"
import { useUserStore } from "@/store/UserStore"
import { getLoginUrl } from "@/composables/RequireLogin"
import LeftNav from "@/layouts/LeftNav.vue"
Expand Down
49 changes: 0 additions & 49 deletions VueApp/src/layouts/ViperLayoutSimple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const props = defineProps<{
}>()

const userStore = useUserStore()
const environment = import.meta.env.VITE_ENVIRONMENT
const viperHome = import.meta.env.VITE_VIPER_HOME
const currentYear = new Date().getFullYear()
</script>
Expand All @@ -36,54 +35,6 @@ const currentYear = new Date().getFullYear()
height-hint="98"
class="bg-white text-dark"
>
<div
v-show="false"
id="headerPlaceholder"
>
<a href="/"
><img
src="https://viper.vetmed.ucdavis.edu/images/vetmed_logo.jpg"
alt="UC Davis Veterinary Medicine logo"
border="0"
width="134"
height="24"
/></a>
<div
id="topPlaceholder"
class="row items-center"
>
<a
class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--rectangle q-btn--actionable q-focusable q-hoverable q-btn--no-uppercase q-btn--dense gt-sm text-white"
tabindex="0"
:href="viperHome"
>
<span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<i
class="q-icon notranslate material-icons"
aria-hidden="true"
role="img"
>home</i
>
<span class="mainLayoutViper">VIPER 2.0</span>
<q-badge
v-if="environment == 'DEVELOPMENT'"
color="negative"
role="presentation"
class="mainLayoutViperMode"
>Development</q-badge
>
<q-badge
v-if="environment == 'TEST'"
color="negative"
role="presentation"
class="mainLayoutViperMode"
>Test</q-badge
>
</span>
</a>
</div>
</div>
<q-toolbar>
<q-btn
flat
Expand Down
Loading
Loading