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 @@ -16,6 +16,7 @@ module.exports = {
files_fileinfo: path.join(__dirname, 'core/src', 'files/fileinfo.js'),
install: path.join(__dirname, 'core/src', 'install.ts'),
login: path.join(__dirname, 'core/src', 'login.js'),
login_flow: path.join(__dirname, 'core/src', 'login-flow.ts'),
main: path.join(__dirname, 'core/src', 'main.js'),
maintenance: path.join(__dirname, 'core/src', 'maintenance.js'),
'public-page-menu': path.resolve(__dirname, 'core/src', 'public-page-menu.ts'),
Expand Down
85 changes: 52 additions & 33 deletions core/Controller/ClientFlowLoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\Token\IToken;
Expand All @@ -35,11 +36,11 @@
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
use OCP\Session\Exceptions\SessionNotAvailableException;
use OCP\Util;

#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class ClientFlowLoginController extends Controller {
Expand All @@ -61,6 +62,7 @@ public function __construct(
private IEventDispatcher $eventDispatcher,
private ITimeFactory $timeFactory,
private IConfig $config,
private IInitialState $initialState,
) {
parent::__construct($appName, $request);
}
Expand Down Expand Up @@ -135,24 +137,36 @@ public function showAuthPickerPage(string $clientIdentifier = '', string $user =
$csp->addAllowedFormActionDomain('nc://*');
}

$this->initialState->provideInitialState('loginFlowState', 'auth');
$this->initialState->provideInitialState('loginFlowAuth', [
'client' => $clientName,
'clientIdentifier' => $clientIdentifier,
'instanceName' => $this->defaults->getName(),
'stateToken' => $stateToken,
'serverHost' => $this->getServerPath(),
'oauthState' => $this->session->get('oauth.state'),
'direct' => (bool)$direct,
'providedRedirectUri' => $providedRedirectUri,
'loginRedirectUrl' => $this->urlGenerator->linkToRoute(
'core.ClientFlowLogin.grantPage',
[
'stateToken' => $stateToken,
'clientIdentifier' => $clientIdentifier,
'oauthState' => $this->session->get('oauth.state'),
'user' => $user,
'direct' => $direct,
'providedRedirectUri' => $providedRedirectUri,
]),
'appTokenUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.apptokenRedirect'),
]);


Util::addScript('core', 'login_flow');
$response = new StandaloneTemplateResponse(
$this->appName,
'loginflow/authpicker',
[
'client' => $clientName,
'clientIdentifier' => $clientIdentifier,
'instanceName' => $this->defaults->getName(),
'urlGenerator' => $this->urlGenerator,
'stateToken' => $stateToken,
'serverHost' => $this->getServerPath(),
'oauthState' => $this->session->get('oauth.state'),
'user' => $user,
'direct' => $direct,
'providedRedirectUri' => $providedRedirectUri,
],
'guest'
'loginflow',
renderAs: 'guest'
);

$response->setContentSecurityPolicy($csp);
return $response;
}
Expand Down Expand Up @@ -188,26 +202,31 @@ public function grantPage(
$csp->addAllowedFormActionDomain('nc://*');
}

/** @var IUser $user */
$user = $this->userSession->getUser();

\assert($user !== null);

$this->initialState->provideInitialState('loginFlowState', 'grant');
$this->initialState->provideInitialState('loginFlowGrant', [
'actionUrl' => $this->urlGenerator->linkToRouteAbsolute(
'core.ClientFlowLogin.generateAppPassword',
),
'client' => $clientName,
'clientIdentifier' => $clientIdentifier,
'instanceName' => $this->defaults->getName(),
'stateToken' => $stateToken,
'serverHost' => $this->getServerPath(),
'oauthState' => $this->session->get('oauth.state'),
'direct' => $direct,
'providedRedirectUri' => $providedRedirectUri,
'userDisplayName' => $user->getDisplayName(),
'userId' => $user->getUID(),
]);

Util::addScript('core', 'login_flow');
$response = new StandaloneTemplateResponse(
$this->appName,
'loginflow/grant',
[
'userId' => $user->getUID(),
'userDisplayName' => $user->getDisplayName(),
'client' => $clientName,
'clientIdentifier' => $clientIdentifier,
'instanceName' => $this->defaults->getName(),
'urlGenerator' => $this->urlGenerator,
'stateToken' => $stateToken,
'serverHost' => $this->getServerPath(),
'oauthState' => $this->session->get('oauth.state'),
'direct' => $direct,
'providedRedirectUri' => $providedRedirectUri,
],
'guest'
'loginflow',
renderAs: 'guest'
);

$response->setContentSecurityPolicy($csp);
Expand Down
62 changes: 36 additions & 26 deletions core/Controller/ClientFlowLoginV2Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Defaults;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Security\ISecureRandom;
use OCP\Server;
use OCP\Util;

/**
* @psalm-import-type CoreLoginFlowV2Credentials from ResponseDefinitions
Expand All @@ -58,6 +59,7 @@ public function __construct(
private Defaults $defaults,
private ?string $userId,
private IL10N $l10n,
private IInitialState $initialState,
) {
parent::__construct($appName, $request);
}
Expand Down Expand Up @@ -122,18 +124,21 @@ public function showAuthPickerPage(string $user = '', int $direct = 0): Standalo
);
$this->session->set(self::STATE_NAME, $stateToken);

$this->initialState->provideInitialState('loginFlowState', 'auth');
$this->initialState->provideInitialState('loginFlowAuth', [
'client' => $flow->getClientName(),
'instanceName' => $this->defaults->getName(),
'stateToken' => $stateToken,
'loginRedirectUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.grantPage', ['stateToken' => $stateToken, 'user' => $user, 'direct' => $direct]),
'appTokenUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.apptokenRedirect'),
]);


Util::addScript('core', 'login_flow');
return new StandaloneTemplateResponse(
$this->appName,
'loginflowv2/authpicker',
[
'client' => $flow->getClientName(),
'instanceName' => $this->defaults->getName(),
'urlGenerator' => $this->urlGenerator,
'stateToken' => $stateToken,
'user' => $user,
'direct' => $direct,
],
'guest'
'loginflow',
renderAs: 'guest'
);
}

Expand Down Expand Up @@ -161,22 +166,26 @@ public function grantPage(?string $stateToken, int $direct = 0): StandaloneTempl
return $this->loginTokenForbiddenClientResponse();
}

/** @var IUser $user */
$user = $this->userSession->getUser();
\assert($user !== null);

$this->initialState->provideInitialState('loginFlowState', 'grant');
$this->initialState->provideInitialState('loginFlowGrant', [
'actionUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.generateAppPassword'),
'userId' => $user->getUID(),
'userDisplayName' => $user->getDisplayName(),
'client' => $flow->getClientName(),
'instanceName' => $this->defaults->getName(),
'stateToken' => $stateToken,
'direct' => $direct === 1,
]);


Util::addScript('core', 'login_flow');
return new StandaloneTemplateResponse(
$this->appName,
'loginflowv2/grant',
[
'userId' => $user->getUID(),
'userDisplayName' => $user->getDisplayName(),
'client' => $flow->getClientName(),
'instanceName' => $this->defaults->getName(),
'urlGenerator' => $this->urlGenerator,
'stateToken' => $stateToken,
'direct' => $direct,
],
'guest'
'loginflow',
renderAs: 'guest'
);
}

Expand Down Expand Up @@ -260,11 +269,12 @@ public function generateAppPassword(?string $stateToken): Response {

private function handleFlowDone(bool $result): StandaloneTemplateResponse {
if ($result) {
Util::addScript('core', 'login_flow');
$this->initialState->provideInitialState('loginFlowState', 'done');
return new StandaloneTemplateResponse(
$this->appName,
'loginflowv2/done',
[],
'guest'
'loginflow',
renderAs: 'guest'
);
}

Expand Down
19 changes: 0 additions & 19 deletions core/js/login/authpicker.js

This file was deleted.

32 changes: 0 additions & 32 deletions core/js/login/grant.js

This file was deleted.

49 changes: 49 additions & 0 deletions core/src/components/LoginFlow/LoginFlowAuthAppToken.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import { getRequestToken } from '@nextcloud/auth'
import { t } from '@nextcloud/l10n'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
import NcTextField from '@nextcloud/vue/components/NcTextField'
defineProps<{
appTokenUrl: string
direct: boolean
stateToken: string
}>()
const requestToken = getRequestToken()
</script>

<template>
<form :action="appTokenUrl" :class="$style.loginFlowAuthAppToken" method="post">
<NcFormBox>
<NcTextField name="user" :label="t('core', 'Login')" />
<NcPasswordField name="password" :label="t('core', 'App password')" />
</NcFormBox>
<input type="hidden" name="stateToken" :value="stateToken">
<input type="hidden" name="requesttoken" :value="requestToken">
<input
v-if="direct"
type="hidden"
name="direct"
value="1">

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

<style module>
.loginFlowAuthAppToken {
display: flex;
flex-direction: column;
gap: var(--default-grid-baseline);
}
</style>
26 changes: 26 additions & 0 deletions core/src/components/LoginFlow/LoginFlowContainer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
defineProps<{
heading: string
}>()
</script>

<template>
<NcGuestContent class="picker-window" :class="$style.loginFlowContainer">
<h2>{{ heading }}</h2>
<slot />
</NcGuestContent>
</template>

<style module>
.loginFlowContainer {
display: flex;
flex-direction: column;
}
</style>
Loading
Loading