mini_test_phone_test.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. <?php
  2. /** Real sessions in a transaction; all account/session changes are rolled back. */
  3. require dirname(__DIR__) . '/vendor/autoload.php';
  4. \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
  5. \support\App::loadAllConfig(['route']);
  6. use app\miniapi\auth\TestPhoneIdentityProvider;
  7. use app\miniapi\auth\SessionVerifier;
  8. use app\miniapi\exception\ApiException;
  9. use app\miniapi\model\MiniUser;
  10. use app\miniapi\model\MiniSession;
  11. use app\miniapi\service\TestPhoneLogin;
  12. use app\miniapi\service\AuthService;
  13. $checks = 0;
  14. function check(bool $ok, string $label): void
  15. {
  16. global $checks;
  17. $checks++;
  18. if (!$ok) {
  19. throw new RuntimeException($label);
  20. }
  21. }
  22. function reject(callable $action, int $code): void
  23. {
  24. try {
  25. $action();
  26. } catch (ApiException $error) {
  27. check($error->getCode() === $code, 'Expected rejection ' . $code);
  28. return;
  29. }
  30. throw new RuntimeException('Missing rejection ' . $code);
  31. }
  32. function settings(array $changes): void
  33. {
  34. $property = new ReflectionProperty(\Webman\Config::class, 'config');
  35. $all = $property->getValue();
  36. $all['mini_api'] = array_replace($all['mini_api'], $changes);
  37. $property->setValue(null, $all);
  38. (new ReflectionProperty(\Webman\Config::class, 'flatCache'))->setValue(null, []);
  39. }
  40. $original = config('mini_api');
  41. $db = (new MiniUser())->getConnection();
  42. $db->beginTransaction();
  43. try {
  44. $service = new TestPhoneLogin();
  45. $input = ['phone' => TestPhoneIdentityProvider::PHONES['enterprise_admin'], 'code' => '66666'];
  46. settings(['environment' => 'production', 'test_phone_login' => true]);
  47. reject(fn () => $service->login($input), 50301);
  48. settings(['environment' => 'local', 'test_phone_login' => false]);
  49. reject(fn () => $service->login($input), 50301);
  50. settings(['environment' => 'local', 'test_phone_login' => true]);
  51. settings(['test_phone_allowed_ips' => ['127.0.0.1', '::1']]);
  52. $service->assertSourceAllowed('127.0.0.1');
  53. reject(fn () => $service->assertSourceAllowed('192.168.1.10'), 40301);
  54. reject(fn () => $service->login($input + ['user_id' => 1]), 42201);
  55. reject(fn () => $service->login(['phone' => '13012345681', 'code' => '66666']), 40102);
  56. reject(fn () => $service->login(['phone' => '13012345678', 'code' => '66665']), 40102);
  57. reject(fn () => $service->login(['phone' => ['13012345678'], 'code' => '66666']), 42201);
  58. $users = [];
  59. foreach (TestPhoneIdentityProvider::PHONES as $role => $phone) {
  60. $users[$role] = MiniUser::query()->firstOrCreate(
  61. ['appid' => TestPhoneIdentityProvider::APP_ID, 'openid' => $phone],
  62. ['nickname' => 'test', 'status' => 1, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s')]
  63. );
  64. }
  65. $count = MiniUser::query()->count();
  66. $sessions = [];
  67. foreach (TestPhoneIdentityProvider::PHONES as $role => $phone) {
  68. $sessions[$role] = $service->login(['phone' => $phone, 'code' => '66666']);
  69. check($sessions[$role]['user_id'] === (string) $users[$role]->id, 'Correct account for ' . $role);
  70. check(count($sessions[$role]['identities']) <= 1, 'At most one role for ' . $role);
  71. }
  72. check(count(array_unique(array_column($sessions, 'user_id'))) === 3, 'Distinct test accounts');
  73. check(MiniUser::query()->count() === $count, 'No duplicate users');
  74. $one = $sessions['enterprise_admin'];
  75. $two = $service->login($input);
  76. check($one['user_id'] === $two['user_id'], 'Stable identity');
  77. check($one['access_token'] !== $two['access_token'], 'Fresh random sessions');
  78. check((new SessionVerifier())->verify($one['access_token'])->userId === (string) $users['enterprise_admin']->id, 'Session authenticated');
  79. check(MiniSession::query()->where('token_hash', hash('sha256', $one['access_token']))->exists(), 'Token hash stored');
  80. settings(['test_phone_login' => false]);
  81. reject(fn () => (new SessionVerifier())->verify($one['access_token']), 40101);
  82. settings(['test_phone_login' => true, 'environment' => 'production']);
  83. reject(fn () => (new SessionVerifier())->verify($one['access_token']), 40101);
  84. settings(['environment' => 'local']);
  85. MiniUser::query()->where('id', $users['enterprise_admin']->id)->update(['status' => 0]);
  86. reject(fn () => $service->login($input), 40302);
  87. reject(fn () => (new SessionVerifier())->verify($one['access_token']), 40101);
  88. MiniUser::query()->where('id', $users['enterprise_admin']->id)->update(['status' => 1]);
  89. (new AuthService())->logout($one['access_token']);
  90. reject(fn () => (new SessionVerifier())->verify($one['access_token']), 40101);
  91. check((new SessionVerifier())->verify($two['access_token'])->userId === (string) $users['enterprise_admin']->id, 'Other session still works');
  92. } finally {
  93. $db->rollBack();
  94. settings($original);
  95. }
  96. echo "PASS: $checks test phone checks, database changes rolled back.\n";