Skip to content

Instantly share code, notes, and snippets.

@kraih
Last active September 25, 2018 18:34
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