fix: add app config to control onetime case

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2025-10-10 12:48:56 +02:00 committed by Maksim Sukharev
parent e7d0ed2020
commit bacb4323de
4 changed files with 30 additions and 32 deletions

View file

@ -20,6 +20,7 @@ use OCP\Config\ValueType;
* Please Add & Manage your Config Keys in that file and keep the Lexicon up to date!
*/
class ConfigLexicon implements ILexicon {
public const LOGIN_QRCODE_ONETIME = 'qrcode_onetime';
public const USER_SETTINGS_EMAIL = 'email';
public const USER_LIST_SHOW_STORAGE_PATH = 'user_list_show_storage_path';
public const USER_LIST_SHOW_USER_BACKEND = 'user_list_show_user_backend';
@ -33,7 +34,9 @@ class ConfigLexicon implements ILexicon {
}
public function getAppConfigs(): array {
return [];
return [
new Entry(key: self::LOGIN_QRCODE_ONETIME, type: ValueType::BOOL, defaultRaw: false, definition: 'Use onetime QR codes for app passwords', note: 'Limits compatibility for mobile apps to versions released in 2026 or later'),
];
}
public function getUserConfigs(): array {

View file

@ -14,12 +14,14 @@ use OC\Authentication\Token\INamedToken;
use OC\Authentication\Token\IProvider;
use OC\Authentication\Token\RemoteWipe;
use OCA\Settings\Activity\Provider;
use OCA\Settings\ConfigLexicon;
use OCP\Activity\IManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Services\IAppConfig;
use OCP\Authentication\Exceptions\ExpiredTokenException;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\Exceptions\WipeTokenException;
@ -32,49 +34,31 @@ use OCP\Session\Exceptions\SessionNotAvailableException;
use Psr\Log\LoggerInterface;
class AuthSettingsController extends Controller {
/** @var IProvider */
private $tokenProvider;
/** @var RemoteWipe */
private $remoteWipe;
/**
* @param string $appName
* @param IRequest $request
* @param IProvider $tokenProvider
* @param ISession $session
* @param ISecureRandom $random
* @param string|null $userId
* @param IUserSession $userSession
* @param IManager $activityManager
* @param RemoteWipe $remoteWipe
* @param LoggerInterface $logger
*/
public function __construct(
string $appName,
IRequest $request,
IProvider $tokenProvider,
private IProvider $tokenProvider,
private ISession $session,
private ISecureRandom $random,
private ?string $userId,
private IUserSession $userSession,
private IManager $activityManager,
RemoteWipe $remoteWipe,
private IAppConfig $appConfig,
private RemoteWipe $remoteWipe,
private LoggerInterface $logger,
) {
parent::__construct($appName, $request);
$this->tokenProvider = $tokenProvider;
$this->remoteWipe = $remoteWipe;
}
/**
* @NoSubAdminRequired
*
* @param bool $oneTime If set to true, the returned token can only be used to get the actual app password a single time
* @param bool $qrcodeLogin If set to true, the returned token could be (depending on server settings) a onetime password, that can only be used to get the actual app password a single time
*/
#[NoAdminRequired]
#[PasswordConfirmationRequired]
public function create(string $name = '', bool $oneTime = false): JSONResponse {
public function create(string $name = '', bool $qrcodeLogin = false): JSONResponse {
if ($this->checkAppToken()) {
return $this->getServiceNotAvailableResponse();
}
@ -100,10 +84,16 @@ class AuthSettingsController extends Controller {
return $this->getServiceNotAvailableResponse();
}
if ($oneTime) {
$name = 'One time login';
$type = IToken::ONETIME_TOKEN;
$scope = [];
if ($qrcodeLogin) {
if ($this->appConfig->getAppValueBool(ConfigLexicon::LOGIN_QRCODE_ONETIME)) {
$name = 'One time login';
$type = IToken::ONETIME_TOKEN;
$scope = [];
} else {
$name = 'QR Code login';
$type = IToken::PERMANENT_TOKEN;
$scope = null;
}
} elseif ($name === '') {
// No name is only allowed for one time logins
return $this->getServiceNotAvailableResponse();

View file

@ -113,7 +113,7 @@ export default defineComponent({
async handleQrCodeClick() {
await confirmPassword()
const { data } = await axios.post<ITokenResponse>(generateUrl('/settings/personal/authtokens'), { name: '', oneTime: true })
const { data } = await axios.post<ITokenResponse>(generateUrl('/settings/personal/authtokens'), { qrcodeLogin: true })
await spawnDialog(AccountQrLoginDialog, { data })
},

View file

@ -41,13 +41,16 @@ const buttons = [{
callback: () => undefined,
}]
const isOneTimeToken = (props.data?.deviceToken?.type ?? 1) === 3
const qrUrl = computed(() => {
const user = props.data?.loginName ?? ''
const password = props.data?.token ?? ''
const path = isOneTimeToken ? 'onetime-login' : 'login'
const server = getBaseUrl()
// TODO return different result for error handling (to not provide invalid URL)
return `nc://onetime-login/user:${user}&password:${password}&server:${server}`
return `nc://${path}/user:${user}&password:${password}&server:${server}`
})
const expirationTimestamp = (props.data?.deviceToken?.lastActivity ? props.data.deviceToken.lastActivity * 1_000 : Date.now()) + 120_000
@ -77,8 +80,10 @@ function onClosing(result: unknown) {
{{ t('core', 'Use {productName} mobile client you want to connect to scan the code', { productName }) }}
</p>
<QR :value="qrUrl" />
<!-- TRANSLATORS Intl will provide a conjunction, e.g. 'Code will expire in 30 seconds' -->
{{ t('core', 'Code will expire {timeCountdown} or after use', { timeCountdown }) }}
<template v-if="isOneTimeToken">
<!-- TRANSLATORS Intl will provide a conjunction, e.g. 'Code will expire in 30 seconds' -->
{{ t('core', 'Code will expire {timeCountdown} or after use', { timeCountdown }) }}
</template>
</div>
</NcDialog>
</template>