File Coverage

blib/lib/Plack/App/Login/Password.pm
Criterion Covered Total %
statement 50 80 62.5
branch 11 26 42.3
condition 1 12 8.3
subroutine 14 16 87.5
pod n/a
total 76 134 56.7


line stmt bran cond sub pod time code
1             package Plack::App::Login::Password;
2              
3 4     4   275879 use base qw(Plack::Component::Tags::HTML);
  4         9  
  4         2991  
4 4     4   382534 use strict;
  4         10  
  4         149  
5 4     4   24 use warnings;
  4         9  
  4         223  
6              
7 4     4   2737 use Plack::Request;
  4         337777  
  4         217  
8 4     4   2370 use Plack::Response;
  4         6271  
  4         195  
9 4     4   2323 use Plack::Session;
  4         2656  
  4         274  
10 4         29 use Plack::Util::Accessor qw(generator lang login_cb logo_image_url message_cb
11 4     4   32 redirect_login redirect_error register_link tags_after text title);
  4         10  
12 4     4   3143 use Tags::HTML::Container;
  4         78073  
  4         244  
13 4     4   2773 use Tags::HTML::Login::Access 0.10;
  4         1235033  
  4         3915  
14              
15             our $VERSION = 0.05;
16              
17             sub _css {
18 4     4   3918 my ($self, $env) = @_;
19              
20 4         31 $self->{'_container'}->process_css;
21 4         1369 $self->{'_login_access'}->process_css({
22             'error' => 'red',
23             'info' => 'blue',
24             });
25              
26 4         12527 return;
27             }
28              
29             sub _login_check {
30 0     0   0 my ($self, $env, $body_parameters_hr) = @_;
31              
32 0 0 0     0 if (! exists $body_parameters_hr->{'login'}
33             || $body_parameters_hr->{'login'} ne 'login') {
34              
35 0         0 $self->_message($env, 'error', 'There is no login POST.');
36 0         0 return 0;
37             }
38 0 0 0     0 if (! defined $body_parameters_hr->{'username'} || ! $body_parameters_hr->{'username'}) {
39 0         0 $self->_message($env, 'error', "Missing username.");
40 0         0 return 0;
41             }
42 0 0 0     0 if (! defined $body_parameters_hr->{'password'} || ! $body_parameters_hr->{'password'}) {
43 0         0 $self->_message($env, 'error', "Missing password.");
44 0         0 return 0;
45             }
46              
47 0         0 return 1;
48             }
49              
50             sub _message {
51 0     0   0 my ($self, $env, $message_type, $message) = @_;
52              
53 0 0       0 if (defined $self->message_cb) {
54 0         0 $self->message_cb->($env, $message_type, $message);
55             }
56              
57 0         0 return;
58             }
59              
60             sub _prepare_app {
61 9     9   666154 my $self = shift;
62              
63             # Defaults which rewrite defaults in module which I am inheriting.
64 9 100       47 if (! defined $self->generator) {
65 5         91 $self->generator(__PACKAGE__.'; Version: '.$VERSION);
66             }
67              
68 9 100       83 if (! defined $self->title) {
69 7         41 $self->title('Login page');
70             }
71              
72             # Inherite defaults.
73 9         81 $self->SUPER::_prepare_app;
74              
75             # Defaults from this module.
76 7         2063 my %p = (
77             'css' => $self->css,
78             'tags' => $self->tags,
79             );
80              
81             # Tags helper for login button.
82 7 100       87 $self->{'_login_access'} = Tags::HTML::Login::Access->new(
    50          
    100          
83             %p,
84             defined $self->lang ? (
85             'lang' => $self->lang,
86             ) : (),
87             'logo_image_url' => $self->logo_image_url,
88             'register_url' => $self->register_link,
89             defined $self->tags_after ? (
90             'tags_after' => $self->tags_after,
91             ) : (),
92             defined $self->text ? (
93             'text' => $self->text,
94             ) : (),
95             );
96              
97 4         106149 $self->{'_container'} = Tags::HTML::Container->new(%p);
98              
99 4         1934 return;
100             }
101              
102             sub _process_actions {
103 4     4   151 my ($self, $env) = @_;
104              
105 4 50 33     28 if (defined $self->login_cb && $env->{'REQUEST_METHOD'} eq 'POST') {
106 0         0 my $req = Plack::Request->new($env);
107 0         0 my $body_params_hr = $req->body_parameters;
108 0         0 my ($status, $messages_ar) = $self->_login_check($env, $body_params_hr);
109 0         0 my $res = Plack::Response->new;
110 0 0       0 if ($status) {
111 0 0       0 if ($self->login_cb->($env, $body_params_hr->{'username'},
112             $body_params_hr->{'password'})) {
113              
114 0         0 $self->_message($env, 'info',
115             "User '$body_params_hr->{'username'}' is logged.");
116 0         0 $res->redirect($self->redirect_login);
117             } else {
118 0         0 $self->_message($env, 'error', 'Bad login.');
119 0         0 $res->redirect($self->redirect_error);
120             }
121             } else {
122 0         0 $res->redirect($self->redirect_error);
123             }
124 0         0 $self->psgi_app($res->finalize);
125             }
126              
127 4         51 return;
128             }
129              
130             sub _tags_middle {
131 4     4   22926 my ($self, $env) = @_;
132              
133 4         14 my $messages_ar = [];
134 4 50       50 if (exists $env->{'psgix.session'}) {
135 0         0 my $session = Plack::Session->new($env);
136 0         0 $messages_ar = $session->get('messages');
137 0         0 $session->set('messages', []);
138             }
139             $self->{'_container'}->process(
140             sub {
141 4     4   3542 $self->{'_login_access'}->process($messages_ar);
142             },
143 4         50 );
144              
145 4         41149 return;
146             }
147              
148             1;
149              
150             __END__
151              
152             =pod
153              
154             =encoding utf8
155              
156             =head1 NAME
157              
158             Plack::App::Login::Password - Plack login/password application.
159              
160             =head1 SYNOPSIS
161              
162             use Plack::App::Login::Password;
163              
164             my $obj = Plack::App::Login::Password->new(%parameters);
165             my $psgi_ar = $obj->call($env);
166             my $app = $obj->to_app;
167              
168             =head1 METHODS
169              
170             =head2 C<new>
171              
172             my $obj = Plack::App::Login::Password->new(%parameters);
173              
174             Constructor.
175              
176             =over 8
177              
178             =item * C<author>
179              
180             Author string to HTML head.
181              
182             Default value is undef.
183              
184             =item * C<content_type>
185              
186             Content type for output.
187              
188             Default value is 'text/html; charset=__ENCODING__'.
189              
190             =item * C<css>
191              
192             Instance of L<CSS::Struct::Output> object.
193              
194             Default value is L<CSS::Struct::Output::Raw> instance.
195              
196             =item * C<css_init>
197              
198             Reference to array with L<CSS::Struct> structure.
199              
200             Default value is CSS initialization from L<Tags::HTML::Page::Begin> like
201              
202             * {
203             box-sizing: border-box;
204             margin: 0;
205             padding: 0;
206             }
207              
208             =item * C<encoding>
209              
210             Set encoding for output.
211              
212             Default value is 'utf-8'.
213              
214             =item * C<favicon>
215              
216             Link to favicon.
217              
218             Default value is undef.
219              
220             =item * C<flag_begin>
221              
222             Flag that means begin of html writing via L<Tags::HTML::Page::Begin>.
223              
224             Default value is 1.
225              
226             =item * C<flag_end>
227              
228             Flag that means end of html writing via L<Tags::HTML::Page::End>.
229             Default value is 1.
230              
231             =item * C<generator>
232              
233             HTML generator string.
234              
235             Default value is 'Plack::App::Login; Version: __VERSION__'.
236              
237             =item * C<lang>
238              
239             Language in ISO 639-2 code.
240              
241             Default value is undef.
242              
243             =item * C<login_cb>
244              
245             Callback for main login.
246             Arguments for callback are: C<$env>, C<username> and C<$password>.
247             Returns 0/1 for (un)successful login.
248              
249             Default value is undef.
250              
251             =item * C<logo_image_url>
252              
253             URL to logo image.
254              
255             Default value is undef.
256              
257             =item * C<message_cb>
258              
259             Callback to process message from application.
260             Arguments for callback are: C<$env>, C<$message_type> and C<$message>.
261             Returns undef.
262              
263             Default value is undef.
264              
265             =item * C<psgi_app>
266              
267             PSGI application to run instead of normal process.
268             Intent of this is change application in C<_process_actions> method.
269              
270             Default value is undef.
271              
272             =item * C<redirect_login>
273              
274             Redirect URL after successful login.
275              
276             Default value is undef.
277              
278             =item * C<redirect_error>
279              
280             Redirect URL after error in login.
281              
282             Default value is undef.
283              
284             =item * C<register_link>
285              
286             URL to registering page.
287              
288             Default value is undef.
289              
290             =item * C<script_js>
291              
292             Reference to array with Javascript code strings.
293              
294             Default value is [].
295              
296             =item * C<script_js_src>
297              
298             Reference to array with Javascript URLs.
299              
300             Default value is [].
301              
302             =item * C<status_code>
303              
304             HTTP status code.
305              
306             Default value is 200.
307              
308             =item * C<tags>
309              
310             Instance of L<Tags::Output> object.
311              
312             Default value is
313              
314             Tags::Output::Raw->new(
315             'xml' => 1,
316             'no_simple' => ['script', 'textarea'],
317             'preserved' => ['pre', 'style'],
318             );
319              
320             =item * C<tags_after>
321              
322             Reference to array with L<Tags> code to use after form.
323              
324             Default value is undef.
325              
326             =item * C<text>
327              
328             Hash reference with keys defined language in ISO 639-2 code and value with hash reference with texts.
329              
330             Required keys are 'login', 'password_label', 'username_label' and 'submit'.
331              
332             See more in L<Tags::HTML::Login::Access>.
333              
334             Default value is undef.
335              
336             =item * C<title>
337              
338             Page title.
339              
340             Default value is 'Login page'.
341              
342             =back
343              
344             Returns instance of object.
345              
346             =head2 C<call>
347              
348             my $psgi_ar = $obj->call($env);
349              
350             Implementation of login page.
351              
352             Returns reference to array (PSGI structure).
353              
354             =head2 C<to_app>
355              
356             my $app = $obj->to_app;
357              
358             Creates Plack application.
359              
360             Returns Plack::Component object.
361              
362             =head1 EXAMPLE
363              
364             =for comment filename=login_password_psgi.pl
365              
366             use strict;
367             use warnings;
368              
369             use CSS::Struct::Output::Indent;
370             use Plack::App::Login::Password;
371             use Plack::Runner;
372             use Tags::Output::Indent;
373              
374             # Run application.
375             my $app = Plack::App::Login::Password->new(
376             'css' => CSS::Struct::Output::Indent->new,
377             'generator' => 'Plack::App::Login::Password',
378             'tags' => Tags::Output::Indent->new(
379             'preserved' => ['style'],
380             'xml' => 1,
381             ),
382             )->to_app;
383             Plack::Runner->new->run($app);
384              
385             # Output:
386             # HTTP::Server::PSGI: Accepting connections at http://0:5000/
387              
388             # > curl http://localhost:5000/
389             # <!DOCTYPE html>
390             # <html lang="en">
391             # <head>
392             # <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
393             # <meta name="generator" content="Plack::App::Login::Password"
394             # />
395             # <meta name="viewport" content="width=device-width, initial-scale=1.0" />
396             # <title>
397             # Login page
398             # </title>
399             # <style type="text/css">
400             # * {
401             # box-sizing: border-box;
402             # margin: 0;
403             # padding: 0;
404             # }
405             # .container {
406             # display: flex;
407             # align-items: center;
408             # justify-content: center;
409             # height: 100vh;
410             # }
411             # .form-login {
412             # width: 300px;
413             # background-color: #f2f2f2;
414             # padding: 20px;
415             # border-radius: 5px;
416             # box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
417             # }
418             # .form-login fieldset {
419             # border: none;
420             # padding: 0;
421             # margin-bottom: 20px;
422             # }
423             # .form-login legend {
424             # font-weight: bold;
425             # margin-bottom: 10px;
426             # }
427             # .form-login p {
428             # margin: 0;
429             # padding: 10px 0;
430             # }
431             # .form-login label {
432             # display: block;
433             # font-weight: bold;
434             # margin-bottom: 5px;
435             # }
436             # .form-login input[type="text"], .form-login input[type="password"] {
437             # width: 100%;
438             # padding: 8px;
439             # border: 1px solid #ccc;
440             # border-radius: 3px;
441             # }
442             # .form-login button[type="submit"] {
443             # width: 100%;
444             # padding: 10px;
445             # background-color: #4CAF50;
446             # color: #fff;
447             # border: none;
448             # border-radius: 3px;
449             # cursor: pointer;
450             # }
451             # .form-login button[type="submit"]:hover {
452             # background-color: #45a049;
453             # }
454             # </style>
455             # </head>
456             # <body>
457             # <div class="container">
458             # <div class="inner">
459             # <form class="form-login" method="post">
460             # <fieldset>
461             # <legend>
462             # Login
463             # </legend>
464             # <p>
465             # <label for="username">
466             # User name
467             # </label>
468             # <input type="text" name="username" id="username" />
469             # </p>
470             # <p>
471             # <label for="password">
472             # Password
473             # </label>
474             # <input type="password" name="password" id="password" />
475             # </p>
476             # <p>
477             # <button type="submit" name="login" value="login">
478             # Login
479             # </button>
480             # </p>
481             # </fieldset>
482             # </form>
483             # </div>
484             # </div>
485             # </body>
486             # </html>
487              
488             # Output screenshot is in images/ directory.
489              
490             =begin html
491              
492             <a href="https://raw.githubusercontent.com/michal-josef-spacek/Plack-App-Login-Password/master/images/login_password_psgi.png">
493             <img src="https://raw.githubusercontent.com/michal-josef-spacek/Plack-App-Login-Password/master/images/login_password_psgi.png" alt="Web app example" width="300px" height="300px" />
494             </a>
495              
496             =end html
497              
498             =head1 DEPENDENCIES
499              
500             L<Plack::Component::Tags::HTML>,
501             L<Plack::Request>,
502             L<Plack::Response>,
503             L<Plack::Session>,
504             L<Plack::Util::Accessor>,
505             L<Tags::HTML::Container>,
506             L<Tags::HTML::Login::Password>.
507              
508             =head1 SEE ALSO
509              
510             =over
511              
512             =item L<Plack::App::Login>
513              
514             Plack login application.
515              
516             =back
517              
518             =head1 REPOSITORY
519              
520             L<https://github.com/michal-josef-spacek/Plack-App-Login-Password>
521              
522             =head1 AUTHOR
523              
524             Michal Josef Špaček L<mailto:skim@cpan.org>
525              
526             L<http://skim.cz>
527              
528             =head1 LICENSE AND COPYRIGHT
529              
530             © 2023-2024 Michal Josef Špaček
531              
532             BSD 2-Clause License
533              
534             =head1 VERSION
535              
536             0.05
537              
538             =cut