From 7895598f5f89608c97a77d43284f119215456b1d Mon Sep 17 00:00:00 2001 From: Chenx221 Date: Thu, 28 Mar 2024 17:21:02 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E9=A1=B5=20*=E4=BB=85=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=83=A8=E5=88=86=E7=A7=BB=E6=A4=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controllers/AdminController.php | 39 +++- controllers/UserController.php | 46 +++- models/User.php | 4 +- views/admin/info.php | 363 +++++++++++++++++++++++++++++++- 4 files changed, 431 insertions(+), 21 deletions(-) diff --git a/controllers/AdminController.php b/controllers/AdminController.php index f09d3ab..f412c97 100644 --- a/controllers/AdminController.php +++ b/controllers/AdminController.php @@ -6,6 +6,7 @@ use app\models\User; use app\models\UserSearch; use app\utils\AdminSword; use app\utils\FileSizeHelper; +use OTPHP\TOTP; use Throwable; use Yii; use yii\base\Exception; @@ -46,7 +47,7 @@ class AdminController extends Controller 'user-create' => ['GET', 'POST'], 'user-update' => ['GET', 'POST'], 'user-delete' => ['POST'], - 'info' => ['GET'], + 'info' => ['GET','POST'], 'user-totpoff' => ['POST'], 'user-pwdreset' => ['POST'], ], @@ -95,6 +96,7 @@ class AdminController extends Controller /** * Displays a single User model. + * 显示、处理用户管理中的数据变更 * @param int $id ID * @throws NotFoundHttpException|Throwable if the model cannot be found */ @@ -324,10 +326,39 @@ class AdminController extends Controller } /** - * @return string + * @param string|null $focus + * @return Response|string */ - public function actionInfo(): string + public function actionInfo(string $focus = null): Response|string { - return $this->render('info'); + $model = Yii::$app->user->identity; + $totp_secret = null; + $totp_url = null; + if (!$model->is_otp_enabled) { + $totp = TOTP::generate(); + $totp_secret = $totp->getSecret(); + $totp->setLabel('NetDisk_' . $model->name); + $totp_url = $totp->getProvisioningUri(); + } + if (Yii::$app->request->isPost && $model->load(Yii::$app->request->post())) { + if ($model->save(true,['bio'])) { + Yii::$app->session->setFlash('success', '个人简介已更新'); + return $this->render('info', [ + 'model' => $model, + 'focus' => 'bio', + 'totp_secret' => $totp_secret, + 'totp_url' => $totp_url, + 'is_otp_enabled' => $model->is_otp_enabled + ]); + } + } + return $this->render('info', [ + 'model' => $model, + 'focus' => $focus, + 'totp_secret' => $totp_secret, + 'totp_url' => $totp_url, + 'is_otp_enabled' => $model->is_otp_enabled == 1 + ]); } + // Todo: 测试移植的用户信息页功能 } diff --git a/controllers/UserController.php b/controllers/UserController.php index fb0de11..60bfd7b 100644 --- a/controllers/UserController.php +++ b/controllers/UserController.php @@ -505,14 +505,24 @@ class UserController extends Controller { $model = Yii::$app->user->identity; $model->scenario = 'changePassword'; - + $org_password = $model->password; + //verify old password if ($model->load(Yii::$app->request->post()) && $model->validate()) { - $model->password = Yii::$app->security->generatePasswordHash($model->newPassword); - if ($model->save(false)) { - Yii::$app->session->setFlash('success', 'Password changed successfully.'); + if (!Yii::$app->security->validatePassword($model->oldPassword, $org_password)) { + Yii::$app->session->setFlash('error', '原密码错误'); } else { - Yii::$app->session->setFlash('error', 'Failed to change password.'); + $model->password = Yii::$app->security->generatePasswordHash($model->newPassword); + if ($model->save(false)) { + Yii::$app->session->setFlash('success', 'Password changed successfully.'); + } else { + Yii::$app->session->setFlash('error', 'Failed to change password.'); + } } + }else{ + Yii::$app->session->setFlash('error', 'Failed to validate password.'); + } + if (Yii::$app->user->can('admin')) { + return $this->redirect(['admin/info', 'focus' => 'password']); } return $this->redirect(['user/info', 'focus' => 'password']); } @@ -541,6 +551,9 @@ class UserController extends Controller Yii::$app->session->setFlash('error', '二步验证启用失败,请重新添加'); } } + if (Yii::$app->user->can('admin')) { + return $this->redirect(['admin/info']); + } return $this->redirect(['user/info']); } @@ -598,6 +611,9 @@ class UserController extends Controller } else { // 如果用户没有启用 TOTP,返回一个错误消息 Yii::$app->session->setFlash('error', '获取失败,您还没有启用二步验证。'); + if (Yii::$app->user->can('admin')) { + return $this->redirect(['admin/info', 'focus' => 'advanced']); + } return $this->redirect(['user/info', 'focus' => 'advanced']); } } @@ -622,11 +638,14 @@ class UserController extends Controller public function actionChangeName(): Response { $model = Yii::$app->user->identity; - if ($model->load(Yii::$app->request->post()) && $model->save()) { + if ($model->load(Yii::$app->request->post()) && $model->save(true, ['name'])) { Yii::$app->session->setFlash('success', '昵称已更新'); } else { Yii::$app->session->setFlash('error', '昵称更新失败'); } + if (Yii::$app->user->can('admin')) { + return $this->redirect(['admin/info']); + } return $this->redirect(['user/info']); } @@ -644,6 +663,9 @@ class UserController extends Controller ]); } else { Yii::$app->session->setFlash('error', '非Ajax请求'); + if (Yii::$app->user->can('admin')) { + return $this->redirect(['admin/info']); + } return $this->redirect('info'); } } @@ -662,6 +684,9 @@ class UserController extends Controller $publicKeyCredentialSourceRepository = $this->findCredentialModel($id); if ($publicKeyCredentialSourceRepository->user_id !== Yii::$app->user->id) { Yii::$app->session->setFlash('error', '非法操作'); + if (Yii::$app->user->can('admin')) { + return $this->redirect(['admin/info']); + } return $this->redirect('info'); } $publicKeyCredentialSourceRepository->delete(); @@ -672,6 +697,9 @@ class UserController extends Controller ]); } else { Yii::$app->session->setFlash('error', '非Pjax请求,无法删除'); + if (Yii::$app->user->can('admin')) { + return $this->redirect(['admin/info']); + } return $this->redirect('info'); } } @@ -872,10 +900,10 @@ class UserController extends Controller } Yii::$app->user->login($user, $remember === 1 ? 3600 * 24 * 30 : 0); $publicKeyCredentialSourceRepository1->saveCredential($publicKeyCredentialSource, '', false); - if(Yii::$app->user->can('admin')){ - return $this->asJson(['verified' => true,'redirectTo' => 'index.php?r=admin%2Findex']); + if (Yii::$app->user->can('admin')) { + return $this->asJson(['verified' => true, 'redirectTo' => 'index.php?r=admin%2Findex']); } - return $this->asJson(['verified' => true,'redirectTo' => 'index.php']); + return $this->asJson(['verified' => true, 'redirectTo' => 'index.php']); } // Optional, but highly recommended, you can save the credential source as it may be modified // during the verification process (counter may be higher). diff --git a/models/User.php b/models/User.php index 6960449..e59b3a7 100644 --- a/models/User.php +++ b/models/User.php @@ -73,14 +73,14 @@ class User extends ActiveRecord implements IdentityInterface [['username', 'password', 'email', 'password2'], 'required', 'on' => 'register'], [['username', 'password', 'email'], 'required', 'on' => 'addUser'], ['username', 'string', 'min' => 3, 'max' => 12], - ['password', 'string', 'min' => 6, 'max' => 24], + ['password', 'string', 'min' => 6], ['password2', 'compare', 'compareAttribute' => 'password', 'on' => 'register'], ['email', 'email', 'on' => ['register', 'addUser']], ['username', 'unique', 'on' => ['register', 'addUser']], ['email', 'unique', 'on' => ['register', 'addUser']], [['oldPassword', 'newPassword', 'newPasswordRepeat'], 'required', 'on' => 'changePassword'], ['oldPassword', 'validatePassword2', 'on' => 'changePassword'], - ['newPassword', 'string', 'min' => 6, 'max' => 24, 'on' => 'changePassword'], + ['newPassword', 'string', 'min' => 6, 'on' => 'changePassword'], ['newPasswordRepeat', 'compare', 'compareAttribute' => 'newPassword', 'on' => 'changePassword'], ['newPassword', 'compare', 'compareAttribute' => 'oldPassword', 'operator' => '!=', 'message' => '新密码不能与旧密码相同', 'on' => 'changePassword'], ]; diff --git a/views/admin/info.php b/views/admin/info.php index ffa14ce..955631f 100644 --- a/views/admin/info.php +++ b/views/admin/info.php @@ -1,8 +1,359 @@ -

