Просмотр исходного кода

fix(auth): hide nav when login required

Co-authored-by: Cursor <cursoragent@cursor.com>
jamesread 16 часов назад
Родитель
Сommit
0ed5b0e27c

+ 3 - 3
frontend/resources/vue/App.vue

@@ -308,7 +308,7 @@ async function updateHeaderFromInit () {
   bannerMessage.value = window.initResponse.bannerMessage || ''
   bannerMessage.value = window.initResponse.bannerMessage || ''
   bannerCss.value = window.initResponse.bannerCss || ''
   bannerCss.value = window.initResponse.bannerCss || ''
   showFooter.value = window.initResponse.showFooter
   showFooter.value = window.initResponse.showFooter
-  showNavigation.value = window.initResponse.showNavigation
+  showNavigation.value = !!window.initResponse.showNavigation && !loginRequired.value
   showLogs.value = window.initResponse.showLogList
   showLogs.value = window.initResponse.showLogList
   showDiagnostics.value = window.initResponse.showDiagnostics
   showDiagnostics.value = window.initResponse.showDiagnostics
   showVersionNumber.value = window.initResponse.effectivePolicy?.showVersionNumber ?? true
   showVersionNumber.value = window.initResponse.effectivePolicy?.showVersionNumber ?? true
@@ -321,8 +321,6 @@ async function updateHeaderFromInit () {
 
 
   applyStyleMods()
   applyStyleMods()
   loadCustomJsIfEnabled()
   loadCustomJsIfEnabled()
-
-  renderNavigation()
   applyTheme().catch((err) => {
   applyTheme().catch((err) => {
     console.warn('Failed to load theme CSS:', err)
     console.warn('Failed to load theme CSS:', err)
   })
   })
@@ -333,6 +331,8 @@ async function updateHeaderFromInit () {
     return
     return
   }
   }
 
 
+  renderNavigation()
+
   if (headerSearchEnabled.value) {
   if (headerSearchEnabled.value) {
     indexSearchHints(window.initResponse.searchHints)
     indexSearchHints(window.initResponse.searchHints)
     indexRootDashboardEntries(getRootDashboardEntries())
     indexRootDashboardEntries(getRootDashboardEntries())

+ 27 - 0
integration-tests/tests/authRequireGuestsToLogin/authRequireGuestsToLogin.mjs

@@ -38,6 +38,33 @@ describe('config: authRequireGuestsToLogin', function () {
     expect(loginElements.length).to.be.greaterThan(0)
     expect(loginElements.length).to.be.greaterThan(0)
 
 
     console.log('✓ Login page loaded correctly')
     console.log('✓ Login page loaded correctly')
+  })
+
+  it('Guest does not receive navigation or dashboards before login', async function () {
+    await webdriver.get(runner.baseUrl())
+
+    await webdriver.wait(async () => {
+      return await webdriver.executeScript(
+        'return !!(window.initResponse && window.initResponse.loginRequired)'
+      )
+    }, 15000, 'Init should report loginRequired')
+
+    const initState = await webdriver.executeScript(`
+      const init = window.initResponse || {}
+      return {
+        showNavigation: !!init.showNavigation,
+        rootDashboards: init.rootDashboards || [],
+        rootDashboardEntries: init.rootDashboardEntries || []
+      }
+    `)
+
+    expect(initState.showNavigation).to.equal(false)
+    expect(initState.rootDashboards).to.deep.equal([])
+    expect(initState.rootDashboardEntries).to.deep.equal([])
+
+    const sidebars = await webdriver.findElements(By.css('#mainnav, #picocrank-sidebar'))
+    expect(sidebars.length).to.equal(0)
 
 
+    console.log('✓ Navigation and dashboards hidden while login is required')
   })
   })
 })
 })

+ 5 - 0
integration-tests/tests/authRequireGuestsToLogin/config.yaml

@@ -51,3 +51,8 @@ actions:
   shell: sleep 2
   shell: sleep 2
   icon: "&#x1F971"
   icon: "&#x1F971"
 
 
+dashboards:
+- title: Secret Board
+  contents:
+  - title: Ping Google.com
+

+ 12 - 2
service/internal/api/api.go

@@ -1349,11 +1349,11 @@ func (api *oliveTinAPI) Init(ctx ctx.Context, req *connect.Request[apiv1.InitReq
 func (api *oliveTinAPI) buildInitResponse(user *authpublic.AuthenticatedUser) *apiv1.InitResponse {
 func (api *oliveTinAPI) buildInitResponse(user *authpublic.AuthenticatedUser) *apiv1.InitResponse {
 	loginRequired := user.IsGuest() && api.cfg.AuthRequireGuestsToLogin
 	loginRequired := user.IsGuest() && api.cfg.AuthRequireGuestsToLogin
 	currentVersion, availableVersion, showNewVersions := initVersionFields(user, api.cfg)
 	currentVersion, availableVersion, showNewVersions := initVersionFields(user, api.cfg)
-	rootDashboardEntries := api.buildRootDashboardEntries(user, api.cfg.Dashboards)
+	showNavigation, rootDashboardEntries := api.initNavigationFields(user, loginRequired)
 
 
 	res := &apiv1.InitResponse{
 	res := &apiv1.InitResponse{
 		ShowFooter:                api.cfg.ShowFooter,
 		ShowFooter:                api.cfg.ShowFooter,
-		ShowNavigation:            api.cfg.ShowNavigation,
+		ShowNavigation:            showNavigation,
 		ShowNewVersions:           showNewVersions,
 		ShowNewVersions:           showNewVersions,
 		AvailableVersion:          availableVersion,
 		AvailableVersion:          availableVersion,
 		CurrentVersion:            currentVersion,
 		CurrentVersion:            currentVersion,
@@ -1388,6 +1388,16 @@ func (api *oliveTinAPI) buildInitResponse(user *authpublic.AuthenticatedUser) *a
 	return res
 	return res
 }
 }
 
 
+// initNavigationFields hides sidebar navigation and dashboard titles from guests
+// that must log in before using OliveTin.
+func (api *oliveTinAPI) initNavigationFields(user *authpublic.AuthenticatedUser, loginRequired bool) (bool, []*apiv1.RootDashboard) {
+	if loginRequired {
+		return false, nil
+	}
+
+	return api.cfg.ShowNavigation, api.buildRootDashboardEntries(user, api.cfg.Dashboards)
+}
+
 func initVersionFields(user *authpublic.AuthenticatedUser, cfg *config.Config) (currentVersion string, availableVersion string, showNewVersions bool) {
 func initVersionFields(user *authpublic.AuthenticatedUser, cfg *config.Config) (currentVersion string, availableVersion string, showNewVersions bool) {
 	if !user.EffectivePolicy.ShowVersionNumber {
 	if !user.EffectivePolicy.ShowVersionNumber {
 		return "", "", false
 		return "", "", false

+ 30 - 0
service/internal/api/api_init_search_hints_test.go

@@ -87,6 +87,36 @@ func TestInitOmitsSearchHintsWhenLoginRequired(t *testing.T) {
 	assert.Nil(t, resp.Msg.SearchHints)
 	assert.Nil(t, resp.Msg.SearchHints)
 }
 }
 
 
+func TestInitHidesNavigationAndDashboardsWhenLoginRequired(t *testing.T) {
+	cfg := config.DefaultConfig()
+	cfg.AuthRequireGuestsToLogin = true
+	cfg.ShowNavigation = true
+	cfg.Actions = []*config.Action{
+		{Title: "Ping", Shell: "echo ping"},
+	}
+	cfg.Dashboards = []*config.DashboardComponent{
+		{
+			Title: "Secret Board",
+			Contents: []*config.DashboardComponent{
+				{Title: "Ping"},
+			},
+		},
+	}
+	cfg.Sanitize()
+
+	testExecutor := executor.DefaultExecutor(cfg)
+	testExecutor.RebuildActionMap()
+	testServer, client := getNewTestServerAndClientWithExecutor(cfg, testExecutor)
+	defer testServer.Close()
+
+	resp, err := client.Init(context.Background(), connect.NewRequest(&apiv1.InitRequest{}))
+	require.NoError(t, err)
+	require.True(t, resp.Msg.LoginRequired)
+	assert.False(t, resp.Msg.ShowNavigation)
+	assert.Empty(t, resp.Msg.RootDashboards)
+	assert.Empty(t, resp.Msg.RootDashboardEntries)
+}
+
 func TestInitOmitsSearchHintsWhenHeaderSearchDisabled(t *testing.T) {
 func TestInitOmitsSearchHintsWhenHeaderSearchDisabled(t *testing.T) {
 	entities.ClearEntitiesOfType("server")
 	entities.ClearEntitiesOfType("server")
 	t.Cleanup(func() {
 	t.Cleanup(func() {