Skip to content

Invite tokens never expire — add 24h TTL (filterable) #11

Description

@dd32

Summary

Invite tokens currently have no TTL — once created, they remain valid indefinitely until revoke_invite() is called. A lost URL from months ago is still live.

Agreed in review to add a 24-hour expiry.

Where

includes/class-map-invite.phpcreate_invite_url(), get_post_by_token(), handle_invite_request().

Suggested fix

Store a created-at timestamp alongside the (hashed, per #10) token. On lookup in get_post_by_token() and in handle_invite_request(), reject tokens older than MAP_INVITE_TTL (default DAY_IN_SECONDS, filterable).

Sketch (assuming we land #10 first so the storage shape changes anyway):

const TOKEN_META_KEY = '_map_invite_token';  // now stores an array: [ 'hash' => ..., 'created' => int ]

public static function create_invite_url( int $post_id ): string {
    $token = wp_generate_password( 32, false );
    update_post_meta(
        $post_id,
        self::TOKEN_META_KEY,
        array(
            'hash'    => hash( 'sha256', $token ),
            'created' => time(),
        )
    );
    return self::build_url( $token );
}

public static function get_post_by_token( string $token ): ?\WP_Post {
    if ( '' === $token ) {
        return null;
    }
    $hash = hash( 'sha256', $token );
    $ttl  = (int) apply_filters( 'map_invite_ttl', DAY_IN_SECONDS );

    // Query by hash via a meta_query on the serialized sub-key.
    // Simpler: query by post_status etc., then hash_equals + TTL check in PHP.
    …
}

The array meta shape plus a TTL check means lookup can no longer be a single meta_value equality query. Two realistic approaches:

  1. Store hash as the meta value (scalar) and keep created in a second meta key _map_invite_created. Preserves the simple meta_value lookup; requires updating both on create/revoke.
  2. Store as serialized array (as sketched). Query posts by a lightweight hash index (either by also keeping a _map_invite_hash scalar alongside, or by shifting to a custom table — related to the broader storage discussion in Update README with project description and Playground link #7).

Option 1 is the smaller change.

Handling expiry in handle_invite_request()

When an expired token is presented, surface the same "invalid or revoked" error message (wp_die) — don't leak whether it was expired vs never-existed vs revoked.

Optionally: delete expired tokens as a side-effect of encountering them, so they don't persist in the DB forever.

Filter

apply_filters( 'map_invite_ttl', DAY_IN_SECONDS ) so sites that want longer/shorter can override.

Tests to add

  • Token older than TTL is rejected.
  • Token within TTL is accepted.
  • Filter can override TTL.
  • handle_invite_request with expired token shows the same error page as an invalid token.

References

Internal security review, Apr 2026. Depends on / coordinates with #10.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions