Skip to content

Commit

Permalink
allow api usage from the browser
Browse files Browse the repository at this point in the history
  • Loading branch information
vti committed May 3, 2017
1 parent fb0b2e5 commit 0a12061
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 42 deletions.
18 changes: 11 additions & 7 deletions lib/Crafty.pm
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,19 @@ sub to_psgi {

enable '+Crafty::Middleware::Routes', routes => $self->build_routes;

enable_if { $_[0]->{PATH_INFO} !~ m{^/api/} } '+Crafty::Middleware::Access', config => $self->config;
enable_if { $_[0]->{PATH_INFO} =~ m{^/api/} } 'Auth::Basic', authenticator => sub {
my ($username, $password, $env) = @_;
enable '+Crafty::Middleware::User', config => $self->config;

return unless my $user = $self->config->user($username);
enable '+Crafty::Middleware::Access',
config => $self->config,
denier => sub {
my ($env) = @_;

my $checker = Crafty::Password->new(hashing => $user->{hashing}, salt => $user->{salt});
return $checker->equals($username, $password, $user->{password});
};
if ($env->{PATH_INFO} =~ m{^/api/}) {
return [ 401, [], ['Authentication needed'] ];
}

return [ 302, [ Location => '/login' ], [] ];
};

return $psgi;
};
Expand Down
32 changes: 3 additions & 29 deletions lib/Crafty/Middleware/Access.pm
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,15 @@ use base 'Plack::Middleware';

use Plack::Util::Accessor qw(
config
denier
);

use Plack::Session;

sub call {
my $self = shift;
my ($env) = @_;

my $route = $env->{'crafty.route'};
my $username = $self->_username_from_session($env);

if ($username) {
$env->{'crafty.username'} = $username;
}
my $username = $env->{'crafty.username'};

my $global_mode = $self->config->config->{access}->{mode} // 'private';

Expand All @@ -30,31 +25,10 @@ sub call {
$access = 'private' if $global_mode eq 'private' && $route->name ne 'Login';

if ($access && $access eq 'private' && !$username) {
return $self->_redirect_to_login;
return $self->denier->($env);
}

return $self->app->($env);
}

sub _redirect_to_login {
my $self = shift;

return [ 302, [ 'Location' => '/login' ], [] ];
}

sub _username_from_session {
my $self = shift;
my ($env) = @_;

my $session = Plack::Session->new($env);
return unless $session;

my $username = $session->get('username');
return unless $username;

return unless $self->config->user($username);

return $username;
}

1;
64 changes: 64 additions & 0 deletions lib/Crafty/Middleware/User.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package Crafty::Middleware::User;

use strict;
use warnings;

use base 'Plack::Middleware';

use Plack::Util::Accessor qw(
config
);

use MIME::Base64 ();
use Plack::Session;
use Crafty::Password;

sub call {
my $self = shift;
my ($env) = @_;

my $route = $env->{'crafty.route'};

my $username = $self->_username_from_session($env);
$username //= $self->_username_from_basic_auth($env);

$env->{'crafty.username'} = $username;

return $self->app->($env);
}

sub _username_from_session {
my $self = shift;
my ($env) = @_;

my $session = Plack::Session->new($env);
return unless $session;

my $username = $session->get('username');
return unless $username;

return unless $self->config->user($username);

return $username;
}

sub _username_from_basic_auth {
my $self = shift;
my ($env) = @_;

return unless my $auth = $env->{HTTP_AUTHORIZATION};
return unless $auth =~ m/^Basic\s+(.*)$/i;

my ($username, $password) = split /:/, (MIME::Base64::decode($1) || ":"), 2;

return unless $username && $password;

return unless my $user = $self->config->user($username);

my $checker = Crafty::Password->new(hashing => $user->{hashing}, salt => $user->{salt});
return unless $checker->equals($username, $password, $user->{password});

return $username;
}

1;
11 changes: 5 additions & 6 deletions t/middleware/access.t
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ subtest 'grants access to login page always' => sub {
my $routes = _build_routes();
my $route = $routes->match('/login');

my $res = $build->call({ 'crafty.route' => $route, 'psgix.session' => {}, 'psgix.session.options' => {} });
my $res = $build->call({ 'crafty.route' => $route });

is $res->[0], 200;
};
Expand All @@ -26,18 +26,18 @@ subtest 'grants access to public page' => sub {
my $routes = _build_routes();
my $route = $routes->match('/public');

my $res = $build->call({ 'crafty.route' => $route, 'psgix.session' => {}, 'psgix.session.options' => {} });
my $res = $build->call({ 'crafty.route' => $route });

is $res->[0], 200;
};

subtest 'denies access to private page when no user' => sub {
my $build = _build();
my $build = _build(denier => sub { [ 302, [], [] ] });

my $routes = _build_routes();
my $route = $routes->match('/private');

my $res = $build->call({ 'crafty.route' => $route, 'psgix.session' => {}, 'psgix.session.options' => {} });
my $res = $build->call({ 'crafty.route' => $route });

is $res->[0], 302;
};
Expand All @@ -48,8 +48,7 @@ subtest 'grants access to private page to correct user' => sub {
my $routes = _build_routes();
my $route = $routes->match('/private');

my $res = $build->call(
{ 'crafty.route' => $route, 'psgix.session' => { username => 'username' }, 'psgix.session.options' => {} });
my $res = $build->call({ 'crafty.route' => $route, 'crafty.username' => 'user' });

is $res->[0], 200;
};
Expand Down
79 changes: 79 additions & 0 deletions t/middleware/user.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use strict;
use warnings;

use Test::More;
use Test::Deep;
use TestSetup;

use MIME::Base64 ();

use_ok 'Crafty::Middleware::User';

subtest 'loads user from session' => sub {
my $build = _build();

my $env = { 'psgix.session' => { username => 'username' } };
$build->call($env);

is $env->{'crafty.username'}, 'username';
};

subtest 'does not load basic auth not valid' => sub {
my $build = _build();

my $env = {
'psgix.session' => {},
HTTP_AUTHORIZATION => 'Basic 123'
};

$build->call($env);

ok !$env->{'crafty.username'};
};

subtest 'does not load basic auth with unknown user' => sub {
my $build = _build();

my $env = {
'psgix.session' => {},
HTTP_AUTHORIZATION => 'Basic ' . MIME::Base64::encode_base64('unknown:wrong_password')
};

$build->call($env);

ok !$env->{'crafty.username'};
};

subtest 'does not load basic auth with wrong password' => sub {
my $build = _build();

my $env = {
'psgix.session' => {},
HTTP_AUTHORIZATION => 'Basic ' . MIME::Base64::encode_base64('username:wrong_password')
};

$build->call($env);

ok !$env->{'crafty.username'};
};

subtest 'loads user from basic auth' => sub {
my $build = _build();

my $env =
{ 'psgix.session' => {}, HTTP_AUTHORIZATION => 'Basic ' . MIME::Base64::encode_base64('username:password') };

$build->call($env);

is $env->{'crafty.username'}, 'username';
};

done_testing;

sub _build {
return Crafty::Middleware::User->new(
config => TestSetup->build_config,
app => sub { [ 200, [], ['granted'] ] },
@_
);
}

0 comments on commit 0a12061

Please sign in to comment.