admin/index

+/* @var $this yii\web\View */ +/* @var $model app\models\User */ +/* @var $focus string */ +/* @var $totp_secret string */ +/* @var $totp_url string */ +/* @var $is_otp_enabled bool */ -

- 这里是管理员页面.建设中 -

+use app\assets\FontAwesomeAsset; +use app\assets\SimpleWebAuthnBrowser; +use app\models\PublicKeyCredentialSourceRepository; +use app\models\User; +use app\utils\IPLocation; +use Endroid\QrCode\Color\Color; +use Endroid\QrCode\Encoding\Encoding; +use Endroid\QrCode\ErrorCorrectionLevel; +use Endroid\QrCode\QrCode; +use Endroid\QrCode\RoundBlockSizeMode; +use Endroid\QrCode\Writer\PngWriter; +use yii\bootstrap5\ActiveForm; +use yii\bootstrap5\Html; +use yii\bootstrap5\Modal; +use yii\data\ActiveDataProvider; +use yii\helpers\Url; +use yii\web\JqueryAsset; +use yii\web\View; +use yii\widgets\Pjax; + +$this->title = '个人设置'; +FontAwesomeAsset::register($this); +JqueryAsset::register($this); +SimpleWebAuthnBrowser::register($this); +$this->registerCssFile('@web/css/user-info.css'); +$details = IPLocation::getDetails($model->last_login_ip); // IP LOCATION + +// QR-CODE +if (!is_null($totp_secret)) { + $writer = new PngWriter(); + $qrCode = QrCode::create($totp_url) + ->setEncoding(new Encoding('UTF-8')) + ->setErrorCorrectionLevel(ErrorCorrectionLevel::Low) + ->setSize(300) + ->setMargin(10) + ->setRoundBlockSizeMode(RoundBlockSizeMode::Margin) + ->setForegroundColor(new Color(0, 0, 0)) + ->setBackgroundColor(new Color(255, 255, 255)); + $result = $writer->write($qrCode); +} + +// totp +$user = new User(); + +// Dark Mode +$darkMode = Yii::$app->user->identity->dark_mode; +?> + +
+ +

