mirror of
https://github.com/elyby/accounts.git
synced 2024-12-02 03:31:05 +05:30
Implemented Rs256 jwt encryption algorithm
This commit is contained in:
parent
3dbf29d34c
commit
3f9ee42539
@ -7,6 +7,8 @@ EMAILS_RENDERER_HOST=http://emails-renderer:3000
|
|||||||
|
|
||||||
## Security params
|
## Security params
|
||||||
JWT_USER_SECRET=
|
JWT_USER_SECRET=
|
||||||
|
JWT_PUBLIC_KEY=
|
||||||
|
JWT_PRIVATE_KEY=
|
||||||
|
|
||||||
## External services
|
## External services
|
||||||
RECAPTCHA_PUBLIC=
|
RECAPTCHA_PUBLIC=
|
||||||
|
@ -9,12 +9,15 @@ use DateInterval;
|
|||||||
use DateTime;
|
use DateTime;
|
||||||
use Emarref\Jwt\Algorithm\AlgorithmInterface;
|
use Emarref\Jwt\Algorithm\AlgorithmInterface;
|
||||||
use Emarref\Jwt\Algorithm\Hs256;
|
use Emarref\Jwt\Algorithm\Hs256;
|
||||||
|
use Emarref\Jwt\Algorithm\Rs256;
|
||||||
use Emarref\Jwt\Claim;
|
use Emarref\Jwt\Claim;
|
||||||
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
|
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
|
||||||
use Emarref\Jwt\Exception\VerificationException;
|
use Emarref\Jwt\Exception\VerificationException;
|
||||||
|
use Emarref\Jwt\HeaderParameter\Custom;
|
||||||
use Emarref\Jwt\Token;
|
use Emarref\Jwt\Token;
|
||||||
use Emarref\Jwt\Verification\Context as VerificationContext;
|
use Emarref\Jwt\Verification\Context as VerificationContext;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Webmozart\Assert\Assert;
|
||||||
use Yii;
|
use Yii;
|
||||||
use yii\base\InvalidConfigException;
|
use yii\base\InvalidConfigException;
|
||||||
use yii\web\UnauthorizedHttpException;
|
use yii\web\UnauthorizedHttpException;
|
||||||
@ -43,6 +46,10 @@ class Component extends YiiUserComponent {
|
|||||||
|
|
||||||
public $secret;
|
public $secret;
|
||||||
|
|
||||||
|
public $publicKey;
|
||||||
|
|
||||||
|
public $privateKey;
|
||||||
|
|
||||||
public $expirationTimeout = 'PT1H';
|
public $expirationTimeout = 'PT1H';
|
||||||
|
|
||||||
public $sessionTimeout = 'P7D';
|
public $sessionTimeout = 'P7D';
|
||||||
@ -54,8 +61,16 @@ class Component extends YiiUserComponent {
|
|||||||
|
|
||||||
public function init() {
|
public function init() {
|
||||||
parent::init();
|
parent::init();
|
||||||
if (!$this->secret) {
|
Assert::notEmpty($this->secret, 'secret must be specified');
|
||||||
throw new InvalidConfigException('secret must be specified');
|
Assert::notEmpty($this->publicKey, 'public key must be specified');
|
||||||
|
Assert::notEmpty($this->privateKey, 'private key must be specified');
|
||||||
|
|
||||||
|
if (!($this->publicKey = file_get_contents($this->publicKey))) {
|
||||||
|
throw new InvalidConfigException('invalid public key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($this->privateKey = file_get_contents($this->privateKey))) {
|
||||||
|
throw new InvalidConfigException('invalid private key');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +153,18 @@ class Component extends YiiUserComponent {
|
|||||||
throw new VerificationException('Incorrect token encoding', 0, $e);
|
throw new VerificationException('Incorrect token encoding', 0, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
$context = new VerificationContext(EncryptionFactory::create($this->getAlgorithm()));
|
$algorithm = $this->getAlgorithm();
|
||||||
|
$version = $notVerifiedToken->getHeader()->findParameterByName('v');
|
||||||
|
if ($version === null) {
|
||||||
|
$algorithm = new Hs256($this->secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
$encryption = EncryptionFactory::create($algorithm);
|
||||||
|
if ($version !== null) {
|
||||||
|
$encryption->setPublicKey($this->publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = new VerificationContext($encryption);
|
||||||
$context->setSubject(self::JWT_SUBJECT_PREFIX);
|
$context->setSubject(self::JWT_SUBJECT_PREFIX);
|
||||||
$jwt->verify($notVerifiedToken, $context);
|
$jwt->verify($notVerifiedToken, $context);
|
||||||
|
|
||||||
@ -205,15 +231,18 @@ class Component extends YiiUserComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getAlgorithm(): AlgorithmInterface {
|
public function getAlgorithm(): AlgorithmInterface {
|
||||||
return new Hs256($this->secret);
|
return new Rs256();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serializeToken(Token $token): string {
|
protected function serializeToken(Token $token): string {
|
||||||
return (new Jwt())->serialize($token, EncryptionFactory::create($this->getAlgorithm()));
|
$encryption = EncryptionFactory::create($this->getAlgorithm())->setPrivateKey($this->privateKey);
|
||||||
|
|
||||||
|
return (new Jwt())->serialize($token, $encryption);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createToken(Account $account): Token {
|
protected function createToken(Account $account): Token {
|
||||||
$token = new Token();
|
$token = new Token();
|
||||||
|
$token->addHeader(new Custom('v', 1));
|
||||||
foreach ($this->getClaims($account) as $claim) {
|
foreach ($this->getClaims($account) as $claim) {
|
||||||
$token->addClaim($claim);
|
$token->addClaim($claim);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ return [
|
|||||||
'user' => [
|
'user' => [
|
||||||
'class' => api\components\User\Component::class,
|
'class' => api\components\User\Component::class,
|
||||||
'secret' => getenv('JWT_USER_SECRET'),
|
'secret' => getenv('JWT_USER_SECRET'),
|
||||||
|
'publicKey' => getenv('JWT_PUBLIC_KEY') ?: '/data/certs/public.crt',
|
||||||
|
'privateKey' => getenv('JWT_PRIVATE_KEY') ?: '/data/certs/private.key',
|
||||||
],
|
],
|
||||||
'log' => [
|
'log' => [
|
||||||
'traceLevel' => YII_DEBUG ? 3 : 0,
|
'traceLevel' => YII_DEBUG ? 3 : 0,
|
||||||
|
@ -189,6 +189,8 @@ class ComponentTest extends TestCase {
|
|||||||
'enableSession' => false,
|
'enableSession' => false,
|
||||||
'loginUrl' => null,
|
'loginUrl' => null,
|
||||||
'secret' => 'secret',
|
'secret' => 'secret',
|
||||||
|
'publicKey' => 'data/certs/public.crt',
|
||||||
|
'privateKey' => 'data/certs/private.key',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ use common\tests\_support\ProtectedCaller;
|
|||||||
use common\tests\fixtures\AccountFixture;
|
use common\tests\fixtures\AccountFixture;
|
||||||
use Emarref\Jwt\Claim;
|
use Emarref\Jwt\Claim;
|
||||||
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
|
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
|
||||||
|
use Emarref\Jwt\HeaderParameter\Custom;
|
||||||
use Emarref\Jwt\Token;
|
use Emarref\Jwt\Token;
|
||||||
use Yii;
|
use Yii;
|
||||||
|
|
||||||
@ -33,10 +34,11 @@ class JwtIdentityTest extends TestCase {
|
|||||||
*/
|
*/
|
||||||
public function testFindIdentityByAccessTokenWithExpiredToken() {
|
public function testFindIdentityByAccessTokenWithExpiredToken() {
|
||||||
$token = new Token();
|
$token = new Token();
|
||||||
|
$token->addHeader(new Custom('v', 1));
|
||||||
$token->addClaim(new Claim\IssuedAt(1464593193));
|
$token->addClaim(new Claim\IssuedAt(1464593193));
|
||||||
$token->addClaim(new Claim\Expiration(1464596793));
|
$token->addClaim(new Claim\Expiration(1464596793));
|
||||||
$token->addClaim(new Claim\Subject('ely|' . $this->tester->grabFixture('accounts', 'admin')['id']));
|
$token->addClaim(new Claim\Subject('ely|' . $this->tester->grabFixture('accounts', 'admin')['id']));
|
||||||
$expiredToken = (new Jwt())->serialize($token, EncryptionFactory::create(Yii::$app->user->getAlgorithm()));
|
$expiredToken = (new Jwt())->serialize($token, EncryptionFactory::create(Yii::$app->user->getAlgorithm())->setPrivateKey(Yii::$app->user->privateKey));
|
||||||
|
|
||||||
JwtIdentity::findIdentityByAccessToken($expiredToken);
|
JwtIdentity::findIdentityByAccessToken($expiredToken);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,8 @@ class LogoutFormTest extends TestCase {
|
|||||||
'enableSession' => false,
|
'enableSession' => false,
|
||||||
'loginUrl' => null,
|
'loginUrl' => null,
|
||||||
'secret' => 'secret',
|
'secret' => 'secret',
|
||||||
|
'publicKey' => 'data/certs/public.crt',
|
||||||
|
'privateKey' => 'data/certs/private.key',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,8 @@ class ChangePasswordFormTest extends TestCase {
|
|||||||
'enableSession' => false,
|
'enableSession' => false,
|
||||||
'loginUrl' => null,
|
'loginUrl' => null,
|
||||||
'secret' => 'secret',
|
'secret' => 'secret',
|
||||||
|
'publicKey' => 'data/certs/public.crt',
|
||||||
|
'privateKey' => 'data/certs/private.key',
|
||||||
]]);
|
]]);
|
||||||
$component->shouldNotReceive('terminateSessions');
|
$component->shouldNotReceive('terminateSessions');
|
||||||
|
|
||||||
@ -121,6 +123,8 @@ class ChangePasswordFormTest extends TestCase {
|
|||||||
'enableSession' => false,
|
'enableSession' => false,
|
||||||
'loginUrl' => null,
|
'loginUrl' => null,
|
||||||
'secret' => 'secret',
|
'secret' => 'secret',
|
||||||
|
'publicKey' => 'data/certs/public.crt',
|
||||||
|
'privateKey' => 'data/certs/private.key',
|
||||||
]]);
|
]]);
|
||||||
$component->shouldReceive('terminateSessions')->once()->withArgs([$account, Component::KEEP_CURRENT_SESSION]);
|
$component->shouldReceive('terminateSessions')->once()->withArgs([$account, Component::KEEP_CURRENT_SESSION]);
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ class EnableTwoFactorAuthFormTest extends TestCase {
|
|||||||
'enableSession' => false,
|
'enableSession' => false,
|
||||||
'loginUrl' => null,
|
'loginUrl' => null,
|
||||||
'secret' => 'secret',
|
'secret' => 'secret',
|
||||||
|
'publicKey' => 'data/certs/public.crt',
|
||||||
|
'privateKey' => 'data/certs/private.key',
|
||||||
]]);
|
]]);
|
||||||
$component->shouldReceive('terminateSessions')->withArgs([$account, Component::KEEP_CURRENT_SESSION]);
|
$component->shouldReceive('terminateSessions')->withArgs([$account, Component::KEEP_CURRENT_SESSION]);
|
||||||
|
|
||||||
|
@ -13,7 +13,11 @@ use const common\LATEST_RULES_VERSION;
|
|||||||
class AccountOwnerTest extends TestCase {
|
class AccountOwnerTest extends TestCase {
|
||||||
|
|
||||||
public function testIdentityIsNull() {
|
public function testIdentityIsNull() {
|
||||||
$component = mock(Component::class . '[findIdentityByAccessToken]', [['secret' => 'secret']]);
|
$component = mock(Component::class . '[findIdentityByAccessToken]', [[
|
||||||
|
'secret' => 'secret',
|
||||||
|
'publicKey' => 'data/certs/public.crt',
|
||||||
|
'privateKey' => 'data/certs/private.key',
|
||||||
|
]]);
|
||||||
$component->shouldDeferMissing();
|
$component->shouldDeferMissing();
|
||||||
$component->shouldReceive('findIdentityByAccessToken')->andReturn(null);
|
$component->shouldReceive('findIdentityByAccessToken')->andReturn(null);
|
||||||
|
|
||||||
@ -34,7 +38,11 @@ class AccountOwnerTest extends TestCase {
|
|||||||
$identity = mock(IdentityInterface::class);
|
$identity = mock(IdentityInterface::class);
|
||||||
$identity->shouldReceive('getAccount')->andReturn($account);
|
$identity->shouldReceive('getAccount')->andReturn($account);
|
||||||
|
|
||||||
$component = mock(Component::class . '[findIdentityByAccessToken]', [['secret' => 'secret']]);
|
$component = mock(Component::class . '[findIdentityByAccessToken]', [[
|
||||||
|
'secret' => 'secret',
|
||||||
|
'publicKey' => 'data/certs/public.crt',
|
||||||
|
'privateKey' => 'data/certs/private.key',
|
||||||
|
]]);
|
||||||
$component->shouldDeferMissing();
|
$component->shouldDeferMissing();
|
||||||
$component->shouldReceive('findIdentityByAccessToken')->withArgs(['token'])->andReturn($identity);
|
$component->shouldReceive('findIdentityByAccessToken')->withArgs(['token'])->andReturn($identity);
|
||||||
|
|
||||||
|
@ -34,7 +34,11 @@ class OauthClientOwnerTest extends TestCase {
|
|||||||
$identity->shouldReceive('getAccount')->andReturn($account);
|
$identity->shouldReceive('getAccount')->andReturn($account);
|
||||||
|
|
||||||
/** @var Component|\Mockery\MockInterface $component */
|
/** @var Component|\Mockery\MockInterface $component */
|
||||||
$component = mock(Component::class . '[findIdentityByAccessToken]', [['secret' => 'secret']]);
|
$component = mock(Component::class . '[findIdentityByAccessToken]', [[
|
||||||
|
'secret' => 'secret',
|
||||||
|
'publicKey' => 'data/certs/public.crt',
|
||||||
|
'privateKey' => 'data/certs/private.key',
|
||||||
|
]]);
|
||||||
$component->shouldDeferMissing();
|
$component->shouldDeferMissing();
|
||||||
$component->shouldReceive('findIdentityByAccessToken')->withArgs(['token'])->andReturn($identity);
|
$component->shouldReceive('findIdentityByAccessToken')->withArgs(['token'])->andReturn($identity);
|
||||||
|
|
||||||
|
1
data/.gitignore
vendored
1
data/.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
*
|
*
|
||||||
|
!certs/*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
28
data/certs/private.key
Normal file
28
data/certs/private.key
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDbTqmRpLg3XjDH
|
||||||
|
3Z97uHdNq4F5j77Rp+M7ctyfUhtb+U7VWppjk2Dxyp2/iPzKK3K0lC91zlnxO4HT
|
||||||
|
jFCWTIQzSfiFx/Z6nbUXYFZunzRkbt6UgXjUhnYLSIVvNDneph/BZTSxNThky7a8
|
||||||
|
weng1+1e7cYcYx7pJWUXB9XINEKdyZ/pF+kB8UPK/LCLY4jFTm7t+N1Rm1R6VpEy
|
||||||
|
VqhwoDTefkiP9H/QZBp4Ihy48v/NTtgHdsc3Yz+//M6km39MmxIh4wBrZiIictzg
|
||||||
|
5Xmd1vXamDYFbGZHpKRuujCSufZaglrjGvgaAq1lSS+Cwc5eNCDTlw8OWGJyeSMy
|
||||||
|
AvYKK5pnAgMBAAECggEBAKcg02kCtsC7L0GhS6Dle0XdpdYWDb2IzErJxghEckUt
|
||||||
|
QT6mxXGNJxwc5QrKQptvcQLcyy5kC3cjelTVYbSoqzbK8HJDaTsYZKFj8XpsKWlA
|
||||||
|
dK+H26Vasyr2IXoVuuRKhXjEv9ssS8XE2YYP4URQSb1GRuvrPes/bEKY3fqsmPfU
|
||||||
|
/rpaUNG9OvskfIDzT+VoEe5RfPW0+uchHZHypWdnhSxLC/oH8KjcUxmCdQ3q46fT
|
||||||
|
2GhDJnDLXC8MGbyUp7Nw+eSg+4UTCjaNqV7c4vOSXqSBPch7nYFf1YqYuseok21t
|
||||||
|
UK1G55JrBfsUAmldSi1UVdnAanVRNZiC2LsdDe9PpUECgYEA7kVk7nFqtHqx6EOz
|
||||||
|
4p6AeDlslrPEWz996AgV1qezBboGlpPkDv+of5cOG4ZMpDJD5KbSIJXTPC06G+3V
|
||||||
|
VgYpg7cYO9il3I5vaxo64dC9Ib5HQe8UTreVI5763S7Zq7V0jWKOzrlKzA/KQl3x
|
||||||
|
1kHXS5levDp1uuwAdRBn6DvXnv0CgYEA66ALVI1BUU+OhqSGRQu9pZATfyB5hJaD
|
||||||
|
1iICiOgl1LRwMJW7/uWUTQ+h5H3lYDmyf+y9/8x8jTfEVZYEwV2bw9wzII87YA9R
|
||||||
|
pKQl+HMlynrgYWZ2Z94mRFs3poxU8AgpU9MDN84b2cHyP3TGhQjkdtdyFE4lcCiQ
|
||||||
|
yQqnWa+BBjMCgYEArKeKQKHcoVT7D4PnmIIkM3ng7r7qvPggAv/A219/gNnQplIa
|
||||||
|
AqhM78+EgHtrk9t8iPY88zG99DANmGlZmlEyyefl3o/ZeB2aLPC/1BvOwOHBfsyA
|
||||||
|
WZ37qukrfRTS0/LTtxPAyZlI0t9qP3cVo5zoJjbHh/uQjdcvaaRutsCOOP0CgYEA
|
||||||
|
10TB9T6UdVgM6+A2N7CxVCicV2HxA3yL+D/cNv55SaqMcSbrucY/xmPI0btfq5kr
|
||||||
|
BorhT2mgRVi0zEiiEZOXMsrj/xQ899cnDRdXBXUWCrZWd0YoWV7xcTQxVL0TALVE
|
||||||
|
JKw9XWe1tC3oR6dFk9d6+0R8miaHN8An/zT3jg21AFcCgYEAslWiTkT1ULAAhlHa
|
||||||
|
KLbSW1slYJR8/i9mwIDOoD2BvVJUSqbowAogD4mXRm6S77AxoQX4nygzE6XscR4V
|
||||||
|
h+fINRJeh7yrFk5x/GUjh7tQo9EITjY89X0s35hZ27i61l66eZ5u06j4xE5+Y424
|
||||||
|
HMsBjKAmKFNPebTWFcAlXXaeCPU=
|
||||||
|
-----END PRIVATE KEY-----
|
16
data/certs/public.crt
Normal file
16
data/certs/public.crt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICljCCAX4CCQDA6sdDyK1Y/zANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJC
|
||||||
|
WTAeFw0xOTA3MjQxMDI5NTdaFw0yMTA3MjMxMDI5NTdaMA0xCzAJBgNVBAYTAkJZ
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA206pkaS4N14wx92fe7h3
|
||||||
|
TauBeY++0afjO3Lcn1IbW/lO1VqaY5Ng8cqdv4j8yitytJQvdc5Z8TuB04xQlkyE
|
||||||
|
M0n4hcf2ep21F2BWbp80ZG7elIF41IZ2C0iFbzQ53qYfwWU0sTU4ZMu2vMHp4Nft
|
||||||
|
Xu3GHGMe6SVlFwfVyDRCncmf6RfpAfFDyvywi2OIxU5u7fjdUZtUelaRMlaocKA0
|
||||||
|
3n5Ij/R/0GQaeCIcuPL/zU7YB3bHN2M/v/zOpJt/TJsSIeMAa2YiInLc4OV5ndb1
|
||||||
|
2pg2BWxmR6Skbrowkrn2WoJa4xr4GgKtZUkvgsHOXjQg05cPDlhicnkjMgL2Ciua
|
||||||
|
ZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB+i6Q3Ltg5MPEqHZ3GCpsFMV+xWKp5
|
||||||
|
TSgguFr422az9v/Da01VHOX884D0dZt1r6W+zzfIQzIXpRqQkl4YuS1N17Q/KN3E
|
||||||
|
7rJ0R7gsXM7+KiGVrZyoZlxRaRXCiErUWBOxamIPy07zOWLnWa1kZZNDvgiurMbF
|
||||||
|
yaREQargFM8G91zkA6XiMXFoermARYB6RLtyHD0EC3I2DSZpOuMD9Kg1k/uw6f3W
|
||||||
|
xwsQY6kpzoZkYfTqoM4ky16yNPRf9vsej2dYlRr1YPWWQOicY1TrwFJMKoogylTD
|
||||||
|
lN61u8WED7Z8M00F6FYuuFffzt2Si9GrYeTuf8ZShpKiDqK0P22oiAao
|
||||||
|
-----END CERTIFICATE-----
|
Loading…
Reference in New Issue
Block a user