PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / auth / module.php

module.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/auth/module.php

144 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Auth module (spec 013-authentication-profile).
4 *
5 * Login/logout/session, signup, Google OAuth, profile sync + verification recheck.
6 * Relocated from the monolith: REST classes from includes/API/{Login,SignUp}.php and
7 * the auth-owned half of Profile.php; the Google OAuth callback handler from
8 * Plugin::google_login_handler(). Behavior byte-identical (same routes, same query
9 * params, same option keys).
10 *
11 * @package Templately
12 */
13
14 namespace Templately\Modules\Auth;
15
16 use Templately\Core\Module_Base;
17 use Templately\Utils\Database;
18 use Templately\Utils\Response\ErrorCode;
19 use Templately\Modules\Auth\REST\Login;
20 use Templately\Modules\Auth\REST\Profile;
21 use Templately\Modules\Auth\REST\SignUp;
22
23 class Module extends Module_Base {
24
25 public function get_name(): string {
26 return 'auth';
27 }
28
29 protected function init_hooks(): void {
30 add_action( 'init', [ $this, 'google_login_handler' ] );
31 }
32
33 public function register_rest_routes(): void {
34 Login::get_instance()->register_routes();
35 SignUp::get_instance()->register_routes();
36 Profile::get_instance()->register_routes();
37 }
38
39 public function google_login_handler() {
40 // Stop if not a templately google login request
41 if ( empty( $_GET['templately_google_login'] ) ) {
42 return;
43 }
44
45 if ( wp_doing_ajax() || wp_doing_cron() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
46 return;
47 }
48
49 // Checked before the token is consumed: the callback can land while the
50 // auth cookie is missing (expired session, cookie not yet set), and WP
51 // will bounce the user through wp-login and back to this same URL.
52 // Burning the token here would fail that legitimate retry.
53 if ( ! is_user_logged_in() ) {
54 return;
55 }
56
57 $state = '';
58 if ( ! empty( $_GET['templately_state'] ) ) {
59 $state = sanitize_text_field( wp_unslash( $_GET['templately_state'] ) );
60 } elseif ( ! empty( $_GET['state'] ) ) {
61 $state = sanitize_text_field( wp_unslash( $_GET['state'] ) );
62 }
63
64 $state_user_id = false;
65 if ( ! empty( $state ) ) {
66 $state_user_id = Database::get_transient( 'google_state_' . $state );
67 Database::delete_transient( 'google_state_' . $state );
68 }
69
70 $is_authorized = false !== $state_user_id
71 && intval( $state_user_id ) === get_current_user_id()
72 && current_user_can( 'delete_posts' );
73
74 $redirect_url = remove_query_arg( [ 'templately_google_login', 'templately_state', 'api_key', 'error', 'state', 'redirect-to' ] );
75
76 if ( ! $is_authorized ) {
77 $error_code = ErrorCode::AUTH_STATE_INVALID;
78 } elseif ( ! empty( $_GET['error'] ) ) {
79 // The provider answers with one of its own slugs (oauth_failed,
80 // provider_error, invalid_state, …). It is DROPPED rather than mapped:
81 // the value is attacker-controlled, none of the slugs mean anything
82 // different to the user, and forwarding one would put an unvetted
83 // string back into a URL the sign-in screen reads.
84 $error_code = ErrorCode::AUTH_PROVIDER_FAILED;
85 } elseif ( ! empty( $_GET['api_key'] ) ) {
86 $request = new \WP_REST_Request( 'POST', '/templately/v1/login' );
87 $request->set_param( 'viaAPI', true );
88 $request->set_param( 'api_key', sanitize_text_field( $_GET['api_key'] ) );
89
90 /**
91 * @var Login $login
92 */
93 $login = Login::get_instance();
94 $login->permission_check( $request );
95
96 // login() pins the write target to the acting user itself — no pin
97 // here, or its finally would release ours mid-request.
98 $response = $login->login();
99
100 if ( ! is_wp_error( $response ) && ! empty( $response['user'] ) ) {
101 $redirect_path = ! empty( $_GET['redirect-to'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect-to'] ) ) : '';
102 if ( ! empty( $redirect_path ) ) {
103 if ( filter_var( $redirect_path, FILTER_VALIDATE_URL ) ) {
104 $redirect_url = $redirect_path;
105 } else {
106 $is_templately = strpos( $redirect_url, 'page=templately' ) !== false;
107 $is_elementor = strpos( $redirect_url, 'action=elementor' ) !== false;
108 // Gutenberg editor usually has action=edit or is a block editor page
109 $is_gutenberg = ( strpos( $redirect_url, 'action=edit' ) !== false || strpos( $redirect_url, 'post_type=' ) !== false ) && ! $is_elementor;
110
111 if ( $is_templately || $is_elementor || $is_gutenberg ) {
112 $redirect_url = add_query_arg( 'path', ltrim( $redirect_path, '/' ), $redirect_url );
113
114 // Always open the modal in editors after google login
115 if ( $is_elementor || $is_gutenberg ) {
116 $redirect_url = add_query_arg( 'templately_open_modal', '1', $redirect_url );
117 }
118 }
119 }
120 }
121
122 wp_safe_redirect( $redirect_url );
123 exit;
124 } else {
125 $error_code = Login::resolve_error_code( $response );
126 }
127 } else {
128 $error_code = ErrorCode::AUTH_MISSING_API_KEY;
129 }
130
131 // ONLY a code travels back — never a message. The message is authored
132 // upstream, can contain markup, and anything placed in a query param is
133 // attacker-controlled by the time the sign-in screen renders it. The
134 // client maps the code to its own translated copy (errorCatalog) and
135 // falls back to generic copy for a code it does not recognise.
136 $redirect_url = add_query_arg( [
137 'templately_error' => rawurlencode( $error_code ),
138 ], $redirect_url );
139
140 wp_safe_redirect( $redirect_url );
141 exit;
142 }
143 }
144