use strict; use warnings; package Jifty::Plugin::Authentication::Password::Mixin::Model::User; use Jifty::DBI::Schema; use base 'Jifty::DBI::Record::Plugin'; use Digest::MD5 qw''; our @EXPORT = qw(password_is hashed_password_is regenerate_auth_token has_alternative_auth); =head1 NAME Jifty::Plugin::Authentication::Password::Mixin::Model::User - password plugin user mixin model =head1 SYNOPSIS package MyApp::Model::User; use Jifty::DBI::Schema; use MyApp::Record schema { # custom column defrinitions }; use Jifty::Plugin::User::Mixin::Model::User; # name, email, email_confirmed use Jifty::Plugin::Authentication::Password::Mixin::Model::User; # ^^ password, auth_token =head1 DESCRIPTION This mixin model is added to the application's account model for use with the password authentication plugin. This mixin should be used in combination with L. =head1 SCHEMA This mixin adds the following columns to the model schema: =head2 auth_token This is a unique identifier used when confirming a user's email account and recovering a lost password. =head2 password This is the user's password. It will be stored in the database after being processed through L, so the password cannot be directly recovered from the database. =cut use Jifty::Plugin::Authentication::Password::Record schema { column auth_token => render_as 'unrendered', type is 'varchar', default is '', label is _('Authentication token'); column password => is unreadable, label is _('Password'), type is 'varchar', hints is _('Your password should be at least six characters'), render_as 'password', filters are 'Jifty::DBI::Filter::SaltHash'; }; =head1 METHODS =head2 register_triggers Adds the triggers to the model this mixin is added to. =cut sub register_triggers { my $self = shift; $self->add_trigger(name => 'after_create', callback => \&after_create); $self->add_trigger(name => 'after_set_password', callback => \&after_set_password); $self->add_trigger(name => 'validate_password', callback => \&validate_password, abortable =>1); } =head2 password_is PASSWORD Checks if the user's password matches the provided I. =cut sub password_is { my $self = shift; my $pass = shift; return undef unless $self->_value('password'); my ($hash, $salt) = @{$self->_value('password')}; return 1 if ( $hash eq Digest::MD5::md5_hex($pass . $salt) ); return undef; } =head2 hashed_password_is HASH TOKEN Check if the given I is the result of hashing our (already salted and hashed) password with I. This can be used in cases where the pre-hashed password is sent during login as an additional security precaution (such as could be done via Javascript). =cut sub hashed_password_is { my $self = shift; my $hash = shift; my $token = shift; my $password = $self->_value('password'); return $password && Digest::MD5::md5_hex("$token " . $password->[0]) eq $hash; } =head2 validate_password Makes sure that the password is six characters long or longer, unless we have alternative means to authenticate. =cut sub validate_password { my $self = shift; my $new_value = shift; return 1 if $self->has_alternative_auth(); return ( 0, _('Passwords need to be at least six characters long') ) if length($new_value) < 6; return 1; } =head2 after_create This trigger is added to the account model. It automatically sends a notification email to the user for password confirmation. See L. =cut sub after_create { my $self = shift; # We get a reference to the return value of insert my $value = shift; return unless $$value; $self->load($$value); $self->regenerate_auth_token; if ( $self->id and $self->email and not $self->email_confirmed ) { Jifty->app_class('Notification','ConfirmEmail')->new( to => $self )->send; } else { warn $self->id . " " .$self->email; } } =head2 has_alternative_auth If your model supports other means of authentication, you should have this method return true, so the C field can optionally be null and authentication with password is disabled in that case. =cut sub has_alternative_auth { } =head2 after_set_password Regenerate auth tokens on password change =cut sub after_set_password { my $self = shift; $self->regenerate_auth_token; } =head2 regenerate_auth_token Generate a new auth_token for this user. This will invalidate any existing feed URLs. =cut sub regenerate_auth_token { my $self = shift; my $auth_token = ''; $auth_token .= unpack('H2', chr(int rand(256))) for (1..16); $self->__set(column => 'auth_token', value => $auth_token); } =head1 SEE ALSO L, L =head1 LICENSE Jifty is Copyright 2005-2007 Best Practical Solutions, LLC. Jifty is distributed under the same terms as Perl itself. =cut 1;