PluginProbe
پینوا | Pinova / trunk
پینوا | Pinova vtrunk
pinova / src / API / UserAPI.php

UserAPI.php in پینوا | Pinova trunk, at src/API/UserAPI.php

428 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 namespace Pinova\API;
5
6 use Carbon\Carbon;
7 use Exception;
8 use Pinova\Exceptions\SendOTPException;
9 use Pinova\Helper;
10 use Pinova\Helpers\JWT;
11 use Pinova\Models\OTP;
12 use Pinova\Objects\Identifier;
13 use Pinova\Pinova;
14 use Pinova\Services\ChannelService;
15 use Pinova\Services\FirewallService;
16 use Pinova\Services\OTPService;
17 use Pinova\Services\UserService;
18 use Pinova\Services\ValidationService;
19 use WP_REST_Request;
20
21 defined( 'ABSPATH' ) || exit;
22
23 class UserAPI extends RestAPI {
24
25 public function register_routes() {
26
27 register_rest_route( 'pinova/user', '/authenticate', [
28 'methods' => 'POST',
29 'callback' => [ $this, 'authenticate' ],
30 'permission_callback' => [ $this, 'permission_callback' ],
31 'args' => [
32 'identifier' => [
33 'required' => true,
34 'validate_callback' => [ ValidationService::class, 'identifier' ],
35 ],
36 ],
37 ] );
38
39 register_rest_route( 'pinova/user', '/login/password', [
40 'methods' => 'POST',
41 'callback' => [ $this, 'login_password' ],
42 'permission_callback' => [ $this, 'permission_callback' ],
43 'args' => [
44 'identifier' => [
45 'required' => true,
46 'validate_callback' => [ ValidationService::class, 'identifier' ],
47 ],
48 'password' => [
49 'required' => true,
50 'validate_callback' => [ ValidationService::class, 'password' ],
51 ],
52 ],
53 ] );
54
55 register_rest_route( 'pinova/user', '/login/otp', [
56 'methods' => 'POST',
57 'callback' => [ $this, 'login_otp' ],
58 'permission_callback' => [ $this, 'permission_callback' ],
59 'args' => [
60 'jwt' => [
61 'required' => true,
62 ],
63 'code' => [
64 'required' => true,
65 'validate_callback' => [ ValidationService::class, 'code' ],
66 ],
67 ],
68 ] );
69
70 register_rest_route( 'pinova/auth', '/forgot/verify', [
71 'methods' => 'POST',
72 'callback' => [ $this, 'forgot_verify' ],
73 'permission_callback' => [ $this, 'permission_callback' ],
74 'args' => [
75 'jwt' => [
76 'required' => true,
77 ],
78 'code' => [
79 'required' => true,
80 'validate_callback' => [ ValidationService::class, 'code' ],
81 ],
82 ],
83 ] );
84
85 register_rest_route( 'pinova/auth', '/forgot/change', [
86 'methods' => 'POST',
87 'callback' => [ $this, 'forgot_change' ],
88 'permission_callback' => [ $this, 'permission_callback' ],
89 'args' => [
90 'jwt' => [
91 'required' => true,
92 ],
93 'reset_key' => [
94 'required' => true,
95 'validate_callback' => [ ValidationService::class, 'reset_key' ],
96 ],
97 'password_1' => [
98 'required' => true,
99 'validate_callback' => [ ValidationService::class, 'new_password' ],
100 ],
101 ],
102 ] );
103 }
104
105 /**
106 * @param WP_REST_Request $request
107 *
108 * @return void
109 */
110 public function authenticate( WP_REST_Request $request ): void {
111
112 /** @var Identifier $identifier */
113 $identifier = $request->get_param( 'identifier' );
114 $force_otp = boolval( $request->get_param( 'force_otp' ) );
115 $forget = boolval( $request->get_param( 'forget' ) );
116
117 $data = [
118 'has_password' => false,
119 'has_account' => false,
120 'login_method' => 'otp',
121 'ttl' => JWT::DEFAULT_TTL,
122 ];
123
124 $user_id = UserService::match( $identifier );
125 // @todo set attempt, prevent user enumeration
126
127 if ( $user_id ) {
128
129 $data['has_account'] = true;
130
131 $user = get_user_by( 'id', $user_id );
132
133 $data['has_password'] = ! str_starts_with( $user->user_pass, 'NO_PASSWORD_' );
134
135 if ( $identifier->is_email() || $identifier->is_username() ) {
136 $data['login_method'] = 'password';
137 }
138
139 if ( $user->get( 'pinova_login_method' ) == 'password' ) {
140 $data['login_method'] = 'password';
141 }
142
143 if ( $data['has_password'] && Pinova::get_option( 'advanced.default_login_method' ) == 'password' ) {
144 $data['login_method'] = 'password';
145 }
146
147 } elseif ( $forget ) {
148 self::response( false, __( 'حساب کاربری یافت نشد.', 'pinova' ) );
149 } elseif ( ! Pinova::users_can_register() ) {
150 self::response( false, __( 'عضویت در سایت غیرفعال است.', 'pinova' ) );
151 } elseif ( $identifier->is_email() ) {
152 self::response( false, __( 'حساب کاربری با این ای�
153 یل وجود ندارد. برای عضویت از تلفن ه�
154 راه استفاده کنید.', 'pinova' ) );
155 }
156
157 $force_otp = $force_otp || $forget;
158
159 if ( $force_otp ) {
160 $data['login_method'] = 'otp';
161 }
162
163 $message = null;
164
165 if ( $data['login_method'] == 'otp' ) {
166
167 if ( $identifier->is_username() ) {
168 self::response( false, __( 'برای دریافت کد تایید، از تلفن ه�
169 راه یا ای�
170 یل استفاده کنید.', 'pinova' ) );
171 }
172
173 /** @var OTP $current_otp */
174 $current_otp = OTP::query()
175 ->where( 'identifier', $identifier->get_value() )
176 ->where( 'expires_at', '>', Carbon::now() )
177 ->first();
178
179 if ( $current_otp ) {
180
181 $ttl = $current_otp->expires_at->diffInSeconds();
182
183 if ( $current_otp->isVerified() ) {
184
185 if ( $force_otp ) {
186 self::response( false, sprintf( 'برای دریافت کد تایید %d ثانیه دیگر تلاش کنید.', $ttl ) );
187 } elseif ( $data['has_password'] ) {
188 $data['login_method'] = 'password';
189 }
190
191 } else {
192
193 $data['ttl'] = $ttl;
194 $data['jwt'] = JWT::encode( [
195 'otp_id' => $current_otp->id,
196 ], $ttl );
197
198 }
199
200 $message = ChannelService::get_message( $current_otp->channels, $identifier, $user_id );
201
202 } else {
203
204 try {
205
206 $type = OTP::TYPE_REGISTER;
207
208 if ( $user_id ) {
209 $type = OTP::TYPE_LOGIN;
210
211 if ( $forget ) {
212 $type = OTP::TYPE_FORGET;
213 }
214 }
215
216 $channels = ChannelService::get_channels( $identifier );
217
218 [
219 $data['jwt'],
220 $successful_channels,
221 ] = OTPService::create( $identifier, $channels, $type, $user_id );
222
223 $message = ChannelService::get_message( $successful_channels, $identifier, $user_id );
224
225 } catch ( SendOTPException $e ) {
226
227 if ( $force_otp ) {
228 self::response( false, $e->getMessage() );
229 } elseif ( $data['has_password'] ) {
230 $data['login_method'] = 'password';
231 } else {
232 self::response( false, $e->getMessage() );
233 }
234
235 } catch ( Exception $e ) {
236 self::response( false, $e->getMessage() );
237 }
238
239 }
240
241 }
242
243 self::response( true, $message, $data );
244 }
245
246 /**
247 * @param WP_REST_Request $request
248 *
249 * @return void
250 */
251 public function login_password( WP_REST_Request $request ): void {
252
253 /** @var Identifier $identifier */
254 $identifier = $request->get_param( 'identifier' );
255 $password = $request->get_param( 'password' );
256
257 $user_id = UserService::match( $identifier );
258 // @todo set attempt, prevent user enumeration
259
260 if ( ! $user_id ) {
261 self::response( false, __( 'اطلاعات ورود �
262 عتبر ن�
263 ی‌باشد.', 'pinova' ) );
264 }
265
266 $user = get_user_by( 'id', $user_id );
267
268 if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
269 self::response( false, __( 'اطلاعات ورود �
270 عتبر ن�
271 ی‌باشد.', 'pinova' ) );
272 }
273
274 UserService::login( $user_id, 'password' );
275
276 self::response( true, __( 'ورود با �
277 وفقیت انجا�
278 شد.', 'pinova' ) );
279 }
280
281 /**
282 * @param WP_REST_Request $request
283 *
284 * @return void
285 */
286 public function login_otp( WP_REST_Request $request ): void {
287
288 $jwt = $request->get_param( 'jwt' );
289 $code = $request->get_param( 'code' );
290
291 try {
292 $user = OTPService::verify( $jwt, $code );
293 } catch ( Exception $e ) {
294 self::response( false, $e->getMessage() );
295 }
296
297 UserService::login( $user->ID, 'otp' );
298
299 self::response( true, __( 'ورود با �
300 وفقیت انجا�
301 شد.', 'pinova' ) );
302 }
303
304 /**
305 * @param WP_REST_Request $request
306 *
307 * @return void
308 */
309 public function forgot_verify( WP_REST_Request $request ): void {
310
311 $jwt = $request->get_param( 'jwt' );
312 $code = $request->get_param( 'code' );
313
314 try {
315 $user = OTPService::verify( $jwt, $code );
316 } catch ( Exception $e ) {
317 self::response( false, $e->getMessage() );
318 }
319
320 clean_user_cache( $user->ID );
321 $reset_key = get_password_reset_key( $user );
322
323 self::response( true, null, [
324 'jwt' => UserService::generate_jwt( $user->ID ),
325 'reset_key' => $reset_key,
326 ] );
327 }
328
329 /**
330 * @param WP_REST_Request $request
331 *
332 * @return void
333 */
334 public function forgot_change( WP_REST_Request $request ): void {
335
336 $jwt = $request->get_param( 'jwt' );
337 $reset_key = $request->get_param( 'reset_key' );
338 $password = $request->get_param( 'password_1' );
339 $password_confirm = $request->get_param( 'password_2' );
340
341 if ( $password !== $password_confirm ) {
342 self::response( false, __( 'ر�
343 ز عبور و تکرار ر�
344 ز عبور یکسان نیستند.', 'pinova' ) );
345 }
346
347 try {
348 $user_id = UserService::parse_jwt( $jwt );
349 } catch ( Exception $e ) {
350 self::response( false, $e->getMessage() );
351 }
352
353 $user = get_user_by( 'id', $user_id );
354
355 $user = check_password_reset_key( $reset_key, $user->user_login );
356
357 if ( is_wp_error( $user ) ) {
358 self::response( false, $user->get_error_message() );
359 }
360
361 reset_password( $user, $password );
362
363 UserService::login( $user_id, 'password' );
364
365 self::response( true, __( 'ر�
366 زعبور با �
367 وفقیت بازنشانی شد و به سیست�
368 وارد شدید.', 'pinova' ) );
369 }
370
371 /**
372 * The middleware to check if user can log out
373 *
374 * @param WP_REST_Request $request
375 *
376 * @return bool
377 */
378 public function permission_callback_logout( WP_REST_Request $request ): bool {
379
380 if ( ! is_user_logged_in() ) {
381 self::response( false, __( 'در ابتدا وارد شوید.', 'pinova' ) );
382 }
383
384 return true;
385 }
386
387
388 /**
389 * The middleware to check if user can access the routes and set initial important values in 'request' param
390 *
391 * @param WP_REST_Request $request
392 *
393 * @return bool
394 */
395 public function permission_callback( WP_REST_Request $request ): bool {
396
397 if ( is_user_logged_in() ) {
398 self::response( false, __( 'ش�
399 ا وارد شده اید.', 'pinova' ) );
400 }
401
402 if ( FirewallService::is_ip_blocked() ) {
403 self::response( false, __( 'آدرس آی.پی ش�
404 ا �
405 سدود شده است.', 'pinova' ) );
406 }
407
408 return true;
409 }
410
411 /**
412 * @param bool $success
413 * @param string|null $message
414 * @param array $data
415 *
416 * @return no-return
417 */
418 public static function response( bool $success, ?string $message = null, array $data = [] ): void {
419
420 if ( $success ) {
421 $data['back_url'] = Helper::get_login_back_url();
422 }
423
424 parent::response( $success, $message, $data );
425 }
426
427 }
428