Skip to content

feat(auth): add REST API routes for login, refresh, and logout - #3026

Open
saadman30 wants to merge 35 commits into
4.2.0from
fix/rest-idor-issues
Open

saadman30 wants to merge 35 commits into
4.2.0from
fix/rest-idor-issues

Conversation

@saadman30

Copy link
Copy Markdown
Collaborator
  • Introduced new REST API routes for user authentication: /auth/login, /auth/refresh, and /auth/logout.
  • Updated permission callbacks for various existing routes to use more specific permission checks.
  • Enhanced author detail retrieval to include user metadata conditionally based on permissions.
  • Improved response handling in course and quiz endpoints to ensure proper data structure and access control.

- Introduced new REST API routes for user authentication: `/auth/login`, `/auth/refresh`, and `/auth/logout`.
- Updated permission callbacks for various existing routes to use more specific permission checks.
- Enhanced author detail retrieval to include user metadata conditionally based on permissions.
- Improved response handling in course and quiz endpoints to ensure proper data structure and access control.
- Introduced new methods for processing authenticated read, write, and delete requests, ensuring that API key permissions are validated alongside user authentication.
- Refactored the existing process_api_request method to delegate permission checks to the new methods, improving code clarity and maintainability.
- Updated documentation to reflect changes in permission handling and method responsibilities.
- Updated permission callbacks for login, refresh, and logout routes to use a default true return value, streamlining access control.
- Introduced new methods in RestAuth class to determine route types (login, refresh, logout) for better clarity and maintainability.
- Enhanced documentation to reflect the changes in permission handling and route identification.
- Changed permission checks in the RestAuth class from 'administrator' to 'manage_options' for generating and revoking API keys.
- This adjustment aligns permission handling with WordPress best practices, ensuring that only users with the appropriate capabilities can manage API keys.
…class

- Replaced the deprecated base64_encode and base64_decode functions with sodium_bin2base64 and sodium_base642bin for enhanced security and JWT compatibility.
- Updated method documentation to clarify that the encoding and decoding are now JWT-safe and do not include padding.
- Added error handling for decoding to ensure robustness against invalid input.
- Changed header names from 'X-Tutor-Api-Key' and 'X-Tutor-User-Token' to 'Tutor-Api-Key' and 'Tutor-User-Token' for consistency and clarity.
- Updated method documentation to reflect the new header names, ensuring accurate API usage guidance.
- Simplified the get_api_credentials_from_request method to exclusively read API key and secret from Tutor-Api-* headers, removing support for Basic auth and PHP_AUTH_USER/PW.
- Updated method documentation to reflect the changes in credential retrieval, ensuring clarity for API users.
- Updated the handling of the REDIRECT_HTTP_AUTHORIZATION server variable to sanitize its value using sanitize_text_field before assigning it to the headers array.
- This change enhances security by ensuring that the authorization header is properly sanitized, preventing potential security vulnerabilities.
- Updated the `quiz_attempt_details` method to sanitize the quiz ID parameter using the `Input::sanitize` method, improving security against potential injection attacks.
- Modified the `get_quiz_attempt_ans` method to accept the attempt ID instead of the quiz ID, ensuring that answers are fetched based on the specific attempt, preventing cross-user answer leaks.
- Enhanced method documentation to reflect the changes and clarify the purpose of the parameters.
- Eliminated the login rate limit functionality, including constants and methods related to tracking failed login attempts.
- Updated the login authentication process to remove rate limiting checks, simplifying the login flow.
- Adjusted method documentation to reflect the removal of rate limiting features, ensuring clarity for future development.
- Added functionality to manage access and refresh token lifetimes through a new settings form in the admin dashboard.
- Introduced new constants and methods in the RestAuth class to handle token lifetime configurations, including validation for minimum and maximum values.
- Updated the manage-api-keys.js script to handle form submissions for saving token settings with appropriate user feedback.
- Enhanced the manage-tokens.php view to display and allow editing of token lifetime settings.
- Improved overall documentation for new methods and constants related to token management.
- Updated the REST_Author, REST_Course, and REST_Quiz classes to use `self::send()` instead of `static::send()`, ensuring consistency in method calls.
- This change improves clarity and aligns with best practices for method referencing within the same class context.
- Updated the RestAuth class to introduce new constants and methods for managing access and refresh token lifetimes, allowing for unlimited expiration settings.
- Refactored existing methods to convert token lifetimes from seconds to days for better usability in the admin UI.
- Enhanced the manage-tokens.php view to reflect these changes, including validation for token lifetime inputs and user-friendly messaging regarding expiration settings.
- Improved documentation for new methods and constants related to token management, ensuring clarity for future development.
…th class

