Forum

Thread tagged as: Addons, Suggestions, Members

Perch Members - One Use Links

Hi All,

I am building an online shop using the official perch_shop which is capable of selling both consignment and digital products.

One of the issues I have encountered revolves around allowing customers to purchase digital products as a guest (i.e not creating a username/password combo) and how to securely allow a customer to reset their password. To resolve this I thought that implementing a one use code system would be the right way to go, thus authenticating the customer without exposing usable information unnecessarily in an email.

The code I have written is currently extending the PerchShop_Runtime and is as follows:

Code

PHP

class CustomShop_MembersRuntime extends PerchShop_Runtime
{
    private static $instance;

    private $timeout;

    public static function fetch()
    {
        if (!isset(self::$instance)) self::$instance = new CustomShop_MembersRuntime ;
        return self::$instance;
    }

    /**
     *
     * Delete a unique code
     *
     * @param $memberId int
     * @param $code string
     * @return bool
     *
     */

    public function deleteCode($memberID, $code)
    {
        // Create DB handle
        $db         = PerchDB::fetch();

        // Create delete query
        $sql = "DELETE FROM `shop_codes` WHERE `memberID` = '".$memberID."' AND `code` = '".$code."' LIMIT 1";

        $result = $db->execute($sql);

        if ($result) {
            return "This code has now been invalidated";
        }

        return "Error, code was not deleted";
    }

    /**
     *
     * Create a unique code
     *
     * @param $memberID int
     * @return $code string
     *
     */

    private function createCode($memberID) 
    {
        // Create a new code
        $code = hash("sha1",uniqid($memberID, true));

        // return a 20 character string
        return $code;
    }

    /**
     *
     * Assign a code to a specific member
     *
     * @param $memberID int
     * @param $code_type int 1
     * @return bool
     *
     */

    public function assignCode($memberID, $code_type = 1)
    {
        // Create DB handle
        $db         = PerchDB::fetch();

        // Create the code
        $code       = $this->createCode($memberID);
        if (!is_int($code_type)) {
            $code_type = intval($code_type);
        }

        // Create expiry information
        $DT         = new DateTime;
        $now        = $DT->format("Y-m-d H:m:s");
        $DT->modify('+1 day');
        $expires    = $DT->format("Y-m-d H:m:s");

        // Create insert query
        $sql  = "INSERT INTO `shop_codes` SET `memberID`='".$memberID."', `code`='".$code."', `code_type` = ".$code_type.", `expires_at` = '".$expires."', `created_at` = '".$now."' ";

        // Assign a code to a given member
        $result = $db->execute($sql);

        if ($result) {
            // If the query runs, return true
            return true;
        }

        // If the query errors, return false
        return false;
    }


    /**
     *
     * Remove all codes not used within the last 24hrs
     *
     * @return $result
     *
     */

    public function clearUnusedCodes() 
    {
        // Create DB handle
        $db     = PerchDB::fetch();

        // Get the current time
        $DT     = new DateTime;
        $now    = $DT->format("Y-m-d H:m:s");

        // Create the selective delete query
        $sql = "DELETE FROM `shop_codes` WHERE `expires_at` < '".$now."'";

        // Delete all codes which have timed out within the last 24hrs
        $result = $db->execute($sql);

        if ($result) {
            // If the query runs, return true
            return "Codes have been deleted";
        }

        // If the query errors, return false
        return "No codes deleted";
    }
}

SQL

TABLES

#### UP ####

CREATE TABLE `shop_code_types` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code_type` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `shop_codes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `memberID` int(10) NOT NULL,
  `code` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `code_type` int(2) NOT NULL,
  `expires_at` datetime DEFAULT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `fk_shop_code_types_id` (`code_type`),
  CONSTRAINT `fk_shop_code_types_id` FOREIGN KEY (`code_type`) REFERENCES `shop_code_types` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


#### DOWN ####

DROP TABLE `shop_codes`;
DROP TABLE `shop_code_types`;

DATA

INSERT INTO `shop_code_type` (`id`,`code_type`) VALUES (1,"Password Reset");
INSERT INTO `shop_code_type` (`id`,`code_type`) VALUES (2,"Download Link");

Discussion/Questions

With a little modification this code has the basics to generate and delete codes based upon details gathered from the perch2_members table (in this case memberID). For password resets, I also plan on using information drawn from either the perch2_members or perch2_shop_customers tables for further pieces of authentication before allowing the user to reset their password. I have yet to start coding for the download link scenario as I would like to finish the password functionality first.

My problems start when it comes to combining the functionality shown above with the existing perch_members and perch_shop addons. I need to find ways of resolving the following:

1) How to extend/overwrite the perch_members\runtime.php->perch_members_form_handler() method to replace the exisiting "reset" case. 2) How to integrate the use one time links methods with the perch_members addon functionality.

Any feedback and/or suggestions on how this could be improved or replaced by existing perch functionality would be very much appreciated!

Stephen Holt

Stephen Holt 0 points

  • 5 years ago
Drew McLellan

Drew McLellan 2638 points
Perch Support

We've been rolling password reset tokens throughout the product, and Members will surely be next.

I'm not sure I really follow the other use case.

It should go without saying that extending private classes on beta software is a terrible idea! If you're on the beta it'd be better to suggest your use case and help shape the product to achieve the sort of thing you need. That's the point of the programme.

Hi Drew,

Thanks for getting back to me on this. The second use case was written in a hurry and without much thought about specifics so i'll expand on it here.

The main scenario I am trying to cover is as follows:

A customer is given access to a downloadable resource but they do not have a password in order to login to the members area to view that resource bucket.

I would like to be able to send them a one use link which will authenticate them and download the resource before expiring. However, just having the ability to generate one time links within the members app would be handy for a number of scenarios.

On your last point, I agree that extending beta software is pretty terrible idea and wherever possible I am trying to avoid extending and modifying perch code. However I am under time pressure to get required functionality finished before launch of our e-commerce site.

There are a number of suggestions I would like to put forwards but I only have access to the general slack channel at this time. If you could send me an invite it would be very much appreciated.

Drew McLellan

Drew McLellan 2638 points
Perch Support

If you're not on the beta, where did you get the software from?