Skip to content

Commit 2957e85

Browse files
committed
Use hydroserver-ts built-in vars for auth state
1 parent 1826009 commit 2957e85

File tree

9 files changed

+125
-48
lines changed

9 files changed

+125
-48
lines changed

apps/data-management/src/bootstrap/__tests__/appInitialization.spec.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const {
1919
onMock: vi.fn(),
2020
sessionState: {
2121
isAuthenticated: false,
22-
getBrowserSessionAccount: vi.fn(),
22+
hasAccessToken: false,
23+
account: null as Record<string, unknown> | null,
2324
},
2425
testPinia: { value: null as Pinia | null },
2526
}))
@@ -63,7 +64,8 @@ describe('app initialization bootstrap', () => {
6364
testPinia.value = pinia
6465
localStorage.clear()
6566
sessionState.isAuthenticated = false
66-
sessionState.getBrowserSessionAccount.mockReset()
67+
sessionState.hasAccessToken = false
68+
sessionState.account = null
6769
createHydroServerMock.mockReset()
6870
fetchAllVocabulariesMock.mockReset()
6971
userGetMock.mockReset()
@@ -109,11 +111,9 @@ describe('app initialization bootstrap', () => {
109111
it('does not mark workspace bootstrap complete when initialized while logged out', async () => {
110112
createHydroServerMock.mockResolvedValue(undefined)
111113
fetchAllVocabulariesMock.mockResolvedValue(undefined)
112-
sessionState.getBrowserSessionAccount.mockResolvedValue(null)
113114

114115
const {
115116
hasBootstrappedWorkspaces,
116-
isHydroServerAuthenticated,
117117
startAppInitialization,
118118
isAppInitializing,
119119
} = await import('../appInitialization')
@@ -128,24 +128,22 @@ describe('app initialization bootstrap', () => {
128128
it('hydrates authenticated ui state from the browser session when no oidc user is cached', async () => {
129129
createHydroServerMock.mockResolvedValue(undefined)
130130
fetchAllVocabulariesMock.mockResolvedValue(undefined)
131-
sessionState.getBrowserSessionAccount.mockResolvedValue({
131+
sessionState.isAuthenticated = true
132+
sessionState.account = {
132133
email: 'browser@example.com',
133134
firstName: 'Browser',
134135
lastName: 'Session',
135-
})
136+
}
136137

137138
const {
138139
hasBootstrappedWorkspaces,
139-
isHydroServerAuthenticated,
140140
startAppInitialization,
141141
} = await import('../appInitialization')
142142

143143
await startAppInitialization()
144144

145145
const userStore = useUserStore()
146146

147-
expect(sessionState.getBrowserSessionAccount).toHaveBeenCalledTimes(1)
148-
expect(isHydroServerAuthenticated.value).toBe(true)
149147
expect(userStore.user.email).toBe('browser@example.com')
150148
expect(userStore.user.firstName).toBe('Browser')
151149
expect(hasBootstrappedWorkspaces.value).toBe(false)
@@ -155,9 +153,9 @@ describe('app initialization bootstrap', () => {
155153

156154
it('loads vocabularies, user, and workspaces after first session initialization', async () => {
157155
sessionState.isAuthenticated = true
156+
sessionState.hasAccessToken = true
158157
createHydroServerMock.mockResolvedValue(undefined)
159158
fetchAllVocabulariesMock.mockResolvedValue(undefined)
160-
sessionState.getBrowserSessionAccount.mockResolvedValue(null)
161159
userGetMock.mockResolvedValue({
162160
status: 200,
163161
data: { email: 'user@example.com' },
@@ -169,7 +167,6 @@ describe('app initialization bootstrap', () => {
169167

170168
const {
171169
hasBootstrappedWorkspaces,
172-
isHydroServerAuthenticated,
173170
startAppInitialization,
174171
isAppInitializing,
175172
} = await import('../appInitialization')
@@ -195,7 +192,6 @@ describe('app initialization bootstrap', () => {
195192
'workspace-2',
196193
])
197194
expect(workspaceStore.selectedWorkspace?.id).toBe('workspace-1')
198-
expect(isHydroServerAuthenticated.value).toBe(true)
199195
expect(hasBootstrappedWorkspaces.value).toBe(true)
200196
expect(isAppInitializing.value).toBe(false)
201197
})

apps/data-management/src/bootstrap/appInitialization.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ const hydroServerHost =
1212

1313
const isHydroServerInitializing = ref(false)
1414
export const isHydroServerReady = ref(false)
15-
export const isHydroServerAuthenticated = ref(false)
1615
export const isAppInitializing = ref(false)
1716
export const hasBootstrappedWorkspaces = ref(false)
1817
export const initializationError = ref<unknown>(null)
@@ -35,20 +34,11 @@ export async function initializeAuthenticatedState() {
3534
user.value = new User()
3635
setWorkspaces([])
3736
hasBootstrappedWorkspaces.value = false
38-
isHydroServerAuthenticated.value = hs.session.isAuthenticated
3937

40-
if (!hs.session.isAuthenticated) {
41-
let browserSessionAccount: User | null = null
42-
try {
43-
browserSessionAccount = await hs.session.getBrowserSessionAccount()
44-
} catch (error) {
45-
recordInitializationError('Error fetching browser session', error)
46-
}
38+
if (!hs.session.isAuthenticated) return
4739

48-
if (!browserSessionAccount) return
49-
50-
user.value = browserSessionAccount
51-
isHydroServerAuthenticated.value = true
40+
if (!hs.session.hasAccessToken) {
41+
user.value = hs.session.account ?? new User()
5242
return
5343
}
5444

@@ -85,8 +75,8 @@ export async function initializeAuthenticatedState() {
8575
function attachSessionListeners() {
8676
if (sessionListenersAttached) return
8777

88-
hs.on('session:changed', (authenticated: boolean) => {
89-
isHydroServerAuthenticated.value = authenticated
78+
hs.on('session:changed', () => {
79+
void initializeAuthenticatedState()
9080
})
9181
hs.on('session:expired', () => {
9282
void initializeAuthenticatedState()
@@ -119,7 +109,6 @@ export function startAppInitialization() {
119109
})
120110
.then(() => {
121111
isHydroServerReady.value = true
122-
isHydroServerAuthenticated.value = hs.session.isAuthenticated
123112
attachSessionListeners()
124113
})
125114
.catch((error) => {

apps/data-management/src/components/base/Navbar.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969

7070
<v-spacer />
7171

72-
<template v-if="isHydroServerAuthenticated">
72+
<template v-if="hs.session.isAuthenticated">
7373
<v-btn data-testid="account-menu-button" elevation="2" rounded>
7474
<v-icon :icon="mdiAccountCircle" />
7575
<v-icon :icon="mdiMenuDown" />
@@ -139,7 +139,7 @@
139139
<v-divider />
140140

141141
<v-list density="compact" nav>
142-
<template v-if="isHydroServerAuthenticated">
142+
<template v-if="hs.session.isAuthenticated">
143143
<v-list-item
144144
to="/profile"
145145
:prepend-icon="mdiAccountCircle"
@@ -171,7 +171,6 @@
171171

172172
<script setup lang="ts">
173173
import { useDisplay } from 'vuetify/lib/framework.mjs'
174-
import { isHydroServerAuthenticated } from '@/bootstrap/appInitialization'
175174
import { Snackbar } from '@/utils/notifications'
176175
import { ref } from 'vue'
177176
import { useDataVisStore } from '@/store/dataVisualization'

apps/data-management/src/config/Home.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</h4>
1515
</div>
1616

17-
<div v-if="isHydroServerAuthenticated">
17+
<div v-if="hs.session.isAuthenticated">
1818
<h5 class="text-h5 mb-8 has-text-shadow">
1919
Logged in as {{ user?.firstName }}
2020
{{ user?.lastName }}
@@ -212,7 +212,6 @@ import ogcLogo from '@/assets/ogc-min.png'
212212
import cirohLogo from '@/assets/CIROH_logo_transparent-min.png'
213213
import sensorThingsLogo from '@/assets/sensorThings-min.png'
214214
import hydroWhiteImg from '@/assets/hydroserver-white-min.png'
215-
import { isHydroServerAuthenticated } from '@/bootstrap/appInitialization'
216215
import { storeToRefs } from 'pinia'
217216
import { useUserStore } from '@/store/user'
218217
import hs from '@hydroserver/client'

apps/data-management/src/main.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,27 @@ import vuetify from '@/plugins/vuetify'
99
import pinia from '@/plugins/pinia'
1010
import { injectClarity } from '@/plugins/clarity'
1111
import { settings } from '@/config/settings'
12-
import { startAppInitialization } from '@/bootstrap/appInitialization'
12+
import {
13+
startAppInitialization,
14+
waitForHydroServerInitialization,
15+
} from '@/bootstrap/appInitialization'
1316

1417
const app = createApp(App)
1518

1619
app.use(pinia)
1720

18-
// Kick off session initialization before router navigation starts, but do not
19-
// block first paint on the rest of the app bootstrap work.
20-
void startAppInitialization()
21+
async function bootstrap() {
22+
// Kick off session initialization before router navigation starts, but do not
23+
// block first paint on the rest of the app bootstrap work.
24+
void startAppInitialization()
25+
await waitForHydroServerInitialization()
2126

22-
app.use(router)
23-
app.use(vuetify)
24-
settings.analyticsConfiguration.enableClarityAnalytics &&
25-
settings.analyticsConfiguration.clarityProjectId &&
26-
injectClarity(settings.analyticsConfiguration.clarityProjectId)
27-
app.mount('#app')
27+
app.use(router)
28+
app.use(vuetify)
29+
settings.analyticsConfiguration.enableClarityAnalytics &&
30+
settings.analyticsConfiguration.clarityProjectId &&
31+
injectClarity(settings.analyticsConfiguration.clarityProjectId)
32+
app.mount('#app')
33+
}
34+
35+
void bootstrap()

apps/data-management/src/router/router.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ router.beforeEach(
3737
await waitForHydroServerInitialization()
3838
if (!isHydroServerReady.value) return false
3939

40-
if (!hs.session.isAuthenticated && to.meta.requiresAuth) {
41-
await hs.session.login(to.fullPath)
42-
return false
40+
if (to.meta.requiresAuth) {
41+
return await hs.session.ensureAuthorized(to.fullPath)
4342
}
4443
}
4544
)

apps/data-management/src/router/routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export const routes: RouteRecordRaw[] = [
112112
window.location.assign(hs.session.accountProfileUrl)
113113
return false
114114
},
115-
meta: { requiresAuth: true, title: 'Profile' },
115+
meta: { title: 'Profile' },
116116
},
117117
{
118118
path: '/metadata',

packages/hydroserver-ts/src/api/__tests__/session.service.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ describe('SessionService', () => {
157157

158158
it('does not block initialization on a silent bootstrap when no cached user exists', async () => {
159159
managerState.getUser.mockResolvedValue(null)
160+
apiFetchMock.mockResolvedValue({ ok: false, data: null })
160161
const client = new HydroServer({ host: 'https://hydro.example.com' })
161162
const emitSpy = vi.spyOn(client, 'emit')
162163
const session = new SessionService(client)
@@ -195,4 +196,52 @@ describe('SessionService', () => {
195196
{ credentials: 'include' }
196197
)
197198
})
199+
200+
it('treats the browser session as authenticated after initialization', async () => {
201+
managerState.getUser.mockResolvedValue(null)
202+
apiFetchMock.mockResolvedValue({
203+
ok: true,
204+
data: {
205+
account: {
206+
email: 'user@example.com',
207+
firstName: 'Signed',
208+
},
209+
},
210+
})
211+
212+
const session = new SessionService(
213+
new HydroServer({ host: 'https://hydro.example.com' })
214+
)
215+
216+
await session.initialize()
217+
218+
expect(session.isAuthenticated).toBe(true)
219+
expect(session.hasAccessToken).toBe(false)
220+
expect(session.account).toMatchObject({
221+
email: 'user@example.com',
222+
firstName: 'Signed',
223+
})
224+
})
225+
226+
it('redirects through login when a protected route needs an access token', async () => {
227+
managerState.getUser.mockResolvedValue(null)
228+
apiFetchMock.mockResolvedValue({
229+
ok: true,
230+
data: {
231+
account: {
232+
email: 'user@example.com',
233+
},
234+
},
235+
})
236+
237+
const session = new SessionService(
238+
new HydroServer({ host: 'https://hydro.example.com' })
239+
)
240+
await session.initialize()
241+
242+
await expect(session.ensureAuthorized('/sites')).resolves.toBe(false)
243+
expect(managerState.signinRedirect).toHaveBeenCalledWith({
244+
state: { returnTo: '/sites' },
245+
})
246+
})
198247
})

0 commit comments

Comments
 (0)