Skip to content

Instantly share code, notes, and snippets.

@kraih
Last active September 25, 2018 18:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kraih/a1f793dfd1aeafcf7f9fd86d8416f7dd to your computer and use it in GitHub Desktop.
Save kraih/a1f793dfd1aeafcf7f9fd86d8416f7dd to your computer and use it in GitHub Desktop.
Bits and pieces that will hopefully some day become an OpenID plugin for Mojolicious (with alternative dummy backend for testing and token backend for API users)
package MyApp::Controller::Auth;
use Mojo::Base 'Mojolicious::Controller';
sub check {
my $self = shift;
my $role = $self->stash('role');
my $user = $self->current_user;
# User needs to log in or a different role
$self->render('permissions', status => 403) and return undef
unless $user && $self->users->has_role($user, $role);
return 1;
}
sub logout {
my $self = shift;
delete $self->session->{user};
$self->redirect_to('dashboard');
}
1;
package MyApp::Controller::Auth::Dummy;
use Mojo::Base 'Mojolicious::Controller';
sub login {
my $self = shift;
my $user = $self->users->find_or_create(
login => 'tester',
email => 'tester@example.com',
fullname => 'Dummy Test User',
roles => ['manager', 'admin']
);
$self->session(user => $user->{login});
$self->redirect_to('dashboard');
}
1;
package MyApp;
use Mojo::Base 'Mojolicious';
...
sub startup {
my $self = shift;
...
# Authentication
my $public = $self->routes;
my $bot = $public->under('/')->to('Auth::Token#check');
my $manager = $public->under('/' => {role => 'manager'})->to('Auth#check');
my $admin = $public->under('/' => {role => 'admin'})->to('Auth#check');
if ($config->{openid}) {
$public->get('/login')->to('Auth::OpenID#login')->name('login');
$public->get('/openid')->to('Auth::OpenID#openid')->name('openid');
$public->get('/response')->to('Auth::OpenID#response')->name('response');
}
else { $public->get('/login')->to('Auth::Dummy#login')->name('login') }
$public->get('/logout')->to('Auth#logout')->name('logout');
...
# Bot API
$bot->get(...)->...;
# Manager API
$manager->post(...)->...;
# Admin API
$admin->patch(...)->...;
...
}
1;
package MyApp::Controller::Auth::OpenID;
use Mojo::Base 'Mojolicious::Controller';
use LWP::UserAgent;
use Net::OpenID::Consumer;
sub login {
my $self = shift;
$self->csrf_token;
$self->redirect_to('openid');
}
sub openid {
my $self = shift;
my $base = $self->req->url->base->to_string;
my $csr = Net::OpenID::Consumer->new(
ua => LWP::UserAgent->new,
required_root => $base,
consumer_secret => $self->csrf_token
);
my $claimed_id
= $csr->claimed_identity($self->app->config->{openid}{provider});
return $self->render(text => $csr->err, status => 403) unless $claimed_id;
$claimed_id->set_extension_args('http://openid.net/extensions/sreg/1.1',
{required => 'email', optional => 'fullname,nickname'});
$claimed_id->set_extension_args(
'http://openid.net/srv/ax/1.0',
{
mode => 'fetch_request',
required => 'email,fullname,nickname,firstname,lastname',
'type.email' => "http://schema.openid.net/contact/email",
'type.fullname' => "http://axschema.org/namePerson",
'type.nickname' => "http://axschema.org/namePerson/friendly",
'type.firstname' => 'http://axschema.org/namePerson/first',
'type.lastname' => 'http://axschema.org/namePerson/last'
}
);
my $check_url = $claimed_id->check_url(
delayed_return => 1,
return_to => $self->url_for('response')->to_abs->to_string,
trust_root => $base
);
return $self->redirect_to($check_url) if $check_url;
$self->render(text => $csr->err, status => 403);
}
sub response {
my $self = shift;
my $params = $self->req->url->query->to_hash;
my $base = $self->req->url->base->to_string;
my $csr = Net::OpenID::Consumer->new(
ua => LWP::UserAgent->new,
required_root => $base,
consumer_secret => $self->csrf_token,
args => $params
);
my ($error, $login, $email, $fullname);
$csr->handle_server_response(
not_openid => sub { $error = 'Not an OpenID message' },
setup_needed => sub { $error = 'Setup not supported' },
cancelled => sub { $error = 'Authentication cancelled' },
verified => sub {
my $vident = shift;
my $sreg = $vident->signed_extension_fields(
'http://openid.net/extensions/sreg/1.1');
my $ax = $vident->signed_extension_fields('http://openid.net/srv/ax/1.0');
$error = 'Missing username'
unless $login = $sreg->{nickname} || $ax->{'value.nickname'};
$email = $sreg->{email} || $ax->{'value.email'};
$fullname = $sreg->{fullname} || $ax->{'value.fullname'};
},
error => sub {
my ($err, $txt) = @_;
$error = "$err: $txt";
},
);
return $self->render(text => $error, status => 403) if $error;
# Create in DB
my $user = $self->users->find_or_create(
login => $login,
email => $email,
fullname => $fullname
);
$self->session(user => $user->{login});
$self->redirect_to('dashboard');
}
1;
package MyApp::Controller::Auth::Token;
use Mojo::Base 'Mojolicious::Controller';
sub check {
my $self = shift;
my $tokens = $self->app->config('tokens');
return 1 unless @$tokens;
$self->_denied and return undef
unless my $auth = $self->req->headers->authorization;
$self->_denied and return undef unless $auth =~ /^Token\ (\S+)$/;
my $token = $1;
$self->_denied and return undef unless grep { $token eq $_ } @$tokens;
return 1;
}
sub _denied {
my $self = shift;
$self->render('permissions', status => 403);
}
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment