diff --git a/.gitignore b/.gitignore index 4667e68..16e53dc 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ themes/default/templates/partial/raw.js.ep tmp/* .zanata-cache/* cover_db/* +*.passwd diff --git a/CHANGELOG b/CHANGELOG index 6f6f338..06e2141 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ Revision history for Lutim - Add localStorage export and import feature - Add link to generate random file in collection - Add modal to modify expiration delay from "myfiles" page + - Add optional authentication (LDAP or htpasswd file) 0.10.4 2018-05-07 - Fix bug in cache system that would allow someone to view an image with an incorrect decryption key diff --git a/cpanfile b/cpanfile index ced403a..8cd5af3 100644 --- a/cpanfile +++ b/cpanfile @@ -45,6 +45,14 @@ feature 'memcached', 'Cache system using Memcached' => sub { requires 'CHI::Driver::Memcached'; requires 'Cache::Memcached'; }; +feature 'ldap', 'LDAP authentication support' => sub { + requires 'Net::LDAP'; + requires 'Mojolicious::Plugin::Authentication'; +}; +feature 'htpasswd', 'Htpasswd authentication support' => sub { + requires 'Apache::Htpasswd'; + requires 'Mojolicious::Plugin::Authentication'; +}; feature 'test' => sub { requires 'Devel::Cover'; }; diff --git a/cpanfile.snapshot b/cpanfile.snapshot index 90ca7f9..eb11ab2 100644 --- a/cpanfile.snapshot +++ b/cpanfile.snapshot @@ -1,5 +1,14 @@ # carton snapshot format: version 1.0 DISTRIBUTIONS + Apache-Htpasswd-1.9 + pathname: K/KM/KMELTZ/Apache-Htpasswd-1.9.tar.gz + provides: + Apache::Htpasswd 1.9 + requirements: + Crypt::PasswdMD5 0 + Digest::SHA 2 + ExtUtils::MakeMaker 0 + MIME::Base64 0 Archive-Zip-1.60 pathname: P/PH/PHRED/Archive-Zip-1.60.tar.gz provides: @@ -29,6 +38,27 @@ DISTRIBUTIONS IO::Seekable 0 Time::Local 0 perl 5.006 + Authen-SASL-2.16 + pathname: G/GB/GBARR/Authen-SASL-2.16.tar.gz + provides: + Authen::SASL 2.16 + Authen::SASL::CRAM_MD5 2.14 + Authen::SASL::EXTERNAL 2.14 + Authen::SASL::Perl 2.14 + Authen::SASL::Perl::ANONYMOUS 2.14 + Authen::SASL::Perl::CRAM_MD5 2.14 + Authen::SASL::Perl::DIGEST_MD5 2.14 + Authen::SASL::Perl::EXTERNAL 2.14 + Authen::SASL::Perl::GSSAPI 0.05 + Authen::SASL::Perl::LOGIN 2.14 + Authen::SASL::Perl::Layer 2.14 + Authen::SASL::Perl::PLAIN 2.14 + requirements: + Digest::HMAC_MD5 0 + Digest::MD5 0 + ExtUtils::MakeMaker 6.42 + Test::More 0 + perl 5.005 B-Hooks-EndOfScope-0.24 pathname: E/ET/ETHER/B-Hooks-EndOfScope-0.24.tar.gz provides: @@ -215,6 +245,14 @@ DISTRIBUTIONS ExtUtils::MakeMaker 0 Storable 0 perl 5.008001 + Convert-ASN1-0.27 + pathname: G/GB/GBARR/Convert-ASN1-0.27.tar.gz + provides: + Convert::ASN1 0.27 + requirements: + ExtUtils::MakeMaker 6.30 + Math::BigInt 1.997 + Test::More 0.90 Crypt-Blowfish-2.14 pathname: D/DP/DPARIS/Crypt-Blowfish-2.14.tar.gz provides: @@ -228,6 +266,16 @@ DISTRIBUTIONS requirements: Digest::MD5 2.00 ExtUtils::MakeMaker 0 + Crypt-PasswdMD5-1.40 + pathname: R/RS/RSAVAGE/Crypt-PasswdMD5-1.40.tgz + provides: + Crypt::PasswdMD5 1.40 + requirements: + Digest::MD5 2.53 + Module::Build 0 + Test::More 0.94 + strict 0 + warnings 0 Crypt-Rijndael-1.13 pathname: L/LE/LEONT/Crypt-Rijndael-1.13.tar.gz provides: @@ -1042,6 +1090,17 @@ DISTRIBUTIONS perl 5.006 strict 0 warnings 0 + Digest-HMAC-1.03 + pathname: G/GA/GAAS/Digest-HMAC-1.03.tar.gz + provides: + Digest::HMAC 1.03 + Digest::HMAC_MD5 1.01 + Digest::HMAC_SHA1 1.03 + requirements: + Digest::MD5 2 + Digest::SHA 1 + ExtUtils::MakeMaker 0 + perl 5.004 Digest-JHash-0.10 pathname: S/SH/SHLOMIF/Digest-JHash-0.10.tar.gz provides: @@ -1075,6 +1134,15 @@ DISTRIBUTIONS Canary::Stability 0 ExtUtils::MakeMaker 6.52 common::sense 0 + Encode-Locale-1.05 + pathname: G/GA/GAAS/Encode-Locale-1.05.tar.gz + provides: + Encode::Locale 1.05 + requirements: + Encode 2 + Encode::Alias 0 + ExtUtils::MakeMaker 0 + perl 5.008 Eval-Closure-0.14 pathname: D/DO/DOY/Eval-Closure-0.14.tar.gz provides: @@ -1188,6 +1256,19 @@ DISTRIBUTIONS File::Spec 0 URI::Escape 0 perl 5.008006 + File-Listing-6.04 + pathname: G/GA/GAAS/File-Listing-6.04.tar.gz + provides: + File::Listing 6.04 + File::Listing::apache 6.04 + File::Listing::dosftp 6.04 + File::Listing::netware 6.04 + File::Listing::unix 6.04 + File::Listing::vms 6.04 + requirements: + ExtUtils::MakeMaker 0 + HTTP::Date 6 + perl 5.006002 File-MimeInfo-0.28 pathname: M/MI/MICHIELB/File-MimeInfo-0.28.tar.gz provides: @@ -1269,6 +1350,46 @@ DISTRIBUTIONS HTML::Tagset 3.20 requirements: ExtUtils::MakeMaker 0 + HTTP-Cookies-6.04 + pathname: O/OA/OALDERS/HTTP-Cookies-6.04.tar.gz + provides: + HTTP::Cookies 6.04 + HTTP::Cookies::Microsoft 6.04 + HTTP::Cookies::Netscape 6.04 + requirements: + Carp 0 + ExtUtils::MakeMaker 0 + HTTP::Date 6 + HTTP::Headers::Util 6 + HTTP::Request 0 + Time::Local 0 + locale 0 + perl 5.008001 + strict 0 + vars 0 + HTTP-Daemon-6.01 + pathname: G/GA/GAAS/HTTP-Daemon-6.01.tar.gz + provides: + HTTP::Daemon 6.01 + HTTP::Daemon::ClientConn 6.01 + requirements: + ExtUtils::MakeMaker 0 + HTTP::Date 6 + HTTP::Request 6 + HTTP::Response 6 + HTTP::Status 6 + IO::Socket 0 + LWP::MediaTypes 6 + Sys::Hostname 0 + perl 5.008001 + HTTP-Date-6.02 + pathname: G/GA/GAAS/HTTP-Date-6.02.tar.gz + provides: + HTTP::Date 6.02 + requirements: + ExtUtils::MakeMaker 0 + Time::Local 0 + perl 5.006002 HTTP-Lite-2.44 pathname: N/NE/NEILB/HTTP-Lite-2.44.tar.gz provides: @@ -1280,6 +1401,52 @@ DISTRIBUTIONS perl 5.005 strict 0 warnings 0 + HTTP-Message-6.18 + pathname: O/OA/OALDERS/HTTP-Message-6.18.tar.gz + provides: + HTTP::Config 6.18 + HTTP::Headers 6.18 + HTTP::Headers::Auth 6.18 + HTTP::Headers::ETag 6.18 + HTTP::Headers::Util 6.18 + HTTP::Message 6.18 + HTTP::Request 6.18 + HTTP::Request::Common 6.18 + HTTP::Response 6.18 + HTTP::Status 6.18 + requirements: + Carp 0 + Compress::Raw::Zlib 0 + Encode 2.21 + Encode::Locale 1 + Exporter 5.57 + ExtUtils::MakeMaker 0 + HTTP::Date 6 + IO::Compress::Bzip2 2.021 + IO::Compress::Deflate 0 + IO::Compress::Gzip 0 + IO::HTML 0 + IO::Uncompress::Bunzip2 2.021 + IO::Uncompress::Gunzip 0 + IO::Uncompress::Inflate 0 + IO::Uncompress::RawInflate 0 + LWP::MediaTypes 6 + MIME::Base64 2.1 + MIME::QuotedPrint 0 + Storable 0 + URI 1.10 + base 0 + perl 5.008001 + strict 0 + warnings 0 + HTTP-Negotiate-6.01 + pathname: G/GA/GAAS/HTTP-Negotiate-6.01.tar.gz + provides: + HTTP::Negotiate 6.01 + requirements: + ExtUtils::MakeMaker 0 + HTTP::Headers 6 + perl 5.008001 Hash-Merge-0.300 pathname: R/RE/REHSACK/Hash-Merge-0.300.tar.gz provides: @@ -1295,6 +1462,15 @@ DISTRIBUTIONS Hash::MoreUtils 0.05 requirements: Test::More 0.90 + IO-HTML-1.001 + pathname: C/CJ/CJM/IO-HTML-1.001.tar.gz + provides: + IO::HTML 1.001 + requirements: + Carp 0 + Encode 2.10 + Exporter 5.57 + ExtUtils::MakeMaker 6.30 IO-Socket-SSL-2.056 pathname: S/SU/SULLR/IO-Socket-SSL-2.056.tar.gz provides: @@ -1516,6 +1692,14 @@ DISTRIBUTIONS requirements: ExtUtils::MakeMaker 0 perl 5.004 + JSON-2.97001 + pathname: I/IS/ISHIGAKI/JSON-2.97001.tar.gz + provides: + JSON 2.97001 + JSON::Backend::PP 2.97001 + requirements: + ExtUtils::MakeMaker 0 + Test::More 0 JSON-MaybeXS-1.004000 pathname: H/HA/HAARG/JSON-MaybeXS-1.004000.tar.gz provides: @@ -1526,6 +1710,13 @@ DISTRIBUTIONS JSON::PP 2.27300 Scalar::Util 0 perl 5.006 + LWP-MediaTypes-6.02 + pathname: G/GA/GAAS/LWP-MediaTypes-6.02.tar.gz + provides: + LWP::MediaTypes 6.02 + requirements: + ExtUtils::MakeMaker 0 + perl 5.006002 List-MoreUtils-0.428 pathname: R/RE/REHSACK/List-MoreUtils-0.428.tar.gz provides: @@ -1920,6 +2111,16 @@ DISTRIBUTIONS Pod::Simple 3.09 Time::Local 1.2 perl 5.010001 + Mojolicious-Plugin-Authentication-1.33 + pathname: J/JJ/JJATRIA/Mojolicious-Plugin-Authentication-1.33.tar.gz + provides: + Mojolicious::Plugin::Authentication 1.33 + requirements: + ExtUtils::MakeMaker 0 + Mojo::Base 0 + perl 5.010 + strict 0 + warnings 0 Mojolicious-Plugin-CHI-0.15 pathname: A/AK/AKRON/Mojolicious-Plugin-CHI-0.15.tar.gz provides: @@ -2420,6 +2621,25 @@ DISTRIBUTIONS Carp 0 ExtUtils::MakeMaker 0 Storable 0 + Net-HTTP-6.18 + pathname: O/OA/OALDERS/Net-HTTP-6.18.tar.gz + provides: + Net::HTTP 6.18 + Net::HTTP::Methods 6.18 + Net::HTTP::NB 6.18 + Net::HTTPS 6.18 + requirements: + Carp 0 + Compress::Raw::Zlib 0 + ExtUtils::MakeMaker 0 + IO::Socket::INET 0 + IO::Uncompress::Gunzip 0 + URI 0 + base 0 + perl 5.006002 + strict 0 + vars 0 + warnings 0 Net-SSLeay-1.85 pathname: M/MI/MIKEM/Net-SSLeay-1.85.tar.gz provides: @@ -2773,6 +2993,13 @@ DISTRIBUTIONS Test::Builder 0.13 Test::Builder::Tester 1.02 perl 5.006 + Text-Soundex-3.05 + pathname: R/RJ/RJBS/Text-Soundex-3.05.tar.gz + provides: + Text::Soundex 3.05 + requirements: + ExtUtils::MakeMaker 0 + if 0 Text-Unidecode-1.30 pathname: S/SB/SBURKE/Text-Unidecode-1.30.tar.gz provides: @@ -2955,12 +3182,86 @@ DISTRIBUTIONS base 0 lib 0 perl 5.008 + WWW-RobotRules-6.02 + pathname: G/GA/GAAS/WWW-RobotRules-6.02.tar.gz + provides: + WWW::RobotRules 6.02 + WWW::RobotRules::AnyDBM_File 6.00 + WWW::RobotRules::InCore 6.02 + requirements: + AnyDBM_File 0 + ExtUtils::MakeMaker 0 + Fcntl 0 + URI 1.10 + perl 5.008001 common-sense-3.74 pathname: M/ML/MLEHMANN/common-sense-3.74.tar.gz provides: common::sense 3.74 requirements: ExtUtils::MakeMaker 0 + libwww-perl-6.35 + pathname: E/ET/ETHER/libwww-perl-6.35.tar.gz + provides: + LWP 6.35 + LWP::Authen::Basic 6.35 + LWP::Authen::Digest 6.35 + LWP::Authen::Ntlm 6.35 + LWP::ConnCache 6.35 + LWP::Debug 6.35 + LWP::Debug::TraceHTTP 6.35 + LWP::DebugFile 6.35 + LWP::MemberMixin 6.35 + LWP::Protocol 6.35 + LWP::Protocol::cpan 6.35 + LWP::Protocol::data 6.35 + LWP::Protocol::file 6.35 + LWP::Protocol::ftp 6.35 + LWP::Protocol::gopher 6.35 + LWP::Protocol::http 6.35 + LWP::Protocol::loopback 6.35 + LWP::Protocol::mailto 6.35 + LWP::Protocol::nntp 6.35 + LWP::Protocol::nogo 6.35 + LWP::RobotUA 6.35 + LWP::Simple 6.35 + LWP::UserAgent 6.35 + libwww::perl undef + requirements: + CPAN::Meta::Requirements 2.120620 + Digest::MD5 0 + Encode 2.12 + Encode::Locale 0 + ExtUtils::MakeMaker 0 + File::Copy 0 + File::Listing 6 + Getopt::Long 0 + HTML::Entities 0 + HTML::HeadParser 0 + HTTP::Cookies 6 + HTTP::Daemon 6 + HTTP::Date 6 + HTTP::Negotiate 6 + HTTP::Request 6 + HTTP::Request::Common 6 + HTTP::Response 6 + HTTP::Status 6.18 + IO::Select 0 + IO::Socket 0 + LWP::MediaTypes 6 + MIME::Base64 2.1 + Module::Metadata 0 + Net::FTP 2.58 + Net::HTTP 6.07 + Scalar::Util 0 + Try::Tiny 0 + URI 1.10 + URI::Escape 0 + WWW::RobotRules 6 + base 0 + perl 5.008001 + strict 0 + warnings 0 namespace-autoclean-0.28 pathname: E/ET/ETHER/namespace-autoclean-0.28.tar.gz provides: @@ -2983,3 +3284,84 @@ DISTRIBUTIONS ExtUtils::MakeMaker 0 Package::Stash 0.23 perl 5.008001 + perl-ldap-0.65 + pathname: M/MA/MARSCHAP/perl-ldap-0.65.tar.gz + provides: + Bundle::Net::LDAP 0.03 + LWP::Protocol::ldap 1.25 + LWP::Protocol::ldapi undef + LWP::Protocol::ldaps undef + Net::LDAP 0.65 + Net::LDAP::ASN 0.12 + Net::LDAP::Bind 1.05 + Net::LDAP::Constant 0.23 + Net::LDAP::Control 0.18 + Net::LDAP::Control::Assertion 0.02 + Net::LDAP::Control::DontUseCopy 0.01 + Net::LDAP::Control::EntryChange 0.02 + Net::LDAP::Control::ManageDsaIT 0.04 + Net::LDAP::Control::MatchedValues 0.02 + Net::LDAP::Control::Paged 0.05 + Net::LDAP::Control::PasswordPolicy 0.04 + Net::LDAP::Control::PersistentSearch 0.04 + Net::LDAP::Control::PostRead 0.03 + Net::LDAP::Control::PreRead 0.04 + Net::LDAP::Control::ProxyAuth 1.09 + Net::LDAP::Control::Relax 0.03 + Net::LDAP::Control::Sort 0.04 + Net::LDAP::Control::SortResult 0.03 + Net::LDAP::Control::SyncDone 0.03 + Net::LDAP::Control::SyncRequest 0.03 + Net::LDAP::Control::SyncState 0.04 + Net::LDAP::Control::VLV 0.06 + Net::LDAP::Control::VLVResponse 0.04 + Net::LDAP::DSML 0.16 + Net::LDAP::DSML::output 0.16 + Net::LDAP::DSML::pp 0.16 + Net::LDAP::Entry 0.27 + Net::LDAP::Extension 1.04 + Net::LDAP::Extension::Cancel 0.02 + Net::LDAP::Extension::Refresh 0.03 + Net::LDAP::Extension::SetPassword 0.06 + Net::LDAP::Extension::WhoAmI 0.02 + Net::LDAP::Extra 0.02 + Net::LDAP::Extra::AD 0.04 + Net::LDAP::Extra::eDirectory 0.03 + Net::LDAP::Filter 0.20 + Net::LDAP::FilterList 0.02 + Net::LDAP::FilterMatch 0.27 + Net::LDAP::Intermediate 0.04 + Net::LDAP::Intermediate::SyncInfo 0.03 + Net::LDAP::LDIF 0.26 + Net::LDAP::Message 1.12 + Net::LDAP::Message::Dummy 1.12 + Net::LDAP::Reference 0.14 + Net::LDAP::RootDSE 0.02 + Net::LDAP::Schema 0.9908 + Net::LDAP::Search 0.14 + Net::LDAP::Util 0.19 + Net::LDAPI 0.04 + Net::LDAPS 0.06 + requirements: + Authen::SASL 2.00 + Convert::ASN1 0.2 + Digest::MD5 0 + ExtUtils::MakeMaker 6.42 + File::Basename 0 + File::Compare 0 + File::Path 0 + HTTP::Negotiate 0 + HTTP::Response 0 + HTTP::Status 0 + IO::File 0 + IO::Socket::SSL 1.26 + JSON 0 + LWP 0 + LWP::MediaTypes 0 + LWP::Protocol 0 + MIME::Base64 0 + Test::More 0 + Text::Soundex 0 + Time::Local 0 + URI::ldap 1.1 + perl 5.008001 diff --git a/lib/Lutim.pm b/lib/Lutim.pm index 47fd498..8e47657 100644 --- a/lib/Lutim.pm +++ b/lib/Lutim.pm @@ -52,6 +52,7 @@ sub startup { dbtype => 'sqlite', db_path => 'minion.db' }, + session_duration => 3600, cache_max_size => 0, memcached_servers => [], quiet_logs => 0, @@ -176,6 +177,106 @@ sub startup { delete @{$wait_for_it}{grep { time - $wait_for_it->{$_} > $self->config->{anti_flood_delay} } keys %{$wait_for_it}} if (defined($wait_for_it)); }); + # Authentication (if configured) + if (defined($self->config('ldap')) || defined($self->config('htpasswd'))) { + if (defined($self->config('ldap'))) { + require Net::LDAP; + } + if (defined($self->config('htpasswd'))) { + require Apache::Htpasswd; + } + die 'Unable to read '.$self->config('htpasswd') if (defined($self->config('htpasswd')) && !-r $self->config('htpasswd')); + $self->plugin('Authentication' => + { + autoload_user => 1, + session_key => 'Lutim', + load_user => sub { + my ($c, $username) = @_; + + return $username; + }, + validate_user => sub { + my ($c, $username, $password, $extradata) = @_; + + if (defined($c->config('ldap'))) { + my $ldap = Net::LDAP->new($c->config->{ldap}->{uri}); + + my $mesg; + if (defined($c->config->{ldap}->{bind_dn}) && defined($c->config->{ldap}->{bind_pwd})) { + # connect to the ldap server using the bind credentials + $mesg = $ldap->bind( + $c->config->{ldap}->{bind_dn}, + password => $c->config->{ldap}->{bind_pwd} + ); + } else { + # anonymous bind + $mesg = $ldap->bind + } + + if ($mesg->code) { + $c->app->log->info('[LDAP INFO] Authenticated bind failed - Login: '.$c->config->{ldap}->{bind_dn}) if defined($c->config->{ldap}->{bind_dn}); + $c->app->log->error('[LDAP ERROR] Error on bind: '.$mesg->error); + return undef; + } + + my $ldap_user_attr = $c->config->{ldap}->{user_attr}; + my $ldap_user_filter = $c->config->{ldap}->{user_filter}; + + # search the ldap database for the user who is trying to login + $mesg = $ldap->search( + base => $c->config->{ldap}->{user_tree}, + filter => "(&($ldap_user_attr=$username)$ldap_user_filter)" + ); + + if ($mesg->code) { + $c->app->log->error('[LDAP ERROR] Error on search: '.$mesg->error); + return undef; + } + + # check to make sure that the ldap search returned at least one entry + my @entries = $mesg->entries; + my $entry = $entries[0]; + + unless (defined $entry) { + $c->app->log->info("[LDAP INFO] Authentication failed - User $username filtered out, IP: ".$c->ip); + return undef; + } + + # retrieve the first user returned by the search + $c->app->log->debug("[LDAP DEBUG] Found user dn: ".$entry->dn); + + # Now we know that the user exists + $mesg = $ldap->bind($entry->dn, + password => $password + ); + + if ($mesg->code) { + $c->app->log->info("[LDAP INFO] Authentication failed - Login: $username, IP: ".$c->ip); + $c->app->log->error('[LDAP ERROR] Authentication failed '.$mesg->error); + return undef; + } + + $c->app->log->info("[LDAP INFO] Authentication successful - Login: $username, IP: ".$c->ip); + } elsif (defined($c->config('htpasswd'))) { + my $htpasswd = new Apache::Htpasswd( + { + passwdFile => $c->config('htpasswd'), + ReadOnly => 1 + } + ); + if (!$htpasswd->htCheckPassword($username, $password)) { + return undef; + } + $c->app->log->info("[Simple authentication successful] login: $username, IP: ".$c->ip); + } + + return $username; + } + } + ); + $self->app->sessions->default_expiration($self->config('session_duration')); + } + $self->defaults(layout => 'default'); $self->provisioning(); @@ -183,6 +284,14 @@ sub startup { # Router my $r = $self->routes; + $r->add_condition(authorized => sub { + my ($r, $c, $captures) = @_; + + return 1 unless (defined($self->config('ldap')) || defined($self->config('htpasswd'))); + + return $c->is_user_authenticated; + }); + $r->options(sub { my $c = shift; $c->res->headers->allow('POST') if (defined($c->config->{allowed_domains})); @@ -190,23 +299,43 @@ sub startup { }); $r->get('/')-> - to('Controller#home')-> + over('authorized')-> + to('Image#home')-> name('index'); + $r->get('/')-> + to('Authent#index'); + + + if (defined $self->config('ldap') || defined $self->config('htpasswd')) { + # Login page + $r->get('/login') + ->to('Authent#index') + ->name('login'); + + # Authentication + $r->post('/login') + ->to('Authent#login'); + + # Logout page + $r->get('/logout') + ->to('Authent#log_out') + ->name('logout'); + } $r->get('/about')-> - to('Controller#about')-> + to('Image#about')-> name('about'); $r->get('/infos')-> - to('Controller#infos')-> + to('Image#infos')-> name('infos'); $r->get('/stats')-> - to('Controller#stats')-> + to('Image#stats')-> name('stats'); $r->get('/lang/:l')-> - to('Controller#change_lang')-> + to('Image#change_lang')-> name('lang'); $r->get('/partial/:file' => sub { @@ -229,60 +358,72 @@ sub startup { ); })->name('gallery'); - $r->get('/myfiles' => sub { - shift->render( - template => 'myfiles' - ); - })->name('myfiles'); + $r->get('/myfiles')-> + over('authorized')-> + name('myfiles'); + $r->get('/myfiles')-> + to('Authent#index'); $r->get('/manifest.webapp')-> - to('Controller#webapp')-> + to('Image#webapp')-> name('manifest.webapp'); $r->get('/zip') - ->to('Controller#zip') + ->to('Image#zip') ->name('zip'); $r->get('/random') - ->to('Controller#random') + ->to('Image#random') ->name('random'); $r->post('/')-> - to('Controller#add')-> + over('authorized')-> + to('Image#add')-> name('add'); + $r->post('/')-> + to('Authent#index'); $r->get('/d/:short/:token')-> - to('Controller#delete')-> + over('authorized')-> + to('Image#delete')-> name('delete'); + $r->get('/d/:short/:token')-> + to('Authent#index'); $r->post('/m/:short/:token')-> - to('Controller#modify')-> + over('authorized')-> + to('Image#modify')-> name('modify'); + $r->post('/m/:short/:token')-> + to('Authent#index'); $r->post('/c')-> - to('Controller#get_counter')-> + over('authorized')-> + to('Image#get_counter')-> name('counter'); + $r->post('/c')-> + to('Authent#index'); $r->get('/about/<:short>.<:f>')-> - to('Controller#about_img')-> + to('Image#about_img')-> name('about_img'); $r->get('/about/:short/<:key>.<:f>')-> - to('Controller#about_img')-> + to('Image#about_img')-> name('about_img'); $r->get('/<:short>.<:f>')-> - to('Controller#short')-> + to('Image#short')-> name('short'); $r->get('/:short')-> - to('Controller#short'); + to('Image#short'); $r->get('/:short/<:key>.<:f>')-> - to('Controller#short'); + to('Image#short'); $r->get('/:short/:key')-> - to('Controller#short'); + to('Image#short'); } 1; diff --git a/lib/Lutim/Controller/Authent.pm b/lib/Lutim/Controller/Authent.pm new file mode 100644 index 0000000..f0da81b --- /dev/null +++ b/lib/Lutim/Controller/Authent.pm @@ -0,0 +1,76 @@ +# vim:set sw=4 ts=4 sts=4 ft=perl expandtab: +package Lutim::Controller::Authent; +use Mojo::Base 'Mojolicious::Controller'; + +sub index { + my $c = shift; + if ($c->is_user_authenticated) { + $c->redirect_to('index'); + } else { + $c->render(template => 'login'); + } +} + +sub login { + my $c = shift; + my $login = $c->param('login'); + my $pwd = $c->param('password'); + my $ref = $c->req->headers->referrer; + + if($c->authenticate($login, $pwd)) { + $c->respond_to( + json => sub { + my $c = shift; + $c->render( + json => { + success => Mojo::JSON->true, + msg => $c->l('You have been successfully logged in.') + } + ); + }, + any => sub { + $c->redirect_to($ref); + } + ); + } else { + my $msg = $c->l('Please, check your credentials: unable to authenticate.'); + $c->respond_to( + json => sub { + my $c = shift; + $c->render( + json => { + success => Mojo::JSON->false, + msg => $msg + } + ); + }, + any => sub { + $c->stash(msg => $msg); + $c->render(template => 'login') + } + ); + } +} + +sub log_out { + my $c = shift; + if ($c->is_user_authenticated) { + $c->logout; + } + $c->respond_to( + json => sub { + my $c = shift; + $c->render( + json => { + success => Mojo::JSON->true, + msg => $c->l('You have been successfully logged out.') + } + ); + }, + any => sub { + $c->render(template => 'logout'); + } + ); +} + +1; diff --git a/lib/Lutim/Controller.pm b/lib/Lutim/Controller/Image.pm similarity index 99% rename from lib/Lutim/Controller.pm rename to lib/Lutim/Controller/Image.pm index 31da9d8..2bfbbac 100644 --- a/lib/Lutim/Controller.pm +++ b/lib/Lutim/Controller/Image.pm @@ -1,5 +1,5 @@ # vim:set sw=4 ts=4 sts=4 expandtab: -package Lutim::Controller; +package Lutim::Controller::Image; use Mojo::Base 'Mojolicious::Controller'; use Mojo::Util qw(url_escape url_unescape b64_encode encode); use Mojo::Asset::Memory; diff --git a/lutim.conf.template b/lutim.conf.template index abf3955..5100e73 100644 --- a/lutim.conf.template +++ b/lutim.conf.template @@ -159,6 +159,29 @@ # } #}, + # set `ldap` if you want that only authenticated users can shorten URLs + # please note that everybody can still use shortend URLs + # optional, no default + #ldap => { + # uri => 'ldaps://ldap.example.org', # server URI + # user_tree => 'ou=users,dc=example,dc=org', # search base DN + # bind_dn => 'uid=ldap_user,ou=users,dc=example,dc=org', # search bind DN + # bind_pwd => 'secr3t', # search bind password + # user_attr => 'uid', # user attribute (uid, mail, sAMAccountName, etc.) + # user_filter => '(!(uid=ldap_user))', # user filter (to exclude some users, etc.) + #}, + + # set `htpasswd` if you want to use an htpasswd file instead of ldap + # create the file with `htpasswd -c lutim.passwd user`, update it with `htpasswd lutim.passwd user2` + # make sure that lutim can read the file! + # optional, no default + #htpasswd => 'lutim.passwd', + + # if you've set ldap or htpasswd above, the session will last `session_duration` seconds before + # the user needs to reauthenticate + # optional, default is 3600 + #session_duration => 3600, + # disable counters of images # set to 1 to disable counters # optional, counters are enabled by default diff --git a/themes/default/lib/Lutim/I18N/lutim.pot b/themes/default/lib/Lutim/I18N/lutim.pot index 99c91c2..59ba5a4 100644 --- a/themes/default/lib/Lutim/I18N/lutim.pot +++ b/themes/default/lib/Lutim/I18N/lutim.pot @@ -32,11 +32,11 @@ msgstr "" msgid "-or-" msgstr "" -#: lib/Lutim.pm:221 lib/Lutim/Command/cron/stats.pm:149 lib/Lutim/Command/cron/stats.pm:163 lib/Lutim/Command/cron/stats.pm:180 themes/default/templates/index.html.ep:5 themes/default/templates/myfiles.html.ep:5 themes/default/templates/partial/raw.js.ep:25 themes/default/templates/partial/raw.js.ep:8 themes/default/templates/raw.html.ep:10 +#: lib/Lutim.pm:350 lib/Lutim/Command/cron/stats.pm:149 lib/Lutim/Command/cron/stats.pm:163 lib/Lutim/Command/cron/stats.pm:180 themes/default/templates/index.html.ep:5 themes/default/templates/myfiles.html.ep:5 themes/default/templates/partial/raw.js.ep:25 themes/default/templates/partial/raw.js.ep:8 themes/default/templates/raw.html.ep:10 msgid "1 year" msgstr "" -#: lib/Lutim.pm:220 lib/Lutim/Command/cron/stats.pm:146 lib/Lutim/Command/cron/stats.pm:160 lib/Lutim/Command/cron/stats.pm:177 themes/default/templates/index.html.ep:4 themes/default/templates/myfiles.html.ep:33 themes/default/templates/myfiles.html.ep:4 themes/default/templates/partial/for_my_delay.html.ep:13 themes/default/templates/partial/lutim.js.ep:149 themes/default/templates/partial/raw.js.ep:22 themes/default/templates/partial/raw.js.ep:5 themes/default/templates/raw.html.ep:7 +#: lib/Lutim.pm:349 lib/Lutim/Command/cron/stats.pm:146 lib/Lutim/Command/cron/stats.pm:160 lib/Lutim/Command/cron/stats.pm:177 themes/default/templates/index.html.ep:4 themes/default/templates/myfiles.html.ep:33 themes/default/templates/myfiles.html.ep:4 themes/default/templates/partial/for_my_delay.html.ep:13 themes/default/templates/partial/lutim.js.ep:149 themes/default/templates/partial/raw.js.ep:22 themes/default/templates/partial/raw.js.ep:5 themes/default/templates/raw.html.ep:7 msgid "24 hours" msgstr "" @@ -44,7 +44,7 @@ msgstr "" msgid ": Error while trying to get the counter." msgstr "" -#: themes/default/templates/partial/navbar.html.ep:69 +#: themes/default/templates/partial/navbar.html.ep:80 msgid "About" msgstr "" @@ -52,7 +52,7 @@ msgstr "" msgid "Active images" msgstr "" -#: lib/Lutim/Controller.pm:327 +#: lib/Lutim/Controller/Image.pm:327 msgid "An error occured while downloading the image." msgstr "" @@ -168,7 +168,7 @@ msgstr "" msgid "For more details, see the homepage of the project." msgstr "" -#: themes/default/templates/partial/navbar.html.ep:72 +#: themes/default/templates/partial/navbar.html.ep:83 msgid "Fork me!" msgstr "" @@ -212,7 +212,7 @@ msgstr "" msgid "Image deleted" msgstr "" -#: lib/Lutim/Controller.pm:753 +#: lib/Lutim/Controller/Image.pm:753 msgid "Image not found." msgstr "" @@ -220,7 +220,7 @@ msgstr "" msgid "Import localStorage data" msgstr "" -#: themes/default/templates/partial/navbar.html.ep:61 +#: themes/default/templates/partial/navbar.html.ep:72 msgid "Informations" msgstr "" @@ -248,7 +248,7 @@ msgstr "" msgid "Keep EXIF tags" msgstr "" -#: themes/default/templates/partial/navbar.html.ep:32 +#: themes/default/templates/partial/navbar.html.ep:43 msgid "Language" msgstr "" @@ -256,7 +256,7 @@ msgstr "" msgid "Let's go!" msgstr "" -#: themes/default/templates/partial/navbar.html.ep:66 +#: themes/default/templates/partial/navbar.html.ep:77 msgid "License:" msgstr "" @@ -264,6 +264,14 @@ msgstr "" msgid "Link for share on social networks" msgstr "" +#: themes/default/templates/login.html.ep:8 +msgid "Login" +msgstr "" + +#: themes/default/templates/partial/navbar.html.ep:33 +msgid "Logout" +msgstr "" + #: themes/default/templates/zip.html.ep:7 msgid "Lutim can't zip so many images at once, so it splitted your demand in multiple URLs." msgstr "" @@ -308,6 +316,10 @@ msgstr "" msgid "Only the uploader! (well, only if he's the only owner of the images' rights before the upload)" msgstr "" +#: themes/default/templates/login.html.ep:12 +msgid "Password" +msgstr "" + #: themes/default/templates/zip.html.ep:12 msgid "Please click on each URL to download the different zip files." msgstr "" @@ -317,6 +329,10 @@ msgstr "" msgid "Please contact the administrator: %1" msgstr "" +#: lib/Lutim/Controller/Authent.pm:36 +msgid "Please, check your credentials: unable to authenticate." +msgstr "" + #: themes/default/templates/gallery.html.ep:43 msgid "Previous (arrow left)" msgstr "" @@ -337,28 +353,32 @@ msgstr "" msgid "Send an image" msgstr "" +#: themes/default/templates/login.html.ep:16 themes/default/templates/logout.html.ep:5 themes/default/templates/partial/navbar.html.ep:37 +msgid "Signin" +msgstr "" + #: themes/default/templates/index.html.ep:151 themes/default/templates/partial/gallery.js.ep:211 themes/default/templates/partial/lutim.js.ep:176 msgid "Something bad happened" msgstr "" #. ($c->config('contact') -#: lib/Lutim/Controller.pm:760 +#: lib/Lutim/Controller/Image.pm:760 msgid "Something went wrong when creating the zip file. Try again later or contact the administrator (%1)." msgstr "" -#: themes/default/templates/partial/navbar.html.ep:44 +#: themes/default/templates/partial/navbar.html.ep:55 msgid "Support the author" msgstr "" -#: themes/default/templates/partial/navbar.html.ep:52 +#: themes/default/templates/partial/navbar.html.ep:63 msgid "Support the author on Liberapay" msgstr "" -#: themes/default/templates/partial/navbar.html.ep:49 +#: themes/default/templates/partial/navbar.html.ep:60 msgid "Support the author on Tipeee" msgstr "" -#: themes/default/templates/partial/navbar.html.ep:55 +#: themes/default/templates/partial/navbar.html.ep:66 msgid "Support the author with bitcoins" msgstr "" @@ -370,7 +390,7 @@ msgstr "" msgid "The Lutim software is a free software, which allows you to download and install it on you own server. Have a look at the AGPL to see what you can do." msgstr "" -#: lib/Lutim/Controller.pm:346 +#: lib/Lutim/Controller/Image.pm:346 msgid "The URL is not valid." msgstr "" @@ -382,19 +402,19 @@ msgstr "" msgid "The data has been successfully imported." msgstr "" -#: lib/Lutim/Controller.pm:159 lib/Lutim/Controller.pm:227 +#: lib/Lutim/Controller/Image.pm:159 lib/Lutim/Controller/Image.pm:227 msgid "The delete token is invalid." msgstr "" #. ($upload->filename) -#: lib/Lutim/Controller.pm:487 +#: lib/Lutim/Controller/Image.pm:487 msgid "The file %1 is not an image." msgstr "" #. ($tx->res->max_message_size) #. ($c->req->max_message_size) #. (config('max_file_size') -#: lib/Lutim/Controller.pm:310 lib/Lutim/Controller.pm:379 themes/default/templates/partial/lutim.js.ep:249 +#: lib/Lutim/Controller/Image.pm:310 lib/Lutim/Controller/Image.pm:379 themes/default/templates/partial/lutim.js.ep:249 msgid "The file exceed the size limit (%1)" msgstr "" @@ -403,16 +423,16 @@ msgid "The graph's datas are not updated in real-time." msgstr "" #. ($image->filename) -#: lib/Lutim/Controller.pm:229 +#: lib/Lutim/Controller/Image.pm:229 msgid "The image %1 has already been deleted." msgstr "" #. ($image->filename) -#: lib/Lutim/Controller.pm:238 lib/Lutim/Controller.pm:243 +#: lib/Lutim/Controller/Image.pm:238 lib/Lutim/Controller/Image.pm:243 msgid "The image %1 has been successfully deleted" msgstr "" -#: lib/Lutim/Controller.pm:167 +#: lib/Lutim/Controller/Image.pm:167 msgid "The image's delay has been successfully modified" msgstr "" @@ -429,7 +449,7 @@ msgid "There is XXXX image(s) in the gallery" msgstr "" #. ($c->config->{contact}) -#: lib/Lutim/Controller.pm:484 +#: lib/Lutim/Controller/Image.pm:484 msgid "There is no more available URL. Retry or contact the administrator. %1" msgstr "" @@ -454,15 +474,15 @@ msgid "Unable to copy to clipboard" msgstr "" #. ($short) -#: lib/Lutim/Controller.pm:107 lib/Lutim/Controller.pm:201 lib/Lutim/Controller.pm:272 +#: lib/Lutim/Controller/Image.pm:107 lib/Lutim/Controller/Image.pm:201 lib/Lutim/Controller/Image.pm:272 msgid "Unable to find the image %1." msgstr "" -#: lib/Lutim/Controller.pm:573 lib/Lutim/Controller.pm:618 lib/Lutim/Controller.pm:657 lib/Lutim/Controller.pm:700 lib/Lutim/Controller.pm:712 lib/Lutim/Controller.pm:723 lib/Lutim/Controller.pm:750 lib/Lutim/Plugin/Helpers.pm:88 +#: lib/Lutim/Controller/Image.pm:573 lib/Lutim/Controller/Image.pm:618 lib/Lutim/Controller/Image.pm:657 lib/Lutim/Controller/Image.pm:700 lib/Lutim/Controller/Image.pm:712 lib/Lutim/Controller/Image.pm:723 lib/Lutim/Controller/Image.pm:750 lib/Lutim/Plugin/Helpers.pm:88 msgid "Unable to find the image: it has been deleted." msgstr "" -#: lib/Lutim/Controller.pm:144 +#: lib/Lutim/Controller/Image.pm:144 msgid "Unable to get counter" msgstr "" @@ -519,6 +539,14 @@ msgstr "" msgid "You can, optionally, request that the image(s) posted on Lutim to be deleted at first view (or download) or after the delay selected from those proposed." msgstr "" +#: lib/Lutim/Controller/Authent.pm:27 +msgid "You have been successfully logged in." +msgstr "" + +#: lib/Lutim/Controller/Authent.pm:66 themes/default/templates/logout.html.ep:3 +msgid "You have been successfully logged out." +msgstr "" + #: themes/default/templates/gallery.html.ep:31 msgid "Zoom in/out" msgstr "" @@ -535,7 +563,7 @@ msgstr "" msgid "core developer" msgstr "" -#: lib/Lutim.pm:219 lib/Lutim/Command/cron/stats.pm:145 lib/Lutim/Command/cron/stats.pm:159 lib/Lutim/Command/cron/stats.pm:176 themes/default/templates/index.html.ep:3 themes/default/templates/myfiles.html.ep:3 themes/default/templates/partial/raw.js.ep:21 themes/default/templates/partial/raw.js.ep:4 themes/default/templates/raw.html.ep:6 +#: lib/Lutim.pm:348 lib/Lutim/Command/cron/stats.pm:145 lib/Lutim/Command/cron/stats.pm:159 lib/Lutim/Command/cron/stats.pm:176 themes/default/templates/index.html.ep:3 themes/default/templates/myfiles.html.ep:3 themes/default/templates/partial/raw.js.ep:21 themes/default/templates/partial/raw.js.ep:4 themes/default/templates/raw.html.ep:6 msgid "no time limit" msgstr "" diff --git a/themes/default/public/css/default-skin/default-skin.css b/themes/default/public/css/default-skin/default-skin.css index c961632..04a7cf2 100644 --- a/themes/default/public/css/default-skin/default-skin.css +++ b/themes/default/public/css/default-skin/default-skin.css @@ -51,7 +51,7 @@ .pswp__button, .pswp__button--arrow--left:before, .pswp__button--arrow--right:before { - background: url(default-skin.png) 0 0 no-repeat; + background: url(default-skin/default-skin.png) 0 0 no-repeat; background-size: 264px 88px; width: 44px; height: 44px; } diff --git a/themes/default/public/css/gallery.min.css b/themes/default/public/css/gallery.min.css index 918b007..6d446e0 100644 --- a/themes/default/public/css/gallery.min.css +++ b/themes/default/public/css/gallery.min.css @@ -1,4 +1,4 @@ /*! PhotoSwipe main CSS by Dmitry Semenov | photoswipe.com | MIT license */ .pswp{display:none;position:absolute;width:100%;height:100%;left:0;top:0;overflow:hidden;-ms-touch-action:none;touch-action:none;z-index:1500;-webkit-text-size-adjust:100%;-webkit-backface-visibility:hidden;outline:0}.pswp *{-webkit-box-sizing:border-box;box-sizing:border-box}.pswp img{max-width:none}.pswp--animate_opacity{opacity:.001;will-change:opacity;-webkit-transition:opacity 333ms cubic-bezier(.4,0,.22,1);transition:opacity 333ms cubic-bezier(.4,0,.22,1)}.pswp--open{display:block}.pswp--zoom-allowed .pswp__img{cursor:-webkit-zoom-in;cursor:-moz-zoom-in;cursor:zoom-in}.pswp--zoomed-in .pswp__img{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.pswp--dragging .pswp__img{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pswp__bg,.pswp__scroll-wrap{position:absolute;left:0;top:0;width:100%;height:100%}.pswp__bg{background:#000;opacity:0;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden}.pswp__scroll-wrap{overflow:hidden}.pswp__container{position:absolute}.pswp__container,.pswp__zoom-wrap{-ms-touch-action:none;touch-action:none;left:0;right:0;top:0;bottom:0}.pswp__container,.pswp__img{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}.pswp__zoom-wrap{position:absolute;width:100%;-webkit-transform-origin:left top;-ms-transform-origin:left top;transform-origin:left top;-webkit-transition:-webkit-transform 333ms cubic-bezier(.4,0,.22,1);transition:transform 333ms cubic-bezier(.4,0,.22,1)}.pswp__bg{will-change:opacity;-webkit-transition:opacity 333ms cubic-bezier(.4,0,.22,1);transition:opacity 333ms cubic-bezier(.4,0,.22,1)}.pswp--animated-in .pswp__bg,.pswp--animated-in .pswp__zoom-wrap{-webkit-transition:none;transition:none}.pswp__container,.pswp__zoom-wrap{-webkit-backface-visibility:hidden}.pswp__img,.pswp__item{position:absolute;top:0;left:0}.pswp__item{right:0;bottom:0;overflow:hidden}.pswp__img{width:auto;height:auto}.pswp__img--placeholder{-webkit-backface-visibility:hidden}.pswp__img--placeholder--blank{background:#222}.pswp--ie .pswp__img{width:100%!important;height:auto!important;left:0;top:0}.pswp__error-msg{position:absolute;left:0;top:50%;width:100%;text-align:center;font-size:14px;line-height:16px;margin-top:-8px;color:#ccc}.pswp__error-msg a{color:#ccc;text-decoration:underline} /*! PhotoSwipe Default UI CSS by Dmitry Semenov | photoswipe.com | MIT license */ -@-webkit-keyframes clockwise{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes clockwise{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes donut-rotate{0%,to{-webkit-transform:rotate(0);transform:rotate(0)}50%{-webkit-transform:rotate(-140deg);transform:rotate(-140deg)}}@keyframes donut-rotate{0%,to{-webkit-transform:rotate(0);transform:rotate(0)}50%{-webkit-transform:rotate(-140deg);transform:rotate(-140deg)}}.pswp__button{position:relative;background:0 0;cursor:pointer;overflow:visible;-webkit-appearance:none;display:block;border:0;padding:0;margin:0;float:right;opacity:.75;-webkit-transition:opacity .2s;transition:opacity .2s;-webkit-box-shadow:none;box-shadow:none}.pswp__button:focus,.pswp__button:hover{opacity:1}.pswp__button:active{outline:0;opacity:.9}.pswp__button::-moz-focus-inner{padding:0;border:0}.pswp__ui--over-close .pswp__button--close{opacity:1}.pswp__button,.pswp__button--arrow--left:before,.pswp__button--arrow--right:before{background:url(default-skin.png) 0 0 no-repeat;background-size:264px 88px;width:44px;height:44px}.pswp__button--arrow--left:before,.pswp__button--arrow--right:before{content:'';top:35px;background-color:rgba(0,0,0,.3);height:30px;width:32px;position:absolute}@media (-webkit-min-device-pixel-ratio:1.1),(-webkit-min-device-pixel-ratio:1.09375),(min-resolution:105dpi),(min-resolution:1.1dppx){.pswp--svg .pswp__button,.pswp--svg .pswp__button--arrow--left:before,.pswp--svg .pswp__button--arrow--right:before{background-image:url(default-skin.svg)}.pswp--svg .pswp__button--arrow--left,.pswp--svg .pswp__button--arrow--right{background:0 0}}.pswp__button--close{background-position:0 -44px}.pswp__button--share{background-position:-44px -44px}.pswp__button--fs{display:none}.pswp--supports-fs .pswp__button--fs{display:block}.pswp--fs .pswp__button--fs{background-position:-44px 0}.pswp__button--zoom{display:none;background-position:-88px 0}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__button--zoom{background-position:-132px 0}.pswp--touch .pswp__button--arrow--left,.pswp--touch .pswp__button--arrow--right{visibility:hidden}.pswp__button--arrow--left,.pswp__button--arrow--right{background:0 0;top:50%;margin-top:-50px;width:70px;height:100px;position:absolute}.pswp__button--arrow--left{left:0}.pswp__button--arrow--right{right:0}.pswp__button--arrow--left:before{left:6px;background-position:-138px -44px}.pswp__button--arrow--right:before{right:6px;background-position:-94px -44px}.pswp__counter,.pswp__share-modal{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pswp__share-modal{display:block;background:rgba(0,0,0,.5);width:100%;height:100%;top:0;left:0;padding:10px;position:absolute;z-index:1600;opacity:0;-webkit-transition:opacity .25s ease-out;transition:opacity .25s ease-out;-webkit-backface-visibility:hidden;will-change:opacity}.pswp__share-modal--hidden{display:none}.pswp__share-tooltip{z-index:1620;position:absolute;background:#fff;top:56px;border-radius:2px;display:block;width:auto;right:44px;-webkit-box-shadow:0 2px 5px rgba(0,0,0,.25);box-shadow:0 2px 5px rgba(0,0,0,.25);-webkit-transform:translateY(6px);-ms-transform:translateY(6px);transform:translateY(6px);-webkit-transition:-webkit-transform .25s;transition:transform .25s;-webkit-backface-visibility:hidden;will-change:transform}.pswp__share-tooltip a{display:block;padding:8px 12px;font-size:14px;line-height:18px}.pswp__share-tooltip a,.pswp__share-tooltip a:hover{text-decoration:none;color:#000}.pswp__share-tooltip a:first-child{border-radius:2px 2px 0 0}.pswp__share-tooltip a:last-child{border-radius:0 0 2px 2px}.pswp__share-modal--fade-in{opacity:1}.pswp__share-modal--fade-in .pswp__share-tooltip{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}.pswp--touch .pswp__share-tooltip a{padding:16px 12px}a.pswp__share--facebook:before{content:'';display:block;width:0;height:0;position:absolute;top:-12px;right:15px;border:6px solid transparent;border-bottom-color:#fff;-webkit-pointer-events:none;-moz-pointer-events:none;pointer-events:none}a.pswp__share--facebook:hover{background:#3e5c9a;color:#fff}a.pswp__share--facebook:hover:before{border-bottom-color:#3e5c9a}a.pswp__share--twitter:hover{background:#55acee;color:#fff}a.pswp__share--pinterest:hover{background:#ccc;color:#ce272d}a.pswp__share--download:hover{background:#ddd}.pswp__counter{position:absolute;left:0;top:0;height:44px;font-size:13px;line-height:44px;color:#fff;opacity:.75;padding:0 10px}.pswp__caption{position:absolute;left:0;bottom:0;width:100%;min-height:44px}.pswp__caption small{font-size:11px;color:#bbb}.pswp__caption__center{text-align:left;max-width:420px;margin:0 auto;font-size:13px;padding:10px;line-height:20px;color:#ccc}.pswp__caption--empty{display:none}.pswp__caption--fake{visibility:hidden}.pswp__preloader{width:44px;height:44px;position:absolute;top:0;left:50%;margin-left:-22px;opacity:0;-webkit-transition:opacity .25s ease-out;transition:opacity .25s ease-out;will-change:opacity;direction:ltr}.pswp__preloader__icn{width:20px;height:20px;margin:12px}.pswp__preloader--active .pswp__preloader__icn{background:url(preloader.gif) 0 0 no-repeat}.pswp--css_animation .pswp__preloader--active,.pswp__preloader--active{opacity:1}.pswp--css_animation .pswp__preloader--active .pswp__preloader__icn{-webkit-animation:clockwise 500ms linear infinite;animation:clockwise 500ms linear infinite}.pswp--css_animation .pswp__preloader--active .pswp__preloader__donut{-webkit-animation:donut-rotate 1000ms cubic-bezier(.4,0,.22,1) infinite;animation:donut-rotate 1000ms cubic-bezier(.4,0,.22,1) infinite}.pswp--css_animation .pswp__preloader__icn{background:0 0;opacity:.75;width:14px;height:14px;position:absolute;left:15px;top:15px;margin:0}.pswp--css_animation .pswp__preloader__cut{position:relative;width:7px;height:14px;overflow:hidden}.pswp--css_animation .pswp__preloader__donut{-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;border:2px solid #fff;border-radius:50%;border-left-color:transparent;border-bottom-color:transparent;position:absolute;top:0;left:0;background:0 0;margin:0}@media screen and (max-width:1024px){.pswp__preloader{position:relative;left:auto;top:auto;margin:0;float:right}}.pswp__ui{-webkit-font-smoothing:auto;visibility:visible;opacity:1;z-index:1550}.pswp__top-bar{position:absolute;left:0;top:0;height:44px;width:100%}.pswp--has_mouse .pswp__button--arrow--left,.pswp--has_mouse .pswp__button--arrow--right,.pswp__caption,.pswp__top-bar{-webkit-backface-visibility:hidden;will-change:opacity;-webkit-transition:opacity 333ms cubic-bezier(.4,0,.22,1);transition:opacity 333ms cubic-bezier(.4,0,.22,1)}.pswp--has_mouse .pswp__button--arrow--left,.pswp--has_mouse .pswp__button--arrow--right{visibility:visible}.pswp__caption,.pswp__top-bar{background-color:rgba(0,0,0,.5)}.pswp__ui--fit .pswp__caption,.pswp__ui--fit .pswp__top-bar{background-color:rgba(0,0,0,.3)}.pswp__ui--idle .pswp__button--arrow--left,.pswp__ui--idle .pswp__button--arrow--right,.pswp__ui--idle .pswp__top-bar{opacity:0}.pswp__ui--hidden .pswp__button--arrow--left,.pswp__ui--hidden .pswp__button--arrow--right,.pswp__ui--hidden .pswp__caption,.pswp__ui--hidden .pswp__top-bar{opacity:.001}.pswp__ui--one-slide .pswp__button--arrow--left,.pswp__ui--one-slide .pswp__button--arrow--right,.pswp__ui--one-slide .pswp__counter{display:none}.pswp__element--disabled{display:none!important}.pswp--minimal--dark .pswp__top-bar{background:0 0} +@-webkit-keyframes clockwise{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes clockwise{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes donut-rotate{0%,to{-webkit-transform:rotate(0);transform:rotate(0)}50%{-webkit-transform:rotate(-140deg);transform:rotate(-140deg)}}@keyframes donut-rotate{0%,to{-webkit-transform:rotate(0);transform:rotate(0)}50%{-webkit-transform:rotate(-140deg);transform:rotate(-140deg)}}.pswp__button{position:relative;background:0 0;cursor:pointer;overflow:visible;-webkit-appearance:none;display:block;border:0;padding:0;margin:0;float:right;opacity:.75;-webkit-transition:opacity .2s;transition:opacity .2s;-webkit-box-shadow:none;box-shadow:none}.pswp__button:focus,.pswp__button:hover{opacity:1}.pswp__button:active{outline:0;opacity:.9}.pswp__button::-moz-focus-inner{padding:0;border:0}.pswp__ui--over-close .pswp__button--close{opacity:1}.pswp__button,.pswp__button--arrow--left:before,.pswp__button--arrow--right:before{background:url(default-skin/default-skin.png) 0 0 no-repeat;background-size:264px 88px;width:44px;height:44px}.pswp__button--arrow--left:before,.pswp__button--arrow--right:before{content:'';top:35px;background-color:rgba(0,0,0,.3);height:30px;width:32px;position:absolute}@media (-webkit-min-device-pixel-ratio:1.1),(-webkit-min-device-pixel-ratio:1.09375),(min-resolution:105dpi),(min-resolution:1.1dppx){.pswp--svg .pswp__button,.pswp--svg .pswp__button--arrow--left:before,.pswp--svg .pswp__button--arrow--right:before{background-image:url(default-skin.svg)}.pswp--svg .pswp__button--arrow--left,.pswp--svg .pswp__button--arrow--right{background:0 0}}.pswp__button--close{background-position:0 -44px}.pswp__button--share{background-position:-44px -44px}.pswp__button--fs{display:none}.pswp--supports-fs .pswp__button--fs{display:block}.pswp--fs .pswp__button--fs{background-position:-44px 0}.pswp__button--zoom{display:none;background-position:-88px 0}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__button--zoom{background-position:-132px 0}.pswp--touch .pswp__button--arrow--left,.pswp--touch .pswp__button--arrow--right{visibility:hidden}.pswp__button--arrow--left,.pswp__button--arrow--right{background:0 0;top:50%;margin-top:-50px;width:70px;height:100px;position:absolute}.pswp__button--arrow--left{left:0}.pswp__button--arrow--right{right:0}.pswp__button--arrow--left:before{left:6px;background-position:-138px -44px}.pswp__button--arrow--right:before{right:6px;background-position:-94px -44px}.pswp__counter,.pswp__share-modal{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pswp__share-modal{display:block;background:rgba(0,0,0,.5);width:100%;height:100%;top:0;left:0;padding:10px;position:absolute;z-index:1600;opacity:0;-webkit-transition:opacity .25s ease-out;transition:opacity .25s ease-out;-webkit-backface-visibility:hidden;will-change:opacity}.pswp__share-modal--hidden{display:none}.pswp__share-tooltip{z-index:1620;position:absolute;background:#fff;top:56px;border-radius:2px;display:block;width:auto;right:44px;-webkit-box-shadow:0 2px 5px rgba(0,0,0,.25);box-shadow:0 2px 5px rgba(0,0,0,.25);-webkit-transform:translateY(6px);-ms-transform:translateY(6px);transform:translateY(6px);-webkit-transition:-webkit-transform .25s;transition:transform .25s;-webkit-backface-visibility:hidden;will-change:transform}.pswp__share-tooltip a{display:block;padding:8px 12px;font-size:14px;line-height:18px}.pswp__share-tooltip a,.pswp__share-tooltip a:hover{text-decoration:none;color:#000}.pswp__share-tooltip a:first-child{border-radius:2px 2px 0 0}.pswp__share-tooltip a:last-child{border-radius:0 0 2px 2px}.pswp__share-modal--fade-in{opacity:1}.pswp__share-modal--fade-in .pswp__share-tooltip{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}.pswp--touch .pswp__share-tooltip a{padding:16px 12px}a.pswp__share--facebook:before{content:'';display:block;width:0;height:0;position:absolute;top:-12px;right:15px;border:6px solid transparent;border-bottom-color:#fff;-webkit-pointer-events:none;-moz-pointer-events:none;pointer-events:none}a.pswp__share--facebook:hover{background:#3e5c9a;color:#fff}a.pswp__share--facebook:hover:before{border-bottom-color:#3e5c9a}a.pswp__share--twitter:hover{background:#55acee;color:#fff}a.pswp__share--pinterest:hover{background:#ccc;color:#ce272d}a.pswp__share--download:hover{background:#ddd}.pswp__counter{position:absolute;left:0;top:0;height:44px;font-size:13px;line-height:44px;color:#fff;opacity:.75;padding:0 10px}.pswp__caption{position:absolute;left:0;bottom:0;width:100%;min-height:44px}.pswp__caption small{font-size:11px;color:#bbb}.pswp__caption__center{text-align:left;max-width:420px;margin:0 auto;font-size:13px;padding:10px;line-height:20px;color:#ccc}.pswp__caption--empty{display:none}.pswp__caption--fake{visibility:hidden}.pswp__preloader{width:44px;height:44px;position:absolute;top:0;left:50%;margin-left:-22px;opacity:0;-webkit-transition:opacity .25s ease-out;transition:opacity .25s ease-out;will-change:opacity;direction:ltr}.pswp__preloader__icn{width:20px;height:20px;margin:12px}.pswp__preloader--active .pswp__preloader__icn{background:url(preloader.gif) 0 0 no-repeat}.pswp--css_animation .pswp__preloader--active,.pswp__preloader--active{opacity:1}.pswp--css_animation .pswp__preloader--active .pswp__preloader__icn{-webkit-animation:clockwise 500ms linear infinite;animation:clockwise 500ms linear infinite}.pswp--css_animation .pswp__preloader--active .pswp__preloader__donut{-webkit-animation:donut-rotate 1000ms cubic-bezier(.4,0,.22,1) infinite;animation:donut-rotate 1000ms cubic-bezier(.4,0,.22,1) infinite}.pswp--css_animation .pswp__preloader__icn{background:0 0;opacity:.75;width:14px;height:14px;position:absolute;left:15px;top:15px;margin:0}.pswp--css_animation .pswp__preloader__cut{position:relative;width:7px;height:14px;overflow:hidden}.pswp--css_animation .pswp__preloader__donut{-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;border:2px solid #fff;border-radius:50%;border-left-color:transparent;border-bottom-color:transparent;position:absolute;top:0;left:0;background:0 0;margin:0}@media screen and (max-width:1024px){.pswp__preloader{position:relative;left:auto;top:auto;margin:0;float:right}}.pswp__ui{-webkit-font-smoothing:auto;visibility:visible;opacity:1;z-index:1550}.pswp__top-bar{position:absolute;left:0;top:0;height:44px;width:100%}.pswp--has_mouse .pswp__button--arrow--left,.pswp--has_mouse .pswp__button--arrow--right,.pswp__caption,.pswp__top-bar{-webkit-backface-visibility:hidden;will-change:opacity;-webkit-transition:opacity 333ms cubic-bezier(.4,0,.22,1);transition:opacity 333ms cubic-bezier(.4,0,.22,1)}.pswp--has_mouse .pswp__button--arrow--left,.pswp--has_mouse .pswp__button--arrow--right{visibility:visible}.pswp__caption,.pswp__top-bar{background-color:rgba(0,0,0,.5)}.pswp__ui--fit .pswp__caption,.pswp__ui--fit .pswp__top-bar{background-color:rgba(0,0,0,.3)}.pswp__ui--idle .pswp__button--arrow--left,.pswp__ui--idle .pswp__button--arrow--right,.pswp__ui--idle .pswp__top-bar{opacity:0}.pswp__ui--hidden .pswp__button--arrow--left,.pswp__ui--hidden .pswp__button--arrow--right,.pswp__ui--hidden .pswp__caption,.pswp__ui--hidden .pswp__top-bar{opacity:.001}.pswp__ui--one-slide .pswp__button--arrow--left,.pswp__ui--one-slide .pswp__button--arrow--right,.pswp__ui--one-slide .pswp__counter{display:none}.pswp__element--disabled{display:none!important}.pswp--minimal--dark .pswp__top-bar{background:0 0} diff --git a/themes/default/templates/login.html.ep b/themes/default/templates/login.html.ep new file mode 100644 index 0000000..ae03247 --- /dev/null +++ b/themes/default/templates/login.html.ep @@ -0,0 +1,18 @@ +% # vim:set sw=4 ts=4 sts=4 ft=html.epl expandtab: +% title 'Lutim login'; +% if (defined stash('msg')) { + <%= stash('msg') %> +% } +
+
+ + +
+
+ + +
+ +
diff --git a/themes/default/templates/logout.html.ep b/themes/default/templates/logout.html.ep new file mode 100644 index 0000000..235d7da --- /dev/null +++ b/themes/default/templates/logout.html.ep @@ -0,0 +1,6 @@ +% # vim:set sw=4 ts=4 sts=4 ft=html.epl expandtab: +% title 'Lutim logout'; +

<%= l('You have been successfully logged out.') %>

+

+ <%= l('Signin') %> +