From 0f577eef4db1189793e0b9a500710c646cb02a27 Mon Sep 17 00:00:00 2001 From: kittentm Date: Wed, 5 Aug 2026 06:36:58 +0200 Subject: [PATCH] bunch of shit --- .gitmodules | 4 + app/app.vue | 4 +- app/components/header.vue | 7 + app/composables/useAuth.js | 42 +- app/middleware/auth.global.js | 1 - app/pages/audit/log/[logid]/index.vue | 497 +++++++++++++++ app/pages/audit/log/index.vue | 561 +++++++++++++++++ app/pages/index.vue | 126 +++- app/pages/search/index.vue | 31 +- app/pages/users/[pid]/index.vue | 665 +++++++++++++++++++++ app/pages/welcome/index.vue | 13 +- bun.lock | 77 ++- nuxt.config.ts | 2 +- package.json | 5 +- public/css/main.css | 183 +++++- public/policy | 1 + server/api/admin/audit-logs.get.ts | 104 ++++ server/api/admin/audit-logs/[logid].get.ts | 43 ++ server/api/admin/rules.get.ts | 109 ++++ server/api/admin/users/[pid].get.ts | 36 ++ server/api/admin/users/[pid].put.ts | 385 ++++++++++++ server/utils/db.ts | 30 +- 22 files changed, 2889 insertions(+), 37 deletions(-) create mode 100644 .gitmodules create mode 100644 app/pages/audit/log/[logid]/index.vue create mode 100644 app/pages/audit/log/index.vue create mode 100644 app/pages/users/[pid]/index.vue create mode 160000 public/policy create mode 100644 server/api/admin/audit-logs.get.ts create mode 100644 server/api/admin/audit-logs/[logid].get.ts create mode 100644 server/api/admin/rules.get.ts create mode 100644 server/api/admin/users/[pid].get.ts create mode 100644 server/api/admin/users/[pid].put.ts diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..92d382f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "public/policy"] + path = public/policy + url = https://git.spbr.net/spacebar/policy.git + branch = main diff --git a/app/app.vue b/app/app.vue index fc033b1..0a6517e 100644 --- a/app/app.vue +++ b/app/app.vue @@ -41,7 +41,7 @@ onMounted(async () => { justify-content: center; align-items: center; min-height: 100vh; - padding-top: 5rem; /* Added padding-top */ + padding-top: 5rem; } .app-viewport, @@ -64,6 +64,6 @@ onMounted(async () => { .footer-wrapper { width: 100%; - margin-top: 8rem; /* Added margin-top */ + margin-top: 8rem; } \ No newline at end of file diff --git a/app/components/header.vue b/app/components/header.vue index 25535f7..db888f4 100644 --- a/app/components/header.vue +++ b/app/components/header.vue @@ -42,6 +42,13 @@ const emit = defineEmits(['logout', 'navigate']) @click="emit('navigate', '/search')"> Search Users + + + diff --git a/app/composables/useAuth.js b/app/composables/useAuth.js index 06c93ee..b56927b 100644 --- a/app/composables/useAuth.js +++ b/app/composables/useAuth.js @@ -1,13 +1,39 @@ -import { ref } from 'vue' +import { ref, computed } from 'vue' const userToken = ref(null) const refreshToken = ref(null) const userProfile = ref(null) +const userRoles = ref([]) const isAuthenticated = ref(false) const isInitialized = ref(false) let sessionPollTimer = null +const extractRolesFromToken = (token) => { + if (!token) return [] + try { + const payloadBase64 = token.split('.')[1] + const base64 = payloadBase64.replace(/-/g, '+').replace(/_/g, '/') + const jsonPayload = decodeURIComponent( + atob(base64) + .split('') + .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) + .join('') + ) + const payload = JSON.parse(jsonPayload) + + const realmRoles = payload?.realm_access?.roles || [] + const clientRoles = Object.values(payload?.resource_access || {}) + .flatMap(client => client.roles || []) + const directRoles = payload?.roles || [] + + return [...realmRoles, ...clientRoles, ...directRoles].map(r => String(r).toLowerCase()) + } catch (e) { + console.warn('Failed to parse roles from token:', e) + return [] + } +} + const getStoredRefreshToken = () => { if (refreshToken.value) { return refreshToken.value @@ -37,6 +63,8 @@ export const useAuth = () => { const setSession = (data) => { userToken.value = data.access_token + userRoles.value = extractRolesFromToken(data.access_token) + if (data.refresh_token) { refreshToken.value = data.refresh_token if (import.meta.client) { @@ -51,6 +79,7 @@ export const useAuth = () => { userToken.value = null refreshToken.value = null userProfile.value = null + userRoles.value = [] isAuthenticated.value = false if (import.meta.client) { localStorage.removeItem('keycloak_refresh_token') @@ -69,6 +98,7 @@ export const useAuth = () => { setSession(data) window.history.replaceState({}, document.title, window.location.pathname) + await fetchUserProfile() startLiveSessionPolling() @@ -123,9 +153,11 @@ export const useAuth = () => { if (res.active) { isAuthenticated.value = true + if (!userProfile.value) { await fetchUserProfile() } + return true } else { return await silentRefresh() @@ -144,6 +176,12 @@ export const useAuth = () => { await navigateTo(redirectTo, { replace }) return false } + + if (result.isFirstLogin) { + await navigateTo('/welcome', { replace: true }) + return true + } + return true } @@ -207,7 +245,9 @@ export const useAuth = () => { return { userToken, + token: userToken, userProfile, + userRoles, isAuthenticated, isInitialized, exchangeCodeForToken, diff --git a/app/middleware/auth.global.js b/app/middleware/auth.global.js index 5cc8e56..a1d7591 100644 --- a/app/middleware/auth.global.js +++ b/app/middleware/auth.global.js @@ -13,7 +13,6 @@ export default defineNuxtRouteMiddleware(async (to) => { await initializeAuth({ redirectTo: '/oauth', replace: true }) } - //whats the shape of Italy? if (!isAuthenticated.value) { return navigateTo('/oauth', { replace: true }) } diff --git a/app/pages/audit/log/[logid]/index.vue b/app/pages/audit/log/[logid]/index.vue new file mode 100644 index 0000000..fd47396 --- /dev/null +++ b/app/pages/audit/log/[logid]/index.vue @@ -0,0 +1,497 @@ + + + + + \ No newline at end of file diff --git a/app/pages/audit/log/index.vue b/app/pages/audit/log/index.vue new file mode 100644 index 0000000..43f00f2 --- /dev/null +++ b/app/pages/audit/log/index.vue @@ -0,0 +1,561 @@ + + + + + \ No newline at end of file diff --git a/app/pages/index.vue b/app/pages/index.vue index 694a68b..e7c1ec8 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -12,9 +12,55 @@ const { const route = useRoute() -const recentLogins = ref([ - { user: 'admin_user', time: '2 mins ago', ip: '192.168.1.10' } -]) +const recentActions = ref([]) +const isLoadingActions = ref(true) + +const FIELD_LABELS = { + account_level: 'Access Level', + country: 'Country', + language: 'Language', + timezone: 'Timezone', + gender: 'Gender', + email: 'Email', + marketing_allowed: 'Marketing Allowed', + off_device_allowed: 'Off-Device Allowed' +} + +const formatDate = (isoStr) => { + if (!isoStr) return 'N/A' + try { + return new Date(isoStr).toLocaleString('en-US', { + dateStyle: 'short', + timeStyle: 'short' + }) + } catch { + return isoStr + } +} + +const parseLogChanges = (rawChanges) => { + if (!rawChanges) return { changes: [] } + if (typeof rawChanges === 'string') { + try { + return JSON.parse(rawChanges) + } catch { + return { changes: [] } + } + } + return rawChanges +} + +const fetchRecentActions = async () => { + isLoadingActions.value = true + try { + const response = await $fetch('/api/admin/audit-logs?limit=3&page=1') + recentActions.value = response.logs || [] + } catch (err) { + console.error('Failed to fetch recent actions:', err) + } finally { + isLoadingActions.value = false + } +} const handleLogout = () => { logout() @@ -26,6 +72,9 @@ const handleNavigate = (path) => { onMounted(async () => { await initializeAuth({ code: route.query.code, redirectTo: '/oauth' }) + if (isAuthenticated.value) { + await fetchRecentActions() + } }) @@ -68,23 +117,76 @@ onMounted(async () => {
-
- RECENT LOGINS +
+ RECENT ACTIONS + View All →
-
    -
