(That title, just so you know, should absolutely be read in the voice of the voiceover dude from The Muppet Show‘s Veterinarian’s Hospital.)

In the week and a half or so since my last entry, I’ve been working on my registration branch, trying to do All The Things ™. Primarily, I wanted to check the username for uniqueness (well, a close approximation thereof) and I wanted to check the email address against a list of domains known to be used by spammers. The username check went pretty smoothly, all things told. I’m using this:

function checkUniqueUsername($normalizedUsername) {
echo "
Unique Username Check
";
// Get the database instance
$pdo = database::getInstance()->getConnection();
//Construct the SQL statement and prepare it.
$sql = "SELECT COUNT(username) AS num FROM users WHERE username = :username";
$stmt = $pdo->prepare($sql);

//Bind the provided username to our prepared statement.
$stmt->bindValue(':username', $normalizedUsername);

//Execute.
$stmt->execute();

//Fetch the row.
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if($row['num'] > 0) {
return false;
} else
echo 'Username is unique.';
return true;
}

Basically, this is a function that is called from the registration page that grabs our database connection info and then asks the database to perform a count of usernames on the users table where the username is the bindValue of :username (meaning $normalizedUsername).

In MySQL, this will return a single-column table, where the column is named “num“. And it will say 0 if there are no entries with that queried username and it will say 1 (… or more?) if 1 (… or more?) exists. Then I’ll return false if there are any entries and return true if there aren’t.

Perfectly logical, perfectly reasonable, works just fine.

So imagine my utter shock and consternation when I tried almost identical code to check the domain of the submitted email address and nothing was flipping showing up in my results. Like, absolutely nothing. Dumping out $row with a var_dump($row) returned a boolean false. What on earth? From my Instagram:

Domain on email is good. But why?
But whyyyyy is the domain gooooood?

You can see in the image that I tested out the query on the MySQL command line and I got 1 row. So why was I getting a boolean false and why wasn’t I getting my 1 count in the row?!

Obviously, the MySQL query, when I typed it out manually on the command line, was fine. And I had logs running, so I could see that the bindValue was working and was fetching the domain in question. So what was the issue?

I took the weekend off because I went to my friend’s wedding in Ontario — I was a bridesmaid — and, rather than sleep like the dead on Sunday night, I coded. I was getting ticked off. Something wasn’t right.

I tried all kinds of things — different bindValue. No bindValue. Different files. Taking the checks out of the functions. Putting them back in. And why on earth was the username check doing just fine but the domain blacklist wasn’t?!?

I finally figured it out on Tuesday evening. I was googling stuff, for the billionth time, and found this issue on GitHub. In it, down a bit, it says:

Running a GRANT command to allow SELECT for this user solved the problem

I froze, the realization crashing over me like a wave. But of course the user has permission, my queries worked… just… fi… I was logged in as root. OH MY GOD, I WAS LOGGED IN AS ROOT.

Fun fact: the user didn’t have permission on the email blacklist table. Just the user table. That’s why everything was empty! That’s why I was getting a boolean false! And I was consistently logging in to the MySQL database as root, which is why those queries worked! I quickly made an edit to my creation SQL file and then manually added permission for my user — voilà. It worked perfectly.

It worked so well that I added the following checks:

  • email blacklist (the original one I’d been having trouble with)
  • disallowed usernames (like admin, etc)
  • voluntary email blacklist (for people who want to opt-out and don’t want to receive invitations to the game)

I still need to compile a list of profane words to prevent people from using them as a username, then add that check.

I also want to add in a unique email check, which will require:

  • storing the email address entered and use this for any communications
  • transforming it all to lowercase for uniqueness, so JULIE @ is the same as julie @
  • stripping periods from the username portion of the email address (julie [at] gmail.com is the same as j.ulie [at] gmail.com is the same as j.u.l.i.e [at] gmail.com) to check for uniqueness
  • stripping any plus signs and anything that follows up through to the @ sign (julie+test [at] gmail.com, for example, is the same as julie+test2 [at] gmail.com) to check for uniqueness

I also want to implement some JavaScript checks, which, shockingly, I actually know how to do:

  • Username between 5 and 16 alphanumeric characters in length
  • Password strength
  • Email address (1) matches email address (2)

Once all the checks are implemented, I want to send a verification email to the email on file, which ideally also holds a “delete me” link that, when clicked, will trigger insertion of that email address into the voluntary blacklist. Of course, if they click the verification link, this will activate the actual account.

Plus, I probably want to ask permission for whether or not I can add them to a mailing list. That seems useful.

So, I’m feeling pretty awesome for realizing it was a permissions issue. I’m feeling slightly less awesome for having made the mistake of testing with root versus testing with the actual user in question, or having forgotten to give the user the right privileges in the first place, but I still fixed it.

And that feels pretty great.

Leave a Reply

Your email address will not be published. Required fields are marked *