<commit_before>package omnitool::common::login_system;

# please see pod documentation included below
# perldoc omnitool::common::login_system

# truth be told, this should be 12.0
$omnitool::common::login_system::VERSION = '6.0';

# load exporter module and export the subroutines
use Exporter;
@ISA = qw(Exporter);
@EXPORT = qw( web_authentication );

# time to grow up
use strict;

# for printing the no-access page
use omnitool::common::ui;

# for creating sessions
use omnitool::common::session;

# for supporting passwords, might come in handy
use omnitool::common::password_sealer;

# let's be an OO module for re-usability and extensibility
sub new {
	my $class = shift;

	# need at least the %$luggage, which will have the Plack request and response handles
	# if we are planning to anything except for test_their_credentials()
	my ($luggage) = @_;

	if (!$luggage) {
		die 'Cannot use login_system without %$luggage.';
	}

	# pack up myself and return
	my $self = bless {
		'luggage' => $luggage,
		'http_host' => lc($$luggage{belt}->{request}->env->{HTTP_HOST}), # hostnames should always be lower-case
		'cookie_id' => 'omnitool_ticket_'.$ENV{OT_COOKIE_ID}, # suffix allows for multiple cookies / systems
														  # that $ENV var set in start_omnitool.bash
		'password_sealer' => omnitool::common::password_sealer->new($luggage),
	}, $class;

	return $self;
	# kick off web_authentication() to get the web login tests going
}

# method to authenticate via an API key, which should be set in $$luggage{params}{api_key}
# we won't get here unless $$luggage{params}{api_key} is filled
sub api_key_athentication {
	my $self = shift;

	my ($remote_ip, $session_username,$tied_to_ip_address);

	# need the client IP for the key to verify
	$remote_ip = $self->{luggage}{belt}->{request}->address;

	# test the key
	($session_username,$tied_to_ip_address) = $self->{luggage}{db}->quick_select(qq{
		select username,tied_to_ip_address from user_api_keys where api_key_string=?
		and expiration_date >= curdate() and status='Active'
	},[ $self->{luggage}{params}{api_key}]);

	# if user not found, we have an error
	if (!$session_username) {
		$self->{luggage}{belt}->{response}->status(401);
		$self->{luggage}{belt}->mr_zebra('ERROR: '.$self->{luggage}{params}{api_key}.' is not a valid/active API key.',2);

	# make sure it's tied to this IP address -- allow for multiple IPs
	# allow for 'Any' option - available to admins only
	} elsif ($tied_to_ip_address ne 'Any' && !$self->{luggage}{belt}->really_in_list($remote_ip, $tied_to_ip_address, "\n")) {
		$self->{luggage}{belt}->{response}->status(403);
		$self->{luggage}{belt}->mr_zebra('ERROR: '.$self->{luggage}{params}{api_key}.' is not tied to '.$remote_ip.'.',2);

	# otherwise, we have a ticket to fly!
	} else {
		# sessions.pm should not create a session for this user/instance if they do not have access
		$self->{luggage}{session} = omnitool::common::session->new(
			'username' => $session_username,
			'db' => $self->{luggage}{db},
			'app_instance' => lc($self->{luggage}{hostname}),
			'hostname' => $self->{luggage}{hostname},
			'belt' => $self->{luggage}{belt}
		);

		# see if they have a timezone name from a previous web authentication
		($self->{luggage}{timezone_name}) = $self->{luggage}{db}->quick_select(qq{
			select timezone_name from otstatedata.authenticated_users where username=?
			and timezone_name like '%/%' order by code desc limit 1
		},[ $session_username]);

		# final test to make sure they have access
		if (!$self->{luggage}{session}->{tools_keys}[0] || $self->{luggage}{session}->{no_access}) {
			$self->{luggage}{belt}->mr_zebra('ERROR: You have no access rights in this Application Instance.',2);
		}
		# otherwise, we are good to go and can return silently
	}

}