- Modified the permission check logic to ensure that only the 'Delete' and 'All' permissions are considered valid for API key deletion.
- Updated documentation to clarify the changes and align with pre-4.0.10 Pro route allowlists, specifying that 'Write' and 'Read/Write' do not authorize DELETE actions.
- Updated the version annotations in the REST_Quiz and RestAuth classes to reflect the new version 4.2.0, ensuring accurate documentation of changes and features.
- This change aligns the documentation with the latest updates and clarifies the version history for future reference.
…n REST API

- Changed the permission callback in the RestAPI class to use 'permission_course_content' for better access control.
- Updated the REST_Course class to unset 'post_password' along with 'filter' for improved data handling.
- Enhanced the REST_Rating class to ensure that private user fields are hidden from reviews unless the user has permission to view them.
- Modified the can_view_course_content method in RestAuth to check for valid course types, improving security and data integrity.
- Introduced a new REST_Posts class to encapsulate shared post retrieval logic, enhancing code reusability and maintainability.
- Updated REST_Course_Announcement, REST_Course, REST_Quiz, and REST_Topic classes to utilize the new REST_Posts methods for fetching published child posts and sanitizing course content items.
- Improved data handling by ensuring only published posts are retrieved, enhancing the overall security and integrity of the API responses.
- Introduced a new private method `get_id_arg_schema` in the RestAPI class to standardize the ID argument schema across multiple REST routes.
- Replaced inline validation logic with the new method for improved code readability and maintainability.
- Ensured consistent validation and sanitization for ID parameters in various API endpoints, enhancing overall security and code reusability.
- Updated the api_auth method to ensure that user identity is strictly derived from a valid access token, aligning with kid-based API permission.
- Removed unnecessary checks for user_id when processing tutor API requests, enhancing the clarity and efficiency of the authentication flow.
- Improved error handling by returning false when the access token is invalid or missing, ensuring better security and response consistency.
- Replaced manual JWT encoding and decoding logic with dedicated methods `encode_jwt` and `decode_jwt` for improved clarity and maintainability.
- Updated the `issue_refresh_token` method to issue signed refresh JWTs, streamlining the refresh token process and enhancing security.
- Improved the `find_refresh_session` method to verify refresh JWTs, ensuring that user-specific checks are performed efficiently.
- Added detailed documentation for new JWT methods, clarifying their purpose and usage within the RestAuth class.
…methods

- Eliminated the SSL requirement checks from the `rest_login`, `rest_refresh`, and `rest_logout` methods in the RestAuth class to simplify the authentication flow.
- Removed the private method `require_ssl_for_auth`, streamlining the code and enhancing maintainability.
- This change focuses on improving the clarity of the authentication process while ensuring that the core functionality remains intact.
…ntrol

