mirror of
https://github.com/elyby/accounts.git
synced 2025-03-09 17:59:09 +05:30
Merge branch 'develop'
This commit is contained in:
commit
759e711c78
@ -46,7 +46,7 @@ test:frontend:
|
||||
- frontend/node_modules
|
||||
script:
|
||||
- cd frontend
|
||||
- npm i --silent > /dev/null
|
||||
- npm run build:install --silent
|
||||
- npm run lint --silent
|
||||
# - npm run flow --silent # disabled due to missing libelf.so.1 in docker container
|
||||
- npm run test --silent
|
||||
|
@ -21,7 +21,7 @@ COPY ./composer.json /var/www/composer.json
|
||||
|
||||
# Устанавливаем зависимости PHP
|
||||
RUN cd .. \
|
||||
&& composer install --no-interaction --no-suggest --no-dev --optimize-autoloader \
|
||||
&& composer install --no-interaction --no-suggest --no-dev --classmap-authoritative \
|
||||
&& cd -
|
||||
|
||||
# Устанавливаем зависимости для Node.js
|
||||
@ -34,7 +34,7 @@ COPY ./frontend/scripts /var/www/frontend/scripts
|
||||
COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils
|
||||
|
||||
RUN cd ../frontend \
|
||||
&& npm install --quiet --depth -1 \
|
||||
&& npm run build:install \
|
||||
&& cd -
|
||||
|
||||
# Удаляем ключи из production контейнера на всякий случай
|
||||
|
@ -34,7 +34,7 @@ COPY ./frontend/scripts /var/www/frontend/scripts
|
||||
COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils
|
||||
|
||||
RUN cd ../frontend \
|
||||
&& npm install --quiet --depth -1 \
|
||||
&& npm run build:install \
|
||||
&& cd -
|
||||
|
||||
# Наконец переносим все сорцы внутрь контейнера
|
||||
|
174
api/components/TestData.php
Normal file
174
api/components/TestData.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
namespace api\components;
|
||||
|
||||
use Closure;
|
||||
use Yii;
|
||||
use yii\base\ActionEvent;
|
||||
use yii\helpers\Json;
|
||||
use yii\web\Request;
|
||||
|
||||
class TestData {
|
||||
|
||||
private const MAP = [
|
||||
'signup/index' => 'beforeSignup',
|
||||
'signup/repeat-message' => 'beforeRepeatMessage',
|
||||
'signup/confirm' => 'beforeSignupConfirm',
|
||||
'authentication/forgot-password' => 'beforeForgotPassword',
|
||||
'authentication/recover-password' => 'beforeRecoverPassword',
|
||||
'default/get' => 'beforeAccountGet',
|
||||
'default/email-verification' => 'beforeAccountEmailVerification',
|
||||
'default/new-email-verification' => 'beforeAccountNewEmailVerification',
|
||||
'default/email' => 'beforeAccountChangeEmail',
|
||||
];
|
||||
|
||||
public static function getInstance(): callable {
|
||||
return Closure::fromCallable([new static(), 'beforeAction']);
|
||||
}
|
||||
|
||||
public function beforeAction(ActionEvent $event): void {
|
||||
$id = $event->action->controller->id . '/' . $event->action->id;
|
||||
if (!isset(self::MAP[$id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$handler = self::MAP[$id];
|
||||
$request = Yii::$app->request;
|
||||
$response = Yii::$app->response;
|
||||
$result = $this->$handler($request, $response);
|
||||
if ($result === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response->content = Json::encode($result);
|
||||
|
||||
// Prevent request execution
|
||||
$event->isValid = false;
|
||||
$event->handled = true;
|
||||
}
|
||||
|
||||
public function beforeSignup(Request $request): ?array {
|
||||
$email = $request->post('email');
|
||||
if ($email === 'let-me-register@ely.by') {
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function beforeRepeatMessage(Request $request): ?array {
|
||||
$email = $request->post('email');
|
||||
if ($email === 'let-me-register@ely.by' || $email === 'let-me-repeat@ely.by') {
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function beforeSignupConfirm(Request $request): ?array {
|
||||
$email = $request->post('key');
|
||||
if ($email === 'LETMEIN') {
|
||||
return [
|
||||
'success' => true,
|
||||
'access_token' => 'dummy_token',
|
||||
'expires_in' => time() + 60,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function beforeForgotPassword(Request $request): ?array {
|
||||
$login = $request->post('login');
|
||||
if ($login === 'let-me-recover@ely.by') {
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'canRepeatIn' => time() + 60,
|
||||
'repeatFrequency' => 60,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function beforeRecoverPassword(Request $request): ?array {
|
||||
$key = $request->post('key');
|
||||
if ($key === 'LETMEIN') {
|
||||
return [
|
||||
'success' => true,
|
||||
'access_token' => 'dummy_token',
|
||||
'expires_in' => time() + 60,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function beforeAccountGet(Request $request): ?array {
|
||||
$httpAuth = $request->getHeaders()->get('authorization');
|
||||
if ($httpAuth === 'Bearer dummy_token') {
|
||||
return [
|
||||
'id' => 1,
|
||||
'uuid' => 'f63cd5e1-680f-4c2d-baa2-cc7bb174b71a',
|
||||
'username' => 'dummy',
|
||||
'isOtpEnabled' => false,
|
||||
'registeredAt' => time(),
|
||||
'lang' => 'en',
|
||||
'elyProfileLink' => 'http://ely.by/u1',
|
||||
'email' => 'let-me-register@ely.by',
|
||||
'isActive' => true,
|
||||
'passwordChangedAt' => time(),
|
||||
'hasMojangUsernameCollision' => false,
|
||||
'shouldAcceptRules' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function beforeAccountEmailVerification(Request $request): ?array {
|
||||
$httpAuth = $request->getHeaders()->get('authorization');
|
||||
if ($httpAuth === 'Bearer dummy_token') {
|
||||
$password = $request->post('password');
|
||||
if (empty($password)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => [
|
||||
'password' => 'error.password_required',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function beforeAccountNewEmailVerification(Request $request): ?array {
|
||||
$key = $request->post('key');
|
||||
if ($key === 'LETMEIN') {
|
||||
return [
|
||||
'success' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function beforeAccountChangeEmail(Request $request): ?array {
|
||||
$key = $request->post('key');
|
||||
if ($key === 'LETMEIN') {
|
||||
return [
|
||||
'success' => true,
|
||||
'email' => 'brand-new-email@ely.by',
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ namespace api\components\User;
|
||||
use common\models\Account;
|
||||
use Emarref\Jwt\Claim\Subject;
|
||||
use Emarref\Jwt\Exception\ExpiredException;
|
||||
use Emarref\Jwt\Exception\InvalidSubjectException;
|
||||
use Emarref\Jwt\Token;
|
||||
use Exception;
|
||||
use Yii;
|
||||
@ -28,7 +29,8 @@ class JwtIdentity implements IdentityInterface {
|
||||
$component = Yii::$app->user;
|
||||
try {
|
||||
$token = $component->parseToken($rawToken);
|
||||
} catch (ExpiredException $e) {
|
||||
} catch (ExpiredException | InvalidSubjectException $e) {
|
||||
// InvalidSubjectException is temporary solution and should be removed in the next release
|
||||
throw new UnauthorizedHttpException('Token expired');
|
||||
} catch (Exception $e) {
|
||||
Yii::error($e);
|
||||
|
@ -87,4 +87,5 @@ return [
|
||||
'internal' => api\modules\internal\Module::class,
|
||||
'accounts' => api\modules\accounts\Module::class,
|
||||
],
|
||||
'on beforeAction' => api\components\TestData::getInstance(),
|
||||
];
|
||||
|
@ -7,6 +7,7 @@ use api\components\OAuth2\Grants\AuthCodeGrant;
|
||||
use api\components\OAuth2\Grants\AuthorizeParams;
|
||||
use common\models\Account;
|
||||
use common\models\OauthClient;
|
||||
use common\rbac\Permissions as P;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\Exception\InvalidGrantException;
|
||||
use League\OAuth2\Server\Exception\OAuthException;
|
||||
@ -16,6 +17,11 @@ use yii\helpers\ArrayHelper;
|
||||
|
||||
class OauthProcess {
|
||||
|
||||
private const INTERNAL_PERMISSIONS_TO_PUBLIC_SCOPES = [
|
||||
P::OBTAIN_OWN_ACCOUNT_INFO => 'account_info',
|
||||
P::OBTAIN_ACCOUNT_EMAIL => 'account_email',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var AuthorizationServer
|
||||
*/
|
||||
@ -196,11 +202,21 @@ class OauthProcess {
|
||||
'description' => ArrayHelper::getValue($queryParams, 'description', $client->description),
|
||||
],
|
||||
'session' => [
|
||||
'scopes' => array_keys($scopes),
|
||||
'scopes' => $this->fixScopesNames(array_keys($scopes)),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function fixScopesNames(array $scopes): array {
|
||||
foreach ($scopes as &$scope) {
|
||||
if (isset(self::INTERNAL_PERMISSIONS_TO_PUBLIC_SCOPES[$scope])) {
|
||||
$scope = self::INTERNAL_PERMISSIONS_TO_PUBLIC_SCOPES[$scope];
|
||||
}
|
||||
}
|
||||
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
private function buildErrorResponse(OAuthException $e): array {
|
||||
$response = [
|
||||
'success' => false,
|
||||
|
@ -3,7 +3,6 @@ namespace api\models\authentication;
|
||||
|
||||
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
|
||||
use api\models\base\ApiForm;
|
||||
use api\validators\TotpValidator;
|
||||
use common\emails\EmailHelper;
|
||||
use common\helpers\Error as E;
|
||||
use api\traits\AccountFinder;
|
||||
@ -20,17 +19,11 @@ class ForgotPasswordForm extends ApiForm {
|
||||
|
||||
public $login;
|
||||
|
||||
public $totp;
|
||||
|
||||
public function rules() {
|
||||
return [
|
||||
['captcha', ReCaptchaValidator::class],
|
||||
['login', 'required', 'message' => E::LOGIN_REQUIRED],
|
||||
['login', 'validateLogin'],
|
||||
['totp', 'required', 'when' => function(self $model) {
|
||||
return !$this->hasErrors() && $model->getAccount()->is_otp_enabled;
|
||||
}, 'message' => E::TOTP_REQUIRED],
|
||||
['totp', 'validateTotp'],
|
||||
['login', 'validateActivity'],
|
||||
['login', 'validateFrequency'],
|
||||
];
|
||||
@ -44,21 +37,6 @@ class ForgotPasswordForm extends ApiForm {
|
||||
}
|
||||
}
|
||||
|
||||
public function validateTotp($attribute) {
|
||||
if ($this->hasErrors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$account = $this->getAccount();
|
||||
if (!$account->is_otp_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$validator = new TotpValidator(['account' => $account]);
|
||||
$validator->window = 1;
|
||||
$validator->validateAttribute($this, $attribute);
|
||||
}
|
||||
|
||||
public function validateActivity($attribute) {
|
||||
if (!$this->hasErrors()) {
|
||||
$account = $this->getAccount();
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
return [
|
||||
'version' => '1.1.18',
|
||||
'version' => '1.1.19',
|
||||
'vendorPath' => dirname(__DIR__, 2) . '/vendor',
|
||||
'components' => [
|
||||
'cache' => [
|
||||
|
@ -33,9 +33,8 @@
|
||||
"codeception/codeception": "2.3.6",
|
||||
"codeception/specify": "*",
|
||||
"codeception/verify": "*",
|
||||
"phploc/phploc": "^3.0.1",
|
||||
"mockery/mockery": "dev-master#87fc5f641657833f7c96f14ed3067effe94a0824",
|
||||
"php-mock/php-mock-mockery": "dev-mockery-1.0.0#03956ed4b34ae25bc20a0677500f4f4b416f976c"
|
||||
"mockery/mockery": "^1.0.0",
|
||||
"php-mock/php-mock-mockery": "^1.2.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
@ -49,15 +48,8 @@
|
||||
{
|
||||
"type": "git",
|
||||
"url": "git@gitlab.ely.by:elyby/email-renderer.git"
|
||||
},
|
||||
{
|
||||
"type": "git",
|
||||
"url": "git@github.com:erickskrauch/php-mock-mockery.git"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
"phploc" : "phploc ./api ./common ./console"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"common/consts.php"
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace codeception\api\functional;
|
||||
|
||||
use OTPHP\TOTP;
|
||||
use tests\codeception\api\_pages\AuthenticationRoute;
|
||||
use tests\codeception\api\FunctionalTester;
|
||||
|
||||
@ -34,30 +33,6 @@ class ForgotPasswordCest {
|
||||
'login' => 'error.login_not_exist',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->route->forgotPassword('AccountWithEnabledOtp');
|
||||
$I->canSeeResponseContainsJson([
|
||||
'success' => false,
|
||||
'errors' => [
|
||||
'totp' => 'error.totp_required',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->route->forgotPassword('AccountWithEnabledOtp');
|
||||
$I->canSeeResponseContainsJson([
|
||||
'success' => false,
|
||||
'errors' => [
|
||||
'totp' => 'error.totp_required',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->route->forgotPassword('AccountWithEnabledOtp', '123456');
|
||||
$I->canSeeResponseContainsJson([
|
||||
'success' => false,
|
||||
'errors' => [
|
||||
'totp' => 'error.totp_incorrect',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testForgotPasswordByEmail(FunctionalTester $I) {
|
||||
@ -72,13 +47,6 @@ class ForgotPasswordCest {
|
||||
$this->assertSuccessResponse($I, true);
|
||||
}
|
||||
|
||||
public function testForgotPasswordByAccountWithOtp(FunctionalTester $I) {
|
||||
$I->wantTo('create new password recover request by passing username and otp totp');
|
||||
$totp = TOTP::create('BBBB');
|
||||
$this->route->forgotPassword('AccountWithEnabledOtp', $totp->now());
|
||||
$this->assertSuccessResponse($I, true);
|
||||
}
|
||||
|
||||
public function testDataForFrequencyError(FunctionalTester $I) {
|
||||
$I->wantTo('get info about time to repeat recover password request');
|
||||
$this->route->forgotPassword('Notch');
|
||||
|
@ -24,7 +24,7 @@ class AuthCodeCest {
|
||||
'ely',
|
||||
'http://ely.by',
|
||||
'code',
|
||||
[P::MINECRAFT_SERVER_SESSION],
|
||||
[P::MINECRAFT_SERVER_SESSION, 'account_info', 'account_email'],
|
||||
'test-state'
|
||||
));
|
||||
$I->canSeeResponseCodeIs(200);
|
||||
@ -35,7 +35,7 @@ class AuthCodeCest {
|
||||
'client_id' => 'ely',
|
||||
'redirect_uri' => 'http://ely.by',
|
||||
'response_type' => 'code',
|
||||
'scope' => 'minecraft_server_session',
|
||||
'scope' => 'minecraft_server_session,account_info,account_email',
|
||||
'state' => 'test-state',
|
||||
],
|
||||
'client' => [
|
||||
@ -46,6 +46,8 @@ class AuthCodeCest {
|
||||
'session' => [
|
||||
'scopes' => [
|
||||
'minecraft_server_session',
|
||||
'account_info',
|
||||
'account_email',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
@ -6,7 +6,6 @@ use api\models\authentication\ForgotPasswordForm;
|
||||
use Codeception\Specify;
|
||||
use common\models\EmailActivation;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use OTPHP\TOTP;
|
||||
use tests\codeception\api\unit\TestCase;
|
||||
use tests\codeception\common\fixtures\AccountFixture;
|
||||
use tests\codeception\common\fixtures\EmailActivationFixture;
|
||||
@ -41,21 +40,6 @@ class ForgotPasswordFormTest extends TestCase {
|
||||
$this->assertEmpty($model->getErrors('login'), 'empty errors if login is exists');
|
||||
}
|
||||
|
||||
public function testValidateTotp() {
|
||||
$model = new ForgotPasswordForm();
|
||||
$model->login = 'AccountWithEnabledOtp';
|
||||
$model->totp = '123456';
|
||||
$model->validateTotp('totp');
|
||||
$this->assertEquals(['error.totp_incorrect'], $model->getErrors('totp'));
|
||||
|
||||
$totp = TOTP::create('BBBB');
|
||||
$model = new ForgotPasswordForm();
|
||||
$model->login = 'AccountWithEnabledOtp';
|
||||
$model->totp = $totp->now();
|
||||
$model->validateTotp('totp');
|
||||
$this->assertEmpty($model->getErrors('totp'));
|
||||
}
|
||||
|
||||
public function testValidateActivity() {
|
||||
$model = new ForgotPasswordForm([
|
||||
'login' => $this->tester->grabFixture('accounts', 'not-activated-account')['username'],
|
||||
|
Loading…
x
Reference in New Issue
Block a user