# subroutine to drive the web browser authenatication process
# checks to see if they are authenticated, and set up the session here if they are
# if session finds no keys, they have no access and we need to inform them via show_login_screen()
# if they aren't authenticated, call show_login_screen() to show the login form
sub web_authentication {
	my ($cookies, $authentication_code, $c, $conn, $hostname, $j, $luggage, $timezone_name, $session_username, $our_output, $require_password_change, $ui);

	# myself has my parameters
	my $self = shift;

	# need the client IP for a variety of purposes
	$self->{luggage}{remote_ip} = $self->{luggage}{belt}->{request}->address;
	# I was tying cookies to remote IP addresses, but I took that out for
	# being too unwiedly, especially when you have TWCWIFI changing up your IP every 5 min.
	# Still used for limiting login attempts to 10.

	# if they sent a username and password -- and we are not updating an omnitool user -- test their credentials
	# $$luggage{uri} !~ /tools\/otusers\/(create|update)\/send_json_data/ &&
	if ($self->{luggage}{params}{attempt_login} && $self->{luggage}{params}{username} && $self->{luggage}{params}{password}) {
		$self->test_their_credentials();
	}

	# grab the cookie
	$authentication_code = $self->{luggage}{belt}->{request}->cookies->{ $self->{cookie_id} };

	if ($authentication_code) { # has cookie, is it still a valid session for this client's IP address?

		# have they successfully authenticated already?
		# and what are their password-status variables?
		($session_username,$timezone_name,$require_password_change) = $self->{luggage}{db}->quick_select(qq{
			select username,timezone_name,require_password_change from otstatedata.authenticated_users
			where concat(rand_string,'_',code)=?
		},[$authentication_code]);
		# also grab any previously-set timezone_name which they may have
		# sent (via their browser)

		# taking out IP restriction: and remote_ip=? / $self->{luggage}{remote_ip}
		# we will save it to the table though, for possible login checks

		# if they have authenticated already, we should check to see that they have access to this instance
		if ($session_username) {
			# sessions.pm should not create a session for this user/instance if they do not have access
			$self->{luggage}{session} = omnitool::common::session->new(
				'username' => $session_username,
				'db' => $self->{luggage}{db},
				'app_instance' => lc($self->{luggage}{hostname}),
				'hostname' => $self->{luggage}{hostname},
				'belt' => $self->{luggage}{belt}
			);

			# did they or their browser send a timezone_name?
			# update the table and add it to our luggage
			if ($self->{luggage}{params}{set_timezone_name}) {

				# update the DB:
				$self->{luggage}{db}->do_sql(qq{
					update otstatedata.authenticated_users
					set timezone_name=? where concat(rand_string,'_',code)=?
				},[$self->{luggage}{params}{set_timezone_name}, $authentication_code]);

			}

			# get it into the luggage
			if (!$timezone_name && $self->{luggage}{params}{set_timezone_name}) {
				$self->{luggage}{timezone_name} = $self->{luggage}{params}{set_timezone_name};
			} else {
				$self->{luggage}{timezone_name} = $timezone_name;
			}
			# default to GMT
			$self->{luggage}{timezone_name} ||= 'Etc/UTC';

			# need to have access to at least one tool to have access to this application instance
			# stop them for having no access to this instance if that is the case
			$self->{luggage}{no_access} = 1 if !$self->{luggage}{session}->{tools_keys}[0] || $self->{luggage}{session}->{no_access};
		}
	}

	# if they don't have access or have not authenticated, we need to instruct them to authenticate
	if ($self->{luggage}{no_access} || !$self->{luggage}{session}->{username}) {
		# where are we trying to get to?
		$self->{luggage}{params}{requested_uri} = $self->{luggage}{uri} if !$self->{luggage}{params}{requested_uri};
		# if they are calling for main.pm (or another handler), then we can present the main login screen
		if ($self->{luggage}{params}{requested_uri} =~ /^\/(ui|tool)/) {
			$self->{luggage}{belt}->mr_zebra('Authentication needed.',2);

		} elsif ($self->{luggage}{no_access}) {

			# use Data::Dumper;
			# $self->{luggage}{belt}->mr_zebra(Dumper($self->{luggage}),2);
			# $self->{luggage}{belt}->mr_zebra('You do not have access to this area.',2);
			$ui = omnitool::common::ui->new(
				'luggage' => $self->{luggage},
			);
			$our_output = $ui->error_no_access();
			$self->{luggage}{belt}->mr_zebra($our_output);

		# if they just authenticated and are getting the cookie, avoid showing the login form
		} elsif ($self->{just_authenticated}) {
			$self->show_redirection_page();

		# otherwise, plain text telling omnitool_routines.js to re-authenticate
		} else {
			# use the subroutine below to show the login form
			$self->show_login_screen();
		}

	# if we know who they are, but they want or need to change their password, show the change-password screen
	} elsif ($self->{luggage}{params}{change_my_password} || $require_password_change ne 'No') {
		$self->show_password_change_screen($require_password_change);
	}

}

