package Mojolicious::Plugin::HttpBasicAuth;

use Mojo::Base 'Mojolicious::Plugin';
use Mojo::ByteStream;
use Mojo::Util qw{b64_encode b64_decode};

our $VERSION = '0.03';

sub register {
    my ($plugin, $app, $user_defaults) = @_;

    push @{ $app->renderer->classes }, __PACKAGE__;

    my %defaults = %$user_defaults;
    $defaults{realm} //= 'WWW';
    $defaults{validate} //= sub {
        die('please define a validate callback');
    };
    $defaults{invalid} //= sub {
        my $controller = shift;
        return (
            json => { json     => { error => 'HTTP 401: Unauthorized' } },
            html => { template => 'auth/basic' },
            any  => { data     => 'HTTP 401: Unauthorized' }
        );
    };

    $app->renderer->add_helper(
        basic_auth => sub {
            my $controller = shift;
            my $params     = shift // {};
            my %options    = (%defaults, %$params);

            # Sent credentials
            my $auth = b64_decode($plugin->_auth_header($controller) || '');

            # No credentials entered
            return $plugin->_unauthorized($controller, $options{realm}, $options{invalid}) unless ($auth);

            # Verification within callback
            return 1 if $options{validate} and $options{validate}->($controller, split(/:/, $auth, 2));

            # Not verified
            return $plugin->_unauthorized($controller, $options{realm}, $options{invalid});
        }
    );
}

sub _auth_header {
    my $plugin     = shift;
    my $controller = shift;
    my $auth       = $controller->req->headers->authorization || $controller->req->env->{'X_HTTP_AUTHORIZATION'} || $controller->req->env->{'HTTP_AUTHORIZATION'};

    if ($auth && $auth =~ m/Basic (.*)/) {
        $auth = $1;
    }

    return $auth;
}

sub _unauthorized {
    my ($plugin, $controller, $realm, $callback) = @_;

    $controller->res->headers->www_authenticate("Basic realm=\"$realm\"");
    $controller->respond_to($callback->($controller));
    $controller->res->code(401);
    $controller->rendered;

    return;
}

1;
__DATA__

@@ auth/basic.html.ep
<h1>Authorization Required</h1>
<p>This server could not verify that you
are authorized to access the document
requested.  Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.</p>

__END__

=encoding utf8

=head1 NAME

Mojolicious::Plugin::HttpBasicAuth - Http-Basic-Authentication implementation

=head1 SYNOPSIS

  # in your startup
  $self->plugin(
      'http_basic_auth', {
          validate => sub {
              my $c         = shift;
              my $loginname = shift;
              my $password  = shift;
              return 1 if($loginname eq 'Homer' && $password eq 'Marge');
              return 0;
          },
          realm => 'Homers Home'
      }
  );

  # in your routes
  sub index {
      my $self = shift;
      return unless $self->basic_auth(\%options);
      $self->render();
  }


=head1 DESCRIPTION

L<Mojolicious::Plugin::HttpBasicAuth> is a implemntation of the Http-Basic-Authentication

=head1 OPTIONS

L<Mojolicious::Plugin::HttpBasicAuth> supports the following options.

=head2 realm

  $self->plugin('http_basic_auth', {realm => 'My Castle!'});

HTTP-Realm, defaults to 'WWW'

=head2 validate

  $self->plugin('http_basic_auth', {
      validate => sub {
            my $c          = shift;
            my $loginname  = shift;
            my $password   = shift;
            return 1 if($loginname eq 'Homer' && $password eq 'Marge');
            return 0;
      }
  });

Validation callback to verify user. This option is B<mandatory>.

=head2 invalid

  $self->plugin('http_basic_auth', {
      invalid => sub {
          my $controller = shift;
          return (
              json => { json     => { error => 'HTTP 401: Unauthorized' } },
              html => { template => 'auth/basic' },
              any  => { data     => 'HTTP 401: Unauthorized' }
          );
      }
  });

Callback vor invalid requests, default can be seen here. Return values are dispatched to L<Mojolicious::Controller/"respond_to">

=head1 HELPERS

L<Mojolicious::Plugin::HttpBasicAuth> implements the following helpers.

=head2 basic_auth

  return unless $self->basic_auth({realm => 'Marges Kitchen'});

All default options can be overwritten in every call.

=head1 METHODS

L<Mojolicious::Plugin::HttpBasicAuth> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.

=head2 register

  my $route = $plugin->register(Mojolicious->new);
  my $route = $plugin->register(Mojolicious->new, {realm => 'Fort Knox'});

Register renderer and helper in L<Mojolicious> application.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.

=head1 AUTHOR

Patrick Grämer, L<http://graemer.org>.

=cut
