Skip to content

Commit 891d0fc

Browse files
authored
Merge pull request #57526 from nextcloud/refactor/loginflow-to-vue
refactor(core): migrate login flow ui from jQuery to Vue
2 parents a8369cd + 942a92f commit 891d0fc

43 files changed

Lines changed: 814 additions & 396 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build/frontend-legacy/webpack.modules.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = {
1616
files_fileinfo: path.join(__dirname, 'core/src', 'files/fileinfo.js'),
1717
install: path.join(__dirname, 'core/src', 'install.ts'),
1818
login: path.join(__dirname, 'core/src', 'login.js'),
19+
login_flow: path.join(__dirname, 'core/src', 'login-flow.ts'),
1920
main: path.join(__dirname, 'core/src', 'main.js'),
2021
maintenance: path.join(__dirname, 'core/src', 'maintenance.js'),
2122
'public-page-menu': path.resolve(__dirname, 'core/src', 'public-page-menu.ts'),

core/Controller/ClientFlowLoginController.php

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use OCP\AppFramework\Http\RedirectResponse;
2626
use OCP\AppFramework\Http\Response;
2727
use OCP\AppFramework\Http\StandaloneTemplateResponse;
28+
use OCP\AppFramework\Services\IInitialState;
2829
use OCP\AppFramework\Utility\ITimeFactory;
2930
use OCP\Authentication\Exceptions\InvalidTokenException;
3031
use OCP\Authentication\Token\IToken;
@@ -35,11 +36,11 @@
3536
use OCP\IRequest;
3637
use OCP\ISession;
3738
use OCP\IURLGenerator;
38-
use OCP\IUser;
3939
use OCP\IUserSession;
4040
use OCP\Security\ICrypto;
4141
use OCP\Security\ISecureRandom;
4242
use OCP\Session\Exceptions\SessionNotAvailableException;
43+
use OCP\Util;
4344

4445
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
4546
class ClientFlowLoginController extends Controller {
@@ -61,6 +62,7 @@ public function __construct(
6162
private IEventDispatcher $eventDispatcher,
6263
private ITimeFactory $timeFactory,
6364
private IConfig $config,
65+
private IInitialState $initialState,
6466
) {
6567
parent::__construct($appName, $request);
6668
}
@@ -135,24 +137,36 @@ public function showAuthPickerPage(string $clientIdentifier = '', string $user =
135137
$csp->addAllowedFormActionDomain('nc://*');
136138
}
137139

140+
$this->initialState->provideInitialState('loginFlowState', 'auth');
141+
$this->initialState->provideInitialState('loginFlowAuth', [
142+
'client' => $clientName,
143+
'clientIdentifier' => $clientIdentifier,
144+
'instanceName' => $this->defaults->getName(),
145+
'stateToken' => $stateToken,
146+
'serverHost' => $this->getServerPath(),
147+
'oauthState' => $this->session->get('oauth.state'),
148+
'direct' => (bool)$direct,
149+
'providedRedirectUri' => $providedRedirectUri,
150+
'loginRedirectUrl' => $this->urlGenerator->linkToRoute(
151+
'core.ClientFlowLogin.grantPage',
152+
[
153+
'stateToken' => $stateToken,
154+
'clientIdentifier' => $clientIdentifier,
155+
'oauthState' => $this->session->get('oauth.state'),
156+
'user' => $user,
157+
'direct' => $direct,
158+
'providedRedirectUri' => $providedRedirectUri,
159+
]),
160+
'appTokenUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.apptokenRedirect'),
161+
]);
162+
163+
164+
Util::addScript('core', 'login_flow');
138165
$response = new StandaloneTemplateResponse(
139166
$this->appName,
140-
'loginflow/authpicker',
141-
[
142-
'client' => $clientName,
143-
'clientIdentifier' => $clientIdentifier,
144-
'instanceName' => $this->defaults->getName(),
145-
'urlGenerator' => $this->urlGenerator,
146-
'stateToken' => $stateToken,
147-
'serverHost' => $this->getServerPath(),
148-
'oauthState' => $this->session->get('oauth.state'),
149-
'user' => $user,
150-
'direct' => $direct,
151-
'providedRedirectUri' => $providedRedirectUri,
152-
],
153-
'guest'
167+
'loginflow',
168+
renderAs: 'guest'
154169
);
155-
156170
$response->setContentSecurityPolicy($csp);
157171
return $response;
158172
}
@@ -188,26 +202,31 @@ public function grantPage(
188202
$csp->addAllowedFormActionDomain('nc://*');
189203
}
190204

191-
/** @var IUser $user */
192205
$user = $this->userSession->getUser();
193-
206+
\assert($user !== null);
207+
208+
$this->initialState->provideInitialState('loginFlowState', 'grant');
209+
$this->initialState->provideInitialState('loginFlowGrant', [
210+
'actionUrl' => $this->urlGenerator->linkToRouteAbsolute(
211+
'core.ClientFlowLogin.generateAppPassword',
212+
),
213+
'client' => $clientName,
214+
'clientIdentifier' => $clientIdentifier,
215+
'instanceName' => $this->defaults->getName(),
216+
'stateToken' => $stateToken,
217+
'serverHost' => $this->getServerPath(),
218+
'oauthState' => $this->session->get('oauth.state'),
219+
'direct' => $direct,
220+
'providedRedirectUri' => $providedRedirectUri,
221+
'userDisplayName' => $user->getDisplayName(),
222+
'userId' => $user->getUID(),
223+
]);
224+
225+
Util::addScript('core', 'login_flow');
194226
$response = new StandaloneTemplateResponse(
195227
$this->appName,
196-
'loginflow/grant',
197-
[
198-
'userId' => $user->getUID(),
199-
'userDisplayName' => $user->getDisplayName(),
200-
'client' => $clientName,
201-
'clientIdentifier' => $clientIdentifier,
202-
'instanceName' => $this->defaults->getName(),
203-
'urlGenerator' => $this->urlGenerator,
204-
'stateToken' => $stateToken,
205-
'serverHost' => $this->getServerPath(),
206-
'oauthState' => $this->session->get('oauth.state'),
207-
'direct' => $direct,
208-
'providedRedirectUri' => $providedRedirectUri,
209-
],
210-
'guest'
228+
'loginflow',
229+
renderAs: 'guest'
211230
);
212231

213232
$response->setContentSecurityPolicy($csp);

core/Controller/ClientFlowLoginV2Controller.php

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,17 @@
2626
use OCP\AppFramework\Http\RedirectResponse;
2727
use OCP\AppFramework\Http\Response;
2828
use OCP\AppFramework\Http\StandaloneTemplateResponse;
29+
use OCP\AppFramework\Services\IInitialState;
2930
use OCP\Authentication\Exceptions\InvalidTokenException;
3031
use OCP\Defaults;
3132
use OCP\IL10N;
3233
use OCP\IRequest;
3334
use OCP\ISession;
3435
use OCP\IURLGenerator;
35-
use OCP\IUser;
3636
use OCP\IUserSession;
3737
use OCP\Security\ISecureRandom;
3838
use OCP\Server;
39+
use OCP\Util;
3940

4041
/**
4142
* @psalm-import-type CoreLoginFlowV2Credentials from ResponseDefinitions
@@ -58,6 +59,7 @@ public function __construct(
5859
private Defaults $defaults,
5960
private ?string $userId,
6061
private IL10N $l10n,
62+
private IInitialState $initialState,
6163
) {
6264
parent::__construct($appName, $request);
6365
}
@@ -122,18 +124,21 @@ public function showAuthPickerPage(string $user = '', int $direct = 0): Standalo
122124
);
123125
$this->session->set(self::STATE_NAME, $stateToken);
124126

127+
$this->initialState->provideInitialState('loginFlowState', 'auth');
128+
$this->initialState->provideInitialState('loginFlowAuth', [
129+
'client' => $flow->getClientName(),
130+
'instanceName' => $this->defaults->getName(),
131+
'stateToken' => $stateToken,
132+
'loginRedirectUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.grantPage', ['stateToken' => $stateToken, 'user' => $user, 'direct' => $direct]),
133+
'appTokenUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.apptokenRedirect'),
134+
]);
135+
136+
137+
Util::addScript('core', 'login_flow');
125138
return new StandaloneTemplateResponse(
126139
$this->appName,
127-
'loginflowv2/authpicker',
128-
[
129-
'client' => $flow->getClientName(),
130-
'instanceName' => $this->defaults->getName(),
131-
'urlGenerator' => $this->urlGenerator,
132-
'stateToken' => $stateToken,
133-
'user' => $user,
134-
'direct' => $direct,
135-
],
136-
'guest'
140+
'loginflow',
141+
renderAs: 'guest'
137142
);
138143
}
139144

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

164-
/** @var IUser $user */
165169
$user = $this->userSession->getUser();
170+
\assert($user !== null);
171+
172+
$this->initialState->provideInitialState('loginFlowState', 'grant');
173+
$this->initialState->provideInitialState('loginFlowGrant', [
174+
'actionUrl' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.generateAppPassword'),
175+
'userId' => $user->getUID(),
176+
'userDisplayName' => $user->getDisplayName(),
177+
'client' => $flow->getClientName(),
178+
'instanceName' => $this->defaults->getName(),
179+
'stateToken' => $stateToken,
180+
'direct' => $direct === 1,
181+
]);
166182