title) ?>

+ + +
+
+

+ +

+
+
+ + field($model, 'bio')->textarea(['rows' => 6])->label('简介') ?> +
+ 'btn btn-success']) ?> +
+ +
+
+
+
+

+ +

+
+
+ Url::to(['user/change-password']), + 'method' => 'post' + ]); ?> + field($model, 'oldPassword')->passwordInput()->label('原密码') ?> + field($model, 'newPassword')->passwordInput()->label('新密码') ?> + field($model, 'newPasswordRepeat')->passwordInput()->label('重复新密码') ?> +
+ 'btn btn-success']) ?> +
+ +
+
+
+
+

+ +

+
+
+

二步验证

+
+

使用除您密码之外的第二种方法来增强您账号的安全性。

+
    +
  • +
    + + TOTP (Authenticator app) +
    +
    +
    + > + +
    +
    +
  • +
  • +
    + + 备用码 +
    +
    + 'btn btn-outline-primary btn-sm', 'id' => 'generate-backup-codes']) ?> +
    +
  • +
  • +
    + + Passwordless验证 (Webauthn) (BETA) +
    +
    + + "webauthn_add", 'type' => 'button', 'class' => 'btn btn-primary btn-sm']) ?> + "webauthn_verify", 'type' => 'button', 'class' => 'btn btn-primary btn-sm']) ?> + "webauthn_detail", 'type' => 'button', 'class' => 'btn btn-primary btn-sm']) ?> +
    + + +
  • +
