Problems, need help? Have a tip or advice? Post it here.
9 posts Page 1 of 1
I'm using Couch on a website that includes a members section for the organization's internal documents and communications. I would like to use Couch's user management and access control to provide access to the private area, but there seem to be limitations to Couch's functionality that I would like to overcome.

1) Since registered users don't have access to the admin panel, there seems to be no way to allow them the ability to manage their own username and password. I don't mind issuing a username/password manually, but I would like the user to be able to change them to something personal, or to be able to change the password if they forget and have to reset it.

2)My current configuration places the members area in it's own subdomain and protects the entire directory with htpasswd. Is there a way to use Couch to protect an entire directory like this rather than designating the access level of each page and template, then using url cloaking to protect every linked file?

2a)I haven't used url cloaking yet and don't fully understand how it works. Does cloaking prevent all unauthorized access to the file, including robots and those who know or guess at the path name?

My php skills are rudimentary, but if this capability doesn't already exist, I'd be willing to poke around in the code (now that it's open, and when I can find the time) to see if I can implement it for myself. I imagine that you'd just need to pluck the individual user management page out of the code and allow access to a logged-in user through a link. But I'd need to find the appropriate section(s) of code and learn the appropriate variables and dependencies. I haven't had much of a chance to look closely at the code yet, but I look forward to monkeying around with it to see what I can learn.
Hello and welcome, Tim :)

I'll try to answer your queries -
but I would like the user to be able to change them to something personal, or to be able to change the password if they forget and have to reset it.

This has been mentioned in the docs, and I reiterate, Couch's user-management/access-control part is not up to the mark (yet). For now this feature is pretty rudimentary and not really suitable for creating membership type sites as would be evident by the lack of the very feature you mentioned.

That said, if it is only the capability of modifying self username/password that is required, I think it shouldn't be difficult to implement using a little PHP on the frontend templates.

If you wish, I can try and get you something. Let me know.
Is there a way to use Couch to protect an entire directory like this rather than designating the access level of each page and template, then using url cloaking to protect every linked file?

Couch can control only the templates (and their cloned pages) that it manages so cannot really control access to the entire folder.

For controlling access to templates, it is not necessary to set checks on individual pages. Check can be set on a template itself.
So, for example, if your subdomain consists of two templates - index.php and blog.php you can control access to both sections by putting the following somewhere at the top of each template -
Code: Select all
<cms:if k_logged_out >
    <cms:redirect k_login_link />
</cms:if>


Does cloaking prevent all unauthorized access to the file, including robots and those who know or guess at the path name?

Primarily cloaking (as the name suggests) hides the real location of the files from everybody so that the files cannot be accessed directly. Now that the files can be accessed only through the cms:cloak_url tag, this gives us the opportunity to put in further constrains - e.g. an expiry time for the link, level of the user who can access it etc.

So to answer your question, if you specify the 'access_level' parameter with the cloak_url tag, yes it would prevent all unauthorized access including robots etc.
Code: Select all
<cms:cloak_url
    link="<cms:show k_admin_link/>uploads/file/secure/test.jpg"
    access_level='2'
/>

Finally, please feel free to explore and modify the code to your fill :)

Hope this helps.
Others have said it, but let me add my voice to the chorus. I've never seen better documentation and support for, well… anything. Even just your offer to whip up some code for me goes beyond the call of duty and is more than my small license fee is worth. I understand that the more advanced user management features of Couch are still in development.

In the meantime I came up with my own basic front end solution. It appears to be working, but there are some areas that you or someone else may be able to improve.