183+
184+
Util::addScript('core', 'login_flow');
167185
return new StandaloneTemplateResponse(
168186
$this->appName,
169-
'loginflowv2/grant',
170-
[
171-
'userId' => $user->getUID(),
172-
'userDisplayName' => $user->getDisplayName(),
173-
'client' => $flow->getClientName(),
174-
'instanceName' => $this->defaults->getName(),
175-
'urlGenerator' => $this->urlGenerator,
176-
'stateToken' => $stateToken,
177-
'direct' => $direct,
178-
],
179-
'guest'
187+
'loginflow',
188+
renderAs: 'guest'
180189
);
181190
}
182191

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

261270
private function handleFlowDone(bool $result): StandaloneTemplateResponse {
262271
if ($result) {
272+
Util::addScript('core', 'login_flow');
273+
$this->initialState->provideInitialState('loginFlowState', 'done');
263274
return new StandaloneTemplateResponse(
264275
$this->appName,
265-
'loginflowv2/done',
266-
[],
267-
'guest'
276+
'loginflow',
277+
renderAs: 'guest'
268278
);
269279
}
270280

core/js/login/authpicker.js

Lines changed: 0 additions & 19 deletions
This file was deleted.

core/js/login/grant.js

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import { getRequestToken } from '@nextcloud/auth'
8+
import { t } from '@nextcloud/l10n'
9+
import NcButton from '@nextcloud/vue/components/NcButton'
10+
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
11+
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
12+
import NcTextField from '@nextcloud/vue/components/NcTextField'
13+
14+
defineProps<{
15+
appTokenUrl: string
16+
direct: boolean
17+
stateToken: string
18+
}>()
19+
20+
const requestToken = getRequestToken()
21+
</script>
22+
23+
<template>
24+
<form :action="appTokenUrl" :class="$style.loginFlowAuthAppToken" method="post">
25+
<NcFormBox>
26+
<NcTextField name="user" :label="t('core', 'Login')" />
27+
<NcPasswordField name="password" :label="t('core', 'App password')" />
28+
</NcFormBox>
29+
<input type="hidden" name="stateToken" :value="stateToken">
30+
<input type="hidden" name="requesttoken" :value="requestToken">
31+
<input
32+
v-if="direct"
33+
type="hidden"
34+
name="direct"
35+
value="1">
36+
37+
<NcButton type="submit" variant="primary" wide>
38+
{{ t('core', 'Grant access') }}
39+
</NcButton>
40+
</form>
41+
</template>
42+
43+
<style module>
44+
.loginFlowAuthAppToken {
45+
display: flex;
46+
flex-direction: column;
47+
gap: var(--default-grid-baseline);
48+
}
49+
</style>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
8+
9+
defineProps<{
10+
heading: string
11+
}>()
12+
</script>
13+
14+
<template>
15+
<NcGuestContent class="picker-window" :class="$style.loginFlowContainer">
16+
<h2>{{ heading }}</h2>
17+
<slot />
18+
</NcGuestContent>
19+
</template>
20+
21+
<style module>
22+
.loginFlowContainer {
23+
display: flex;
24+
flex-direction: column;
25+
}
26+
</style>

0 commit comments

Comments
 (0)