' . esc_html__( 'This store does not offer this kind of sign-in.', 'agent-adapt' ) . '
' ); } // phpcs:disable WordPress.Security.NonceVerification -- GET is display-only; POST is verified below. $src = 'POST' === $method ? $_POST : $_GET; $p = array(); foreach ( self::PARAMS as $k ) { $p[ $k ] = isset( $src[ $k ] ) && is_string( $src[ $k ] ) ? (string) wp_unslash( $src[ $k ] ) : ''; } // phpcs:enable // 1. Client and redirect_uri: failures are SHOWN, never redirected. $agent = AgentAdapt_Agents::resolve( $p['client_id'] ); if ( is_wp_error( $agent ) || ! AgentAdapt_Agents::redirect_allowed( $agent, $p['redirect_uri'] ) ) { AgentAdapt_Pages::send( 400, __( 'This sign-in link isn\'t valid', 'agent-adapt' ), '' . esc_html__( 'The assistant that sent you here could not be verified, or it asked to return somewhere it has not registered. No sign-in has happened. Go back to your assistant and try again.', 'agent-adapt' ) . '
' ); } // 2. Everything else goes back to the agent with an error. if ( 'code' !== $p['response_type'] ) { self::back( $p, array( 'error' => 'unsupported_response_type' ) ); } if ( 'S256' !== $p['code_challenge_method'] || ! preg_match( '/^[A-Za-z0-9_-]{43,128}$/', $p['code_challenge'] ) ) { self::back( $p, array( 'error' => 'invalid_request', 'error_description' => 'PKCE with S256 is required' ) ); } $scopes = AgentAdapt_Signin::parse( $p['scope'], 'direct' ); if ( is_wp_error( $scopes ) ) { self::back( $p, array( 'error' => 'invalid_scope' ) ); } // RFC 8707: a named resource must be one this store issues tokens for, // and a public client (MCP's own sign-in) must name its MCP server. if ( '' !== $p['resource'] && ! AgentAdapt_Urls::known_resource( $p['resource'] ) ) { self::back( $p, array( 'error' => 'invalid_target' ) ); } if ( ! empty( $agent['public'] ) && ! AgentAdapt_Urls::is_bearer_resource( $p['resource'] ) ) { self::back( $p, array( 'error' => 'invalid_target', 'error_description' => 'name this store\'s MCP server as the resource' ) ); } $scopes = AgentAdapt_Signin::for_agent( $scopes, $agent['client_hash'] ); if ( ! $scopes ) { self::back( $p, array( 'error' => 'invalid_scope', 'error_description' => 'none of the requested scopes is available to this agent' ) ); } // 3. The customer signs in themselves, on WordPress's own login. if ( ! is_user_logged_in() ) { if ( 'POST' === $method ) { AgentAdapt_Pages::send( 403, __( 'Please sign in', 'agent-adapt' ), '' ); } wp_safe_redirect( wp_login_url( self::self_url( $p ) ) ); exit; } $user_id = get_current_user_id(); if ( ! AgentAdapt_Eligibility::eligible( $user_id ) ) { $decline = add_query_arg( array_map( 'rawurlencode', array( 'error' => 'access_denied', 'state' => $p['state'], 'iss' => AgentAdapt_Urls::issuer() ) ), $p['redirect_uri'] ); AgentAdapt_Pages::send( 403, __( 'This account can\'t be connected', 'agent-adapt' ), '' . esc_html__( 'Store staff and administrator accounts cannot be connected to a personal assistant. Sign in with your customer account instead.', 'agent-adapt' ) . '
' . AgentAdapt_Pages::account_line( self::self_url( $p ) ) . '' . esc_html__( 'Return to your assistant', 'agent-adapt' ) . '
' ); } if ( 'GET' === $method ) { self::show( $agent, $p, $scopes ); } // 4. POST: the decision. $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, self::nonce_action( $p ) ) ) { AgentAdapt_Pages::send( 403, __( 'This page expired', 'agent-adapt' ), '' . esc_html__( 'Go back to your assistant and start again.', 'agent-adapt' ) . '
' ); } $approved = isset( $_POST['decision'] ) && 'approve' === sanitize_key( wp_unslash( $_POST['decision'] ) ); $picked = isset( $_POST['scopes'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['scopes'] ) ) : array(); $chosen = array_values( array_intersect( $scopes, $picked ) ); // Only what was asked for. if ( ! $approved || ! $chosen ) { AgentAdapt_Events::record( 'consent.declined', null, array( 'client_hash' => $agent['client_hash'], 'channel' => 'web' ) ); self::back( $p, array( 'error' => 'access_denied' ) ); } $code = AgentAdapt_Codes::create_auth( $agent['client_hash'], $user_id, implode( ' ', $chosen ), $p['redirect_uri'], $p['code_challenge'], $p['resource'] ); self::back( $p, array( 'code' => $code ) ); } /** * The consent page. * * @param array