AuthService.php 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. <?php
  2. declare(strict_types=1);
  3. namespace app\miniapi\service;
  4. use app\miniapi\auth\Principal;
  5. use app\miniapi\auth\WechatIdentityProvider;
  6. use app\miniapi\auth\IdentityProvider;
  7. use app\miniapi\model\MiniUser;
  8. use app\miniapi\model\MiniSession;
  9. use app\miniapi\http\Fields;
  10. use app\miniapi\exception\ApiException;
  11. final class AuthService
  12. {
  13. public function __construct(private readonly IdentityProvider $provider = new WechatIdentityProvider())
  14. {
  15. }
  16. public function login(array $input): array
  17. {
  18. Fields::only($input, ['code']);
  19. $identity = $this->provider->exchange(Fields::text($input, 'code', 512));
  20. return $this->issue($identity);
  21. }
  22. /** Internal only. The HTTP controller never accepts provider identity from clients. */
  23. public function issue(array $identity): array
  24. {
  25. return (new MiniUser())->getConnection()->transaction(function () use ($identity) {
  26. $now = date('Y-m-d H:i:s');
  27. MiniUser::query()->insertOrIgnore($identity + [
  28. 'nickname' => '',
  29. 'status' => 1,
  30. 'created_at' => $now,
  31. 'updated_at' => $now,
  32. ]);
  33. return $this->sessionFor(MiniUser::query()->where($identity)->lockForUpdate()->firstOrFail());
  34. });
  35. }
  36. /**
  37. * Issues a session for an account that already exists, so a verified phone reaches the same
  38. * user and therefore the same granted identity as the WeChat login it was bound to.
  39. */
  40. public function issueForExisting(MiniUser $user): array
  41. {
  42. return (new MiniUser())->getConnection()->transaction(function () use ($user) {
  43. return $this->sessionFor(MiniUser::query()->where('id', $user->id)->lockForUpdate()->firstOrFail());
  44. });
  45. }
  46. private function sessionFor(MiniUser $user): array
  47. {
  48. if ((int) $user->status !== 1) {
  49. throw new ApiException(40302, '账号已停用', 403);
  50. }
  51. $now = date('Y-m-d H:i:s');
  52. $token = bin2hex(random_bytes(32));
  53. $expires = date('Y-m-d H:i:s', time() + (int) config('mini_api.session_ttl', 604800));
  54. MiniSession::query()->insert([
  55. 'user_id' => $user->id,
  56. 'token_hash' => hash('sha256', $token),
  57. 'expires_at' => $expires,
  58. 'created_at' => $now,
  59. ]);
  60. return [
  61. 'access_token' => $token,
  62. 'token_type' => 'Bearer',
  63. 'expires_at' => $expires,
  64. 'user_id' => (string) $user->id,
  65. 'identities' => (new Access(new Principal((string) $user->id)))->identities(),
  66. ];
  67. }
  68. public function logout(string $token): array
  69. {
  70. MiniSession::query()->where('token_hash', hash('sha256', $token))->whereNull('revoked_at')->update(['revoked_at' => date('Y-m-d H:i:s')]);
  71. return ['logged_out' => true];
  72. }
  73. }