| 1 | import type { RouteLocationNormalized } from "vue-router" |
| 2 | import type { JWTRole, RouteMetaAuth } from "@/types/auth.d" |
| 3 | import { decodeJwt } from "jose" |
| 4 | import _castArray from "lodash/castArray" |
| 5 | import _toNumber from "lodash/toNumber" |
| 6 | import { useAuthStore } from "@/stores/auth" |
| 7 | import { AuthUserRole } from "@/types/auth.d" |
| 8 | |
| 9 | export function isDebounceTimeOver(lastCheck: Date | null) { |
| 10 | const debounceTime = useAuthStore().tokenDebounceTime |
| 11 | return !lastCheck || lastCheck.getTime() + _toNumber(debounceTime) * 1000 < Date.now() |
| 12 | } |
| 13 | |
| 14 | /** |
| 15 | * @param token jwt token |
| 16 | * @param threshold in seconds |
| 17 | */ |
| 18 | export function isJwtExpiring(token: string, threshold: number): boolean { |
| 19 | try { |
| 20 | // NOTE: decodeJwt is intentionally used here without signature verification. |
| 21 | // This is a client-side SPA — we only read the `exp` claim to trigger a |
| 22 | // proactive refresh before the token expires. All cryptographic validation |
| 23 | // (signature, audience, issuer) is enforced server-side on every API call. |
| 24 | // This usage is by design and not a security vulnerability. // noqa: CWE-347 |
| 25 | const { exp } = decodeJwt(token) || {} |
| 26 | return exp ? Date.now() / 1000 > exp - threshold : true |
| 27 | } catch { |
| 28 | return false |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | export function authCheck(route: RouteLocationNormalized) { |
| 33 | const { checkAuth, authRedirect, auth, roles }: RouteMetaAuth = route.meta |
| 34 | const authStore = useAuthStore() |
| 35 | |
| 36 | // Logout handling |
| 37 | if (route?.redirectedFrom?.name === "Logout") authStore.setLogout() |
| 38 | |
| 39 | // Auth check: if not logged or role not granted |
| 40 | const loginPath = `/login${window.location.search}` |
| 41 | |
| 42 | if (auth && !authStore.isLogged) { |
| 43 | window.location.replace(loginPath) |
| 44 | return loginPath |
| 45 | } |
| 46 | |
| 47 | if (auth && roles && !authStore.isRoleGranted(roles)) { |
| 48 | window.location.replace(loginPath) |
| 49 | return loginPath |
| 50 | } |
| 51 | |
| 52 | if (checkAuth && authStore.isLogged) { |
| 53 | return roles && !authStore.isRoleGranted(roles) ? route.path : authRedirect || "/" |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | export function jwtRoleToUserRole(scope: JWTRole | JWTRole[]): AuthUserRole { |
| 58 | const role = _castArray(scope)[0]?.toLowerCase() as JWTRole |
| 59 | return role === "admin" ? AuthUserRole.Admin : role === "analyst" ? AuthUserRole.Analyst : AuthUserRole.Unknown |
| 60 | } |