Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/frontend-legacy/webpack.modules.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
'unsupported-browser': path.join(__dirname, 'core/src', 'unsupported-browser.js'),
'unsupported-browser-redirect': path.join(__dirname, 'core/src', 'unsupported-browser-redirect.js'),
public: path.join(__dirname, 'core/src', 'public.ts'),
public_share_auth: path.join(__dirname, 'core/src', 'public-share-auth.ts'),
'twofactor-request-token': path.join(__dirname, 'core/src', 'twofactor-request-token.ts'),
},
dashboard: {
Expand Down
6 changes: 6 additions & 0 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3268,6 +3268,12 @@
<code><![CDATA[listen]]></code>
</DeprecatedMethod>
</file>
<file src="core/templates/publicshareauth.php">
<DeprecatedMethod>
<code><![CDATA[provideInitialState]]></code>
<code><![CDATA[provideInitialState]]></code>
</DeprecatedMethod>
</file>
<file src="lib/base.php">
<InvalidArgument>
<code><![CDATA[$restrictions]]></code>
Expand Down
40 changes: 0 additions & 40 deletions core/css/publicshareauth.css

This file was deleted.

57 changes: 0 additions & 57 deletions core/js/publicshareauth.js

This file was deleted.

14 changes: 14 additions & 0 deletions core/src/public-share-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { getCSPNonce } from '@nextcloud/auth'
import Vue from 'vue'
import PublicShareAuth from './views/PublicShareAuth.vue'

__webpack_nonce__ = getCSPNonce()

const View = Vue.extend(PublicShareAuth)
const app = new View()
app.$mount('#core-public-share-auth')
133 changes: 133 additions & 0 deletions core/src/views/PublicShareAuth.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import type { ShareType } from '@nextcloud/sharing'

import { getRequestToken } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { getSharingToken } from '@nextcloud/sharing/public'
import { NcTextField } from '@nextcloud/vue'
import { getCurrentInstance, onMounted, ref } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'

const publicShareAuth = loadState<{
canResendPassword: boolean
shareType: ShareType
identityOk?: boolean | null
invalidPassword?: boolean
}>('core', 'publicShareAuth')

const requestToken = getRequestToken()
const sharingToken = getSharingToken()
const { shareType, invalidPassword, canResendPassword } = publicShareAuth

const hasIdentityCheck = typeof publicShareAuth.identityOk === 'boolean'
const showIdentityCheck = ref(typeof publicShareAuth.identityOk === 'boolean')
const password = ref('')
const email = ref('')

// TODO: Remove when using Vue 3
onMounted(() => {
const instance = getCurrentInstance()
if (instance) {
// @ts-expect-error Vue internals
(instance.proxy.$el as HTMLElement)?.classList.add('guest-box')
}
})
</script>

<template>
<NcGuestContent :class="$style.publicShareAuth">
<h2>{{ t('core', 'This share is password-protected') }}</h2>
<form
v-show="!showIdentityCheck"
:class="$style.publicShareAuth__form"
method="POST">
<NcNoteCard v-if="invalidPassword" type="error">
{{ t('core', 'The password is wrong or expired. Please try again or request a new one.') }}
</NcNoteCard>

<NcPasswordField
v-model="password"
:label="t('core', 'Password')"
autofocus
autocomplete="new-password"
autocapitalize="off"
spellcheck="false"
name="password" />

<input type="hidden" name="requesttoken" :value="requestToken">
<input type="hidden" name="sharingToken" :value="sharingToken">
<input type="hidden" name="sharingType" :value="shareType">

<NcButton type="submit" variant="primary" wide>
{{ t('core', 'Submit') }}
</NcButton>
</form>

<form
v-if="showIdentityCheck"
:class="$style.publicShareAuth__form"
method="POST">
<NcNoteCard v-if="!hasIdentityCheck" type="info">
{{ t('core', 'Please type in your email address to request a temporary password') }}
</NcNoteCard>

<NcNoteCard v-else :type="publicShareAuth.identityOk ? 'success' : 'error'">
{{ publicShareAuth.identityOk ? t('core', 'Password sent!') : t('core', 'You are not authorized to request a password for this share') }}
</NcNoteCard>

<NcTextField
v-model="email"
type="email"
name="identityToken"
:label="t('core', 'Email address')" />
<input type="hidden" name="requesttoken" :value="requestToken">
<input type="hidden" name="sharingToken" :value="sharingToken">
<input type="hidden" name="sharingType" :value="shareType">
<input type="hidden" name="passwordRequest" value="">

<NcFormBox row>
<NcButton wide @click="showIdentityCheck = false">
{{ t('core', 'Back') }}
</NcButton>
<NcButton type="submit" variant="primary" wide>
{{ t('core', 'Request password') }}
</NcButton>
</NcFormBox>
</form>

<!-- request password button -->
<NcButton
v-if="canResendPassword && !showIdentityCheck"
:class="$style.publicShareAuth__forgotPasswordButton"
wide
@click="showIdentityCheck = true">
{{ t('core', 'Forgot password') }}
</NcButton>
</NcGuestContent>
</template>

<style module>
.publicShareAuth {
max-width: 400px !important;
}

.publicShareAuth__form {
display: flex;
flex-direction: column;
gap: calc(2 * var(--default-grid-baseline));
}

.publicShareAuth__forgotPasswordButton {
margin-top: calc(3 * var(--default-grid-baseline));
}
</style>
2 changes: 1 addition & 1 deletion core/templates/layout.guest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
</div>
</header>
<?php endif; ?>
<div>
<div class="guest-content">
<h1 class="hidden-visually">
<?php p($theme->getName()); ?>
</h1>
Expand Down
91 changes: 13 additions & 78 deletions core/templates/publicshareauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,84 +4,19 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

/** @var array $_ */
/** @var \OCP\IL10N $l */
\OCP\Util::addStyle('core', 'guest');
\OCP\Util::addStyle('core', 'publicshareauth');
\OCP\Util::addScript('core', 'publicshareauth');
?>

<div class="guest-box">
<!-- password prompt form. It should be hidden when we show the email prompt form -->
<?php if (!isset($_['identityOk'])): ?>
<form method="post" id="password-input-form">
<?php else: ?>
<form method="post" id="password-input-form" style="display:none;">
<?php endif; ?>
<fieldset class="warning">
<?php if (!isset($_['wrongpw'])): ?>
<div class="warning-info"><?php p($l->t('This share is password-protected')); ?></div>
<?php endif; ?>
<?php if (isset($_['wrongpw'])): ?>
<div class="warning wrongPasswordMsg"><?php p($l->t('The password is wrong or expired. Please try again or request a new one.')); ?></div>
<?php endif; ?>
<p>
<label for="password" class="infield"><?php p($l->t('Password')); ?></label>
<input type="hidden" id="requesttoken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
<input type="password" name="password" id="password"
placeholder="<?php p($l->t('Password')); ?>" value=""
autocomplete="new-password" autocapitalize="off" spellcheck="false"
autofocus />
<input type="hidden" name="sharingToken" value="<?php p($_['share']->getToken()) ?>" id="sharingToken">
<input type="hidden" name="sharingType" value="<?php p($_['share']->getShareType()) ?>" id="sharingType">
<input type="submit" id="password-submit"
class="svg icon-confirm input-button-inline" value="" disabled="disabled" />
</p>
</fieldset>
</form>
/** @var array{share: \OCP\Share\IShare, identityOk?: bool, wrongpw?: bool} $_ */

<!-- email prompt form. It should initially be hidden -->
<?php if (isset($_['identityOk'])): ?>
<form method="post" id="email-input-form">
<?php else: ?>
<form method="post" id="email-input-form" style="display:none;">
<?php endif; ?>
<fieldset class="warning">
<div class="warning-info" id="email-prompt"><?php p($l->t('Please type in your email address to request a temporary password')); ?></div>
<p>
<input type="email" id="email" name="identityToken" placeholder="<?php p($l->t('Email address')); ?>" />
<input type="submit" id="password-request" name="passwordRequest" class="svg icon-confirm input-button-inline" value="" disabled="disabled"/>
<input type="hidden" id="requesttoken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
<input type="hidden" name="sharingToken" value="<?php p($_['share']->getToken()) ?>" id="sharingToken">
<input type="hidden" name="sharingType" value="<?php p($_['share']->getShareType()) ?>" id="sharingType">
</p>
<?php if (isset($_['identityOk'])): ?>
<?php if ($_['identityOk']): ?>
<div class="warning-info" id="identification-success"><?php p($l->t('Password sent!')); ?></div>
<?php else: ?>
<div class="warning" id="identification-failure"><?php p($l->t('You are not authorized to request a password for this share')); ?></div>
<?php endif; ?>
<?php endif; ?>
</fieldset>
</form>
\OCP\Util::addStyle('core', 'guest');
\OCP\Util::addScript('core', 'public_share_auth');

<!-- request password button -->
<?php if (!isset($_['identityOk']) && $_['share']->getShareType() === $_['share']::TYPE_EMAIL && !$_['share']->getSendPasswordByTalk()): ?>
<a id="request-password-button-not-talk"><?php p($l->t('Forgot password?')); ?></a>
<?php endif; ?>
$initialState = \OCP\Server::get(\OCP\IInitialStateService::class);
$initialState->provideInitialState('files_sharing', 'sharingToken', $_['share']->getToken());
$initialState->provideInitialState('core', 'publicShareAuth', [
'identityOk' => $_['identityOk'] ?? null,
'shareType' => $_['share']->getShareType(),
'invalidPassword' => $_['wrongpw'] ?? null,
'canResendPassword' => $_['share']->getShareType() === \OCP\Share\IShare::TYPE_EMAIL && !$_['share']->getSendPasswordByTalk(),
]);
?>

<!-- back to showShare button -->
<form method="get">
<fieldset>
<a
href=""
id="request-password-back-button"
<?php if (isset($_['identityOk'])): ?>
style="display:block;">
<?php else: ?>
style="display:none;">
<?php endif; ?>
<?php p($l->t('Back')); ?></a>
</fieldset>
</form>
</div>
<div id="core-public-share-auth" class="guest-box" ></div>
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const defaultShareContext: ShareContext = {
*
* @param context The current share context (defaults to `defaultShareContext` if not provided).
* @return The share URL.
* @throws Error if the share context has no URL.
* @throws {Error} if the share context has no URL.
*/
export function getShareUrl(context: ShareContext = defaultShareContext): string {
if (!context.url) {
Expand Down
Loading
Loading