+
+ +

主题

+
+
+ > + +
+
+ > + +
+
+ +

删除账户

+
+

这个操作不支持撤回,请谨慎操作。

+ +
+
+
+
+
+ '

更改用户头像

', + 'id' => 'avatarModal', +]); + +echo Html::tag('div', ' +

要修改头像,请前往Gravatar,使用相同的电子邮箱地址创建/登录账户后对头像进行管理

+
Q: 修改头像后不起作用?
A: 尝试ctrl+F5强制刷新或清除cache后刷新页面
更多帮助', ['class' => 'modal-body']); + +Modal::end(); + +// 修改用户昵称的Modal +Modal::begin([ + 'title' => '

修改用户昵称

', + 'id' => 'changeAccountName', + 'size' => 'modal-sm', +]); + +$form = ActiveForm::begin([ + 'action' => ['user/change-name'], + 'method' => 'post' +]); +echo $form->field($user, 'name')->textInput()->label('新的用户昵称:'); +echo Html::submitButton('确认修改', ['class' => 'btn btn-primary']); +ActiveForm::end(); + +Modal::end(); + +// 删除账户Modal +Modal::begin([ + 'title' => '

确定?

', + 'id' => 'deleteAccountModal', + 'size' => 'modal-sm', +]); + +echo Html::tag('div', '确定要删除这个账户?', ['class' => 'modal-body']); + +echo Html::beginForm(['user/delete'], 'post', ['id' => 'delete-form']); + +echo '
'; +echo Html::checkbox('deleteConfirm', false, ['label' => '确认', 'id' => 'deleteConfirm']); +echo '
'; + +echo '
'; +echo Html::submitButton('继续删除', ['class' => 'btn btn-danger', 'disabled' => true, 'id' => 'deleteButton']); +echo '
'; + +echo Html::endForm(); + +Modal::end(); + +// 二步验证Modal +Modal::begin([ + 'title' => '

需要进一步操作以启用二步验证

', + 'id' => 'totpSetupModal', + 'size' => 'model-xl', +]); +?> +
+
+ QR Code +
+
+

使用兼容TOTP的应用程序扫描左侧二维码以添加二步验证

+

推荐以下二步验证器::

+ +
+ + + +
+ ['user/setup-two-factor'], + 'method' => 'post' + ]); + echo Html::activeHiddenInput($user, 'otp_secret', ['value' => $totp_secret]); + echo $form->field($user, 'totp_input')->textInput()->label('最后一步! 输入TOTP应用程序上显示的密码以启用二步验证'); + echo Html::submitButton('启用二步验证', ['class' => 'btn btn-primary']); + ActiveForm::end(); + } + ?> +
+
+ '

管理已添加的Webauthn设备

', + 'id' => 'credentialModal', + 'size' => 'modal-xl modal-fullscreen-xl-down', +]); + +echo Html::tag('div', '你可以在下方查看和删除已经添加的Webauthn设备', ['class' => 'modal-body']); +$dataProvider = new ActiveDataProvider([ + 'query' => PublicKeyCredentialSourceRepository::find()->where(['user_id' => Yii::$app->user->id]), +]); +// 使用 GridView 小部件显示数据 +Pjax::begin(); +echo Html::tag('div', '', ['id' => 'pjax-container']); +Pjax::end(); +Modal::end(); +$this->registerJsFile('@web/js/user-info.js', ['depends' => [JqueryAsset::class, SimpleWebAuthnBrowser::class], 'position' => View::POS_END]); +?>