- Replaced existing permission callbacks in the RestAPI class with more specific methods to improve access control for various API endpoints.
- Introduced new permission methods such as `permission_public_catalog`, `permission_course_detail`, and `permission_authenticated_course_content` to streamline permission handling.
- Enhanced the REST_Author class to retrieve published instructor course IDs, ensuring public profile parity.
- Updated the REST_Course class to conditionally reveal course content based on user authentication status, improving the user experience for guests and enrolled users.
- Added new utility methods in RestAuth for better permission management and user access checks.
- Added validation to reject permissions not in the available_permissions allowlist.
- Introduced a new private method `is_allowed_api_permission` to check if a permission string is valid.
- Updated existing methods to utilize the new permission validation, improving security and access control for API key generation and permission updates.
- Marked changes with versioning for clarity on enhancements made in version 4.2.0.
- Updated the `permission_public_author` method to clarify permission logic for author cards.
- Revised comments to specify the conditions under which layouts are accessible, enhancing documentation for future reference.
- Removed redundant permission checks to streamline the permission validation process.
- Enhanced the error logging mechanism in the `RestAuth` class to log detailed information when API permission updates fail.
- Added a user-friendly error message for clients while keeping the server-side error details secure.
- This change aims to improve debugging and maintain security by not exposing sensitive error information to users.
@saadman30
saadman30 changed the base branch from 4.1.0 to 4.1.1 October 6, 2026 09:41
- Updated the response handling in the REST API classes (`REST_Author`, `REST_Course`, `REST_Course_Announcement`, `REST_Lesson`, `REST_Quiz`, `REST_Rating`, `REST_Topic`) to utilize a centralized response method for consistency and improved readability.
- Removed redundant response array constructions and replaced them with a unified `response` method that standardizes success and error responses.
- Enhanced error handling by ensuring appropriate HTTP status codes are returned for various scenarios, improving the overall API response structure.
@saadman30
saadman30 marked this pull request as ready for review October 7, 2026 05:45
@saadman30
saadman30 changed the base branch from 4.1.1 to 4.2.0 October 7, 2026 05:59
- Revised the API documentation in `README.md` to replace Basic Authentication with JWT authentication details, including setup instructions and authentication flow.
- Added new authentication-related files for Login, Logout, and Refresh Token endpoints, detailing request structures and expected responses.
- Updated existing course and curriculum API endpoints to utilize Bearer JWT for authentication, ensuring consistency across the documentation.
- Enhanced environment configuration examples to include new variables for JWT handling.
- Added functionality in `REST_Quiz` to conditionally reveal quiz answers based on user permissions and settings.
- Implemented `is_quiz_details_hidden_for_user` method in `RestAuth` to check if quiz details should be hidden for students, aligning with the "Hide Quiz Details" option.
- Updated quiz answer handling to ensure students do not see correctness when details are hidden or before a finished attempt, improving user experience and security.
- Refactored the `REST_Course` class to utilize `tutor_utils()->get_raw_course_price()` for fetching course prices, enhancing data consistency.
- Added `sale_price` to the course response, providing more comprehensive pricing information for API consumers.
- Improved the `to_catalog_card_dto` method to streamline price handling and ensure accurate pricing data is returned.
- Revised the required permissions for various API endpoints to clarify that any valid API key permission (`Read`, `Write`, `Delete`, `Read/Write`, or `All`) is acceptable for authentication.
- Updated multiple documentation files to ensure consistency in permission requirements across the API, enhancing clarity for developers integrating with the TutorLMS API.
- Clarified the API Key & Secret permission options in the documentation, specifying that the Free version supports `Read` permission, while the Pro version offers additional permissions including `Write`, `Delete`, `Read/Write`, and `All`.
- This update enhances the understanding of available permissions for developers working with the Tutor LMS REST API.
Comment thread classes/RestAPI.php
$this->namespace,
'/auth/login',
array(
'methods' => 'POST',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use WP_REST_Server const here

Comment thread classes/RestAPI.php
$this->namespace,
'/auth/refresh',
array(
'methods' => 'POST',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment thread classes/RestAPI.php
$this->namespace,
'/auth/logout',
array(
'methods' => 'POST',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here

Comment thread classes/RestAPI.php
'/auth/login',
array(
'methods' => 'POST',
'callback' => array( RestAuth::class, 'rest_login' ),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this class is dedicated to the REST API and is named RestAuth, the rest_ method prefix is unnecessary. We can simply name the method login or handle_login.

Comment thread classes/RestAPI.php
'/auth/refresh',
array(
'methods' => 'POST',
'callback' => array( RestAuth::class, 'rest_refresh' ),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment thread classes/RestAPI.php
'/auth/logout',
array(
'methods' => 'POST',
'callback' => array( RestAuth::class, 'rest_logout' ),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment thread classes/RestAPI.php
array(
'methods' => 'POST',
'callback' => array( RestAuth::class, 'rest_login' ),
'permission_callback' => array( RestAuth::class, 'process_api_request' ),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RestAuth constructor registers multiple hooks. If multiple routes are registered, these hooks may be registered repeatedly, leading to unexpected behavior.

We can add an optional $register_hooks parameter to control hook registration:

public function __construct( $register_hooks = true ) {
    if ( ! $register_hooks ) {
        return;
    }

    // Register hooks.
}

In RestAPI.php class

$auth_cls = new RestAuth( false );
array( $auth_cls, 'rest_login' ),

Comment thread classes/RestAPI.php
'course',
),
'permission_callback' => array( RestAuth::class, 'process_api_request' ),
'permission_callback' => array( RestAuth::class, 'permission_public_catalog' ),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since permission_public_catalog return true, we can simply use permission_callback => __return_true

@@ -0,0 +1,7 @@
vars {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should not be included here. For local development, developers need to copy local.bru.example to local.bru and configure their credentials.

- Revised endpoint documentation for courses, quizzes, and related resources to enhance clarity and consistency.
- Updated descriptions for various API routes, including course announcements, contents, ratings, and instructor information.
- Improved response codes and messages for better understanding of API behavior.
- Clarified authentication requirements and permissions for accessing course and quiz data, ensuring accurate guidance for developers.
Comment thread restapi/REST_Course.php
public function course( WP_REST_Request $request ) {
$order = sanitize_text_field( $request->get_param( 'order' ) );
$orderby = sanitize_text_field( $request->get_param( 'orderby' ) );
$order = self::sanitize_course_order( $request->get_param( 'order' ) );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need another helper, we have already QueryHelper::get_valid_sort_order

Comment thread restapi/REST_Course.php
$category = wp_get_post_terms( $post->ID, $this->course_cat_tax );

is_a( $author, 'WP_User' ) ? $post->post_author = $author->data : new \stdClass();
$tag = wp_get_post_terms( $post->ID, $this->course_tag_tax );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tax suffix in the variable name is misleading because it can be confused with taxonomy. We already have course tax configuration, which makes the naming even more confusing.

Comment thread restapi/REST_Course.php
*
* @return string ASC or DESC.
*/
private static function sanitize_course_order( $order ) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have already QueryHelper::get_valid_sort_order

Comment thread restapi/REST_Lesson.php
*
* @return object
*/
private static function to_full_lesson_dto( $post, $topic_id ) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add this to $post param WP_Post $post

Comment thread restapi/REST_Quiz.php

$quiz = REST_Posts::to_public_post_dto( $quiz_post );

$wpdb->q_t = $wpdb->prefix . $this->t_quiz_question; // Question table.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have $wpdb->tutor_quiz_questions.
@see tutor/classes/Tutor.php

Comment thread restapi/RestAuth.php
*/
public static function can_reveal_quiz_answers( $quiz_id, $user_id = 0 ) {
$quiz_id = absint( $quiz_id );
$user_id = $user_id ? absint( $user_id ) : get_current_user_id();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here tutor_utils()->get_user_id( $user_id )

Comment thread restapi/RestAuth.php
*/
public static function can_view_user_private_fields( $target_user_id, $viewer_id = 0 ) {
$target_user_id = absint( $target_user_id );
$viewer_id = $viewer_id ? absint( $viewer_id ) : get_current_user_id();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment thread restapi/RestAuth.php
}

$username = sanitize_text_field( (string) $request->get_param( 'username' ) );
$password = (string) $request->get_param( 'password' );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Security]
We must enforce a maximum password length to prevent denial-of-service attacks caused by hashing excessively long passwords.

Comment thread restapi/RestAuth.php
* Refresh access token (rotates refresh token).
*
* @since 4.2.0
* @since 4.2.0 No API key/secret; reuses kid stored with the refresh token.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate since tag

Comment thread restapi/RestAuth.php
*
* @return string
*/
private static function encode_jwt( array $claims ) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can create a JwtService.php class to handle all JWT-related operations and a TokenRevocationService.php class to manage token invalidation and revocation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants