<commit_before>
package VNDB::Util::Auth;


use strict;
use warnings;
use Exporter 'import';
use Digest::SHA qw|sha1 sha1_hex sha256|;
use Crypt::URandom 'urandom';
use Crypt::ScryptKDF 'scrypt_raw';
use Encode 'encode_utf8';
use TUWF ':html';
use VNDB::Func;


our @EXPORT = qw|
  authInit authLogin authLogout authInfo authCan authPreparePass
  authPrepareReset authValidateReset authGetCode authCheckCode authPref
|;


sub randomascii {
  return join '', map chr($_%92+33), unpack 'C*', urandom shift;
}


# Fetches and parses the auth cookie.
# Returns (uid, encrypted_token) on success, (0, '') on failure.
sub parsecookie {
  # Earlier versions of the auth cookie didn't have the dot separator, so that's optional.
  return ($_[0]->reqCookie('auth')||'') =~ /^([a-zA-Z0-9]{40})\.?(\d+)$/ ? ($2, sha1 pack 'H*', $1) : (0, '');
}


# initializes authentication information and checks the vndb_auth cookie
sub authInit {
  my $self = shift;

  my($uid, $token_e) = parsecookie($self);
  $self->{_auth} = $uid && $self->dbUserGet(uid => $uid, session => $token_e, what => 'extended notifycount prefs')->[0];

  # update the sessions.lastused column if lastused < now()-'6 hours'
  $self->dbSessionUpdateLastUsed($uid, $token_e) if $self->{_auth} && $self->{_auth}{session_lastused} < time()-6*3600;

  # Drop the cookie if it's not valid
  $self->resCookie(auth => undef) if !$self->{_auth} && $self->reqCookie('auth');
}


# login, arguments: user, password, url-to-redirect-to-on-success
# returns 1 on success (redirected), 0 otherwise (no reply sent)
sub authLogin {
  my $self = shift;
  my $user = lc(scalar shift);
  my $pass = shift;
  my $to = shift;

  if(_authCheck($self, $user, $pass)) {
    my $token = urandom(20);
    my $cookie = unpack('H*', $token).'.'.$self->{_auth}{id};
    $self->dbSessionAdd($self->{_auth}{id}, sha1 $token);

    $self->resRedirect($to, 'post');
    $self->resCookie(auth => $cookie, expires => time + 31536000); # keep the cookie for 1 year
    return 1;
  }

  return 0;
}


# clears authentication cookie and redirects to /
sub authLogout {
  my $self = shift;

  my($uid, $token_e) = parsecookie($self);
  $self->dbSessionDel($uid, $token_e) if $uid;

  $self->resRedirect('/', 'temp');
  $self->resCookie(auth => undef);

  # set l10n cookie if the user has a preferred language set
  my $l10n = $self->authPref('l10n');
  $self->resCookie(l10n => $l10n, expires => time()+31536000) if $l10n; # keep 1 year
}<commit_msg>Set httponly flag on auth cookie<commit_after>
package VNDB::Util::Auth;


use strict;
use warnings;
use Exporter 'import';
use Digest::SHA qw|sha1 sha1_hex sha256|;
use Crypt::URandom 'urandom';
use Crypt::ScryptKDF 'scrypt_raw';
use Encode 'encode_utf8';
use TUWF ':html';
use VNDB::Func;


our @EXPORT = qw|
  authInit authLogin authLogout authInfo authCan authPreparePass
  authPrepareReset authValidateReset authGetCode authCheckCode authPref
|;


sub randomascii {
  return join '', map chr($_%92+33), unpack 'C*', urandom shift;
}


# Fetches and parses the auth cookie.
# Returns (uid, encrypted_token) on success, (0, '') on failure.
sub parsecookie {
  # Earlier versions of the auth cookie didn't have the dot separator, so that's optional.
  return ($_[0]->reqCookie('auth')||'') =~ /^([a-zA-Z0-9]{40})\.?(\d+)$/ ? ($2, sha1 pack 'H*', $1) : (0, '');
}


# initializes authentication information and checks the vndb_auth cookie
sub authInit {
  my $self = shift;

  my($uid, $token_e) = parsecookie($self);
  $self->{_auth} = $uid && $self->dbUserGet(uid => $uid, session => $token_e, what => 'extended notifycount prefs')->[0];

  # update the sessions.lastused column if lastused < now()-'6 hours'
  $self->dbSessionUpdateLastUsed($uid, $token_e) if $self->{_auth} && $self->{_auth}{session_lastused} < time()-6*3600;

  # Drop the cookie if it's not valid
  $self->resCookie(auth => undef) if !$self->{_auth} && $self->reqCookie('auth');
}


# login, arguments: user, password, url-to-redirect-to-on-success
# returns 1 on success (redirected), 0 otherwise (no reply sent)
sub authLogin {
  my $self = shift;
  my $user = lc(scalar shift);
  my $pass = shift;
  my $to = shift;

  if(_authCheck($self, $user, $pass)) {
    my $token = urandom(20);
    my $cookie = unpack('H*', $token).'.'.$self->{_auth}{id};
    $self->dbSessionAdd($self->{_auth}{id}, sha1 $token);

    $self->resRedirect($to, 'post');
    $self->resCookie(auth => $cookie, httponly => 1, expires => time + 31536000); # keep the cookie for 1 year
    return 1;
  }

  return 0;
}


# clears authentication cookie and redirects to /
sub authLogout {
  my $self = shift;

  my($uid, $token_e) = parsecookie($self);
  $self->dbSessionDel($uid, $token_e) if $uid;

  $self->resRedirect('/', 'temp');
  $self->resCookie(auth => undef);

  # set l10n cookie if the user has a preferred language set
  my $l10n = $self->authPref('l10n');
  $self->resCookie(l10n => $l10n, expires => time()+31536000) if $l10n; # keep 1 year
}
{'fraction_non_alphanumeric': 0.17236524537409492, 'fraction_numerical': 0.025945293644408687, 'mean_word_length': 4.434972677595629, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 62, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6848233', 'n_tokens_mistral': 1891, 'n_tokens_neox': 1752, 'n_words': 505}