Merge pull request #60165 from nextcloud/fix/recommended-apps-password-confirmation

fix(core): prompt for password once when installing recommended apps
This commit is contained in:
Louis 2026-05-06 15:33:42 +02:00 committed by GitHub
commit 2fcc7b14b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 178 additions and 39 deletions

View file

@ -62,8 +62,8 @@
import axios from '@nextcloud/axios'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { PwdConfirmationMode } from '@nextcloud/password-confirmation'
import { generateUrl, imagePath } from '@nextcloud/router'
import pLimit from 'p-limit'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import logger from '../../logger.js'
@ -147,35 +147,41 @@ export default {
},
methods: {
installApps() {
this.installingApps = true
const limit = pLimit(1)
const installing = this.recommendedApps
async installApps() {
const apps = this.recommendedApps
.filter((app) => !app.active && app.isCompatible && app.canInstall && app.isSelected)
.map((app) => limit(async () => {
logger.info(`installing ${app.id}`)
app.loading = true
return axios.post(generateUrl('settings/apps/enable'), { appIds: [app.id], groups: [] })
.catch((error) => {
logger.error(`could not install ${app.id}`, { error })
app.isSelected = false
app.installationError = true
})
.then(() => {
logger.info(`installed ${app.id}`)
app.loading = false
app.active = true
})
}))
logger.debug(`installing ${installing.length} recommended apps`)
Promise.all(installing)
.then(() => {
logger.info('all recommended apps installed, redirecting …')
if (apps.length === 0) {
return
}
window.location = this.defaultPageUrl
this.installingApps = true
apps.forEach((app) => {
app.loading = true
})
const appIds = apps.map((app) => app.id)
logger.debug(`installing ${apps.length} recommended apps`, { appIds })
try {
await axios.post(
generateUrl('settings/apps/enable'),
{ appIds, groups: [] },
{ confirmPassword: PwdConfirmationMode.Strict },
)
apps.forEach((app) => {
app.loading = false
app.active = true
})
.catch((error) => logger.error('could not install recommended apps', { error }))
logger.info('all recommended apps installed, redirecting …')
window.location = this.defaultPageUrl
} catch (error) {
logger.error('could not install recommended apps', { error })
apps.forEach((app) => {
app.loading = false
app.isSelected = false
app.installationError = true
})
this.installingApps = false
}
},
customIcon(appId) {

View file

@ -4,11 +4,15 @@
*/
import { getCSPNonce } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import { translate as t } from '@nextcloud/l10n'
import { addPasswordConfirmationInterceptors } from '@nextcloud/password-confirmation'
import Vue from 'vue'
import RecommendedApps from './components/setup/RecommendedApps.vue'
import logger from './logger.js'
addPasswordConfirmationInterceptors(axios)
__webpack_nonce__ = getCSPNonce()
Vue.mixin({

View file

@ -4,6 +4,9 @@
*/
import { randomString } from '../../support/utils/randomString.ts'
import { handlePasswordConfirmation } from '../core-utils.ts'
type RecommendedAppsMode = 'skip' | 'install-success' | 'install-failure'
/**
* DO NOT RENAME THIS FILE to .cy.ts
@ -30,6 +33,22 @@ describe('Can install Nextcloud', { testIsolation: true, retries: 0 }, () => {
sharedSetup()
})
it('Sqlite - Install recommended apps (success)', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
cy.get('[data-cy-setup-form-field="dbtype-sqlite"] input').check({ force: true })
sharedSetup('install-success')
})
it('Sqlite - Install recommended apps (failure)', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
cy.get('[data-cy-setup-form-field="dbtype-sqlite"] input').check({ force: true })
sharedSetup('install-failure')
})
it('MySQL', () => {
cy.visit('/')
cy.get('[data-cy-setup-form]').should('be.visible')
@ -110,8 +129,12 @@ describe('Can install Nextcloud', { testIsolation: true, retries: 0 }, () => {
/**
* Shared admin setup function for the Nextcloud setup
*
* @param mode How to handle the recommended apps screen at the end of the
* install assistant: skip it, exercise the install button with a
* stubbed success response, or stub a failure response.
*/
function sharedSetup() {
function sharedSetup(mode: RecommendedAppsMode = 'skip') {
const randAdmin = 'admin-' + randomString(10)
// mock appstore
@ -140,10 +163,41 @@ function sharedSetup() {
.should('be.visible')
})
// Skip the setup apps
cy.get('[data-cy-setup-recommended-apps-skip]').click()
if (mode === 'skip') {
// Skip the setup apps
cy.get('[data-cy-setup-recommended-apps-skip]').click()
// Go to files
cy.visit('/apps/files/')
cy.get('[data-cy-files-content]').should('be.visible')
// Go to files
cy.visit('/apps/files/')
cy.get('[data-cy-files-content]').should('be.visible')
return
}
// Stub the bulk enable endpoint so we exercise the frontend flow without
// hitting the real app store.
cy.intercept('POST', '**/settings/apps/enable', mode === 'install-success'
? { statusCode: 200, body: { data: { update_required: false } } }
: { statusCode: 500, body: { data: { message: 'Forced failure' } } }).as('enableApps')
cy.get('[data-cy-setup-recommended-apps-install]').click()
// The strict password-confirmation dialog must appear and must result in a
// Basic auth header on the enable request.
cy.findByRole('dialog', { name: 'Authentication required' })
.should('be.visible')
handlePasswordConfirmation(randAdmin)
cy.wait('@enableApps')
.its('request.headers.authorization')
.should('match', /^Basic /)
if (mode === 'install-success') {
// Frontend redirects via window.location to the default page.
cy.location('pathname', { timeout: 10000 })
.should('not.include', '/core/apps/recommended')
} else {
// Stay on the recommended-apps page and surface the per-app error state.
cy.location('pathname').should('include', '/core/apps/recommended')
cy.get('[data-cy-setup-recommended-apps]')
.should('contain.text', 'App download or installation failed')
}
}

4
dist/core-common.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
SPDX-License-Identifier: BSD-3-Clause
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
SPDX-FileCopyrightText: webfansplz
SPDX-FileCopyrightText: perfect-debounce developers
SPDX-FileCopyrightText: hookable developers
SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: debounce developers
SPDX-FileCopyrightText: atomiks
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
@ -13,11 +18,21 @@ SPDX-FileCopyrightText: Matt Zabriskie
SPDX-FileCopyrightText: GitHub Inc.
SPDX-FileCopyrightText: Feross Aboukhadijeh
SPDX-FileCopyrightText: Evan You
SPDX-FileCopyrightText: Eduardo San Martin Morote
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
SPDX-FileCopyrightText: David Clark
SPDX-FileCopyrightText: Christoph Wurst
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
SPDX-FileCopyrightText: Anthony Fu <anthonyfu117@hotmail.com>
This file is generated from multiple sources. Included packages:
- @floating-ui/core
- version: 1.7.5
- license: MIT
- @floating-ui/utils
- version: 0.2.11
- license: MIT
- @nextcloud/auth
- version: 2.5.3
- license: GPL-3.0-or-later
@ -27,6 +42,9 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/browser-storage
- version: 0.5.0
- license: GPL-3.0-or-later
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- semver
- version: 7.7.2
- license: ISC
@ -42,6 +60,48 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/logger
- version: 3.0.3
- license: GPL-3.0-or-later
- @nextcloud/vue
- version: 9.6.0
- license: AGPL-3.0-or-later
- @vue/devtools-shared
- version: 8.1.0
- license: MIT
- @vue/reactivity
- version: 3.5.30
- license: MIT
- @vue/runtime-core
- version: 3.5.30
- license: MIT
- @vue/runtime-dom
- version: 3.5.30
- license: MIT
- @vue/shared
- version: 3.5.30
- license: MIT
- @vueuse/core
- version: 14.2.1
- license: MIT
- @vueuse/shared
- version: 14.2.1
- license: MIT
- perfect-debounce
- version: 2.1.0
- license: MIT
- @vue/devtools-api
- version: 8.1.0
- license: MIT
- @vue/devtools-kit
- version: 8.1.0
- license: MIT
- vue-router
- version: 5.0.3
- license: MIT
- vue
- version: 3.5.30
- license: MIT
- @nextcloud/password-confirmation
- version: 6.1.0
- license: MIT
- @nextcloud/router
- version: 3.1.0
- license: GPL-3.0-or-later
@ -54,15 +114,27 @@ This file is generated from multiple sources. Included packages:
- base64-js
- version: 1.5.1
- license: MIT
- birpc
- version: 2.9.0
- license: MIT
- css-loader
- version: 7.1.2
- license: MIT
- debounce
- version: 3.0.0
- license: MIT
- dompurify
- version: 3.4.0
- license: (MPL-2.0 OR Apache-2.0)
- escape-html
- version: 1.0.3
- license: MIT
- focus-trap
- version: 8.0.1
- license: MIT
- hookable
- version: 5.5.3
- license: MIT
- ieee754
- version: 1.2.1
- license: BSD-3-Clause
@ -75,6 +147,9 @@ This file is generated from multiple sources. Included packages:
- style-loader
- version: 4.0.0
- license: MIT
- tabbable
- version: 6.4.0
- license: MIT
- vue-loader
- version: 15.11.1
- license: MIT

File diff suppressed because one or more lines are too long