Hi all.

I am trying to extend my one-time login tokens, however, it seems my changes are not working:

I googled and searched, but the only thing I could see changing this was modifying hook_user_password_reset() and its associated $timeout variable.

I set the value to 604800 (one week in seconds) and when I attempt to use it I see the expiry is only one day away (today is 05/17/2010):

This is a one-time login for rbrash and will expire on Tue, 05/18/2010 - 09:18.

Click on this button to login to the site and change your password.

This login can be used only once.

My code looks like:

<?php
/**
 * Menu callback; process one time login link and redirects to the user page on success.
 */
function tokenextend_user_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action = NULL) {
  global $user;

  // Check if the user is already logged in. The back button is often the culprit here.
  if ($user->uid) {
    drupal_set_message(t('You have already used this one-time login link. It is not necessary to use this link to login anymore. You are already logged in.'));
    drupal_goto();
  }
  else {
    // Time out, in seconds, until login URL expires. 24 hours = 86400 seconds.
    $timeout = 604800;
    $current = time();
    // Some redundant checks for extra security ?
    if ($timestamp < $current && $account = user_load(array('uid' => $uid, 'status' => 1)) ) {
      // Deny one-time login to blocked accounts.
      if (drupal_is_denied('user', $account->name) || drupal_is_denied('mail', $account->mail)) {
        drupal_set_message(t('You have tried to use a one-time login for an account which has been blocked.'), 'error');
        drupal_goto();
      }

      // No time out for first time login.
      if ($account->login && $current - $timestamp > $timeout) {
        drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
        drupal_goto('user/password');
      }
      else if ($account->uid && $timestamp > $account->login && $timestamp < $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) {
        // First stage is a confirmation form, then login
        if ($action == 'login') {
          watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp));
          // Set the new user.
          $user = $account;
          // user_authenticate_finalize() also updates the login timestamp of the
          // user, which invalidates further use of the one-time login link.
          user_authenticate_finalize($form_state['values']);
          drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to login. Please change your password.'));
          drupal_goto('user/'. $user->uid .'/edit');
        }
        else {
          $form['message'] = array('#value' => t('<p>This is a one-time login for %user_name and will expire on %expiration_date.</p><p>Click on this button to login to the site and change your password.</p>', array('%user_name' => $account->name, '%expiration_date' => format_date($timestamp + $timeout))));
          $form['help'] = array('#value' => '<p>'. t('This login can be used only once.') .'</p>');
          $form['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
          $form['#action'] = url("user/reset/$uid/$timestamp/$hashed_pass/login");
          return $form;
        }
      }
      else {
        drupal_set_message(t('You have tried to use a one-time login link which has either been used or is no longer valid. Please request a new one using the form below.'));
        drupal_goto('user/password');
      }
    }
    else {
      // Deny access, no more clues.
      // Everything will be in the watchdog's URL for the administrator to check.
      drupal_access_denied();
    }
  }
}

This must be surely a common problem, anyone have this working or did I miss a contrib module?

Comments

codepeak-1’s picture

I'm also looking for information regarding this issue. Did you manage to successfully extend the expiration date on one-time-login links?

davidbessler’s picture

Me too.

blisteringherb’s picture

I had the same issue and ended up solving the problem with a hook_menu_alter() to override the user_pass_reset function that comes with core. You can then use whatever function you'd like to override the core function.

DarrellDuane’s picture

+1. blisteringherb, could you post your code ?

Darrell Duane
d@duane.com

sunriser’s picture

function mymodule_menu_alter(&$items){
	$items['user/reset/%/%/%']['file'] = 'mymodule.pages.inc';
        $items['user/reset/%/%/%']['file path'] = drupal_get_path('module', 'mymodule');
}

don't forget to clear the cache

patchshorts’s picture

Here is a full module I wrote for this for Drupal 7, I'll contribute it when complete:

In the module folder create the directory extend_login_links, which is where you'd create these files.

Create the file extend_login_links.info & fill it with these contents:

name = Extend Login Links
description = Extends the login links to a specified time.
core = 7.x
files[] = extend_login_links.module

create the file extend_login_links.module & fill it with these contents:

<?php
/**
 * @file
 * TODO: Enter file description here.
 */

/**
 * Implements hook_help().
 */
function extend_login_links_help($path, $arg) {
  /* INFO:
   * The help hook is for displaying helpful messages at the top of pages indicated
   * by $section to further explain how they work. Adding certain "keywords" to the end of 
   * a given path (like admin/modules#description) will cause this text to display elsewhere
   * in the page as well (in this case, in the description section for the given module).
   */
  switch ($path) {
    case 'admin/help#extend_login_links':
      return t("TODO: Create admin help text.");
    // OPTIONAL: Add additional cases for other paths that should display help text.
  }
}

/**
 * Implements hook_menu().
 */