+
- \ No newline at end of file + + diff --git a/app/pages/search/index.vue b/app/pages/search/index.vue index b03fbfa..8e14b4c 100644 --- a/app/pages/search/index.vue +++ b/app/pages/search/index.vue @@ -16,7 +16,6 @@ const isSearching = ref(false) const hasSearched = ref(false) const errorMessage = ref('') -// pagination state const currentPage = ref(1) const totalPages = ref(0) const totalResults = ref(0) @@ -30,6 +29,11 @@ const handleNavigate = (path) => { navigateTo(path) } +const navigateToUser = (pid) => { + if (!pid) return + navigateTo(`/users/${pid}`) +} + const executeSearch = async (page = 1) => { const query = searchQuery.value.trim() if (!query) return @@ -133,7 +137,9 @@ onMounted(async () => {
{
-
- {{ user.email }} +
+
+ {{ user.email }} +
+
@@ -193,4 +207,11 @@ onMounted(async () => { - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/pages/users/[pid]/index.vue b/app/pages/users/[pid]/index.vue new file mode 100644 index 0000000..161cd0f --- /dev/null +++ b/app/pages/users/[pid]/index.vue @@ -0,0 +1,665 @@ + + + + + \ No newline at end of file diff --git a/app/pages/welcome/index.vue b/app/pages/welcome/index.vue index aa2d8e4..f2069d0 100644 --- a/app/pages/welcome/index.vue +++ b/app/pages/welcome/index.vue @@ -1,6 +1,7 @@