二步验证功能(1/2)
引入totp、qrcode相关依赖 解决一点user上的安全问题
This commit is contained in:
parent
f390d80e4f
commit
febc732e2d
@ -44,7 +44,10 @@
|
|||||||
"google/recaptcha": "^1.3",
|
"google/recaptcha": "^1.3",
|
||||||
"vlucas/phpdotenv": "^5.6",
|
"vlucas/phpdotenv": "^5.6",
|
||||||
"yiisoft/yii2-httpclient": "^2.0",
|
"yiisoft/yii2-httpclient": "^2.0",
|
||||||
"ipinfo/ipinfo": "^3.1"
|
"ipinfo/ipinfo": "^3.1",
|
||||||
|
"spomky-labs/otphp": "^11.2",
|
||||||
|
"ext-gd": "*",
|
||||||
|
"endroid/qr-code": "^5.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"yiisoft/yii2-debug": "~2.1.0",
|
"yiisoft/yii2-debug": "~2.1.0",
|
||||||
|
@ -3,12 +3,13 @@
|
|||||||
namespace app\controllers;
|
namespace app\controllers;
|
||||||
|
|
||||||
use app\models\User;
|
use app\models\User;
|
||||||
use app\models\UserSearch;
|
|
||||||
use app\utils\FileSizeHelper;
|
use app\utils\FileSizeHelper;
|
||||||
|
use OTPHP\TOTP;
|
||||||
use ReCaptcha\ReCaptcha;
|
use ReCaptcha\ReCaptcha;
|
||||||
use Yii;
|
use Yii;
|
||||||
use yii\base\Exception;
|
use yii\base\Exception;
|
||||||
use yii\base\InvalidConfigException;
|
use yii\base\InvalidConfigException;
|
||||||
|
use yii\filters\AccessControl;
|
||||||
use yii\httpclient\Client;
|
use yii\httpclient\Client;
|
||||||
use yii\web\Controller;
|
use yii\web\Controller;
|
||||||
use yii\web\NotFoundHttpException;
|
use yii\web\NotFoundHttpException;
|
||||||
@ -23,16 +24,41 @@ class UserController extends Controller
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function behaviors()
|
public function behaviors(): array
|
||||||
{
|
{
|
||||||
return array_merge(
|
return array_merge(
|
||||||
parent::behaviors(),
|
parent::behaviors(),
|
||||||
[
|
[
|
||||||
|
'access' => [
|
||||||
|
'class' => AccessControl::class,
|
||||||
|
'rules' => [
|
||||||
|
[
|
||||||
|
'allow' => true,
|
||||||
|
'actions' => ['delete', 'info'],
|
||||||
|
'roles' => ['user'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'allow' => true,
|
||||||
|
'actions' => ['login', 'register'],
|
||||||
|
'roles' => ['?', '@'], // everyone can access public share
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'allow' => true,
|
||||||
|
'actions' => ['logout', 'setup-two-factor', 'change-password'],
|
||||||
|
'roles' => ['@'], // everyone can access public share
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
'verbs' => [
|
'verbs' => [
|
||||||
'class' => VerbFilter::className(),
|
'class' => VerbFilter::class,
|
||||||
'actions' => [
|
'actions' => [
|
||||||
|
'login' => ['GET', 'POST'],
|
||||||
|
'logout' => ['GET', 'POST'],
|
||||||
|
'register' => ['GET', 'POST'],
|
||||||
'delete' => ['POST'],
|
'delete' => ['POST'],
|
||||||
|
'info' => ['GET', 'POST'],
|
||||||
'change-password' => ['POST'],
|
'change-password' => ['POST'],
|
||||||
|
'setup-two-factor' => ['POST'],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
@ -40,93 +66,22 @@ class UserController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all User models.
|
* 删除账户(仅自身)
|
||||||
*
|
* @return Response
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function actionIndex()
|
|
||||||
{
|
|
||||||
$searchModel = new UserSearch();
|
|
||||||
$dataProvider = $searchModel->search($this->request->queryParams);
|
|
||||||
|
|
||||||
return $this->render('index', [
|
|
||||||
'searchModel' => $searchModel,
|
|
||||||
'dataProvider' => $dataProvider,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a single User model.
|
|
||||||
* @param int $id ID
|
|
||||||
* @return string
|
|
||||||
* @throws NotFoundHttpException if the model cannot be found
|
|
||||||
*/
|
|
||||||
public function actionView($id)
|
|
||||||
{
|
|
||||||
return $this->render('view', [
|
|
||||||
'model' => $this->findModel($id),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new User model.
|
|
||||||
* If creation is successful, the browser will be redirected to the 'view' page.
|
|
||||||
* @return string|Response
|
|
||||||
*/
|
|
||||||
public function actionCreate()
|
|
||||||
{
|
|
||||||
$model = new User();
|
|
||||||
|
|
||||||
if ($this->request->isPost) {
|
|
||||||
if ($model->load($this->request->post()) && $model->save()) {
|
|
||||||
return $this->redirect(['view', 'id' => $model->id]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$model->loadDefaultValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('create', [
|
|
||||||
'model' => $model,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing User model.
|
|
||||||
* If update is successful, the browser will be redirected to the 'view' page.
|
|
||||||
* @param int $id ID
|
|
||||||
* @return string|Response
|
|
||||||
* @throws NotFoundHttpException if the model cannot be found
|
|
||||||
*/
|
|
||||||
public function actionUpdate($id)
|
|
||||||
{
|
|
||||||
$model = $this->findModel($id);
|
|
||||||
|
|
||||||
if ($this->request->isPost && $model->load($this->request->post()) && $model->save()) {
|
|
||||||
return $this->redirect(['view', 'id' => $model->id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('update', [
|
|
||||||
'model' => $model,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function actionDelete(): Response
|
public function actionDelete(): Response
|
||||||
{
|
{
|
||||||
if (Yii::$app->user->isGuest) {
|
|
||||||
Yii::$app->session->setFlash('error', '滚');
|
|
||||||
return $this->goHome();
|
|
||||||
}
|
|
||||||
|
|
||||||
$model = Yii::$app->user->identity;
|
$model = Yii::$app->user->identity;
|
||||||
|
|
||||||
if ($model->deleteAccount()) {
|
if ($model->deleteAccount()) {
|
||||||
Yii::$app->user->logout();
|
Yii::$app->user->logout();
|
||||||
Yii::$app->session->setFlash('success', 'Account deleted successfully.');
|
Yii::$app->session->setFlash('success', '账户删除成功');
|
||||||
|
return $this->redirect(['user/login']);
|
||||||
} else {
|
} else {
|
||||||
Yii::$app->session->setFlash('error', 'Failed to delete account.');
|
Yii::$app->session->setFlash('error', '账户删除失败');
|
||||||
|
return $this->redirect(['user/info']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->redirect(['user/login']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,7 +91,7 @@ class UserController extends Controller
|
|||||||
* @return User the loaded model
|
* @return User the loaded model
|
||||||
* @throws NotFoundHttpException if the model cannot be found
|
* @throws NotFoundHttpException if the model cannot be found
|
||||||
*/
|
*/
|
||||||
protected function findModel($id)
|
protected function findModel(int $id): User
|
||||||
{
|
{
|
||||||
if (($model = User::findOne(['id' => $id])) !== null) {
|
if (($model = User::findOne(['id' => $id])) !== null) {
|
||||||
return $model;
|
return $model;
|
||||||
@ -150,10 +105,13 @@ class UserController extends Controller
|
|||||||
* visit via https://devs.chenx221.cyou:8081/index.php?r=user%2Flogin
|
* visit via https://devs.chenx221.cyou:8081/index.php?r=user%2Flogin
|
||||||
*
|
*
|
||||||
* @return string|Response
|
* @return string|Response
|
||||||
|
* @throws InvalidConfigException
|
||||||
|
* @throws \yii\httpclient\Exception
|
||||||
*/
|
*/
|
||||||
public function actionLogin(): Response|string
|
public function actionLogin(): Response|string
|
||||||
{
|
{
|
||||||
if (!Yii::$app->user->isGuest) {
|
if (!Yii::$app->user->isGuest) {
|
||||||
|
Yii::$app->session->setFlash('error', '账户已登录,请不要重复登录');
|
||||||
return $this->goHome();
|
return $this->goHome();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,10 +228,9 @@ class UserController extends Controller
|
|||||||
* Logs out the current user.
|
* Logs out the current user.
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function actionLogout()
|
public function actionLogout(): Response
|
||||||
{
|
{
|
||||||
Yii::$app->user->logout();
|
Yii::$app->user->logout();
|
||||||
|
|
||||||
return $this->goHome();
|
return $this->goHome();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +242,11 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function actionRegister(): Response|string
|
public function actionRegister(): Response|string
|
||||||
{
|
{
|
||||||
|
if (!Yii::$app->user->isGuest) {
|
||||||
|
Yii::$app->session->setFlash('error', '账户已登录,无法进行注册操作');
|
||||||
|
return $this->goHome();
|
||||||
|
}
|
||||||
|
|
||||||
$model = new User(['scenario' => 'register']);
|
$model = new User(['scenario' => 'register']);
|
||||||
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
|
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
|
||||||
// 根据 verifyProvider 的值选择使用哪种验证码服务
|
// 根据 verifyProvider 的值选择使用哪种验证码服务
|
||||||
@ -314,7 +276,6 @@ class UserController extends Controller
|
|||||||
if (!is_dir($userFolder)) {
|
if (!is_dir($userFolder)) {
|
||||||
mkdir($userFolder);
|
mkdir($userFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
Yii::$app->session->setFlash('success', 'Registration successful. You can now log in.');
|
Yii::$app->session->setFlash('success', 'Registration successful. You can now log in.');
|
||||||
return $this->redirect(['login']);
|
return $this->redirect(['login']);
|
||||||
} else {
|
} else {
|
||||||
@ -336,15 +297,18 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function actionInfo(string $focus = null): Response|string
|
public function actionInfo(string $focus = null): Response|string
|
||||||
{
|
{
|
||||||
if (Yii::$app->user->isGuest) {
|
|
||||||
Yii::$app->session->setFlash('error', '请先登录');
|
|
||||||
return $this->redirect(['user/login']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$model = Yii::$app->user->identity;
|
$model = Yii::$app->user->identity;
|
||||||
$usedSpace = FileSizeHelper::getDirectorySize(Yii::getAlias(Yii::$app->params['dataDirectory']) . '/' . Yii::$app->user->id);
|
$usedSpace = FileSizeHelper::getDirectorySize(Yii::getAlias(Yii::$app->params['dataDirectory']) . '/' . Yii::$app->user->id);
|
||||||
$vaultUsedSpace = 0; // 保险箱已用空间,暂时为0
|
$vaultUsedSpace = 0; // 保险箱已用空间,暂时为0
|
||||||
$storageLimit = $model->storage_limit;
|
$storageLimit = $model->storage_limit;
|
||||||
|
$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 (Yii::$app->request->isPost && $model->load(Yii::$app->request->post())) {
|
||||||
if ($model->save()) {
|
if ($model->save()) {
|
||||||
Yii::$app->session->setFlash('success', '个人简介已更新');
|
Yii::$app->session->setFlash('success', '个人简介已更新');
|
||||||
@ -354,6 +318,8 @@ class UserController extends Controller
|
|||||||
'vaultUsedSpace' => $vaultUsedSpace, // B
|
'vaultUsedSpace' => $vaultUsedSpace, // B
|
||||||
'storageLimit' => $storageLimit, // MB
|
'storageLimit' => $storageLimit, // MB
|
||||||
'focus' => 'bio',
|
'focus' => 'bio',
|
||||||
|
'totp_secret' => $totp_secret,
|
||||||
|
'totp_url' => $totp_url,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,6 +329,8 @@ class UserController extends Controller
|
|||||||
'vaultUsedSpace' => $vaultUsedSpace, // B
|
'vaultUsedSpace' => $vaultUsedSpace, // B
|
||||||
'storageLimit' => $storageLimit, // MB
|
'storageLimit' => $storageLimit, // MB
|
||||||
'focus' => $focus,
|
'focus' => $focus,
|
||||||
|
'totp_secret' => $totp_secret,
|
||||||
|
'totp_url' => $totp_url,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,10 +340,6 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function actionChangePassword(): Response|string
|
public function actionChangePassword(): Response|string
|
||||||
{
|
{
|
||||||
if (Yii::$app->user->isGuest) {
|
|
||||||
return $this->goHome();
|
|
||||||
}
|
|
||||||
|
|
||||||
$model = Yii::$app->user->identity;
|
$model = Yii::$app->user->identity;
|
||||||
$model->scenario = 'changePassword';
|
$model->scenario = 'changePassword';
|
||||||
|
|
||||||
@ -390,5 +354,21 @@ class UserController extends Controller
|
|||||||
return $this->redirect(['user/info', 'focus' => 'password']);
|
return $this->redirect(['user/info', 'focus' => 'password']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function actionSetupTwoFactor(): string
|
||||||
|
{
|
||||||
|
$user = Yii::$app->user->identity;
|
||||||
|
$totp = TOTP::create();
|
||||||
|
$user->otp_secret = $totp->getSecret();
|
||||||
|
$user->is_otp_enabled = true;
|
||||||
|
$user->save(false);
|
||||||
|
|
||||||
|
$otpauth = $totp->getProvisioningUri($user->username);
|
||||||
|
$qrCodeUrl = 'https://api.qrserver.com/v1/create-qr-code/?data=' . urlencode($otpauth);
|
||||||
|
|
||||||
|
return $this->render('setup-two-factor', ['qrCodeUrl' => $qrCodeUrl]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,21 @@
|
|||||||
/* @var $model app\models\User */
|
/* @var $model app\models\User */
|
||||||
/* @var $usedSpace int */
|
/* @var $usedSpace int */
|
||||||
/* @var $vaultUsedSpace int */
|
/* @var $vaultUsedSpace int */
|
||||||
|
|
||||||
/* @var $storageLimit int */
|
/* @var $storageLimit int */
|
||||||
|
|
||||||
/* @var $focus string */
|
/* @var $focus string */
|
||||||
|
/* @var $totp_secret string */
|
||||||
|
|
||||||
|
/* @var $totp_url string */
|
||||||
|
|
||||||
use app\assets\FontAwesomeAsset;
|
use app\assets\FontAwesomeAsset;
|
||||||
use app\utils\FileSizeHelper;
|
use app\utils\FileSizeHelper;
|
||||||
use app\utils\IPLocation;
|
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\ActiveForm;
|
||||||
use yii\bootstrap5\Html;
|
use yii\bootstrap5\Html;
|
||||||
use yii\bootstrap5\Modal;
|
use yii\bootstrap5\Modal;
|
||||||
@ -35,6 +42,20 @@ $is_unlimited = ($storageLimit === -1); //检查是否为无限制容量
|
|||||||
$usedPercent = $is_unlimited ? 0 : round($usedSpace / ($storageLimit * 1024 * 1024) * 100); //网盘已用百分比
|
$usedPercent = $is_unlimited ? 0 : round($usedSpace / ($storageLimit * 1024 * 1024) * 100); //网盘已用百分比
|
||||||
$vaultUsedPercent = $is_unlimited ? 0 : round($vaultUsedSpace / ($storageLimit * 1024 * 1024) * 100); //保险箱已用百分比
|
$vaultUsedPercent = $is_unlimited ? 0 : round($vaultUsedSpace / ($storageLimit * 1024 * 1024) * 100); //保险箱已用百分比
|
||||||
$totalUsedPercent = min(($usedPercent + $vaultUsedPercent), 100); //总已用百分比
|
$totalUsedPercent = min(($usedPercent + $vaultUsedPercent), 100); //总已用百分比
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
@ -217,6 +238,8 @@ $totalUsedPercent = min(($usedPercent + $vaultUsedPercent), 100); //总已用百
|
|||||||
<input class="form-check-input" type="checkbox" role="switch" id="totp-enabled">
|
<input class="form-check-input" type="checkbox" role="switch" id="totp-enabled">
|
||||||
<label class="form-check-label" for="totp-enabled">启用 TOTP</label>
|
<label class="form-check-label" for="totp-enabled">启用 TOTP</label>
|
||||||
</div>
|
</div>
|
||||||
|
<!--暂时放在这里-->
|
||||||
|
<img src="<?= is_null($totp_secret)?'':$result->getDataUri() ?>" alt="qrcode"/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
@ -275,11 +298,11 @@ echo Html::tag('div', '确定要删除这个账户?', ['class' => 'modal-body'
|
|||||||
echo Html::beginForm(['user/delete'], 'post', ['id' => 'delete-form']);
|
echo Html::beginForm(['user/delete'], 'post', ['id' => 'delete-form']);
|
||||||
|
|
||||||
echo '<div>';
|
echo '<div>';
|
||||||
echo Html::checkbox('deleteConfirm', false, ['label' => '确认','id'=>'deleteConfirm']);
|
echo Html::checkbox('deleteConfirm', false, ['label' => '确认', 'id' => 'deleteConfirm']);
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="text-end">';
|
echo '<div class="text-end">';
|
||||||
echo Html::submitButton('继续删除', ['class' => 'btn btn-danger', 'disabled' => true,'id' => 'deleteButton']);
|
echo Html::submitButton('继续删除', ['class' => 'btn btn-danger', 'disabled' => true, 'id' => 'deleteButton']);
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo Html::endForm();
|
echo Html::endForm();
|
||||||
|
Loading…
Reference in New Issue
Block a user