For instance, I couldn't figure out how to access Couch's stored database variables or security functions. I was pleasantly surprised that the password hashing function worked out of the box, but I don't know why, I just copied and pasted. Like I said before, my skills and experience are limited. I'd especially like to know if the security is adequate, how to make better use of Couch variables and functions, and if there are any hidden pitfalls (I couldn't make any sense of the activation key).

I started with an input form. To keep it simple, I limited it to changing only the password, not the username or other data.

Code: Select all
<form id="change_password" action="" method="post" autocomplete="off" onsubmit="return password_validate()">
   <h1>Change Your Password</h1>
   <p id="pw_error"></p>
   <label>new password<br/><input type="password" id="password" name="password" value="" autocomplete="off" autofocus="autofocus" /></label>
   <label>repeat<br/><input type="password" id="repeat" name="repeat" autocomplete="off"/></label>   
   <br/><input type="submit" value="change password"/>
</form>


The input is validated by javascript, but validation will be repeated when it is sent to the server.

Code: Select all
<script type="text/javascript">
function password_validate() {
   var password = document.getElementById('password').value;
   var repeat = document.getElementById('repeat').value;
   
   if (password.length < 5) {
      document.getElementById('pw_error').style.display = 'block';
      document.getElementById('pw_error').innerHTML = 'Please use at least 5 characters.';
      return false;
   }
   if (password != repeat) {
      document.getElementById('pw_error').style.display = 'block';
      document.getElementById('pw_error').innerHTML = "Ack! They don't match.";
      return false;
   }
}
</script>


In order to have access to Couch variables, I placed the php code inside a <cms:php> tag. If you use this code, be aware that you'll need to add your own database credentials, including the table prefix if you have multiple couch instances on the same server. This is where tying into the Couch database variables would be a really nice feature.

Code: Select all
<cms:php>
$password = mysql_real_escape_string($_POST['password']);
$repeat = mysql_real_escape_string($_POST['repeat']);

         //validate input
if ($password != "") {
   if (strlen($password) < 5) {
      echo "<p>Please use at least 5 characters.</p>";
   }elseif ($password != $repeat){
      echo "<p>Ack! They don't match.</p>";
   }else{
      change_password($password);
   }
//no change
}

function change_password($password){

         // hash the new password
   $AUTH = new KAuth( 0 );
   $hash = $AUTH->hasher->HashPassword( $password );

         // update database
   mysql_connect("localhost", "username", "password") or die(mysql_error());
   mysql_select_db('database_name') or die(mysql_error());
   mysql_query("UPDATE /*prefix?_*/couch_users SET password='$hash' WHERE id='<cms:show k_user_id />'");
       
   echo '<p>Success! Your password has been updated.</p>';
}
</cms:php>


The code presumes that the user is logged in, so we need to require log in. I have also added some very minimal styling, a greeting to the user, and logout link. Here is the complete page including all of the minutiae.

Code: Select all
<?php require_once( 'couch/cms.php' ); ?>
<cms:template title='Password' hidden='1' order='100' />
<cms:if k_logged_out >
    <cms:redirect k_login_link />
</cms:if>
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8" />
   <title>Change Password</title>

   <style>
#change_password {text-align:center;}
#change_password label {display:block;}
#pw_error {display:none; color:red;}
   </style>
</head>

<body>
<p style="text-align:right;">Hello, <cms:show k_user_title/><br/>
<a href="<cms:show k_logout_link />">Logout</a></p>

<div style="text-align:center; color:red;">
<cms:php>
$password = mysql_real_escape_string($_POST['password']);
$repeat = mysql_real_escape_string($_POST['repeat']);

         //validate input
if ($password != "") {
   if (strlen($password) < 5) {
      echo "<p>Please use at least 5 characters.</p>";
   }elseif ($password != $repeat){
      echo "<p>Ack! They don't match.</p>";
   }else{
      change_password($password);
   }
//no change
}

function change_password($password){

         // hash the new password
   $AUTH = new KAuth( 0 );
   $hash = $AUTH->hasher->HashPassword( $password );

         // update database
   mysql_connect("localhost", "username", "password") or die(mysql_error());
   mysql_select_db('database_name') or die(mysql_error());
   mysql_query("UPDATE /*prefix?_*/couch_users SET password='$hash' WHERE id='<cms:show k_user_id />'");
       
   echo '<p>Success! Your password has been updated.</p>';
}
</cms:php>
</div>

<form id="change_password" action="" method="post" autocomplete="off" onsubmit="return password_validate()">
   <h1>Change Your Password</h1>
   <p id="pw_error"></p>
   <label>new password<br/><input type="password" id="password" name="password" value="" autocomplete="off" autofocus="autofocus" /></label>
   <label>repeat<br/><input type="password" id="repeat" name="repeat" autocomplete="off"/></label>   
   <br/><input type="submit" value="change password"/>
</form>

<script type="text/javascript">
function password_validate() {
   var password = document.getElementById('password').value;
   var repeat = document.getElementById('repeat').value;
   
   if (password.length < 5) {
      document.getElementById('pw_error').style.display = 'block';
      document.getElementById('pw_error').innerHTML = 'Please use at least 5 characters.';
      return false;
   }
   if (password != repeat) {
      document.getElementById('pw_error').style.display = 'block';
      document.getElementById('pw_error').innerHTML = "Ack! They don't match.";
      return false;
   }
}
</script>

</body>
</html>
<?php COUCH::invoke(); ?>


Thanks for any and all input, suggestions, improvements, and criticisms.
Hi Tim,

I must say you have made quite an effort :)
Nothing wrong with the code at all.

You mentioned -
If you use this code, be aware that you'll need to add your own database credentials, including the table prefix if you have multiple couch instances on the same server. This is where tying into the Couch database variables would be a really nice feature.
Replying to which - the database 'object' is always globally available as $DB. So, the following code (which is requiring hardcoding the database credentials and table name etc.)
Code: Select all
mysql_connect("localhost", "username", "password") or die(mysql_error());
mysql_select_db('database_name') or die(mysql_error());
mysql_query("UPDATE /*prefix?_*/couch_users SET password='$hash' WHERE id='<cms:show k_user_id />'");

