├── .gitignore ├── cpanfile ├── .travis.yml ├── .mailmap ├── dist.ini ├── t ├── lib │ └── TestUtils.pm ├── 03-current_user.t ├── 01-functional.t └── 02-functional_lazy.t ├── Makefile.PL ├── Changes ├── README.md ├── LICENSE └── lib └── Mojolicious └── Plugin └── Authentication.pm /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | blib* 3 | Makefile 4 | Makefile.old 5 | Build 6 | Build.bat 7 | _build* 8 | .build/ 9 | pm_to_blib* 10 | *.tar.gz 11 | .lwpcookies 12 | cover_db 13 | pod2htm*.tmp 14 | Mojolicious-Plugin-Authentication-* 15 | *.BAK 16 | *.bak 17 | META.json 18 | META.yml 19 | MYMETA.* 20 | *.swp 21 | *.bbprojectd 22 | .DS_Store 23 | *~ 24 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Mojolicious' => '8.0'; 2 | requires 'Exporter' => 0; 3 | 4 | on develop => sub { 5 | recommends 'Test::CheckManifest' => '1.31'; 6 | requires 'Test::CPAN::Changes' => '0.400002'; 7 | requires 'Test::Pod' => '1.41'; 8 | }; 9 | 10 | on test => sub { 11 | requires 'Test::More' => '0.96'; 12 | }; 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | 3 | perl: 4 | - '5.16' 5 | - '5.18' 6 | - '5.20' 7 | - '5.22' 8 | - '5.24' 9 | - dev 10 | - blead 11 | 12 | matrix: 13 | allow_failures: 14 | - perl: blead 15 | 16 | env: 17 | global: 18 | - RELEASE_TESTING=1 19 | 20 | before_install: 21 | - eval $(curl https://travis-perl.github.io/init) --auto 22 | 23 | notifications: 24 | email: false 25 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Ben van Staveren 2 | Ben van Staveren 3 | Ben van Staveren 4 | Ben van Staveren 5 | Doug Bell 6 | Ed J 7 | Edward Wildgoose 8 | Hernan Lopes 9 | José Joaquín Atria 10 | Jose Joaquin Atria 11 | Kartik Thakore 12 | Karpich Dmitry 13 | Krasimir Berov 14 | Красимир Беров 15 | Roman F. 16 | Terrence Brannon 17 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = Mojolicious-Plugin-Authentication 2 | author = Ben van Staveren 3 | license = Perl_5 4 | copyright_holder = Ben van Staveren 5 | copyright_year = 2021 6 | 7 | [NextRelease] 8 | [ReadmeAnyFromPod / MarkdownInBuild] 9 | filename = README.md 10 | 11 | [@Starter::Git] 12 | -remove = Pod2Readme 13 | -remove = Git::Push 14 | revision = 3 15 | managed_versions = 1 16 | regenerate = META.json 17 | regenerate = Makefile.PL 18 | regenerate = README.md 19 | regenerate = LICENSE 20 | Git::GatherDir.exclude_filename[0] = dist.ini 21 | Git::GatherDir.exclude_filename[1] = cpanfile 22 | Git::Commit.commit_msg = Release v%V%t 23 | Git::Tag.tag_message = 24 | Git::Tag.tag_format = %v 25 | 26 | [MinimumPerl] 27 | perl = 5.016 ; Mojolicious depends on 5.16 or greater 28 | 29 | [Prereqs::FromCPANfile] 30 | 31 | [GithubMeta] 32 | issues = 1 33 | 34 | [Git::Contributors] 35 | -------------------------------------------------------------------------------- /t/lib/TestUtils.pm: -------------------------------------------------------------------------------- 1 | package TestUtils; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Exporter 'import'; 7 | use Mojo::Promise; 8 | 9 | our @EXPORT_OK = qw( 10 | load_user_t validate_user_t 11 | load_user_t_p validate_user_t_p 12 | ); 13 | 14 | sub load_user_t { 15 | my $self = shift; 16 | my $uid = shift; 17 | return { 18 | 'username' => 'foo', 19 | 'password' => 'bar', 20 | 'name' => 'Foo' 21 | } 22 | if ( $uid eq 'userid' ) || $uid eq 'useridwithextradata'; 23 | return undef; 24 | } 25 | 26 | sub validate_user_t { 27 | my $self = shift; 28 | my $username = shift || ''; 29 | my $password = shift || ''; 30 | my $extradata = shift || {}; 31 | 32 | return 'useridwithextradata' if($username eq 'foo' && $password eq 'bar' && ( $extradata->{'ohnoes'} || '' ) eq 'itsameme'); 33 | return 'userid' if($username eq 'foo' && $password eq 'bar'); 34 | return undef; 35 | } 36 | 37 | sub load_user_t_p { Mojo::Promise->resolve(load_user_t(@_)) } 38 | 39 | sub validate_user_t_p { Mojo::Promise->resolve(validate_user_t(@_)) } 40 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.025. 2 | use strict; 3 | use warnings; 4 | 5 | use 5.016; 6 | 7 | use ExtUtils::MakeMaker; 8 | 9 | my %WriteMakefileArgs = ( 10 | "ABSTRACT" => "A plugin to make authentication a bit easier", 11 | "AUTHOR" => "Ben van Staveren ", 12 | "CONFIGURE_REQUIRES" => { 13 | "ExtUtils::MakeMaker" => 0 14 | }, 15 | "DISTNAME" => "Mojolicious-Plugin-Authentication", 16 | "LICENSE" => "perl", 17 | "MIN_PERL_VERSION" => "5.016", 18 | "NAME" => "Mojolicious::Plugin::Authentication", 19 | "PREREQ_PM" => { 20 | "Exporter" => 0, 21 | "Mojolicious" => "8.0" 22 | }, 23 | "TEST_REQUIRES" => { 24 | "ExtUtils::MakeMaker" => 0, 25 | "File::Spec" => 0, 26 | "Test::More" => "0.96" 27 | }, 28 | "VERSION" => "1.40", 29 | "test" => { 30 | "TESTS" => "t/*.t" 31 | } 32 | ); 33 | 34 | 35 | my %FallbackPrereqs = ( 36 | "Exporter" => 0, 37 | "ExtUtils::MakeMaker" => 0, 38 | "File::Spec" => 0, 39 | "Mojolicious" => "8.0", 40 | "Test::More" => "0.96" 41 | ); 42 | 43 | 44 | unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { 45 | delete $WriteMakefileArgs{TEST_REQUIRES}; 46 | delete $WriteMakefileArgs{BUILD_REQUIRES}; 47 | $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; 48 | } 49 | 50 | delete $WriteMakefileArgs{CONFIGURE_REQUIRES} 51 | unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; 52 | 53 | WriteMakefile(%WriteMakefileArgs); 54 | -------------------------------------------------------------------------------- /t/03-current_user.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | # Disable IPv6, epoll and kqueue 6 | BEGIN { $ENV{MOJO_NO_IPV6} = $ENV{MOJO_POLL} = 1 } 7 | 8 | use Test::More; 9 | 10 | use Mojo::File qw(path); 11 | use lib path(qw(t lib)).""; 12 | use TestUtils qw(load_user_t validate_user_t load_user_t_p validate_user_t_p); 13 | 14 | # testing code starts here 15 | use Mojolicious::Lite; 16 | use Test::Mojo; 17 | 18 | plugin 'Authentication', { 19 | autoload_user => 1, 20 | load_user => \&load_user_t, 21 | validate_user => \&validate_user_t, 22 | }; 23 | 24 | get '/other/endpoint' => sub { 25 | my $self = shift; 26 | $self->authenticate( 'foo', 'bar' ); 27 | $self->render( text => $self->current_user->{username} ); 28 | }; 29 | 30 | under '/api' => sub { 31 | my $self = shift; 32 | $self->current_user( { username => 'custom' } ); 33 | return 1; 34 | }; 35 | get '/endpoint' => sub { 36 | my $self = shift; 37 | $self->render( text => $self->current_user->{username} ); 38 | }; 39 | under '/'; # reset 40 | 41 | my $t = Test::Mojo->new; 42 | $t->get_ok( '/other/endpoint' )->status_is( 200 )->content_is( 'foo' ); 43 | $t->get_ok( '/api/endpoint' )->status_is( 200 )->content_is( 'custom' ); 44 | 45 | plugin 'Authentication', { 46 | autoload_user => 1, 47 | load_user_p => \&load_user_t_p, 48 | validate_user_p => \&validate_user_t_p, 49 | }; 50 | get '/other/endpoint_p' => sub { 51 | my $c = shift; 52 | $c->authenticate_p( 'foo', 'bar' ) 53 | ->then(sub { $c->current_user_p }) 54 | ->then(sub { $c->render( text => $_[0]->{username} ) }); 55 | }; 56 | under '/api_p' => sub { 57 | my $c = shift; 58 | $c->current_user_p( { username => 'custom' } )->then(sub { $c->continue }); 59 | return undef; 60 | }; 61 | get '/endpoint_p' => sub { 62 | my $c = shift; 63 | $c->current_user_p->then(sub { 64 | $c->render( text => $_[0]->{username} ); 65 | }); 66 | }; 67 | 68 | $t = Test::Mojo->new; 69 | $t->get_ok( '/other/endpoint_p' )->status_is( 200 )->content_is( 'foo' ); 70 | $t->get_ok( '/api_p/endpoint_p' )->status_is( 200 )->content_is( 'custom' ); 71 | 72 | done_testing; 73 | -------------------------------------------------------------------------------- /t/01-functional.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | # Disable IPv6, epoll and kqueue 6 | BEGIN { $ENV{MOJO_NO_IPV6} = $ENV{MOJO_POLL} = 1 } 7 | 8 | use Test::More; 9 | 10 | use Test::Mojo; 11 | use Mojo::File 'path'; 12 | 13 | use lib path(qw( t lib ))->to_string; 14 | use TestUtils; 15 | 16 | package Local::App::Base { 17 | use Mojolicious::Lite; 18 | 19 | plugin Authentication => { 20 | autoload_user => 1, 21 | load_user => \&TestUtils::load_user_t, 22 | validate_user => \&TestUtils::validate_user_t, 23 | }; 24 | 25 | get '/' => sub { 26 | shift->render(text => 'index page'); 27 | }; 28 | 29 | post '/login' => sub { 30 | my $self = shift; 31 | my $u = $self->req->param('u'); 32 | my $p = $self->req->param('p'); 33 | 34 | $self->render( 35 | text => $self->authenticate( $u, $p ) ? 'ok' : 'failed' 36 | ); 37 | }; 38 | 39 | post '/login2' => sub { 40 | my $self = shift; 41 | my $u = $self->req->param('u'); 42 | my $p = $self->req->param('p'); 43 | 44 | my $ok = $self->authenticate( $u, $p, { 'ohnoes' => 'itsameme' } ); 45 | $self->render( text => $ok ? 'ok' : 'failed'); 46 | }; 47 | 48 | get '/authonly' => sub { 49 | my $self = shift; 50 | $self->render( 51 | text => $self->is_user_authenticated 52 | ? 'authenticated' 53 | : 'not authenticated' 54 | ); 55 | }; 56 | 57 | get '/condition/authonly' => ( authenticated => 1 ) => sub { 58 | shift->render( text => 'authenticated condition' ); 59 | }; 60 | 61 | get '/logout' => sub { 62 | my $self = shift; 63 | $self->logout; 64 | $self->render( text => 'logout' ); 65 | }; 66 | } 67 | 68 | subtest 'Basic tests' => sub { 69 | my $t = Test::Mojo->new('Local::App::Base'); 70 | 71 | subtest 'Not logged in' => sub { 72 | $t->get_ok('/') 73 | ->status_is(200) 74 | ->content_is('index page'); 75 | 76 | $t->get_ok('/authonly') 77 | ->status_is(200) 78 | ->content_is('not authenticated'); 79 | 80 | $t->get_ok('/condition/authonly') 81 | ->status_is(404); 82 | }; 83 | 84 | subtest 'Failed login' => sub { 85 | $t->post_ok('/login' => form => { u => 'fnark', p => 'fnork' } ) 86 | ->status_is(200) 87 | ->content_is('failed'); 88 | 89 | $t->get_ok('/authonly') 90 | ->status_is(200) 91 | ->content_is('not authenticated'); 92 | }; 93 | 94 | subtest 'Logged in' => sub { 95 | $t->post_ok('/login' => form => { u => 'foo', p => 'bar' } ) 96 | ->status_is(200) 97 | ->content_is('ok'); 98 | 99 | $t->get_ok('/authonly') 100 | ->status_is(200) 101 | ->content_is('authenticated'); 102 | 103 | $t->get_ok('/condition/authonly') 104 | ->status_is(200) 105 | ->content_is('authenticated condition'); 106 | 107 | $t->get_ok('/logout') 108 | ->status_is(200) 109 | ->content_is('logout'); 110 | 111 | # Make sure we're no longer authenticated 112 | $t->get_ok('/authonly') 113 | ->status_is(200) 114 | ->content_is('not authenticated'); 115 | }; 116 | 117 | subtest 'Logged in with extra data' => sub { 118 | $t->post_ok('/login2' => form => { u => 'foo', p => 'bar' } ) 119 | ->status_is(200) 120 | ->content_is('ok'); 121 | 122 | $t->get_ok('/authonly') 123 | ->status_is(200) 124 | ->content_is('authenticated'); 125 | 126 | $t->get_ok('/condition/authonly') 127 | ->status_is(200) 128 | ->content_is('authenticated condition'); 129 | }; 130 | }; 131 | 132 | package Local::App::Unauthorized { 133 | use Mojolicious::Lite; 134 | 135 | plugin Authentication => { 136 | autoload_user => 1, 137 | fail_render => { status => 401, json => { message => 'Unauthorized' } }, 138 | load_user => \&TestUtils::load_user_t, 139 | validate_user => \&TestUtils::validate_user_t, 140 | }; 141 | 142 | get '/condition/authonly' => ( authenticated => 1 ) => sub { 143 | shift->render( text => 'authenticated condition' ); 144 | }; 145 | } 146 | 147 | subtest 'Tests with fail_render' => sub { 148 | my $t = Test::Mojo->new('Local::App::Unauthorized'); 149 | 150 | $t->get_ok('/condition/authonly') 151 | ->status_is(401) 152 | ->json_is('/message' => 'Unauthorized'); 153 | }; 154 | 155 | package Local::App::Promise { 156 | use Mojolicious::Lite; 157 | 158 | plugin Authentication => { 159 | autoload_user => 1, 160 | load_user_p => \&TestUtils::load_user_t_p, 161 | validate_user_p => \&TestUtils::validate_user_t_p, 162 | }; 163 | 164 | get '/condition/authonly' => ( authenticated => 1 ) => sub { 165 | shift->render( text => 'authenticated condition' ); 166 | }; 167 | } 168 | 169 | subtest 'Tests with async handlers' => sub { 170 | my $t = Test::Mojo->new('Local::App::Promise'); 171 | 172 | $t->get_ok('/condition/authonly') 173 | ->status_is(404); 174 | }; 175 | 176 | done_testing; 177 | -------------------------------------------------------------------------------- /t/02-functional_lazy.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | # Disable IPv6, epoll and kqueue 6 | BEGIN { $ENV{MOJO_NO_IPV6} = $ENV{MOJO_POLL} = 1 } 7 | 8 | use Test::More; 9 | 10 | use Test::Mojo; 11 | use Mojo::File 'path'; 12 | 13 | use lib path(qw( t lib ))->to_string; 14 | use TestUtils; 15 | 16 | package Local::App::Blocking { 17 | use Mojolicious::Lite; 18 | 19 | plugin Authentication => { 20 | autoload_user => 0, 21 | load_user => \&TestUtils::load_user_t, 22 | validate_user => \&TestUtils::validate_user_t, 23 | }; 24 | 25 | get '/' => sub { 26 | shift->render( text => 'index page' ); 27 | }; 28 | 29 | post '/login' => sub { 30 | my $self = shift; 31 | my $u = $self->req->param('u'); 32 | my $p = $self->req->param('p'); 33 | 34 | $self->render( 35 | text => ( $self->authenticate( $u, $p ) ) ? 'ok' : 'failed' 36 | ); 37 | }; 38 | 39 | get '/authonly' => sub { 40 | my $self = shift; 41 | $self->render( text => ( $self->is_user_authenticated ) 42 | ? 'authenticated' 43 | : 'not authenticated' ); 44 | }; 45 | 46 | get '/condition/authonly' => ( authenticated => 1 ) => sub { 47 | shift->render( text => 'authenticated condition' ); 48 | }; 49 | 50 | get '/authonly/lazy' => sub { 51 | my $self = shift; 52 | $self->render( text => ( $self->signature_exists ) 53 | ? 'sign authenticated' 54 | : 'sign not authenticated' ); 55 | }; 56 | 57 | get '/condition/authonly/lazy' => ( signed => 1 ) => sub { 58 | shift->render( text => 'signed authenticated condition' ); 59 | }; 60 | 61 | get '/logout' => sub { 62 | my $self = shift; 63 | $self->logout; 64 | $self->render( text => 'logout' ); 65 | }; 66 | 67 | get '/auto_validate' => sub { 68 | my $self = shift; 69 | 70 | eval { 71 | $self->authenticate( undef, undef, { auto_validate => 'userid' } ); 72 | 1; 73 | } or return $self->reply->exception('failed'); 74 | 75 | $self->render( text => 'ok' ); 76 | }; 77 | } 78 | 79 | subtest 'Blocking tests' => sub { 80 | my $t = Test::Mojo->new('Local::App::Blocking'); 81 | 82 | subtest 'Unauthenticated' => sub { 83 | $t->get_ok('/') 84 | ->status_is(200) 85 | ->content_is('index page'); 86 | 87 | $t->get_ok('/authonly/lazy') 88 | ->status_is(200) 89 | ->content_is('sign not authenticated'); 90 | 91 | $t->get_ok('/condition/authonly/lazy') 92 | ->status_is(404); 93 | }; 94 | 95 | subtest 'Login failed' => sub { 96 | $t->post_ok( '/login' => form => { u => 'fnark', p => 'fnork' } ) 97 | ->status_is(200) 98 | ->content_is('failed'); 99 | 100 | $t->get_ok('/authonly') 101 | ->status_is(200) 102 | ->content_is('not authenticated'); 103 | }; 104 | 105 | subtest 'Logged in' => sub { 106 | $t->post_ok( '/login' => form => { u => 'foo', p => 'bar' } ) 107 | ->status_is(200) 108 | ->content_is('ok'); 109 | 110 | $t->get_ok('/authonly') 111 | ->status_is(200) 112 | ->content_is('authenticated'); 113 | 114 | $t->get_ok('/condition/authonly') 115 | ->status_is(200) 116 | ->content_is('authenticated condition'); 117 | 118 | subtest 'Lazy authentication' => sub { 119 | $t->get_ok('/authonly/lazy') 120 | ->status_is(200) 121 | ->content_is('sign authenticated'); 122 | 123 | $t->get_ok('/condition/authonly/lazy') 124 | ->status_is(200) 125 | ->content_is('signed authenticated condition'); 126 | }; 127 | 128 | # Make sure we're logged out 129 | $t->get_ok('/logout') 130 | ->status_is(200) 131 | ->content_is('logout'); 132 | 133 | $t->get_ok('/authonly') 134 | ->status_is(200) 135 | ->content_is('not authenticated'); 136 | 137 | $t->get_ok('/authonly/lazy') 138 | ->status_is(200) 139 | ->content_is('sign not authenticated'); 140 | }; 141 | 142 | subtest 'Auto-validate' => sub { 143 | $t->get_ok('/auto_validate') 144 | ->status_is(200); 145 | }; 146 | }; 147 | 148 | package Local::App::Async { 149 | use Mojolicious::Lite; 150 | 151 | plugin Authentication => { 152 | autoload_user => 0, 153 | load_user_p => \&TestUtils::load_user_t_p, 154 | validate_user_p => \&TestUtils::validate_user_t_p, 155 | }; 156 | 157 | post '/login' => sub { 158 | my $self = shift; 159 | my $u = $self->req->param('u'); 160 | my $p = $self->req->param('p'); 161 | 162 | $self->authenticate_p( $u, $p )->then( sub { 163 | $self->render( text => $_[0] ? 'ok' : 'failed' ); 164 | }); 165 | }; 166 | 167 | get '/authonly' => sub { 168 | my $self = shift; 169 | $self->is_user_authenticated_p->then( sub { 170 | $self->render( text => $_[0] ? 'authenticated' : 'not authenticated' ); 171 | }); 172 | }; 173 | } 174 | 175 | subtest 'Non blocking tests' => sub { 176 | my $t = Test::Mojo->new('Local::App::Async'); 177 | 178 | $t->post_ok( '/login' => form => { u => 'fnark', p => 'fnork' } ) 179 | ->status_is(200) 180 | ->content_is('failed'); 181 | 182 | $t->get_ok('/authonly') 183 | ->status_is(200) 184 | ->content_is('not authenticated'); 185 | 186 | $t->post_ok( '/login' => form => { u => 'foo', p => 'bar' } ) 187 | ->status_is(200) 188 | ->content_is('ok'); 189 | }; 190 | 191 | done_testing; 192 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Mojolicious-Plugin-Authentication 2 | 3 | {{$NEXT}} 4 | 5 | 1.39 2022-06-10 10:06:06+01:00 Europe/London 6 | 7 | * Fixed: 8 | * Re-instate support for Perl versions under 5.20, which 9 | was accidentally broken in 1.38 10 | 11 | 1.38 2022-06-09 22:08:48+01:00 Europe/London 12 | 13 | * Fixed: 14 | * Fixed an issue that made the test suite fail with 15 | Mojolicious 9.25. 16 | 17 | 1.37 2021-06-10 22:07:08+01:00 Europe/London 18 | 19 | * Fixed: 20 | * Updated documentation to avoid references to 'over', which has 21 | been deprecated in Mojolicious 9.0. The documentation uses 22 | 'requires' instead (seeken) 23 | * Minor changes to the release process 24 | 25 | 1.36 2021-04-21 21:25:29 BST 26 | 27 | No changes from previous release 28 | 29 | 1.35 2021-03-03 21:33:37 GMT (TRIAL RELEASE) 30 | 31 | * Changes: 32 | * Moved dependency specification to cpanfile. 33 | * Distribution should now be installable from the repository as-is 34 | 35 | 1.34 2020-08-03 22:02:52+01:00 Europe/London (TRIAL RELEASE) 36 | 37 | * New: 38 | * Support async callbacks with "_p" appended 39 | 40 | 1.33 2018-04-22 00:05:05+01:00 Europe/London 41 | 42 | * Fixed: 43 | * Fixed some minor typos in the documentation (kberov) 44 | * Fixed a broken link in the project's metadata (fschlich) 45 | 46 | 1.32 2017-05-09 18:18:49+01:00 Europe/London 47 | 48 | * New: 49 | * The fail_render option now also accepts a code reference 50 | * Add contributor metadata for proper attribution. Names are matched 51 | with the ASCII names and emails on CPAN when available. If you spot a 52 | mistake or prefer something different, let me know! 53 | * POD is now tested as part of release cycle 54 | * Fixed: 55 | * Fixed an encoding error in POD 56 | 57 | 1.31 2017-05-09 00:46:25+01:00 Europe/London 58 | 59 | * Fixed: 60 | * Retroactively updated the change log... 61 | 62 | 1.30 2017-05-09 00:36:25+01:00 Europe/London 63 | 64 | * New: 65 | * The current user can be manually set by passing an argument to 66 | current_user (preaction) 67 | * Added a note in the documentation on possible namespace conflicts 68 | * Fixed: 69 | * Fixed an error that made the auto_validate key not behave as 70 | documented (jjatria) 71 | * Fixed the tests for Travis CI (preaction) 72 | * Fixed some typos in sample code (moltar) 73 | * Changes: 74 | * Simplified some of the conditional code in the distribution 75 | 76 | 1.29 2015-10-28 09:35:22+01:00 Europe/Berlin 77 | Implements new parameter to control response from routing via condition 78 | 79 | 80 | 1.28 2015-09-30 09:51:30+02:00 Europe/Paris 81 | Fix for bug that'd consider a defined-but-false return value from load_user/validate_user 82 | as a failed login/load attempt. (via carragom/github) 83 | 84 | 1.27 2015-06-16 21:28:06+07:00 Asia/Jakarta 85 | Merged pull requests fixing the documentation (bridge -> under) 86 | 87 | 1.26 2013-08-27 10:02:37 Asia/Jakarta 88 | Merged a pull request from kthakore that fixed the broken tests - thanks! 89 | Added 'auto_validate' option to extra data in authentication sub 90 | 91 | 1.24 2012-08-02 01:36:17 Asia/Jakarta 92 | Removed the example app until next release to resolve some RPM build issues, it will return in the next release 93 | 94 | 1.23 2012-07-03 13:56:38 Asia/Jakarta 95 | Issue #5 closed; the signed and authenticated conditions only worked if you indicated 'required' to be true, this got fixed. 96 | (Reported by SailingYYC) 97 | 98 | 1.22 2012-03-30 08:43:13 Asia/Jakarta 99 | Ed W contributed some more functionality and renamed some options and methods to make things a bit more sane. 100 | 101 | *NOTE*: The following options and methods have been deprecated: 102 | - The 'lazy' config option is now called 'autoload_user' 103 | - The 'user' helper is now called 'current_user' 104 | - The 'user_exists' helper is now called 'is_user_authenticated' 105 | 106 | Using these options and helpers will now generate deprecation warnings; they will be removed in the next release. 107 | 108 | 1.21 2012-01-11 08:33:07 Asia/Jakarta 109 | Ivo Welch was kind enough to send me his first ever Mojolicious application that demonstrates the usage of this module, and I've included it in the distribution. 110 | Changed the date on copyright notices to 2012 - how time flies! 111 | 112 | 1.20 2011-12-15 15:00:24 Asia/Jakarta 113 | Added "lazy_mode" and "signature_exists" along with some doc patches (meettya) 114 | 115 | 1.19 2011-09-25 21:26:38 Asia/Jakarta 116 | Added an 'extra_data' hashref to the authenticate method that will be passed to your registered callback, 117 | and merged in some documentation fixes courtesy of metaperl. 118 | 119 | 1.18 2011-07-27 01:15:43 Asia/Jakarta 120 | Fixed a bug for newer versions of Mojolicious that no longer send the same set of parameters to a hook 121 | Added the ability to pass extra data to the validate_user callback, it came up as a need in a project so ported it over 122 | 123 | 1.16 2011-06-04 18:29:01 Asia/Jakarta 124 | POD fixes and cleanup 125 | 126 | 1.15 2011-05-02 02:32:12 Asia/Jakarta 127 | Added a README.pod file for Github to display, since the "real" README is generated by Dist::Zilla 128 | 129 | 1.14 2011-04-30 00:39:11 Asia/Jakarta 130 | Code cleanup (memowe) 131 | 132 | 1.13 2011-04-25 17:49:13 Asia/Jakarta 133 | Added extra check on load_user and validate_user configuration options to make sure they're not only there, 134 | but they are actually code refs 135 | 136 | 1.12 2011-04-25 16:00:21 Asia/Jakarta 137 | A little bit of code cleanup, no major changes 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | Mojolicious::Plugin::Authentication - A plugin to make authentication a bit easier 4 | 5 | # SYNOPSIS 6 | 7 | use Mojolicious::Plugin::Authentication; 8 | 9 | $self->plugin('Authentication' => { 10 | autoload_user => 1, 11 | session_key => 'wickedapp', 12 | load_user_p => sub { ... }, 13 | validate_user_p => sub { ... }, 14 | }); 15 | # ... 16 | $self->authenticate_p( 17 | 'username', 'password', 18 | { optional => 'extra data stuff' }, 19 | )->then(sub { 20 | my ($authenticated) = @_; 21 | if ($authenticated) { 22 | # ... 23 | } 24 | }); 25 | 26 | # or, synchronous style 27 | $self->plugin('Authentication' => { 28 | autoload_user => 1, 29 | session_key => 'wickedapp', 30 | load_user => sub { ... }, 31 | validate_user => sub { ... }, 32 | current_user_fn => 'user', # compatibility with old code 33 | }); 34 | my $authenticated = $self->authenticate( 35 | 'username', 'password', 36 | { optional => 'extra data stuff' }, 37 | ); 38 | if ($authenticated) { 39 | ... 40 | } 41 | 42 | # METHODS 43 | 44 | Like other Mojolicious plugins, loading this plugin will import some function 45 | helpers into the namespace of your application. This will not normally cause 46 | any trouble, but be aware that if you define methods with the same names as 47 | those below, you'll likely run into unexpected results. 48 | 49 | ## authenticate($username, $password, $extra\_data\_hashref) 50 | 51 | Authenticate will use the supplied `load_user` and `validate_user` 52 | subroutine refs to see whether a user exists with the given username and 53 | password, and will set up the session accordingly. Returns true when the user 54 | has been successfully authenticated, false otherwise. You can pass additional 55 | data along in the `extra_data` hashref, it will be passed to your 56 | `validate_user` subroutine as-is. If the extra data hash contains a key 57 | `auto_validate`, the value of that key will be used as the UID, and 58 | authenticate will not call your `validate_user` callback; this can be used 59 | when working with OAuth tokens or other authentication mechanisms that do not 60 | use a local username and password form. 61 | 62 | ## authenticate\_p($username, $password, $extra\_data\_hashref) 63 | 64 | As above, but instead of returning a value, returns a promise of 65 | same. Available even if only synchronous callbacks are provided as these 66 | will be "promisified". 67 | 68 | ## is\_user\_authenticated 69 | 70 | Returns true if current\_user() returns some valid object, false otherwise. 71 | 72 | ## is\_user\_authenticated\_p 73 | 74 | As above, but instead of returning a value, returns a promise of same. 75 | 76 | ## current\_user 77 | 78 | Returns the user object as it was returned from the supplied `load_user` 79 | subroutine ref. 80 | 81 | You can change the current user by passing it in, but be careful: This 82 | bypasses the authentication. This is useful if you have multiple ways to 83 | authenticate users and want to re-use authorization checks that use 84 | `current_user`. 85 | 86 | Note that the name of this helper can be changed with 87 | the `current_user_fn` field during initialisation (see 88 | [below](#configuration)). 89 | 90 | ## current\_user\_p 91 | 92 | As above, but instead of returning a value, returns a promise of 93 | same. 94 | 95 | ## reload\_user 96 | 97 | Flushes the current user object and then returns current\_user(). 98 | 99 | ## reload\_user\_p 100 | 101 | As above, but instead of returning a value, returns a promise of 102 | same. 103 | 104 | ## signature\_exists 105 | 106 | Returns true if uid signature exist on the client side (in cookies), false 107 | otherwise. 108 | 109 | Warning: non-secure check! Use this method only for a "fast & dirty" lookup 110 | to see if the client has the proper cookies. May be helpful in some cases 111 | (for example - in counting `guest`/`logged users` or for additional 112 | non-confidential information for `logged users` but not for `guest`). 113 | 114 | ## logout 115 | 116 | Removes the session data for authentication, and effectively logs a user out. 117 | Returns a true value, to allow for chaining. 118 | 119 | # CONFIGURATION 120 | 121 | The following options can be set for the plugin, (but the "REQUIRED" 122 | ones can be replaced with a promise-returning equivalent with `_p` 123 | appended to the key): 124 | 125 | - load\_user (REQUIRED) 126 | 127 | A coderef for user loading (see ["USER LOADING"](#user-loading)) 128 | 129 | - validate\_user (REQUIRED) 130 | 131 | A coderef for user validation (see ["USER VALIDATION"](#user-validation)) 132 | 133 | - session\_key (optional) 134 | 135 | The name of the session key 136 | 137 | - autoload\_user (optional) 138 | 139 | Turn on/off automatic loading of user data - user data can be loaded only if 140 | it be used. May reduce site latency in some cases. 141 | 142 | - current\_user\_fn (optional) 143 | 144 | Set the name for the `current_user()` helper function. `_p` will be 145 | appended for the asynchronous version. 146 | 147 | - fail\_render (optional) 148 | 149 | Specify what is to be rendered when the authenticated condition is not met. 150 | 151 | Set to a coderef which will be called with the following signature: 152 | 153 | sub { 154 | my ($routes, $controller, $captures, $required) = @_; 155 | ... 156 | return $hashref; 157 | } 158 | 159 | The return value of the subroutine will be ignored if it evaluates to false. 160 | If it returns a hash reference, it will be dereferenced and passed as-is 161 | to the controller's `render` function. If you return anything else, you are 162 | going to have a bad time. 163 | 164 | If set directly to a hash reference, that will be passed to `render` instead. 165 | 166 | In order to set the session expiry time, use the following in your startup 167 | routine: 168 | 169 | $app->plugin('authentication', { ... }); 170 | $app->sessions->default_expiration(86400); # set expiry to 1 day 171 | $app->sessions->default_expiration(3600); # set expiry to 1 hour 172 | 173 | # USER LOADING 174 | 175 | The coderef you pass to the load\_user configuration key has the following 176 | signature: 177 | 178 | sub { 179 | my ($app, $uid) = @_; 180 | ... 181 | return $user; 182 | } 183 | 184 | The uid is the value that was originally returned from the `validate_user` 185 | coderef. You must return either a user object (it can be a hashref, arrayref, 186 | or a blessed object) or undef. 187 | 188 | # USER VALIDATION 189 | 190 | User validation is what happens when we need to authenticate someone. The 191 | coderef you pass to the `validate_user` configuration key has the following 192 | signature: 193 | 194 | sub { 195 | my ($c, $username, $password, $extradata) = @_; 196 | ... 197 | return $uid; 198 | } 199 | 200 | You must return either a user id or undef. The user id can be numerical or a 201 | string. Do not return hashrefs, arrayrefs or objects, since the behaviour of 202 | this plugin could get a little bit on the odd side of weird if you do that. 203 | 204 | # EXAMPLES 205 | 206 | For a code example using this, see the `t/01-functional.t` and 207 | `t/02-functional_lazy.t` tests, it uses [Mojolicious::Lite](https://metacpan.org/pod/Mojolicious%3A%3ALite) and this plugin. 208 | 209 | # ROUTING VIA CONDITION 210 | 211 | This plugin also exports a routing condition you can use in order to limit 212 | access to certain documents to only authenticated users. 213 | 214 | $r->route('/foo')->requires(authenticated => 1)->to('mycontroller#foo'); 215 | 216 | my $authenticated_only = $r->route('/members') 217 | ->requires(authenticated => 1) 218 | ->to('members#index'); 219 | 220 | $authenticated_only->route('online')->to('members#online'); 221 | 222 | If someone is not authenticated, these routes will not be considered by the 223 | dispatcher and unless you have set up a catch-all route, a 404 Not Found will 224 | be generated instead. 225 | 226 | And another condition for fast and unsecured checking for users, having a 227 | signature (without validating it). This method just checks client cookies for 228 | uid data existing. 229 | 230 | $r->route('/foo')->requires(signed => 1)->to('mycontroller#foo'); 231 | 232 | This behavior is similar to the "authenticated" condition. 233 | 234 | Prior to Mojolicious 9, use "over" instead of "requires." 235 | 236 | # ROUTING VIA CALLBACK 237 | 238 | If you want to be able to send people to a login page, you will have to use 239 | the following: 240 | 241 | my $members_only = $r->route('/members')->to(cb => sub { 242 | my $self = shift; 243 | 244 | $self->redirect_to('/login') and return 0 245 | unless($self->is_user_authenticated); 246 | 247 | return 1; 248 | }); 249 | 250 | $members_only->route('online')->to('members#online'); 251 | 252 | Lazy and unsecured methods: 253 | 254 | my $members_only = $r->route('/unimportant')->to(cb => sub { 255 | my $self = shift; 256 | 257 | $self->redirect_to('/login') and return 0 258 | unless($self->signature_exists); 259 | 260 | return 1; 261 | }); 262 | 263 | $members_only->route('pages')->to('unimportant#pages'); 264 | 265 | # ROUTING VIA BRIDGE 266 | 267 | If you want to be able to send people to a login page, you will have to use 268 | the following: 269 | 270 | my $auth_bridge = $r->under('/members')->to('auth#check'); 271 | # only visible to logged in users 272 | $auth_bridge->route('/list')->to('members#list'); 273 | 274 | And in your Auth controller you would put: 275 | 276 | sub check { 277 | my $self = shift; 278 | 279 | $self->redirect_to('/login') and return 0 280 | unless($self->is_user_authenticated); 281 | 282 | return 1; 283 | }; 284 | 285 | Lazy and unsecured methods: 286 | 287 | sub check { 288 | my $self = shift; 289 | 290 | $self->redirect_to('/login') and return 0 291 | unless($self->signature_exists); 292 | 293 | return 1; 294 | }; 295 | 296 | # SEE ALSO 297 | 298 | - [Mojolicious::Sessions](https://metacpan.org/pod/Mojolicious%3A%3ASessions) 299 | - [Mojocast 3: Authentication](http://mojocasts.com/e3#) 300 | 301 | # AUTHOR 302 | 303 | - Ben van Staveren, `` 304 | - José Joaquín Atria, `` 305 | 306 | # BUGS / CONTRIBUTING 307 | 308 | Please report any bugs or feature requests through the web interface at 309 | [https://github.com/benvanstaveren/mojolicious-plugin-authentication/issues](https://github.com/benvanstaveren/mojolicious-plugin-authentication/issues). 310 | 311 | # SUPPORT 312 | 313 | You can find documentation for this module with the perldoc command. 314 | 315 | perldoc Mojolicious::Plugin::Authentication 316 | 317 | You can also look for information at: 318 | 319 | - AnnoCPAN: Annotated CPAN documentation 320 | 321 | [http://annocpan.org/dist/Mojolicious-Plugin-Authentication](http://annocpan.org/dist/Mojolicious-Plugin-Authentication) 322 | 323 | - CPAN Ratings 324 | 325 | [http://cpanratings.perl.org/d/Mojolicious-Plugin-Authentication](http://cpanratings.perl.org/d/Mojolicious-Plugin-Authentication) 326 | 327 | - Search CPAN 328 | 329 | [http://search.cpan.org/dist/Mojolicious-Plugin-Authentication/](http://search.cpan.org/dist/Mojolicious-Plugin-Authentication/) 330 | 331 | # ACKNOWLEDGEMENTS 332 | 333 | Andrew Parker 334 | - For pointing out some bugs that crept in; a silent reminder not to 335 | code while sleepy 336 | 337 | Mirko Westermeier (memowe) 338 | - For doing some (much needed) code cleanup 339 | 340 | Terrence Brannon (metaperl) 341 | - Documentation patches 342 | 343 | Karpich Dmitry (meettya) 344 | - `lazy_mode` and `signature_exists` functionality, including a test 345 | and documentation 346 | 347 | Ivo Welch 348 | - For donating his first ever Mojolicious application that shows an 349 | example of how to use this module 350 | 351 | Ed Wildgoose (ewildgoose) 352 | - Adding the `current_user()` functionality, as well as some method 353 | renaming to make things a bit more sane. 354 | 355 | Colin Cyr (SailingYYC) 356 | - For reporting an issue with routing conditions; I really should not 357 | code while sleepy, brainfarts imminent! 358 | 359 | Carlos Ramos (carragom) 360 | - For fixing the bug that'd consider an uid of 0 or "0" to be a problem 361 | 362 | Doug Bell (preaction) 363 | - For improving the Travis CI integration and enabling arguments for 364 | current\_user 365 | 366 | Roman F (moltar) 367 | - For fixing some pesky typos in sample code 368 | 369 | Hernan Lopes (hernan604) 370 | - For updating some deprecated method names in the documentation 371 | 372 | # LICENSE AND COPYRIGHT 373 | 374 | Copyright 2011-2021 Ben van Staveren. 375 | 376 | This program is free software; you can redistribute it and/or modify it 377 | under the terms of either: the GNU General Public License as published 378 | by the Free Software Foundation; or the Artistic License. 379 | 380 | See http://dev.perl.org/licenses/ for more information. 381 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is copyright (c) 2021 by Ben van Staveren. 2 | 3 | This is free software; you can redistribute it and/or modify it under 4 | the same terms as the Perl 5 programming language system itself. 5 | 6 | Terms of the Perl programming language system itself 7 | 8 | a) the GNU General Public License as published by the Free 9 | Software Foundation; either version 1, or (at your option) any 10 | later version, or 11 | b) the "Artistic License" 12 | 13 | --- The GNU General Public License, Version 1, February 1989 --- 14 | 15 | This software is Copyright (c) 2021 by Ben van Staveren. 16 | 17 | This is free software, licensed under: 18 | 19 | The GNU General Public License, Version 1, February 1989 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 1, February 1989 23 | 24 | Copyright (C) 1989 Free Software Foundation, Inc. 25 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 26 | 27 | Everyone is permitted to copy and distribute verbatim copies 28 | of this license document, but changing it is not allowed. 29 | 30 | Preamble 31 | 32 | The license agreements of most software companies try to keep users 33 | at the mercy of those companies. By contrast, our General Public 34 | License is intended to guarantee your freedom to share and change free 35 | software--to make sure the software is free for all its users. The 36 | General Public License applies to the Free Software Foundation's 37 | software and to any other program whose authors commit to using it. 38 | You can use it for your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Specifically, the General Public License is designed to make 42 | sure that you have the freedom to give away or sell copies of free 43 | software, that you receive source code or can get it if you want it, 44 | that you can change the software or use pieces of it in new free 45 | programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of a such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must tell them their rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | GNU GENERAL PUBLIC LICENSE 72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 73 | 74 | 0. This License Agreement applies to any program or other work which 75 | contains a notice placed by the copyright holder saying it may be 76 | distributed under the terms of this General Public License. The 77 | "Program", below, refers to any such program or work, and a "work based 78 | on the Program" means either the Program or any work containing the 79 | Program or a portion of it, either verbatim or with modifications. Each 80 | licensee is addressed as "you". 81 | 82 | 1. You may copy and distribute verbatim copies of the Program's source 83 | code as you receive it, in any medium, provided that you conspicuously and 84 | appropriately publish on each copy an appropriate copyright notice and 85 | disclaimer of warranty; keep intact all the notices that refer to this 86 | General Public License and to the absence of any warranty; and give any 87 | other recipients of the Program a copy of this General Public License 88 | along with the Program. You may charge a fee for the physical act of 89 | transferring a copy. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion of 92 | it, and copy and distribute such modifications under the terms of Paragraph 93 | 1 above, provided that you also do the following: 94 | 95 | a) cause the modified files to carry prominent notices stating that 96 | you changed the files and the date of any change; and 97 | 98 | b) cause the whole of any work that you distribute or publish, that 99 | in whole or in part contains the Program or any part thereof, either 100 | with or without modifications, to be licensed at no charge to all 101 | third parties under the terms of this General Public License (except 102 | that you may choose to grant warranty protection to some or all 103 | third parties, at your option). 104 | 105 | c) If the modified program normally reads commands interactively when 106 | run, you must cause it, when started running for such interactive use 107 | in the simplest and most usual way, to print or display an 108 | announcement including an appropriate copyright notice and a notice 109 | that there is no warranty (or else, saying that you provide a 110 | warranty) and that users may redistribute the program under these 111 | conditions, and telling the user how to view a copy of this General 112 | Public License. 113 | 114 | d) You may charge a fee for the physical act of transferring a 115 | copy, and you may at your option offer warranty protection in 116 | exchange for a fee. 117 | 118 | Mere aggregation of another independent work with the Program (or its 119 | derivative) on a volume of a storage or distribution medium does not bring 120 | the other work under the scope of these terms. 121 | 122 | 3. You may copy and distribute the Program (or a portion or derivative of 123 | it, under Paragraph 2) in object code or executable form under the terms of 124 | Paragraphs 1 and 2 above provided that you also do one of the following: 125 | 126 | a) accompany it with the complete corresponding machine-readable 127 | source code, which must be distributed under the terms of 128 | Paragraphs 1 and 2 above; or, 129 | 130 | b) accompany it with a written offer, valid for at least three 131 | years, to give any third party free (except for a nominal charge 132 | for the cost of distribution) a complete machine-readable copy of the 133 | corresponding source code, to be distributed under the terms of 134 | Paragraphs 1 and 2 above; or, 135 | 136 | c) accompany it with the information you received as to where the 137 | corresponding source code may be obtained. (This alternative is 138 | allowed only for noncommercial distribution and only if you 139 | received the program in object code or executable form alone.) 140 | 141 | Source code for a work means the preferred form of the work for making 142 | modifications to it. For an executable file, complete source code means 143 | all the source code for all modules it contains; but, as a special 144 | exception, it need not include source code for modules which are standard 145 | libraries that accompany the operating system on which the executable 146 | file runs, or for standard header files or definitions files that 147 | accompany that operating system. 148 | 149 | 4. You may not copy, modify, sublicense, distribute or transfer the 150 | Program except as expressly provided under this General Public License. 151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 152 | the Program is void, and will automatically terminate your rights to use 153 | the Program under this License. However, parties who have received 154 | copies, or rights to use copies, from you under this General Public 155 | License will not have their licenses terminated so long as such parties 156 | remain in full compliance. 157 | 158 | 5. By copying, distributing or modifying the Program (or any work based 159 | on the Program) you indicate your acceptance of this license to do so, 160 | and all its terms and conditions. 161 | 162 | 6. Each time you redistribute the Program (or any work based on the 163 | Program), the recipient automatically receives a license from the original 164 | licensor to copy, distribute or modify the Program subject to these 165 | terms and conditions. You may not impose any further restrictions on the 166 | recipients' exercise of the rights granted herein. 167 | 168 | 7. The Free Software Foundation may publish revised and/or new versions 169 | of the General Public License from time to time. Such new versions will 170 | be similar in spirit to the present version, but may differ in detail to 171 | address new problems or concerns. 172 | 173 | Each version is given a distinguishing version number. If the Program 174 | specifies a version number of the license which applies to it and "any 175 | later version", you have the option of following the terms and conditions 176 | either of that version or of any later version published by the Free 177 | Software Foundation. If the Program does not specify a version number of 178 | the license, you may choose any version ever published by the Free Software 179 | Foundation. 180 | 181 | 8. If you wish to incorporate parts of the Program into other free 182 | programs whose distribution conditions are different, write to the author 183 | to ask for permission. For software which is copyrighted by the Free 184 | Software Foundation, write to the Free Software Foundation; we sometimes 185 | make exceptions for this. Our decision will be guided by the two goals 186 | of preserving the free status of all derivatives of our free software and 187 | of promoting the sharing and reuse of software generally. 188 | 189 | NO WARRANTY 190 | 191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 199 | REPAIR OR CORRECTION. 200 | 201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 209 | POSSIBILITY OF SUCH DAMAGES. 210 | 211 | END OF TERMS AND CONDITIONS 212 | 213 | Appendix: How to Apply These Terms to Your New Programs 214 | 215 | If you develop a new program, and you want it to be of the greatest 216 | possible use to humanity, the best way to achieve this is to make it 217 | free software which everyone can redistribute and change under these 218 | terms. 219 | 220 | To do so, attach the following notices to the program. It is safest to 221 | attach them to the start of each source file to most effectively convey 222 | the exclusion of warranty; and each file should have at least the 223 | "copyright" line and a pointer to where the full notice is found. 224 | 225 | 226 | Copyright (C) 19yy 227 | 228 | This program is free software; you can redistribute it and/or modify 229 | it under the terms of the GNU General Public License as published by 230 | the Free Software Foundation; either version 1, or (at your option) 231 | any later version. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU General Public License 239 | along with this program; if not, write to the Free Software 240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA 241 | 242 | 243 | Also add information on how to contact you by electronic and paper mail. 244 | 245 | If the program is interactive, make it output a short notice like this 246 | when it starts in an interactive mode: 247 | 248 | Gnomovision version 69, Copyright (C) 19xx name of author 249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 250 | This is free software, and you are welcome to redistribute it 251 | under certain conditions; type `show c' for details. 252 | 253 | The hypothetical commands `show w' and `show c' should show the 254 | appropriate parts of the General Public License. Of course, the 255 | commands you use may be called something other than `show w' and `show 256 | c'; they could even be mouse-clicks or menu items--whatever suits your 257 | program. 258 | 259 | You should also get your employer (if you work as a programmer) or your 260 | school, if any, to sign a "copyright disclaimer" for the program, if 261 | necessary. Here a sample; alter the names: 262 | 263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 264 | program `Gnomovision' (a program to direct compilers to make passes 265 | at assemblers) written by James Hacker. 266 | 267 | , 1 April 1989 268 | Ty Coon, President of Vice 269 | 270 | That's all there is to it! 271 | 272 | 273 | --- The Artistic License 1.0 --- 274 | 275 | This software is Copyright (c) 2021 by Ben van Staveren. 276 | 277 | This is free software, licensed under: 278 | 279 | The Artistic License 1.0 280 | 281 | The Artistic License 282 | 283 | Preamble 284 | 285 | The intent of this document is to state the conditions under which a Package 286 | may be copied, such that the Copyright Holder maintains some semblance of 287 | artistic control over the development of the package, while giving the users of 288 | the package the right to use and distribute the Package in a more-or-less 289 | customary fashion, plus the right to make reasonable modifications. 290 | 291 | Definitions: 292 | 293 | - "Package" refers to the collection of files distributed by the Copyright 294 | Holder, and derivatives of that collection of files created through 295 | textual modification. 296 | - "Standard Version" refers to such a Package if it has not been modified, 297 | or has been modified in accordance with the wishes of the Copyright 298 | Holder. 299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for 300 | the package. 301 | - "You" is you, if you're thinking about copying or distributing this Package. 302 | - "Reasonable copying fee" is whatever you can justify on the basis of media 303 | cost, duplication charges, time of people involved, and so on. (You will 304 | not be required to justify it to the Copyright Holder, but only to the 305 | computing community at large as a market that must bear the fee.) 306 | - "Freely Available" means that no fee is charged for the item itself, though 307 | there may be fees involved in handling the item. It also means that 308 | recipients of the item may redistribute it under the same conditions they 309 | received it. 310 | 311 | 1. You may make and give away verbatim copies of the source form of the 312 | Standard Version of this Package without restriction, provided that you 313 | duplicate all of the original copyright notices and associated disclaimers. 314 | 315 | 2. You may apply bug fixes, portability fixes and other modifications derived 316 | from the Public Domain or from the Copyright Holder. A Package modified in such 317 | a way shall still be considered the Standard Version. 318 | 319 | 3. You may otherwise modify your copy of this Package in any way, provided that 320 | you insert a prominent notice in each changed file stating how and when you 321 | changed that file, and provided that you do at least ONE of the following: 322 | 323 | a) place your modifications in the Public Domain or otherwise make them 324 | Freely Available, such as by posting said modifications to Usenet or an 325 | equivalent medium, or placing the modifications on a major archive site 326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your 327 | modifications in the Standard Version of the Package. 328 | 329 | b) use the modified Package only within your corporation or organization. 330 | 331 | c) rename any non-standard executables so the names do not conflict with 332 | standard executables, which must also be provided, and provide a separate 333 | manual page for each non-standard executable that clearly documents how it 334 | differs from the Standard Version. 335 | 336 | d) make other distribution arrangements with the Copyright Holder. 337 | 338 | 4. You may distribute the programs of this Package in object code or executable 339 | form, provided that you do at least ONE of the following: 340 | 341 | a) distribute a Standard Version of the executables and library files, 342 | together with instructions (in the manual page or equivalent) on where to 343 | get the Standard Version. 344 | 345 | b) accompany the distribution with the machine-readable source of the Package 346 | with your modifications. 347 | 348 | c) accompany any non-standard executables with their corresponding Standard 349 | Version executables, giving the non-standard executables non-standard 350 | names, and clearly documenting the differences in manual pages (or 351 | equivalent), together with instructions on where to get the Standard 352 | Version. 353 | 354 | d) make other distribution arrangements with the Copyright Holder. 355 | 356 | 5. You may charge a reasonable copying fee for any distribution of this 357 | Package. You may charge any fee you choose for support of this Package. You 358 | may not charge a fee for this Package itself. However, you may distribute this 359 | Package in aggregate with other (possibly commercial) programs as part of a 360 | larger (possibly commercial) software distribution provided that you do not 361 | advertise this Package as a product of your own. 362 | 363 | 6. The scripts and library files supplied as input to or produced as output 364 | from the programs of this Package do not automatically fall under the copyright 365 | of this Package, but belong to whomever generated them, and may be sold 366 | commercially, and may be aggregated with this Package. 367 | 368 | 7. C or perl subroutines supplied by you and linked into this Package shall not 369 | be considered part of this Package. 370 | 371 | 8. The name of the Copyright Holder may not be used to endorse or promote 372 | products derived from this software without specific prior written permission. 373 | 374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 376 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 377 | 378 | The End 379 | 380 | -------------------------------------------------------------------------------- /lib/Mojolicious/Plugin/Authentication.pm: -------------------------------------------------------------------------------- 1 | use warnings; 2 | use strict; 3 | 4 | package Mojolicious::Plugin::Authentication; 5 | 6 | our $VERSION = '1.40'; 7 | 8 | use Mojo::Base 'Mojolicious::Plugin'; 9 | use Mojo::Promise; 10 | 11 | sub register { 12 | my ($self, $app, $args) = @_; 13 | 14 | $args = { %{ $args || {} } }; # copy as mutating 15 | 16 | for my $cb_name (qw(load_user validate_user)) { 17 | my $p_name = $cb_name."_p"; 18 | die __PACKAGE__, ": missing '$cb_name' subroutine ref in parameters\n" 19 | unless grep ref eq 'CODE', @$args{$cb_name, $p_name}; 20 | $args->{$p_name} = sub { 21 | my @r = eval { $args->{$cb_name}->(@_) }; 22 | $@ ? Mojo::Promise->reject($@) : Mojo::Promise->resolve(@r); 23 | } if !$args->{$p_name}; 24 | } 25 | 26 | if (defined $args->{lazy}) { 27 | warn __PACKAGE__, 28 | ": the 'lazy' option is deprecated, ", 29 | "use 'autoload_user' instead\n"; 30 | 31 | $args->{autoload_user} = delete $args->{lazy}; 32 | } 33 | 34 | my $autoload_user = $args->{autoload_user} // 0; 35 | my $session_key = $args->{session_key} || 'auth_data'; 36 | my $our_stash_key = $args->{stash_key} || '__authentication__'; 37 | my $current_user_fn = $args->{current_user_fn} || 'current_user'; 38 | my $load_user_cb = $args->{load_user}; 39 | my $validate_user_cb = $args->{validate_user}; 40 | my $load_user_cb_p = $args->{load_user_p}; 41 | my $validate_user_cb_p= $args->{validate_user_p}; 42 | 43 | my $fail_render = ref $args->{fail_render} eq 'CODE' 44 | ? $args->{fail_render} : sub { $args->{fail_render} }; 45 | 46 | my $user_loader_sub = user_loader_closure( 47 | $our_stash_key, $session_key, $load_user_cb, 48 | ); 49 | my $user_loader_sub_p = user_loader_closure_p( 50 | $our_stash_key, $session_key, $load_user_cb_p, 51 | ); 52 | 53 | my $current_user = user_stash_extractor_closure( 54 | $our_stash_key, $user_loader_sub, 55 | ); 56 | my $current_user_p = user_stash_extractor_closure_p( 57 | $our_stash_key, $user_loader_sub_p, 58 | ); 59 | 60 | $app->helper(authenticate => authenticate_closure( 61 | $our_stash_key, $session_key, $validate_user_cb, $current_user, 62 | )); 63 | $app->helper(authenticate_p => authenticate_closure_p( 64 | $our_stash_key, $session_key, $validate_user_cb_p, $current_user_p, 65 | )); 66 | 67 | $app->hook(before_dispatch => $user_loader_sub_p) if $autoload_user; 68 | 69 | $app->routes->add_condition(authenticated => sub { 70 | my ($r, $c, $captures, $required) = @_; 71 | my $res = (!$required or $c->is_user_authenticated); 72 | 73 | unless ($res) { 74 | my $fail = $fail_render->(@_); 75 | $c->render(%{$fail}) if $fail; 76 | } 77 | return $res; 78 | }); 79 | 80 | $app->routes->add_condition(signed => sub { 81 | my ($r, $c, $captures, $required) = @_; 82 | return (!$required or $c->signature_exists); 83 | }); 84 | 85 | # deprecation handling 86 | $app->helper(user_exists => sub { 87 | warn __PACKAGE__, 88 | ": the 'user_exists' helper is deprecated, ", 89 | "use 'is_user_authenticated' instead\n"; 90 | return shift->is_user_authenticated(@_); 91 | }); 92 | 93 | $app->helper(user => sub { 94 | warn __PACKAGE__, 95 | ": the 'user' helper is deprecated, ", 96 | "use '$current_user_fn' instead\n"; 97 | return shift->$current_user_fn(@_); 98 | }); 99 | 100 | $app->helper(reload_user => sub { 101 | my $c = shift; 102 | # Clear stash to force a reload of the user object 103 | delete $c->stash->{$our_stash_key}; 104 | return $current_user->($c); 105 | }); 106 | $app->helper(reload_user_p => sub { 107 | my $c = shift; 108 | # Clear stash to force a reload of the user object 109 | delete $c->stash->{$our_stash_key}; 110 | return $current_user_p->($c); 111 | }); 112 | 113 | $app->helper(signature_exists => sub { 114 | my $c = shift; 115 | return !!$c->session($session_key); 116 | }); 117 | 118 | $app->helper(is_user_authenticated => sub { 119 | my $c = shift; 120 | return defined $current_user->($c); 121 | }); 122 | $app->helper(is_user_authenticated_p => sub { 123 | my $c = shift; 124 | $current_user_p->($c)->then(sub { 125 | return defined $_[0]; 126 | }); 127 | }); 128 | 129 | $app->helper($current_user_fn => $current_user); 130 | $app->helper($current_user_fn."_p" => $current_user_p); 131 | 132 | $app->helper(logout => sub { 133 | my $c = shift; 134 | delete $c->stash->{$our_stash_key}; 135 | delete $c->session->{$session_key}; 136 | return 1; 137 | }); 138 | } 139 | 140 | # Unconditionally load the user based on uid in session 141 | sub user_loader_closure { 142 | my ($our_stash_key, $session_key, $load_user_cb) = @_; 143 | sub { 144 | my $c = shift; 145 | my $uid = $c->session($session_key); 146 | return if !defined $uid; 147 | my $user = $load_user_cb->($c, $uid); 148 | if ($user) { 149 | $c->stash($our_stash_key => { user => $user }); 150 | } 151 | else { 152 | # cache result that user does not exist 153 | $c->stash($our_stash_key => { no_user => 1 }); 154 | } 155 | }; 156 | } 157 | sub user_loader_closure_p { 158 | my ($our_stash_key, $session_key, $load_user_cb_p) = @_; 159 | sub { 160 | my $c = shift; 161 | my $uid = $c->session($session_key); 162 | return Mojo::Promise->resolve if !defined $uid; 163 | $load_user_cb_p->($c, $uid)->then(sub { 164 | my $user = $_[0]; 165 | if ($user) { 166 | $c->stash($our_stash_key => { user => $user }); 167 | } 168 | else { 169 | # cache result that user does not exist 170 | $c->stash($our_stash_key => { no_user => 1 }); 171 | } 172 | }); 173 | }; 174 | } 175 | 176 | # Fetch the current user object from the stash - loading it if 177 | # not already loaded 178 | sub user_stash_extractor_closure { 179 | my ($our_stash_key, $user_loader_sub) = @_; 180 | sub { 181 | my ($c, $user) = @_; 182 | # Allow setting the current_user 183 | if ( defined $user ) { 184 | $c->stash($our_stash_key => { user => $user }); 185 | return; 186 | } 187 | my $stash = $c->stash($our_stash_key); 188 | $user_loader_sub->($c) 189 | unless $stash->{no_user} or defined $stash->{user}; 190 | $stash = $c->stash($our_stash_key); 191 | return $stash->{user}; 192 | }; 193 | } 194 | sub user_stash_extractor_closure_p { 195 | my ($our_stash_key, $user_loader_sub_p) = @_; 196 | sub { 197 | my ($c, $user) = @_; 198 | # Allow setting the current_user 199 | if ( defined $user ) { 200 | return Mojo::Promise->resolve->then(sub { 201 | $c->stash($our_stash_key => { user => $user }); 202 | }); 203 | } 204 | my $stash = $c->stash($our_stash_key); 205 | my $promise = ($stash->{no_user} or defined $stash->{user}) 206 | ? Mojo::Promise->resolve 207 | : $user_loader_sub_p->($c); 208 | $promise->then(sub { 209 | $stash = $c->stash($our_stash_key); 210 | return $stash->{user}; 211 | }); 212 | }; 213 | } 214 | 215 | sub authenticate_closure { 216 | my ($our_stash_key, $session_key, $validate_user_cb, $current_user) = @_; 217 | sub { 218 | my ($c, $user, $pass, $extradata) = @_; 219 | # if extradata contains "auto_validate", assume the passed username 220 | # is in fact valid, and auto_validate contains the uid; used for 221 | # OAuth and other stuff that does not work with usernames and 222 | # passwords; use this with extreme care if you must 223 | $extradata ||= {}; 224 | my $uid = $extradata->{auto_validate} // 225 | $validate_user_cb->($c, $user, $pass, $extradata); 226 | return undef if !defined $uid; 227 | $c->session($session_key => $uid); 228 | # Clear stash to force reload of any already loaded user object 229 | delete $c->stash->{$our_stash_key}; 230 | return 1 if defined $current_user->($c); 231 | return undef; 232 | }; 233 | } 234 | sub authenticate_closure_p { 235 | my ($our_stash_key, $session_key, $validate_user_cb_p, $current_user_p) = @_; 236 | sub { 237 | my ($c, $user, $pass, $extradata) = @_; 238 | $extradata ||= {}; 239 | my $promise = defined($extradata->{auto_validate}) 240 | ? Mojo::Promise->resolve($extradata->{auto_validate}) 241 | : $validate_user_cb_p->($c, $user, $pass, $extradata); 242 | $promise->then(sub { 243 | my ($uid) = @_; 244 | return undef if !defined $uid; 245 | $c->session($session_key => $uid); 246 | # Clear stash to force reload of any already loaded user object 247 | delete $c->stash->{$our_stash_key}; 248 | $current_user_p->($c); 249 | })->then(sub { 250 | defined $_[0] ? 1 : undef; 251 | }); 252 | }; 253 | } 254 | 255 | 1; 256 | 257 | __END__ 258 | 259 | =encoding UTF-8 260 | 261 | =head1 NAME 262 | 263 | Mojolicious::Plugin::Authentication - A plugin to make authentication a bit easier 264 | 265 | =head1 SYNOPSIS 266 | 267 | use Mojolicious::Plugin::Authentication; 268 | 269 | $self->plugin('Authentication' => { 270 | autoload_user => 1, 271 | session_key => 'wickedapp', 272 | load_user_p => sub { ... }, 273 | validate_user_p => sub { ... }, 274 | }); 275 | # ... 276 | $self->authenticate_p( 277 | 'username', 'password', 278 | { optional => 'extra data stuff' }, 279 | )->then(sub { 280 | my ($authenticated) = @_; 281 | if ($authenticated) { 282 | # ... 283 | } 284 | }); 285 | 286 | # or, synchronous style 287 | $self->plugin('Authentication' => { 288 | autoload_user => 1, 289 | session_key => 'wickedapp', 290 | load_user => sub { ... }, 291 | validate_user => sub { ... }, 292 | current_user_fn => 'user', # compatibility with old code 293 | }); 294 | my $authenticated = $self->authenticate( 295 | 'username', 'password', 296 | { optional => 'extra data stuff' }, 297 | ); 298 | if ($authenticated) { 299 | ... 300 | } 301 | 302 | =head1 METHODS 303 | 304 | Like other Mojolicious plugins, loading this plugin will import some function 305 | helpers into the namespace of your application. This will not normally cause 306 | any trouble, but be aware that if you define methods with the same names as 307 | those below, you'll likely run into unexpected results. 308 | 309 | =head2 authenticate($username, $password, $extra_data_hashref) 310 | 311 | Authenticate will use the supplied C and C 312 | subroutine refs to see whether a user exists with the given username and 313 | password, and will set up the session accordingly. Returns true when the user 314 | has been successfully authenticated, false otherwise. You can pass additional 315 | data along in the C hashref, it will be passed to your 316 | C subroutine as-is. If the extra data hash contains a key 317 | C, the value of that key will be used as the UID, and 318 | authenticate will not call your C callback; this can be used 319 | when working with OAuth tokens or other authentication mechanisms that do not 320 | use a local username and password form. 321 | 322 | =head2 authenticate_p($username, $password, $extra_data_hashref) 323 | 324 | As above, but instead of returning a value, returns a promise of 325 | same. Available even if only synchronous callbacks are provided as these 326 | will be "promisified". 327 | 328 | =head2 is_user_authenticated 329 | 330 | Returns true if current_user() returns some valid object, false otherwise. 331 | 332 | =head2 is_user_authenticated_p 333 | 334 | As above, but instead of returning a value, returns a promise of same. 335 | 336 | =head2 current_user 337 | 338 | Returns the user object as it was returned from the supplied C 339 | subroutine ref. 340 | 341 | You can change the current user by passing it in, but be careful: This 342 | bypasses the authentication. This is useful if you have multiple ways to 343 | authenticate users and want to re-use authorization checks that use 344 | C. 345 | 346 | Note that the name of this helper can be changed with 347 | the C field during initialisation (see 348 | L). 349 | 350 | =head2 current_user_p 351 | 352 | As above, but instead of returning a value, returns a promise of 353 | same. 354 | 355 | =head2 reload_user 356 | 357 | Flushes the current user object and then returns current_user(). 358 | 359 | =head2 reload_user_p 360 | 361 | As above, but instead of returning a value, returns a promise of 362 | same. 363 | 364 | =head2 signature_exists 365 | 366 | Returns true if uid signature exist on the client side (in cookies), false 367 | otherwise. 368 | 369 | Warning: non-secure check! Use this method only for a "fast & dirty" lookup 370 | to see if the client has the proper cookies. May be helpful in some cases 371 | (for example - in counting C/C or for additional 372 | non-confidential information for C but not for C). 373 | 374 | =head2 logout 375 | 376 | Removes the session data for authentication, and effectively logs a user out. 377 | Returns a true value, to allow for chaining. 378 | 379 | =head1 CONFIGURATION 380 | 381 | The following options can be set for the plugin, (but the "REQUIRED" 382 | ones can be replaced with a promise-returning equivalent with C<_p> 383 | appended to the key): 384 | 385 | =over 4 386 | 387 | =item load_user (REQUIRED) 388 | 389 | A coderef for user loading (see L) 390 | 391 | =item validate_user (REQUIRED) 392 | 393 | A coderef for user validation (see L) 394 | 395 | =item session_key (optional) 396 | 397 | The name of the session key 398 | 399 | =item autoload_user (optional) 400 | 401 | Turn on/off automatic loading of user data - user data can be loaded only if 402 | it be used. May reduce site latency in some cases. 403 | 404 | =item current_user_fn (optional) 405 | 406 | Set the name for the C helper function. C<_p> will be 407 | appended for the asynchronous version. 408 | 409 | =item fail_render (optional) 410 | 411 | Specify what is to be rendered when the authenticated condition is not met. 412 | 413 | Set to a coderef which will be called with the following signature: 414 | 415 | sub { 416 | my ($routes, $controller, $captures, $required) = @_; 417 | ... 418 | return $hashref; 419 | } 420 | 421 | The return value of the subroutine will be ignored if it evaluates to false. 422 | If it returns a hash reference, it will be dereferenced and passed as-is 423 | to the controller's C function. If you return anything else, you are 424 | going to have a bad time. 425 | 426 | If set directly to a hash reference, that will be passed to C instead. 427 | 428 | =back 429 | 430 | In order to set the session expiry time, use the following in your startup 431 | routine: 432 | 433 | $app->plugin('authentication', { ... }); 434 | $app->sessions->default_expiration(86400); # set expiry to 1 day 435 | $app->sessions->default_expiration(3600); # set expiry to 1 hour 436 | 437 | =head1 USER LOADING 438 | 439 | The coderef you pass to the load_user configuration key has the following 440 | signature: 441 | 442 | sub { 443 | my ($app, $uid) = @_; 444 | ... 445 | return $user; 446 | } 447 | 448 | The uid is the value that was originally returned from the C 449 | coderef. You must return either a user object (it can be a hashref, arrayref, 450 | or a blessed object) or undef. 451 | 452 | =head1 USER VALIDATION 453 | 454 | User validation is what happens when we need to authenticate someone. The 455 | coderef you pass to the C configuration key has the following 456 | signature: 457 | 458 | sub { 459 | my ($c, $username, $password, $extradata) = @_; 460 | ... 461 | return $uid; 462 | } 463 | 464 | You must return either a user id or undef. The user id can be numerical or a 465 | string. Do not return hashrefs, arrayrefs or objects, since the behaviour of 466 | this plugin could get a little bit on the odd side of weird if you do that. 467 | 468 | =head1 EXAMPLES 469 | 470 | For a code example using this, see the F and 471 | F tests, it uses L and this plugin. 472 | 473 | =head1 ROUTING VIA CONDITION 474 | 475 | This plugin also exports a routing condition you can use in order to limit 476 | access to certain documents to only authenticated users. 477 | 478 | $r->route('/foo')->requires(authenticated => 1)->to('mycontroller#foo'); 479 | 480 | my $authenticated_only = $r->route('/members') 481 | ->requires(authenticated => 1) 482 | ->to('members#index'); 483 | 484 | $authenticated_only->route('online')->to('members#online'); 485 | 486 | If someone is not authenticated, these routes will not be considered by the 487 | dispatcher and unless you have set up a catch-all route, a 404 Not Found will 488 | be generated instead. 489 | 490 | And another condition for fast and unsecured checking for users, having a 491 | signature (without validating it). This method just checks client cookies for 492 | uid data existing. 493 | 494 | $r->route('/foo')->requires(signed => 1)->to('mycontroller#foo'); 495 | 496 | This behavior is similar to the "authenticated" condition. 497 | 498 | Prior to Mojolicious 9, use "over" instead of "requires." 499 | 500 | =head1 ROUTING VIA CALLBACK 501 | 502 | If you want to be able to send people to a login page, you will have to use 503 | the following: 504 | 505 | my $members_only = $r->route('/members')->to(cb => sub { 506 | my $self = shift; 507 | 508 | $self->redirect_to('/login') and return 0 509 | unless($self->is_user_authenticated); 510 | 511 | return 1; 512 | }); 513 | 514 | $members_only->route('online')->to('members#online'); 515 | 516 | Lazy and unsecured methods: 517 | 518 | my $members_only = $r->route('/unimportant')->to(cb => sub { 519 | my $self = shift; 520 | 521 | $self->redirect_to('/login') and return 0 522 | unless($self->signature_exists); 523 | 524 | return 1; 525 | }); 526 | 527 | $members_only->route('pages')->to('unimportant#pages'); 528 | 529 | =head1 ROUTING VIA BRIDGE 530 | 531 | If you want to be able to send people to a login page, you will have to use 532 | the following: 533 | 534 | my $auth_bridge = $r->under('/members')->to('auth#check'); 535 | # only visible to logged in users 536 | $auth_bridge->route('/list')->to('members#list'); 537 | 538 | And in your Auth controller you would put: 539 | 540 | sub check { 541 | my $self = shift; 542 | 543 | $self->redirect_to('/login') and return 0 544 | unless($self->is_user_authenticated); 545 | 546 | return 1; 547 | }; 548 | 549 | Lazy and unsecured methods: 550 | 551 | sub check { 552 | my $self = shift; 553 | 554 | $self->redirect_to('/login') and return 0 555 | unless($self->signature_exists); 556 | 557 | return 1; 558 | }; 559 | 560 | =head1 SEE ALSO 561 | 562 | =over 4 563 | 564 | =item L 565 | 566 | =item L 567 | 568 | =back 569 | 570 | =head1 AUTHOR 571 | 572 | =over 4 573 | 574 | =item Ben van Staveren, C<< >> 575 | 576 | =item José Joaquín Atria, C<< >> 577 | 578 | =back 579 | 580 | =head1 BUGS / CONTRIBUTING 581 | 582 | Please report any bugs or feature requests through the web interface at 583 | L. 584 | 585 | =head1 SUPPORT 586 | 587 | You can find documentation for this module with the perldoc command. 588 | 589 | perldoc Mojolicious::Plugin::Authentication 590 | 591 | You can also look for information at: 592 | 593 | =over 4 594 | 595 | =item * AnnoCPAN: Annotated CPAN documentation 596 | 597 | L 598 | 599 | =item * CPAN Ratings 600 | 601 | L 602 | 603 | =item * Search CPAN 604 | 605 | L 606 | 607 | =back 608 | 609 | =head1 ACKNOWLEDGEMENTS 610 | 611 | Andrew Parker 612 | - For pointing out some bugs that crept in; a silent reminder not to 613 | code while sleepy 614 | 615 | Mirko Westermeier (memowe) 616 | - For doing some (much needed) code cleanup 617 | 618 | Terrence Brannon (metaperl) 619 | - Documentation patches 620 | 621 | Karpich Dmitry (meettya) 622 | - C and C functionality, including a test 623 | and documentation 624 | 625 | Ivo Welch 626 | - For donating his first ever Mojolicious application that shows an 627 | example of how to use this module 628 | 629 | Ed Wildgoose (ewildgoose) 630 | - Adding the C functionality, as well as some method 631 | renaming to make things a bit more sane. 632 | 633 | Colin Cyr (SailingYYC) 634 | - For reporting an issue with routing conditions; I really should not 635 | code while sleepy, brainfarts imminent! 636 | 637 | Carlos Ramos (carragom) 638 | - For fixing the bug that'd consider an uid of 0 or "0" to be a problem 639 | 640 | Doug Bell (preaction) 641 | - For improving the Travis CI integration and enabling arguments for 642 | current_user 643 | 644 | Roman F (moltar) 645 | - For fixing some pesky typos in sample code 646 | 647 | Hernan Lopes (hernan604) 648 | - For updating some deprecated method names in the documentation 649 | 650 | =head1 LICENSE AND COPYRIGHT 651 | 652 | Copyright 2011-2021 Ben van Staveren. 653 | 654 | This program is free software; you can redistribute it and/or modify it 655 | under the terms of either: the GNU General Public License as published 656 | by the Free Software Foundation; or the Artistic License. 657 | 658 | See http://dev.perl.org/licenses/ for more information. 659 | 660 | =cut 661 | --------------------------------------------------------------------------------