# subroutine to check the user's credentials; returns 0 if authentication failure;
# otherwise sets cookie and reloads the page
sub test_their_credentials {
	my ($valid_user, $random_string, $code, $now);

	# myself has my parameters
	my $self = shift;

	# if they have recaptcha enabled, test that portion of the form
	if ($ENV{RECAPTCHA_SITEKEY} && $ENV{RECAPTCHA_SECRET}) {
		my $recaptcha_verify = $self->{luggage}{belt}->recaptcha_verify();
		if (!$recaptcha_verify) { # recaptcha fail = authentication fail
			return 0;
		}
	}

	# can pass an argument to tell it not to set cookie & redirect
	# useful when you want to utilize this module only for credential-testing
	my ($skip_cookie) = @_;

	my ($password_is_expired, $require_password_change);

	# default: do not require password change
	$require_password_change = 'No';

	# use our improved password keeper class to check their password
	$valid_user = $self->{password_sealer}->check_a_users_password( $self->{luggage}{params}{username}, $self->{luggage}{params}{password} );

	# if they don't exist in the main/basic user database, see if there is an
	# system-specific authentication plugin/helper installed for this application
	if (!$valid_user) {
		# this will be saved directly under applications, as users authenticate system-wide
		if (eval "require omnitool::applications::auth_helper") { # it exists and loaded
			# notice that it requires the %$luggage and returns a 1 for success or 0 for fail
			$valid_user = omnitool::applications::auth_helper::authentication_helper($self->{luggage});
		}

	# if we authenticated them via the omnitool_users table, check their password status
	} else {
		($password_is_expired, $require_password_change) = $self->{luggage}{db}->quick_select(qq{
			select (password_set_date < date_sub(curdate(),interval 90 day)),
			require_password_change from omnitool_users
			where username=?
		},[$self->{luggage}{params}{username}]);

		# we will put a value into the otstatedata.authenticated_users table below.
		$require_password_change = 'Expired' if $password_is_expired;

	}

	# if they are valid, and we are not in plack (or want to skip the cookie), return the positive value
	if ($valid_user && ($skip_cookie || !$self->{luggage}{belt}->{response})) {
		return $valid_user;
	# if we are in plack world, log their authentication success and send them on their way
	} elsif ($self->{luggage}{belt}->{response} && $valid_user) {

		# $self->{luggage}{belt}->logger('Trying to set the cookie - '.$valid_user.' - '.$self->{luggage}{belt}->{response},'login_errors');

		# we need a random string; this makes the cookie more secure so that it doesn't just have a set of incremental values
		# so that someone could guess a probable next value...they'd need a client's IP address too.  I am sure I am missing
		# something here; use the utility_belt, as it is a reliable subroutine
		$random_string = $self->{luggage}{belt}->random_string(16);

		# save the successful authentication into the database
		$self->{luggage}{db}->do_sql(qq{
			insert into otstatedata.authenticated_users
			(login_time,rand_string,username,remote_ip,require_password_change)
			values (unix_timestamp(),?,?,?,?)
		},[$random_string, $self->{luggage}{params}{username}, $self->{luggage}{remote_ip}, $require_password_change]);
		$code = $self->{luggage}{db}->{last_insert_id}; # for cookie

		# no more than every 100 seconds, clear any records for them older than 48 hours
		$now = time();
		if ($now =~ /30$/) {
			$self->{luggage}{db}->do_sql(qq{
				delete from otstatedata.authenticated_users
				where username=? and login_time < (unix_timestamp()-172800)
			},[$self->{luggage}{params}{username}]);
		}

		# cookie domain is set in environmental vars; default to primary domain
		$ENV{OT_COOKIE_DOMAIN} ||= '.omnitool.org';

		# reset their login attempt counter. useful for testing and if they are on multiple browsers
		$self->{luggage}{db}->do_sql(qq{
			delete from otstatedata.login_attempts_counter where remote_ip=?
		},[$self->{luggage}{remote_ip}]);

		# bon voyage - have a nice cookie with the authenication ID for the road
		$self->{luggage}{params}{requested_uri} ||= '/'; # default to root
		$self->{luggage}{belt}->{response}->cookies->{ $self->{cookie_id} } = {
			value => $random_string.'_'.$code,
			domain  => $ENV{OT_COOKIE_DOMAIN},
			path => '/',
			# expires => '+24h'
		};
		# useing the redirect with the cookie is the most reliable way to set it
		$self->{luggage}{belt}->{response}->redirect($self->{luggage}{params}{requested_uri});
<commit_msg>Bugfix: make sure cookie-setting works<commit_after>package omnitool::common::login_system;

# please see pod documentation included below
# perldoc omnitool::common::login_system

# truth be told, this should be 12.0
$omnitool::common::login_system::VERSION = '6.0';

# load exporter module and export the subroutines
use Exporter;
@ISA = qw(Exporter);
@EXPORT = qw( web_authentication );

# time to grow up
use strict;

# for printing the no-access page
use omnitool::common::ui;

# for creating sessions
use omnitool::common::session;

# for supporting passwords, might come in handy
use omnitool::common::password_sealer;

# let's be an OO module for re-usability and extensibility
sub new {
	my $class = shift;

	# need at least the %$luggage, which will have the Plack request and response handles
	# if we are planning to anything except for test_their_credentials()
	my ($luggage) = @_;

	if (!$luggage) {
		die 'Cannot use login_system without %$luggage.';
	}

	# pack up myself and return
	my $self = bless {
		'luggage' => $luggage,
		'http_host' => lc($$luggage{belt}->{request}->env->{HTTP_HOST}), # hostnames should always be lower-case
		'cookie_id' => 'omnitool_ticket_'.$ENV{OT_COOKIE_ID}, # suffix allows for multiple cookies / systems
														  # that $ENV var set in start_omnitool.bash
		'password_sealer' => omnitool::common::password_sealer->new($luggage),
	}, $class;

	return $self;
	# kick off web_authentication() to get the web login tests going
}

# method to authenticate via an API key, which should be set in $$luggage{params}{api_key}
# we won't get here unless $$luggage{params}{api_key} is filled
sub api_key_athentication {
	my $self = shift;

	my ($remote_ip, $session_username,$tied_to_ip_address);

	# need the client IP for the key to verify
	$remote_ip = $self->{luggage}{belt}->{request}->address;

	# test the key
	($session_username,$tied_to_ip_address) = $self->{luggage}{db}->quick_select(qq{
		select username,tied_to_ip_address from user_api_keys where api_key_string=?
		and expiration_date >= curdate() and status='Active'
	},[ $self->{luggage}{params}{api_key}]);

	# if user not found, we have an error
	if (!$session_username) {
		$self->{luggage}{belt}->{response}->status(401);
		$self->{luggage}{belt}->mr_zebra('ERROR: '.$self->{luggage}{params}{api_key}.' is not a valid/active API key.',2);

	# make sure it's tied to this IP address -- allow for multiple IPs
	# allow for 'Any' option - available to admins only
	} elsif ($tied_to_ip_address ne 'Any' && !$self->{luggage}{belt}->really_in_list($remote_ip, $tied_to_ip_address, "\n")) {
		$self->{luggage}{belt}->{response}->status(403);
		$self->{luggage}{belt}->mr_zebra('ERROR: '.$self->{luggage}{params}{api_key}.' is not tied to '.$remote_ip.'.',2);

	# otherwise, we have a ticket to fly!
	} else {
		# sessions.pm should not create a session for this user/instance if they do not have access
		$self->{luggage}{session} = omnitool::common::session->new(
			'username' => $session_username,
			'db' => $self->{luggage}{db},
			'app_instance' => lc($self->{luggage}{hostname}),
			'hostname' => $self->{luggage}{hostname},
			'belt' => $self->{luggage}{belt}
		);

		# see if they have a timezone name from a previous web authentication
		($self->{luggage}{timezone_name}) = $self->{luggage}{db}->quick_select(qq{
			select timezone_name from otstatedata.authenticated_users where username=?
			and timezone_name like '%/%' order by code desc limit 1
		},[ $session_username]);

		# final test to make sure they have access
		if (!$self->{luggage}{session}->{tools_keys}[0] || $self->{luggage}{session}->{no_access}) {
			$self->{luggage}{belt}->mr_zebra('ERROR: You have no access rights in this Application Instance.',2);
		}
		# otherwise, we are good to go and can return silently
	}

}

# subroutine to drive the web browser authenatication process
# checks to see if they are authenticated, and set up the session here if they are
# if session finds no keys, they have no access and we need to inform them via show_login_screen()
# if they aren't authenticated, call show_login_screen() to show the login form
sub web_authentication {
	my ($cookies, $authentication_code, $c, $conn, $hostname, $j, $luggage, $timezone_name, $session_username, $our_output, $require_password_change, $ui);

	# myself has my parameters
	my $self = shift;

	# need the client IP for a variety of purposes
	$self->{luggage}{remote_ip} = $self->{luggage}{belt}->{request}->address;
	# I was tying cookies to remote IP addresses, but I took that out for
	# being too unwiedly, especially when you have TWCWIFI changing up your IP every 5 min.
	# Still used for limiting login attempts to 10.

	# if they sent a username and password -- and we are not updating an omnitool user -- test their credentials
	# $$luggage{uri} !~ /tools\/otusers\/(create|update)\/send_json_data/ &&
	if ($self->{luggage}{params}{attempt_login} && $self->{luggage}{params}{username} && $self->{luggage}{params}{password}) {
		$self->test_their_credentials();
	}

	# grab the cookie
	$authentication_code = $self->{luggage}{belt}->{request}->cookies->{ $self->{cookie_id} };

	if ($authentication_code) { # has cookie, is it still a valid session for this client's IP address?

		# have they successfully authenticated already?
		# and what are their password-status variables?
		($session_username,$timezone_name,$require_password_change) = $self->{luggage}{db}->quick_select(qq{
			select username,timezone_name,require_password_change from otstatedata.authenticated_users
			where concat(rand_string,'_',code)=?
		},[$authentication_code]);
		# also grab any previously-set timezone_name which they may have
		# sent (via their browser)

		# taking out IP restriction: and remote_ip=? / $self->{luggage}{remote_ip}
		# we will save it to the table though, for possible login checks

		# if they have authenticated already, we should check to see that they have access to this instance
		if ($session_username) {
			# sessions.pm should not create a session for this user/instance if they do not have access
			$self->{luggage}{session} = omnitool::common::session->new(
				'username' => $session_username,
				'db' => $self->{luggage}{db},
				'app_instance' => lc($self->{luggage}{hostname}),
				'hostname' => $self->{luggage}{hostname},
				'belt' => $self->{luggage}{belt}
			);

			# did they or their browser send a timezone_name?
			# update the table and add it to our luggage
			if ($self->{luggage}{params}{set_timezone_name}) {

				# update the DB:
				$self->{luggage}{db}->do_sql(qq{
					update otstatedata.authenticated_users
					set timezone_name=? where concat(rand_string,'_',code)=?
				},[$self->{luggage}{params}{set_timezone_name}, $authentication_code]);

			}

			# get it into the luggage
			if (!$timezone_name && $self->{luggage}{params}{set_timezone_name}) {
				$self->{luggage}{timezone_name} = $self->{luggage}{params}{set_timezone_name};
			} else {
				$self->{luggage}{timezone_name} = $timezone_name;
			}
			# default to GMT
			$self->{luggage}{timezone_name} ||= 'Etc/UTC';

			# need to have access to at least one tool to have access to this application instance
			# stop them for having no access to this instance if that is the case
			$self->{luggage}{no_access} = 1 if !$self->{luggage}{session}->{tools_keys}[0] || $self->{luggage}{session}->{no_access};
		}
	}

	# if they don't have access or have not authenticated, we need to instruct them to authenticate
	if ($self->{luggage}{no_access} || !$self->{luggage}{session}->{username}) {
		# where are we trying to get to?
		$self->{luggage}{params}{requested_uri} = $self->{luggage}{uri} if !$self->{luggage}{params}{requested_uri};
		# if they are calling for main.pm (or another handler), then we can present the main login screen
		if ($self->{luggage}{params}{requested_uri} =~ /^\/(ui|tool)/) {
			$self->{luggage}{belt}->mr_zebra('Authentication needed.',2);

		} elsif ($self->{luggage}{no_access}) {

			# use Data::Dumper;
			# $self->{luggage}{belt}->mr_zebra(Dumper($self->{luggage}),2);
			# $self->{luggage}{belt}->mr_zebra('You do not have access to this area.',2);
			$ui = omnitool::common::ui->new(
				'luggage' => $self->{luggage},
			);
			$our_output = $ui->error_no_access();
			$self->{luggage}{belt}->mr_zebra($our_output);

		# if they just authenticated and are getting the cookie, avoid showing the login form
		} elsif ($self->{just_authenticated}) {
			$self->show_redirection_page();

		# otherwise, plain text telling omnitool_routines.js to re-authenticate
		} else {
			# use the subroutine below to show the login form
			$self->show_login_screen();
		}

	# if we know who they are, but they want or need to change their password, show the change-password screen
	} elsif ($self->{luggage}{params}{change_my_password} || $require_password_change ne 'No') {
		$self->show_password_change_screen($require_password_change);
	}

}

# subroutine to check the user's credentials; returns 0 if authentication failure;
# otherwise sets cookie and reloads the page
sub test_their_credentials {
	my ($valid_user, $random_string, $code, $now);

	# myself has my parameters
	my $self = shift;

	# if they have recaptcha enabled, test that portion of the form
	if ($ENV{RECAPTCHA_SITEKEY} && $ENV{RECAPTCHA_SECRET}) {
		my $recaptcha_verify = $self->{luggage}{belt}->recaptcha_verify();
		if (!$recaptcha_verify) { # recaptcha fail = authentication fail
			return 0;
		}
	}

	# can pass an argument to tell it not to set cookie & redirect
	# useful when you want to utilize this module only for credential-testing
	my ($skip_cookie) = @_;

	my ($password_is_expired, $require_password_change);

	# default: do not require password change
	$require_password_change = 'No';

	# use our improved password keeper class to check their password
	$valid_user = $self->{password_sealer}->check_a_users_password( $self->{luggage}{params}{username}, $self->{luggage}{params}{password} );

	# if they don't exist in the main/basic user database, see if there is an
	# system-specific authentication plugin/helper installed for this application
	if (!$valid_user) {
		# this will be saved directly under applications, as users authenticate system-wide
		if (eval "require omnitool::applications::auth_helper") { # it exists and loaded
			# notice that it requires the %$luggage and returns a 1 for success or 0 for fail
			$valid_user = omnitool::applications::auth_helper::authentication_helper($self->{luggage});
		}

	# if we authenticated them via the omnitool_users table, check their password status
	} else {
		($password_is_expired, $require_password_change) = $self->{luggage}{db}->quick_select(qq{
			select (password_set_date < date_sub(curdate(),interval 90 day)),
			require_password_change from omnitool_users
			where username=?
		},[$self->{luggage}{params}{username}]);

		# we will put a value into the otstatedata.authenticated_users table below.
		$require_password_change = 'Expired' if $password_is_expired;

	}

	# if they are valid, and we are not in plack (or want to skip the cookie), return the positive value
	if ($valid_user && ($skip_cookie || !$self->{luggage}{belt}->{response})) {
		return $valid_user;
	# if we are in plack world, log their authentication success and send them on their way
	} elsif ($self->{luggage}{belt}->{response} && $valid_user) {

		# $self->{luggage}{belt}->logger('Trying to set the cookie - '.$valid_user.' - '.$self->{luggage}{belt}->{response},'login_errors');

		# we need a random string; this makes the cookie more secure so that it doesn't just have a set of incremental values
		# so that someone could guess a probable next value...they'd need a client's IP address too.  I am sure I am missing
		# something here; use the utility_belt, as it is a reliable subroutine
		$random_string = $self->{luggage}{belt}->random_string(16);

		# save the successful authentication into the database
		$self->{luggage}{db}->do_sql(qq{
			insert into otstatedata.authenticated_users
			(login_time,rand_string,username,remote_ip,require_password_change)
			values (unix_timestamp(),?,?,?,?)
		},[$random_string, $self->{luggage}{params}{username}, $self->{luggage}{remote_ip}, $require_password_change]);
		$code = $self->{luggage}{db}->{last_insert_id}; # for cookie

		# no more than every 100 seconds, clear any records for them older than 48 hours
		$now = time();
		if ($now =~ /30$/) {
			$self->{luggage}{db}->do_sql(qq{
				delete from otstatedata.authenticated_users
				where username=? and login_time < (unix_timestamp()-172800)
			},[$self->{luggage}{params}{username}]);
		}

		# cookie domain is set in environmental vars; default to primary domain
		$ENV{OT_COOKIE_DOMAIN} ||= '.omnitool.org';

		# reset their login attempt counter. useful for testing and if they are on multiple browsers
		$self->{luggage}{db}->do_sql(qq{
			delete from otstatedata.login_attempts_counter where remote_ip=?
		},[$self->{luggage}{remote_ip}]);

		# bon voyage - have a nice cookie with the authenication ID for the road
		$self->{luggage}{params}{requested_uri} ||= '/'; # default to root
		$self->{luggage}{belt}->{response}->cookies->{ $self->{cookie_id} } = {
			value => $random_string.'_'.$code,
			domain  => $ENV{OT_COOKIE_DOMAIN},
			path => '/',
			# expires => '+24h'
		};
		# useing the redirect with the cookie is the most reliable way to set it
		$self->{luggage}{db}->do_sql('commit'); # must commit since we are terminating early
		$self->{luggage}{belt}->{response}->redirect($self->{luggage}{params}{requested_uri});

{'fraction_non_alphanumeric': 0.140527950310559, 'fraction_numerical': 0.0034753031647441583, 'mean_word_length': 4.845904473741085, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 329, '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': '1859220', 'n_tokens_mistral': 9637, 'n_tokens_neox': 8839, 'n_words': 2586}