can be written as follows which will work across installations
Code: Select all
global $DB;
$DB->update( K_TBL_USERS, array('password'=>$hash), "id='" . $DB->sanitize( <cms:show k_user_id /> ). "'" );

Having said that, I think there is an easier way of coding this functionality (and a bit more).
You see, Couch internally is already doing all this grunge work (e.g, as seen when we edit a user in admin-panel). A shortcut would be to 'piggyback' on this existing functionality.

I am attaching a modified script that does just this.

Of all the fields that an admin can edit from the back-end, there are only three that an ordinary user should be given control of - 'display name', 'email' and 'password'. These are the ones this script allows to be modified.
profile.gif
profile.gif (4.45 KiB) Viewed 11452 times

The rest of the fields seen in the admin-panel should not be exposed to an ordinary user (the 'user name' is used internally as unique id and should not be allowed to change, similarly the 'access level' and 'disabled' fields should be off-limits).

I've annotated the code and here is a quick summary.
Basically we create an ordinary Couch form (cms:form) taking care to name the inputs the same as those in the admin-panel. Upon handling a successful form submission, we invoke the same code as used by Couch internally and that is it.

You can style the form to your liking - just remember to use the same names for the input fields as in this script.

Please try it out and let me know if this helps.
(If you happen to get a Unknown tag cms:set_flash error on using this script, please enable 'session.php' by editing your 'couch/addons/kfunctions.php' file).

Thanks

Attachments

Thanks, KK. I'll most certainly use your greatly enhanced version of the script. My only complaint is that it doesn't say "Ack!" It's a shame to miss the chance to say "Ack!" in an error message. But I can fix that.

You see, Couch internally is already doing all this grunge work (e.g, as seen when we edit a user in admin-panel). A shortcut would be to 'piggyback' on this existing functionality.

This is exactly what I was hoping to accomplish. I just couldn't figure out how to do it on my own. You seem to write Couch scripts and templates with the fluency of a native language. All of the custom scripts and templates you've cooked up for people on this forum are a great resource - an entire library of extensions, and a demonstration of the power and flexibility of CouchCMS.

One issue that I've run into with both your script and mine is that sometimes (but not always) I'll change the password and log out, then when I try to log in again I get the invalid password error. The same thing can happen after shutting down the computer and coming back later. I've been able to fiddle around with it - trying again, refreshing the page, trying again, and finally getting it to stick. But I'm afraid my very unsophisticated users would be crushed. I'm pretty certain it's not simple user error on my part when typing in the password. Any ideas about this?

Thanks for your help, and I'm sorry to keep you from work on version 1.4 ;)
Hi Tim,

First of all, thank you for the kind words :)

Coming to the problem you mentioned, let us look at it this way -
My code (as well as yours) is concerned only with persisting the changed password in the database.
Let us assume that the code is buggy and does not persist the changed password properly.

If that be the case, then once you have used the code you'll never be able to login with the new password as it'll never match the one supposedly improperly saved by the code.
Which clearly is not happening in the cases you mentioned - there, you keep getting 'invalid password' error several times but finally do manage to get in which proves that the password was persisted correctly in the first case.

So, rest assured, what you are experiencing has nothing to do with the code I posted above.

Of course, this does not explain as to what is then causing the problem you mentioned.
I think I can hazard a guess.
The next time you run into this error, please pause for a while, count up till 20, then try again.
If you sure you'll get in.

What seems to be happening is - sometimes we manage to enter a wrong password (a pretty normal thing for anybody). Couch, as a security measure places a lock-in for the user for 15 seconds. This is to keep automated bots and brute-force attacks off.
But if you are quick enough, as seems to be the case, and retry within 15 seconds, even if the creds are right this time, the lock-in will cause the attempt to fail.

Somehow, I am pretty sure this is precisely what you are experiencing.
So, please the next time you get the invalid password error, as I said just wait for a little while before retrying and all will be well :)

Hope this helps.
Thanks.
Yes. You're right about the invalid password error. Thanks again!
I find that the mentioned login rate limiting algorithm catches innocent users too easily. I'm sure I'm not the only one who has mistyped their password once and then received the invalid error message for the 2-4 follow-up login attempts within the lockout period. It doesn't help either that there is no lockout-specific error message, resulting in unnecessary confusion.

I think a more user-friendly and equally capable rate limiting algorithm could be utilized:

Allow 3 login attempts. After this, only allow one login attempt per 15 seconds.
Allow 3 login attempts. After this, only allow one login attempt per 15 seconds.
Agreed :)
9 posts Page 1 of 1