Skip to content
Open
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
13 changes: 13 additions & 0 deletions src/auth/methods/BaseAuthMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ abstract class BaseAuthMethod extends Component implements AuthMethodInterface
*/
protected User $user;

/**
* Returns the data needed to render the authentication method’s setup form.
*
* This should be part of AuthMethodInterface, but remains here for 5.x BC
* with auth methods that implement the interface directly.
*
* @return array
*/
public function getSetupData(): array
{
return [];
}

/**
* @inheritdoc
*/
Expand Down
13 changes: 13 additions & 0 deletions src/auth/methods/TOTP.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ public function isActive(): bool
return self::secretFromDb($this->user->id) !== null;
}

/**
* @inheritdoc
*/
public function getSetupData(): array
{
$secret = trim($this->secret());

return [
'secret' => $secret,
'qrCode' => $this->generateQrCode($secret),
];
}
Comment thread
Copilot marked this conversation as resolved.

/**
* @inheritdoc
*/
Expand Down
62 changes: 60 additions & 2 deletions src/controllers/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
namespace craft\controllers;

use Craft;
use craft\auth\methods\AuthMethodInterface;
use craft\auth\methods\BaseAuthMethod;
use craft\auth\methods\RecoveryCodes;
use craft\auth\methods\TOTP;
use craft\helpers\Html;
use craft\helpers\Session as SessionHelper;
use craft\i18n\Locale;
use craft\web\Controller;
use craft\web\View;
use yii\base\InvalidArgumentException;
use yii\base\InvalidConfigException;
use yii\web\Response;

Expand Down Expand Up @@ -96,6 +99,45 @@ public function actionMethodListingHtml(): ?Response
]);
}

/**
* Returns the data for available authentication methods.
*
* @return Response
*/
public function actionGetAvailableMethods(): Response
{
$this->requirePostRequest();
$this->requireAcceptsJson();

Comment thread
timkelty marked this conversation as resolved.
return $this->asJson([
'methods' => array_map(
fn(AuthMethodInterface $method) => $this->authMethodData($method),
Craft::$app->getAuth()->getAvailableMethods(),
),
]);
}

/**
* Returns the data needed to set up an authentication method.
*
* @return Response
*/
public function actionGetSetupData(): Response
{
$this->requirePostRequest();
$this->requireAcceptsJson();
$this->requireElevatedSession();

$class = $this->request->getRequiredBodyParam('method');
try {
$method = Craft::$app->getAuth()->getMethod($class);
} catch (InvalidArgumentException $e) {
return $this->asFailure($e->getMessage());
}

return $this->asJson($method instanceof BaseAuthMethod ? $method->getSetupData() : []);
}

/**
* Remove auth type setup (for 2FA or Passkeys) from the database
*
Expand All @@ -105,14 +147,19 @@ public function actionMethodListingHtml(): ?Response
*/
public function actionRemoveMethod(): ?Response
{
$this->requireCpRequest();
$this->requirePostRequest();
$this->requireAcceptsJson();
$this->requireElevatedSession();

Comment thread
timkelty marked this conversation as resolved.
$methodClass = $this->request->getRequiredBodyParam('method');

$auth = Craft::$app->getAuth();
$method = $auth->getMethod($methodClass);
try {
$method = $auth->getMethod($methodClass);
} catch (InvalidArgumentException $e) {
return $this->asFailure($e->getMessage());
}

$method->remove();

// if that was the last non-Recovery Codes method, remove Recovery Codes too
Expand Down Expand Up @@ -263,6 +310,17 @@ private function passkeyTableHtml(): string
]);
}

private function authMethodData(AuthMethodInterface $method): array
{
return [
'class' => $method::class,
'name' => $method::displayName(),
'description' => $method::description(),
'actionMenuItems' => $method->getActionMenuItems(),
'isActive' => $method->isActive(),
];
}

/**
* Generates new recovery codes.
*
Expand Down
Loading