I spent 2 months working on a user management library, with the intention of selling it on Codecanyon (I've never sold anything there before).
On Codecanyon, there's a screening process for everything that's uploaded, and five days later, aka today, I got an email from them saying this:
Unfortunately your submission doesn't meet our current minimum technical quality requirements for the category it was submitted to. Please consider taking some time to familiarize yourself with our current library and quality levels of popular items before resubmitting.
Here's the demo: http://nauthentication.recgr.com/
Here's a source code excerpt from the main class (user registration method):
/**
* Account registration method.
*
* Validates the input received and inserts it into the database. By default, this method will also send the activation email, and require the user to activate (verify the email address) its account before being able to log in. If $Account_Activation_Required is set to *false* in the Config file, then this method will not send the activation email, but it will instead activate the account automatically, and, will instead of the *activation token*, return *user id*.
*
* *Example:*
* ```php
* // assumes $_POST is coming from a registration form, and can look something like this:
* $_POST = array(
* 'email' => 'jane.doe@gmail.com'
* 'password' => 'my_password_456'
* // optionally, more fields
* );
*
* \NAuthentication\Auth::register($_POST);
* ```
*
* @param string[] $userData Supplied user credentials to insert into the database records. This will usually be email address and password. Passed as an array, where field names are array indexes, and field values are array values.
*
* @throws NAuthException
*
* @return string|int By default, returns user's activation token which was sent to the supplied email address. The token is to be used in activate() method. If $Account_Activation_Required is set to *false* in the Config file, user ID (UID) will be returned instead.
*/
public static function register(array $userData) {
// If either, but not both email or password fields are enforced, that's an NAuthException (password requires email to verify)
if (in_array('email', Config::$Mandatory_Params) xor in_array('password', Config::$Mandatory_Params)) {
throw new NAuthException(Config::$Auth_Exception_Messages['emailPasswordDependency'] . __METHOD__, 14);
}
$expectedVsSupplied = array_diff_key(array_flip(Config::$Mandatory_Params), $userData);
if (count($expectedVsSupplied) > 0) {
throw new NAuthException(Config::$Auth_Exception_Messages['mandatoryParamsMissing'] . implode(", ", array_flip($expectedVsSupplied)), 11);
}
// Mandatory params mustn't be empty
foreach (Config::$Mandatory_Params as $mandatoryParam) {
if (empty($userData[$mandatoryParam])) {
throw new NAuthException(Config::$Auth_Exception_Messages['mandatoryParamEmpty'] . $mandatoryParam . ' in ' . __METHOD__, 12);
}
}
if (isset($userData['email'])) {
if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) throw new NAuthException(Config::$Auth_Exception_Messages['invalidEmailFormat'], 1);
if (self::userExists(['email' => $userData['email'], 'activated' => 'Y'])) {
throw new NAuthException(Config::$Auth_Exception_Messages['userEmailAlreadyExists'], 2);
}
}
// check if there are any input fields that don't have a coresponding database field, and throw an NAuthException if there are (there's an identical check in authenticate(), but that simply discards any non-existing fields)
$usersTableFields = Utils::getTableFields(Config::$Users_Tablename);
$neededFieldsVsExistingFields = array_diff_key($userData, array_flip($usersTableFields));
if (count($neededFieldsVsExistingFields) > 0) {
$usedFieldsNotInDatabase = implode(', ', array_flip($neededFieldsVsExistingFields));
throw new NAuthException(
Config::$Auth_Exception_Messages['missingDatabaseFields'] . 'Used fields which are not in database: ' . $usedFieldsNotInDatabase . ' in table ' . Config::$Users_Tablename . ', at ' . __METHOD__, 4
);
}
if (isset($userData['password'])) {
if (strlen($userData['password']) < Config::$Minimum_Password_Length) throw new NAuthException(Config::$Auth_Exception_Messages['passwordTooShort'] . Config::$Minimum_Password_Length, 5);
$userData['password'] = password_hash($userData['password'], PASSWORD_BCRYPT);
}
$link = Utils::getDatabaseInstance();
if (self::isIPWithinRetriesLimit('register', (isset($userData['email']) ? $userData['email'] : NULL)) === false) {
// in case user already tried to sign up, but the activation email never came, and the user didn't use the
// resendActivationEmail feature, resend it for the user
$activationToken = self::getTokenFromLogs('register', (isset($userData['email']) ? $userData['email'] : NULL));
if (is_null($activationToken)) {
// this should never execute, except if someone edited tablesCleanUp() intervals on their own and and messed it up (rows in the logs table should always have longer lifetime than the rows in activations table)
throw new NAuthException(Config::$Auth_Exception_Messages['registerLimitExceed'], 27);
}
return self::resendActivationEmail($activationToken);
}
// PART ONE: Insert User into DB
$sql = Utils::generatePdoInsertSql($userData, Config::$Users_Tablename);
$stmt = $link->prepare($sql);
$stmt = Utils::bindPdoParams($stmt, $userData);
if (!$stmt->execute()) {
throw new NAuthException(Config::$Auth_Exception_Messages['registrationQueryFailed'], 6);
}
$uid = $link->lastInsertId();
$stmt = null;
// PART TWO: Generate activation token and insert it into DB
$activationToken = self::insertTokenToDatabase($uid, 'activation', $link);
// PART THREE: Compose and send activation email
if (Config::$Account_Activation_Required) {
if (isset($userData['email'])) {
$activationMessage = self::composeEmailMessage('activation', $activationToken);
$sendEmail = self::sendEmail($userData['email'], Config::$Account_Activation_Subject, $activationMessage);
}
self::addIPToLog('register', (isset($userData['email']) ? $userData['email'] : NULL), $activationToken);
} else {
return self::activate($activationToken, $link);
}
// GC
if (1/100 === 50) {
self::tablesCleanUp($link);
}
return $activationToken;
}
Do you guys also think it's bad?
Improvement suggestions I've gotten so far, and that I yet have to do, are regarding line length of my lines - I have to shrink them so they're about 80 chars long.
Another question that's not directly related to code review: what do you guys think my next step should be? Should I sell it on a bunch of other platforms buying/selling platforms, or release it on Github and try to make money by people hiring me to customize/integrate it for them? My idea was to set a low price for the library itself ($5), but try to make some real profit with people hiring me to customize/implement it for them.
-Nino