mirror of
https://github.com/nextcloud/server.git
synced 2026-06-08 08:16:43 -04:00
fix(files_external): broken credentials dialog
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
91533fb6f7
commit
89bf128708
4 changed files with 119 additions and 70 deletions
|
|
@ -40,10 +40,5 @@ return [
|
|||
'url' => '/api/v1/mounts',
|
||||
'verb' => 'GET',
|
||||
],
|
||||
[
|
||||
'name' => 'Api#askNativeAuth',
|
||||
'url' => '/api/v1/auth',
|
||||
'verb' => 'GET',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use OCA\Files_External\Service\UserGlobalStoragesService;
|
|||
use OCA\Files_External\Service\UserStoragesService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\OpenAPI;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
|
|
@ -103,31 +102,4 @@ class ApiController extends OCSController {
|
|||
|
||||
return new DataResponse($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for credentials using a browser's native basic auth prompt
|
||||
* Then returns it if provided
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
|
||||
public function askNativeAuth(): DataResponse {
|
||||
if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
|
||||
$response = new DataResponse([], Http::STATUS_UNAUTHORIZED);
|
||||
$response->addHeader('WWW-Authenticate', 'Basic realm="Storage authentification needed"');
|
||||
return $response;
|
||||
}
|
||||
|
||||
$user = $_SERVER['PHP_AUTH_USER'];
|
||||
$password = $_SERVER['PHP_AUTH_PW'];
|
||||
|
||||
// Reset auth
|
||||
unset($_SERVER['PHP_AUTH_USER']);
|
||||
unset($_SERVER['PHP_AUTH_PW']);
|
||||
|
||||
// Using 401 again to ensure we clear any cached Authorization
|
||||
return new DataResponse([
|
||||
'user' => $user,
|
||||
'password' => $password,
|
||||
], Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,29 +7,39 @@ import type { AxiosResponse } from '@nextcloud/axios'
|
|||
import type { Node } from '@nextcloud/files'
|
||||
import type { StorageConfig } from '../services/externalStorage'
|
||||
|
||||
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { showError, showSuccess, spawnDialog } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import axios from '@nextcloud/axios'
|
||||
import LoginSvg from '@mdi/svg/svg/login.svg?raw'
|
||||
import Vue from 'vue'
|
||||
import Vue, { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { FileAction, DefaultType } from '@nextcloud/files'
|
||||
import { STORAGE_STATUS, isMissingAuthConfig } from '../utils/credentialsUtils'
|
||||
import { isNodeExternalStorage } from '../utils/externalStorageUtils'
|
||||
|
||||
type OCSAuthResponse = {
|
||||
ocs: {
|
||||
meta: {
|
||||
status: string
|
||||
statuscode: number
|
||||
message: string
|
||||
},
|
||||
data: {
|
||||
user?: string,
|
||||
password?: string,
|
||||
}
|
||||
type CredentialResponse = {
|
||||
login?: string,
|
||||
password?: string,
|
||||
}
|
||||
|
||||
async function setCredentials(node: Node, login: string, password: string): Promise<null|true> {
|
||||
const configResponse = await axios.put(generateUrl('apps/files_external/userglobalstorages/{id}', node.attributes), {
|
||||
backendOptions: { user: login, password },
|
||||
}) as AxiosResponse<StorageConfig>
|
||||
|
||||
const config = configResponse.data
|
||||
if (config.status !== STORAGE_STATUS.SUCCESS) {
|
||||
showError(t('files_external', 'Unable to update this external storage config. {statusMessage}', {
|
||||
statusMessage: config?.statusMessage || '',
|
||||
}))
|
||||
return null
|
||||
}
|
||||
|
||||
// Success update config attribute
|
||||
showSuccess(t('files_external', 'New configuration successfully saved'))
|
||||
Vue.set(node.attributes, 'config', config)
|
||||
return true
|
||||
}
|
||||
|
||||
export const action = new FileAction({
|
||||
|
|
@ -57,30 +67,16 @@ export const action = new FileAction({
|
|||
},
|
||||
|
||||
async exec(node: Node) {
|
||||
// always resolve auth request, we'll process the data afterwards
|
||||
// Using fetch as axios have integrated auth handling and X-Requested-With header
|
||||
const response = await fetch(generateOcsUrl('/apps/files_external/api/v1/auth'), {
|
||||
headers: new Headers({ Accept: 'application/json' }),
|
||||
credentials: 'include',
|
||||
})
|
||||
const { login, password } = await new Promise<CredentialResponse>(resolve => spawnDialog(
|
||||
defineAsyncComponent(() => import('../views/CredentialsDialog.vue')),
|
||||
{},
|
||||
(args) => {
|
||||
resolve(args as CredentialResponse)
|
||||
},
|
||||
))
|
||||
|
||||
const data = (await response?.json() || {}) as OCSAuthResponse
|
||||
if (data.ocs.data.user && data.ocs.data.password) {
|
||||
const configResponse = await axios.put(generateUrl('apps/files_external/userglobalstorages/{id}', node.attributes), {
|
||||
backendOptions: data.ocs.data,
|
||||
}) as AxiosResponse<StorageConfig>
|
||||
|
||||
const config = configResponse.data
|
||||
if (config.status !== STORAGE_STATUS.SUCCESS) {
|
||||
showError(t('files_external', 'Unable to update this external storage config. {statusMessage}', {
|
||||
statusMessage: config?.statusMessage || '',
|
||||
}))
|
||||
return null
|
||||
}
|
||||
|
||||
// Success update config attribute
|
||||
showSuccess(t('files_external', 'New configuration successfully saved'))
|
||||
Vue.set(node.attributes, 'config', config)
|
||||
if (login && password) {
|
||||
return await setCredentials(node, login, password)
|
||||
}
|
||||
|
||||
return null
|
||||
|
|
|
|||
86
apps/files_external/src/views/CredentialsDialog.vue
Normal file
86
apps/files_external/src/views/CredentialsDialog.vue
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcDialog :buttons="dialogButtons"
|
||||
class="external-storage-auth"
|
||||
close-on-click-outside
|
||||
data-cy-external-storage-auth
|
||||
is-form
|
||||
:name="t('files_external', 'Storage credentials')"
|
||||
out-transition
|
||||
@submit="$emit('close', {login, password})"
|
||||
@update:open="$emit('close')">
|
||||
<!-- Header -->
|
||||
<NcNoteCard class="external-storage-auth__header"
|
||||
:text="t('files_external', 'To access the storage, you need to provide the authentification informations.')"
|
||||
type="info" />
|
||||
|
||||
<!-- Login -->
|
||||
<NcTextField ref="login"
|
||||
class="external-storage-auth__login"
|
||||
data-cy-external-storage-auth-dialog-login
|
||||
:label="t('files_external', 'Login')"
|
||||
:placeholder="t('files_external', 'Enter the storage login')"
|
||||
minlength="2"
|
||||
name="login"
|
||||
required
|
||||
:value.sync="login" />
|
||||
|
||||
<!-- Password -->
|
||||
<NcPasswordField ref="password"
|
||||
class="external-storage-auth__password"
|
||||
data-cy-external-storage-auth-dialog-password
|
||||
:label="t('files_external', 'Password')"
|
||||
:placeholder="t('files_external', 'Enter the storage password')"
|
||||
name="password"
|
||||
required
|
||||
:value.sync="password" />
|
||||
</NcDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
|
||||
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
|
||||
import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js'
|
||||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CredentialsDialog',
|
||||
|
||||
components: {
|
||||
NcDialog,
|
||||
NcNoteCard,
|
||||
NcTextField,
|
||||
NcPasswordField,
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
t,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
login: '',
|
||||
password: '',
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
dialogButtons() {
|
||||
return [{
|
||||
label: t('files_external', 'Submit'),
|
||||
type: 'primary',
|
||||
nativeType: 'submit',
|
||||
}]
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
Loading…
Reference in a new issue