function extend_login_links_menu() {

  $items = array();

  $items['admin/config/people/extend-login-links'] = array(
    'title' => t('Extend one-time login links'),
    'description' => t('Change how long one-time login links are valid in seconds.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('extend_login_links_admin'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
   );

  return $items;
}
function extend_login_links_menu_alter(&$items){
  $items['user/reset/%/%/%'] = array(
    'title' => 'Reset password',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('extend_login_links_pass_reset', 2, 3, 4),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
    'file' => 'extend_login_links.pages.inc',
    'file path' => drupal_get_path('module','extend_login_links')
  );
}

function extend_login_links_admin() {
  $form = array();

  $form['extend_login_links_numseconds'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum number of seconds'),
    '#default_value' => variable_get('extend_login_links_numseconds', 604800),
    '#size' => 7,
    '#maxlength' => 255,
    '#description' => t("The maximum number of seconds to which one time long links are extended."),
    '#required' => TRUE,
  );

  return system_settings_form($form);
}

create the file extend_login_links.pages.inc & fill it with these contents:

<?php

/**
 * @file
 * User page callback file for the extend_login_links module.
 */

/**
 * Menu callback; process one time login link and redirects to the user page on success.
 */
function extend_login_links_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $action = NULL) {
  global $user;

  // When processing the one-time login link, we have to make sure that a user
  // isn't already logged in.
  if ($user->uid) {
    // The existing user is already logged in.
    if ($user->uid == $uid) {
      drupal_set_message(t('You are logged in as %user. <a href="!user_edit">Change your password.</a>', array('%user' => $user->name, '!user_edit' => url("user/$user->uid/edit"))));
    }
    // A different user is already logged in on the computer.
    else {
      $reset_link_account = user_load($uid);
      if (!empty($reset_link_account)) {
        drupal_set_message(t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please <a href="!logout">logout</a> and try using the link again.',
          array('%other_user' => $user->name, '%resetting_user' => $reset_link_account->name, '!logout' => url('user/logout'))));
      } else {
        // Invalid one-time link specifies an unknown user.
        drupal_set_message(t('The one-time login link you clicked is invalid.'));
      }
    }
    drupal_goto();
  }
  else {
    // Time out, in seconds, until login URL expires. 24 hours = 86400 seconds.
    $timeout = variable_get("extend_login_links_numseconds", 604800);
    $current = REQUEST_TIME;
    // Some redundant checks for extra security ?
    $users = user_load_multiple(array($uid), array('status' => '1'));
    if ($timestamp <= $current && $account = reset($users)) {
      // No time out for first time login.
      if ($account->login && $current - $timestamp > $timeout) {
        drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
        drupal_goto('user/password');
      }
      elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) {
        // First stage is a confirmation form, then login
        if ($action == 'login') {
          watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp));
          // Set the new user.
          $user = $account;
          // user_login_finalize() also updates the login timestamp of the
          // user, which invalidates further use of the one-time login link.
          user_login_finalize();
          drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'));
          // Let the user's password be changed without the current password check.
          $token = drupal_hash_base64(drupal_random_bytes(55));
          $_SESSION['pass_reset_' . $user->uid] = $token;
          drupal_goto('user/' . $user->uid . '/edit', array('query' => array('pass-reset-token' => $token)));
        }
        else {
          $form['message'] = array('#markup' => t('<p>This is a one-time login for %user_name and will expire on %expiration_date.</p><p>Click on this button to log in to the site and change your password.</p>', array('%user_name' => $account->name, '%expiration_date' => format_date($timestamp + $timeout))));
          $form['help'] = array('#markup' => '<p>' . t('This login can be used only once.') . '</p>');
          $form['actions'] = array('#type' => 'actions');
          $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
          $form['#action'] = url("user/reset/$uid/$timestamp/$hashed_pass/login");
          return $form;
        }
      }
      else {
        drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'));
        drupal_goto('user/password');
      }
    }
    else {
      // Deny access, no more clues.
      // Everything will be in the watchdog's URL for the administrator to check.
      drupal_access_denied();
    }
  }
}

Enable the new module and visit /admin/config/people/extend-login-links, set your seconds and you're done.

fonant’s picture

Thank you patchshorts, the code above works nicely for me :)

http://www.fonant.com - Fonant Ltd - Quality websites

noslokire’s picture

Was looking for something close to this, thanks!

Louis Bob’s picture

Thanks for the code.
Unfortunately for me, it displays a blank page only...

ehowland’s picture

This worked for me on current Drupal 7. I did initially have the dreaded White Screen of Death, but it came from my editor wrapping lines and/or including extra non program lines. I carefully recopied the text and it worked as advertised. Sweet.

fonant’s picture

Drupal 7.15 adds a variable to do this, which should make a module to add this to the administration interface much simpler:

#246029: Use a variable for the timeout/expiration of user password reset links (followup)

The new core code does:

$timeout = variable_get('user_password_reset_timeout', 86400);

http://www.fonant.com - Fonant Ltd - Quality websites

schiavone’s picture

Seems too me that you should be able to update user_password_reset_timeout in settings.php

schiavone’s picture

ship it!