package Plack::Middleware::Auth::Htpasswd; use strict; use warnings; use base 'Plack::Middleware::Auth::Basic'; use Plack::Util::Accessor qw(file file_root); use Plack::Request; use Authen::Htpasswd; use MIME::Base64; use Path::Class (); # ABSTRACT: http basic authentication through apache-style .htpasswd files =head1 SYNOPSIS use Plack::Builder; my $app = sub { ... }; builder { enable "Auth::Htpasswd", file => '/path/to/.htpasswd'; $app; }; or builder { enable "Auth::Htpasswd", file_root => '/path/to/my/static/files'; $app; }; =head1 DESCRIPTION This middleware enables HTTP Basic authenication, based on the users in an L. You can either specify the file directly, through the C option, or use the C option to specify the root directory on the filesystem that corresponds to the web application root. This second option is more useful when using an app that is closely tied to the filesystem, such as L. If C is used, the requested path will be inspected, and a file named C<.htpasswd> will be checked in each containing directory, up to the C. The first one found will be used to validate the requested user. =head1 CONFIGURATION =head2 file Name of a .htpasswd file to read authentication information from. Required if C is not set. =head2 file_root Path to the on-disk directory that corresponds to the root URL path of the app. Required C is not set, and ignored if C is set. =head2 realm Realm name to display in the basic authentication dialog. Defaults to 'restricted area'. =cut sub prepare_app { my $self = shift; $self->authenticator(sub { $self->authenticate(@_) }); die "must specify either file or file_root" unless defined $self->file || $self->file_root; return $self->SUPER::prepare_app; } sub _check_password { my $self = shift; my ($file, $user, $pass) = @_; my $htpasswd = Authen::Htpasswd->new($file); my $htpasswd_user = $htpasswd->lookup_user($user); return unless $htpasswd_user; return $htpasswd_user->check_password($pass); } sub authenticate { my $self = shift; my ($user, $pass, $env) = @_; return $self->_check_password($self->file, $user, $pass) if defined $self->file; my $path = Plack::Request->new($env)->path; my $dir = Path::Class::Dir->new($self->file_root); my @htpasswd = $path ne '/' ? reverse map { $_->file('.htpasswd')->stringify } map { $dir = $dir->subdir($_) } split m{/}, $path : ($dir->file('.htpasswd')->stringify); for my $htpasswd (@htpasswd) { next unless -f $htpasswd && -r _; return $self->_check_password($htpasswd, $user, $pass); } return; } =head1 SEE ALSO L L =head1 CREDITS Large parts of this code were modeled after (read: stolen from) L by Tatsuhiko Miyagawa. =begin Pod::Coverage authenticate =end Pod::Coverage =cut 1;