Skip to content

Commit 000590e

Browse files
committed
otp grant
1 parent bb2db67 commit 000590e

File tree

6 files changed

+187
-3
lines changed

6 files changed

+187
-3
lines changed

src/AuthorizationServerFactory.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace OAuthServer;
44

5+
use OAuthServer\Grant\OtpGrant;
56
use Hyperf\Contract\ConfigInterface;
67
use Psr\Container\ContainerInterface;
8+
use OAuthServer\OneTimePasswordInterface;
79
use OAuthServer\Repositories\UserRepository;
810
use League\OAuth2\Server\AuthorizationServer;
911
use League\OAuth2\Server\Grant\AuthCodeGrant;
@@ -54,6 +56,11 @@ public function __invoke()
5456
$this->makePasswordGrant(),
5557
$tokenExpiresIn
5658
);
59+
60+
$server->enableGrantType(
61+
$this->makeOtpGrant(),
62+
$tokenExpiresIn
63+
);
5764

5865
return $server;
5966
});
@@ -102,4 +109,15 @@ public function makePasswordGrant()
102109
$grant->setRefreshTokenTTL(new \DateInterval('P1M'));
103110
});
104111
}
112+
113+
public function makeOtpGrant()
114+
{
115+
$userRepository = make(UserRepository::class);
116+
$refreshTokenRepository = make(RefreshTokenRepository::class);
117+
$oneTimePassword = make(OneTimePasswordInterface::class);
118+
119+
return tap(new OtpGrant($userRepository, $refreshTokenRepository, $oneTimePassword), function ($grant) {
120+
$grant->setRefreshTokenTTL(new \DateInterval('P1M'));
121+
});
122+
}
105123
}

