The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CatalystX::SimpleLogin::Controller::Login;
use Moose;
use Moose::Autobox;
use MooseX::Types::Moose qw/ HashRef ArrayRef ClassName Object Str /;
use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
use CatalystX::SimpleLogin::Form::Login;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }

with qw(
    CatalystX::Component::Traits
    Catalyst::Component::ContextClosure
);

__PACKAGE__->config(
    traits => 'Logout',
);

sub BUILD {
    my $self = shift;
    $self->login_form; # Build login form at construction time
}

has 'username_field' => (
    is => 'ro',
    isa => NonEmptySimpleStr,
    required => 1,
    default => 'username',
);

has 'password_field' => (
    is => 'ro',
    isa => NonEmptySimpleStr,
    required => 1,
    default => 'password',
);

has 'remember_field' => (
    is => 'ro',
    isa => NonEmptySimpleStr,
    required => 1,
    default => 'remember',
);

has 'login_error_message' => (
    is => 'ro',
    isa => NonEmptySimpleStr,
    required => 1,
    default => 'Wrong username or password',
);

has 'extra_auth_fields' => (
    isa => ArrayRef[NonEmptySimpleStr],
    is => 'ro',
    default => sub { [] },
);

has login_form_class => (
    isa => ClassName,
    is => 'rw',
    default => 'CatalystX::SimpleLogin::Form::Login',
);

has login_form_class_roles => (
    isa => ArrayRef[NonEmptySimpleStr],
    is => 'ro',
    default => sub  { [] },
);

has login_form => (
    isa => Object,
    is => 'ro',
    lazy_build => 1,
);

has login_form_args => (
    isa => HashRef,
    is => 'ro',
    default => sub { {} },
);

has login_form_stash_key => (
    is      => 'ro',
    isa     => Str,
    default => 'login_form',
);

has render_login_form_stash_key => (
    is      => 'ro',
    isa     => Str,
    default => 'render_login_form',
);

with 'MooseX::RelatedClassRoles' => { name => 'login_form' };

sub _build_login_form {
	my $self = shift;
	$self->apply_login_form_class_roles($self->login_form_class_roles->flatten)
        if scalar $self->login_form_class_roles->flatten; # FIXME - Should MX::RelatedClassRoles
                                                          #         do this automagically?
	return $self->login_form_class->new( $self->login_form_args );
}

sub render_login_form {
    my ($self, $ctx, $form) = @_;
    return $form->render;
}

sub login
    :Chained('/')
    :PathPart('login')
    :Args(0)
    :ActionClass('REST')
{
    my ($self, $ctx) = @_;

    my $form = $self->login_form;

    $ctx->stash(
        $self->login_form_stash_key        => $form,
        $self->render_login_form_stash_key => $self->make_context_closure(sub {
            my ($ctx) = @_;
            $self->render_login_form($ctx, $form);
        }, $ctx),
    );
}

sub login_GET {}

sub login_POST {
    my ($self, $ctx) = @_;

    my $form = $self->login_form;
    my $p = $ctx->req->body_parameters;
    if ($form->process($p)) {
        if ($ctx->authenticate({
            $self->username_field => $form->field('username')->value,
            $self->password_field => $form->field('password')->value,
            map { $_ => $form->field($_) } @{ $self->extra_auth_fields },
        })) {
            $ctx->extend_session_expires(999999999999)
                if $form->field( $self->remember_field )->value;
            $ctx->res->redirect($self->redirect_after_login_uri($ctx));
        }
        else{
            $form->field( $self->password_field )->add_error( $self->login_error_message );
        }
    }
}

sub redirect_after_login_uri {
    my ($self, $ctx) = @_;
    $ctx->uri_for('/');
}

1;

=head1 NAME

CatalystX::SimpleLogin::Controller::Login - Configurable login controller

=head1 SYNOPSIS

    # For simple useage exmple, see CatalystX::SimpleLogin, this is a
    # full config example
    __PACKAGE__->config(
        'Controller::Login' => {
            login => 'WithRedirect', # Optional, enables redirect-back feature
            actions => {
                login => { # Also optional
                    PathPart => ['theloginpage'], # Change login action to /theloginpage
                },
                logout => {},
            },
        },
    );

=head1 DESCRIPTION

Controller base class which exists to have login roles composed onto it
for the login and logout actions.

=head1 ATTRIBUTES

=head2 username_field

=head2 password_field

=head2 remember_field

=head2

=head1 METHODS

=head2 BUILD

Cause form instance to be built at application startup.

=head2 login

Login action

=head2 login_GET

Displays the login form

=head2 login_POST

Processes a submitted login form, and if correct, logs the user in
and redirects

=head2 redirect_after_login_uri

If you are using WithRedirect (i.e. it has been set in your config),
then you need to set 'redirect_after_login_uri' if you want something
other than the default, which is C<< $c->uri_for('/'); >>

=head2 render_login_form

Renders the login form. By default it just calls the form's render method. If
you want to do something different, like rendering the form with a template
through your view, this is the place to hook into.

=head1 SEE ALSO

=over

=item L<CatalystX::SimpleLogin::ControllerRole::Login::WithRedirect>

=item L<CatalystX::SimpleLogin::Form::Login>

=back

=head1 AUTHORS

See L<CatalystX::SimpleLogin> for authors.

=head1 LICENSE

See L<CatalystX::SimpleLogin> for license.

=cut