This is driving me nuts, I have a login function that checks to make sure that the users credentials are correct, and also checks to see if an 'activation' field for that user is empty (if it wasn't, it means that they haven't activated yet and therefore shouldn't be able to log in). If all of those conditions check out fine, it returns a user id in a variable, and if not it returns false.
Function
The function runs correctly right up until I add the if statement that checks if the variable $activation is empty, using empty(). If the field is truly empty, it returns the user_id like it's supposed to, but if the field isn't empty and still contains the 40 char activation code - it also lets the user log in. Which is ridiculous.
Here is the login function (with irrelevant portions removed):
function loginCheck($email, $password) {
$stmt = $dbh->prepare("SELECT `salt`,`activation` FROM `users` WHERE `email`= :email LIMIT 1");
$stmt->bindParam(':email', $email);
$stmt->execute();
if ($stmt->rowCount() == 1) {
$salt = $stmt->fetchColumn(0);
$activation = $stmt->fetchColumn(1);
if (empty($activation)) {
// another few unrelated tasks and query here to grab user id which is returned below
if ($stmt->execute()) {
return $stmt->fetchColumn(1); // the returned user ID
} else {
return false;
}
} else {
return false; // It should return this false here because the field IS NOT empty!
}
} else {
return false;
}
}
1) I have performed the first query manually, and it does in fact select the fields salt and activation flawlessly.
2) I have checked to make sure that the column being fetched and applied to the var $activation is correct, it is the second column so $activation = $stmt->fetchColumn(1) is fine.
Page
Now on the login.php page which calls the above function, here is the code relating to calling the function and logging in:
$login = loginCheck($email, $password);
if ($login === false) {
$errors[] = 'Unable to log you in';
}
if (!empty($errors)) {
foreach ($errors as $error) {
echo $error, '<br />';
}
} else {
$_SESSION['user_id'] = $login;
header('Location: you/default.php');
exit();
}
I've looked and looked and can't find any errors. Why on earth is this occurring?
EDIT
The activation field in my MySQL table is set to varchar(40) with a collation of utf8_general_ci, and since the activation field is populated with numbers and letters, I'm assuming it's a string.
And yes, the user_id that is returned is the one that relates to the user logging in, so that is correct.
As you can see here: http://php.net/manual/en/pdostatement.fetchcolumn.php,
There is no way to return another column from the same row if you use
PDOStatement::fetchColumn() to retrieve data.
This is because each time you call fetchColumn it will apply over the row next to the row on which the previous call applied.
$salt = $stmt->fetchColumn(0);
$activation = $stmt->fetchColumn(1);
The second call to fetchColumn() is working over the row next to that of the first call. In your case, as there is only one row, fetchColumn() returns NULL, so that's why activation appears as empty.
Use fetch() to retrieve an array with all the values of the row:
$row=$stmt->fetch();
$salt=$row[0];
$activation=$row[1];
Consider this approach..
Columns
`activation_code` varchar(40) not null,
`activated` tinyint(1) not null default '0',
Now create an activate function elsewhere, once complete update activated === 1 for the user
When you do you login check, consider:
Check Username
Check Password
Check activated === 1
I think you are using 'char' data type in the database for the activation. So you better try this code.
if (trim($activation)!= "")
{
}
Cheers!
Prasad.
Related
I'm working on some web code which uses codeigniter and the built in querybuilder to access a database.
I attempt to load data from the database for the current user
$userModel = $this->loadModel('ModelUser');
$name = $this->session->userdata('user');
$user = $userModel->getUser($name);
This is the code forgetUser:
function getUser($username)
{
$this->db->where('username',$username);
$query = $this->db->get('tblusers',1);
$res = $query->result();
if ($query->num_rows() > 0)
{
log_message('debug','Got user. ID = '.$res[0]->id);
foreach($res[0] as $key => $val)
{
$this->$key = $val;
}
return $this;
}
else {
log_message('info','failed to find user '.$username);
return NULL;
}
}
This works fine except when I let the session expire, in which case I get the details of another user.
These are the results of testing getUser:
$userModel->getUser("Admin"); //Got user. ID = Admin_ID
$userModel->getUser("john"); //Got user. ID = John_ID
$userModel->getUser(""); //Failed to find user
$userModel->getUser(null); //Failed to find user
When I log in as Admin then let the session timeout, the top snippet logs the message:
Got user. ID = John_ID
I would expect either Got user. ID = Admin_ID or Failed to find user
When $this->session->userdata('field') does not find an entry, it returns 0 instead of the "" or null that I was testing against.
Logging $this->db->last_query() showed this since the resulting query was:
SELECT * FROM tblusers WHERE username = 0 LIMIT 1;
MySQL will automatically convert strings to integers where the string starts with an integer. A string without an integer will be cast to 0 as explained in this answer. The query was returning the first entry it came across instead of finding no rows as any string that didn't start with 1-9 would match the WHERE clause.
I added a clause to getUser to return NULL if $username == ''. I did try === 0 but that yielded the same error so there's some type coercion going on that I'm not 100% certain of, but this covers the issue nicer than handling the case each time getUser is called.
I am trying to check my database for a key that has already been put in. If the key exists then I need it to check to make sure the username field hasn't been filled. If it has then it needs to throw an error so that it doesn't update and overwrite the information already stored in the database.
Everything works. The update functions etc. the only part that does not work is the checking if the key exists and if the username portion is filled(not sure exactly how to do that) before updating the database.
Thanks,
Cameron Andrews
Code:
// If the Register form has been submitted
$err = array();
if(strlen($_POST['username'])<4 || strlen($_POST['username'])>32){
$err[]='Your username must be between 3 and 32 characters!';
}
if(preg_match('/[^a-z0-9 _]+/i',$_POST['username'])){
$err[]='Your username contains invalid characters!';
}
if(!checkEmail($_POST['email'])){
$err[]='Your email is not valid!';
}
$resultN = mysql_query("SELECT * FROM Users WHERE key='".$_POST['kgen']."'");
while($row = mysql_fetch_array($resultN))//for the results that are returned set the local variables
{
if($_POST['kgen'] == $row['key']){
if($_POST['username'] == $row['usr']){
$err[]='Username already in use';
}
}else if($_POST['kgen'] == ""){
$err[]='Invalid Key Code!';
}else{
$err[]='Error occured please try again';
}
}
if(!count($err)){
// If there are no errors
$_POST['email'] = mysql_real_escape_string($_POST['email']);
$_POST['username'] = mysql_real_escape_string($_POST['username']);
$_POST['pass'] = mysql_real_escape_string($_POST['pass']);
// Escape the input data
$theName = $_POST['name'];
$theUser = $_POST['username'];
$thePass = $_POST['pass'];
$theEmail = $_POST['email'];
$theType = "member";
$theRegIP = $_SERVER['REMOTE_ADDR'];
$theDate = "NOW()";
$theKey = $_POST['kgen'];
// If everything is OK register
mysql_query("UPDATE cad.Users SET name = '$theName', usr = '$theUser', pass = '$thePass', email = '$theEmail', type = '$theType', regIP = '$theRegIP', dt = '$theDate' WHERE Users.key = '$theKey'");
Here is how I would approach it:
I would use mysqli or PDO instead of deprecated mysql functions.
I would rewrite all the queries to use prepared statements instead of concatenating your query string together - you have significant SQL injection vulnerability now.
But, since I am not going to rewrite your entire section of code for you, the rest of my approach will be described based on your current mysql/concatenated-query-string approach.
I would put a unique index on name field, but allow NULL value on the field.
I would simply run an update query rather than trying to run an unnecessary select plus an update.
UPDATE cad.Users
SET
name = '$theName',
usr = '$theUser',
pass = '$thePass',
email = '$theEmail',
type = '$theType',
regIP = '$theRegIP',
dt = NOW() /* don't pass 'NOW()' in single quotes as you are currently doing */
WHERE
Users.key = '$theKey'
AND User.name IS NULL;
If you get an error here you should look at error messaging to determine if update failed due to a unique constraint violation (user tried to enter a name that was already used in another record associated with a different key), or some other unexpected reason.
Assuming there was no error, I would then call mysql_affected_rows() (or appropriate equivalent in mysqli or PDO). If the return value is 1, an update was made. If the return value is 0, then no update was made because you did not have any rows that satisfied the WHERE condition.
If you get 0 affected rows, you can re-query the database if you really want to determine if the cause was no matching key or an existing user name.
SELECT name FROM Users WHERE key = '$theKey';
If you get no rows in the result it is because the key is missing, otherwise it is because the name was not a NULL value.
The net is that in the happy path use case, you only make a single query against the database rather than two, with two queries only being necessary if you want to determine the reason no update occurred for those cases. Your current approach always requires 2 queries.
First of all, you should see this. At second, i see, you have strange logic. Try to simplify it. :) As for me, i think it should looks like this:
<?php
if (strlen($_POST['username']) < 4 || strlen($_POST['username']) > 32) {
throw new RuntimeException('Your username must be between 3 and 32 characters!');
} elseif (preg_match('/[^a-z0-9 _]+/i', $_POST['username'])) {
throw new RuntimeException('Your username contains invalid characters!');
} elseif(!checkEmail($_POST['email'])) {
throw new RuntimeException('Your email is not valid!');
} elseif (empty($_POST['kgen'])) {
throw new RuntimeException('Invalid Key Code!');
}
$resultN = mysql_query("SELECT * FROM Users WHERE `key`='{$_POST['kgen']}' AND `usr`='{$_POST['username']}';");
$user = mysql_fetch_array($resultN);
if (!empty($user)) {
throw new RuntimeException('Username already in use');
}
// if all is fine - update
You can use exceptions for checking error. Benefit - you don't go to next check, if failed prev. You also have ability to show user exception message or reason(better use custom exception for this). Negative - you can't get list of errors.
I am importing GMail contacts from Google API and just want to persist unique email ids into database.
Is it possible to insert unique records using codeigniter's Active Record?
Does CodeIgniter provide it out of the box?
If I make the column unique the query throws exception. After digging into documentation I understood that CodeIgniter doesn't provide try catch blocks.
your email field have to be unique in your table indexes.
$query_string = $this->db->insert_string('table', $data);
$query_string = str_replace('INSERT INTO', 'INSERT IGNORE INTO', $query_string);
$req = $this->db->query($query_string);
if($req->affected_rows() == 1) {
//data inserted
} else {
//email exists already
}
If you use the IGNORE keyword, errors that occur while executing the
INSERT statement are treated as warnings instead. For example, without
IGNORE, a row that duplicates an existing UNIQUE index or PRIMARY KEY
value in the table causes a duplicate-key error and the statement is
aborted. With IGNORE, the row still is not inserted, but no error is
issued. Data conversions that would trigger errors abort the statement
if IGNORE is not specified. With IGNORE, invalid values are adjusted
to the closest values and inserted; warnings are produced but the
statement does not abort.
MySQL INSERT syntax
CI Database Helper
You can create a base model (application/core/MY_Model.php) and implement a save function to insert if new and update if exists. Extend your model from it.
This is an extract from http://thephpcode.com/blog/codeigniter/a-smart-codeigniter-model
public function save($data,$tablename="")
{
if($tablename=="")
{
$tablename = $this->table;
}
$op = 'update';
$keyExists = FALSE;
$fields = $this->db->field_data($tablename);
foreach ($fields as $field)
{
if($field->primary_key==1)
{
$keyExists = TRUE;
if(isset($data[$field->name]))
{
$this->db->where($field->name, $data[$field->name]);
}
else
{
$op = 'insert';
}
}
}
if($keyExists && $op=='update')
{
$this->db->set($data);
$this->db->update($tablename);
if($this->db->affected_rows()==1)
{
return $this->db->affected_rows();
}
}
$this->db->insert($tablename,$data);
return $this->db->affected_rows();
}
If your data comes from a from, CI provides a form_validation class that can validate your form on the server side.By form_validation has a rule called is_unique its checks if the given value is already exist on the database or not. you can see a complete and clear explanation here
Or, you can check it manually before inserting your email in that unqiue column.
$this->db->select('email');
$this->db->where(array('email'=>$email));
$query = $this->db->get('yourtable');
if($query->num_rows() > 0){
// the query returned data, so the email already exist.
}else{
// the email not exists, so you can insert it.
}
I have recently switched from Mysql_* to PDO, heard it's the new, better more secure way of connecting and working with MySQL databases.
I have learned many basics of it like queries, prepare, etc.
What do I want to do
I currently have two text fields, named 'Email' and 'ID'.
When user registers, he enters his email, after he registers he receives his own ID, something unique.
I want him to be able to check the status of his account, without any passwords.
Simply by entering his email, and id and clicking 'submit'.
After clicking submit, the system should check if there's a column with the same email & ID.
If there is a column with these same exact email & IDs, then I can create a while loop to grab information from his account's column like creation date, and others..
My Question
How would I do this?
There's what I've done so far:
if (isset($email) && isset($id) && isset($submit)) {
$fetch = $connect->prepare("SELECT * FROM users WHERE email = :email LIMIT 1");
$fetch->bindValue(':email', $email);
$fetch->execute();
$validate = $fetch->fetchColumn();
if ($validate == 0) {
echo 'failed';
} else {
echo 'not failed';
}
while($row = $fetch->fetch( PDO::FETCH_ASSOC )) {
//We can fetch here...
}
}
My friend suggested me to use fetchColumn() which is replacing mysql_num_columns function, but It doesn't seem to work.
I enter a right email address, and it is still echoing 'Failed' instead of 'Not failed'.
Why doesn't this method work? Have I done this wrong?.
Thanks!
I don't think you want to fetch columns, but rows. You also don't actually check the ID either, but you can just add an AND condition to your query if necessary.
$fetch->execute();
if ($fetch->rowCount()) {
echo "Row was returned; match found";
}
else {
echo "No match found";
exit;
}
$row = $fetch->fetch(PDO::FETCH_ASSOC);
There will probably only be one row for you to fetch as well, but in case there aren't you can use the while loop as you did above.
$validate is a string containing the email address. When doing if ($validate == 0) you're typecasting $validate to an integer, and any non-numeric string becomes (int) 0, and so the comparison is always true.
Change to if ($validate === FALSE).
I am trying to implement a function that will insert a new entry in a database if a field with same name (as the one given) doesn't already exist. In particular I want to restrict duplicate usernames in a table.
The only way I could think was to run a select query and then if that doesn't return anything run the insert query. For some reason though I cant get it to work...
My db select Function
function getAllUsers($user)
{
$stmt = $this->db->stmt_init();
$stmt->prepare('SELECT username from users where username=? ');
$stmt->bind_param("s", $user);
$stmt->bind_result($username);
$stmt->execute();
$results = array();
while($stmt->fetch())
{
$results[] = array('username' => $username);
}
$stmt->close();
return $results;
}
My php code (this is in a different page)
foreach ($GLOBALS['db']->getAllUsers($_POST['username']) as $i)
{
$results = "".$i['username']."";
break;
}
if(strcmp($results, "")==0)
{
if($GLOBALS['db']->addUser($_POST['username'],$_POST['password']))
{
session_destroy();
echo "registerSucces";
}
else
{
session_destroy();
echo "registerError";
}
}
else
{
echo "userNameExists";
}
Can you see whats wrong with this???
Thanks
Mike
Still cant find how to make the above code work but just in case someone needs this: A temporary simple solution is not to compare strings at all and instead have a counter in the foreach loop and then check that upon a desired number(0 in my case)...
SQLite supports the UNIQUE constraint. Simply declare a UNIQUE index on the column and check whether an INSERT fails.
If you're using the username as the primary key in your tables, you can use the INSERT OR IGNORE command instead of checking to see if the username already exists.
If SQLite finds that an INSERT OR IGNORE command will conflict with an existing row in your table, it will simply ignore the command.
You can find out more stuff in the SQLite documentation for the INSERT command