src/Command/OAuthKeyCommand.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,21 @@ public function handle()
4141
{
4242
$force = $this->input->getOption('force');
4343
$length = $this->input->getOption('length');
44+
$path = BASE_PATH . DIRECTORY_SEPARATOR . 'var';
4445

4546
[$publicKey, $privateKey] = [
46-
BASE_PATH . '/var/oauth-public.key',
47-
BASE_PATH . '/var/oauth-private.key',
47+
$path . DIRECTORY_SEPARATOR . 'oauth-public.key',
48+
$path . DIRECTORY_SEPARATOR . 'oauth-private.key',
4849
];
4950

5051
if ((file_exists($publicKey) || file_exists($privateKey)) && ! $force) {
5152
$this->error('Encryption keys already exist. Use the --force option to overwrite them.');
5253
} else {
54+
55+
if (!is_dir($path)) {
56+
mkdir($path, 0755, true);
57+
}
58+
5359
$private = RSA::createKey($this->input ? (int) $length : 4096);
5460
$public = $private->getPublicKey();
5561

src/ConfigProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public function __invoke(): array
5252
[
5353
'id' => 'migration',
5454
'description' => 'The migration for oauth.',
55-
'source' => __DIR__ . '/../migrations/*',
55+
'source' => __DIR__ . '/../migrations',
5656
'destination' => BASE_PATH . '/migrations',
5757
],
5858
],

src/Grant/OtpGrant.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
namespace OAuthServer\Grant;
4+
5+
use DateInterval;
6+
use League\OAuth2\Server\RequestEvent;
7+
use OAuthServer\OneTimePasswordInterface;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use League\OAuth2\Server\Grant\AbstractGrant;
10+
use League\OAuth2\Server\Entities\UserEntityInterface;
11+
use League\OAuth2\Server\Entities\ClientEntityInterface;
12+
use League\OAuth2\Server\Exception\OAuthServerException;
13+
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
14+
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
15+
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
16+
17+
/**
18+
* OTP grant class.
19+
*/
20+
class OtpGrant extends AbstractGrant
21+
{
22+
protected $otp;
23+
24+
/**
25+
* @param UserRepositoryInterface $userRepository
26+
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
27+
* @param OneTimePasswordInterface $otp
28+
*/
29+
public function __construct(
30+
UserRepositoryInterface $userRepository,
31+
RefreshTokenRepositoryInterface $refreshTokenRepository,
32+
OneTimePasswordInterface $otp
33+
) {
34+
$this->setUserRepository($userRepository);
35+
$this->setRefreshTokenRepository($refreshTokenRepository);
36+
$this->otp = $otp;
37+
38+
$this->refreshTokenTTL = new DateInterval('P1M');
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function respondToAccessTokenRequest(
45+
ServerRequestInterface $request,
46+
ResponseTypeInterface $responseType,
47+
DateInterval $accessTokenTTL
48+
) {
49+
// Validate request
50+
$client = $this->validateClient($request);
51+
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
52+
$user = $this->validateUser($request, $client, $this->otp);
53+
54+
// Finalize the requested scopes
55+
$finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
56+
57+
// Issue and persist new access token
58+
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
59+
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
60+
$responseType->setAccessToken($accessToken);
61+
62+
// Issue and persist new refresh token if given
63+
$refreshToken = $this->issueRefreshToken($accessToken);
64+
65+
if ($refreshToken !== null) {
66+
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
67+
$responseType->setRefreshToken($refreshToken);
68+
}
69+
70+
return $responseType;
71+
}
72+
73+
/**
74+
* @param ServerRequestInterface $request
75+
* @param ClientEntityInterface $client
76+
*
77+
* @throws OAuthServerException
78+
*
79+
* @return UserEntityInterface
80+
*/
81+
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client, OneTimePasswordInterface $otp)
82+
{
83+
$phone = $this->getRequestParameter('phone', $request);
84+
85+
if (\is_null($phone)) {
86+
throw OAuthServerException::invalidRequest('phone');
87+
}
88+
89+
$code = $this->getRequestParameter('code', $request);
90+
91+
if (\is_null($code)) {
92+
throw OAuthServerException::invalidRequest('code');
93+
}
94+
95+
$user = $this->userRepository->getUserEntityByOtp(
96+
$phone,
97+
$code,
98+
$this->getIdentifier(),
99+
$client,
100+
$otp
101+
);
102+
103+
if ($user instanceof UserEntityInterface === false) {
104+
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
105+
106+
throw OAuthServerException::invalidGrant();
107+
}
108+
109+
return $user;
110+
}
111+
112+
/**
113+
* {@inheritdoc}
114+
*/
115+
public function getIdentifier()
116+
{
117+
return 'otp';
118+
}
119+
}

src/OneTimePasswordInterface.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace OAuthServer;
4+
5+
interface OneTimePasswordInterface
6+
{
7+
public function verify(string $phone, string $code): bool;
8+
}

src/Repositories/UserRepository.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use RuntimeException;
1313
use Hyperf\DbConnection\Db;
1414
use OAuthServer\Entities\UserEntity;
15+
use OAuthServer\OneTimePasswordInterface;
1516
use League\OAuth2\Server\Entities\ClientEntityInterface;
1617
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
1718

@@ -47,6 +48,38 @@ public function getUserEntityByUserCredentials(
4748
return new UserEntity($user->id);
4849
}
4950

51+
public function getUserEntityByOtp(
52+
$phone,
53+
$code,
54+
$grantType,
55+
ClientEntityInterface $clientEntity,
56+
OneTimePasswordInterface $otp
57+
) {
58+
$provider = $clientEntity->provider ?: config('oauth.provider');
59+
60+
if (is_null($config = config('databases.'.$provider, null))) {
61+
throw new RuntimeException('Unable to determine authentication from configuration.');
62+
}
63+
64+
if ($grantType !== 'otp') {
65+
throw new RuntimeException('Invalid Grant Type');
66+
}
67+
68+
$query = Db::connection($provider);
69+
70+
$user = $query->table('users')->where('phone', $phone)->first();
71+
72+
if (! $user) {
73+
return;
74+
}
75+
76+
if (! $otp->verify($user->phone, $code)) {
77+
return;
78+
}
79+
80+
return new UserEntity($user->id);
81+
}
82+
5083
public function getUserByProviderUserId($id, $client)
5184
{
5285
$provider = $client->provider;

0 commit comments

Comments
 (0)