File Coverage

blib/lib/WWW/Suffit/Plugin/SuffitAuth.pm
Criterion Covered Total %
statement 39 191 20.4
branch 0 54 0.0
condition 0 70 0.0
subroutine 13 23 56.5
pod 1 1 100.0
total 53 339 15.6


line stmt bran cond sub pod time code
1             package WWW::Suffit::Plugin::SuffitAuth;
2 1     1   148605 use strict;
  1         7  
  1         47  
3 1     1   7 use warnings;
  1         2  
  1         72  
4 1     1   711 use utf8;
  1         395  
  1         10  
5              
6             =encoding utf8
7              
8             =head1 NAME
9              
10             WWW::Suffit::Plugin::SuffitAuth - The Suffit plugin for Suffit API authentication and authorization providing
11              
12             =head1 SYNOPSIS
13              
14             sub startup {
15             my $self = shift->SUPER::startup();
16             $self->plugin('SuffitAuth', {
17             configsection => 'SuffitAuth',
18             expiration => 3600, # 1h
19             cache_expiration => 300, # 5m
20             public_key_file => 'suffitauth_pub.key',
21             userfile_format => 'user-%s.json',
22             });
23              
24             # . . .
25             }
26              
27             ... configuration:
28              
29             # SuffitAuth Client configuration
30            
31             ServerURL https://api.example.com/api
32             Insecure on
33             AuthScheme Bearer
34             Token "eyJhb...1Okw"
35             ConnectTimeout 60
36             RequestTimeout 60
37            
38              
39             =head1 DESCRIPTION
40              
41             The Suffit plugin for Suffit API authentication and authorization providing
42              
43             =head1 OPTIONS
44              
45             This plugin supports the following options
46              
47             =head2 cache_expiration
48              
49             cache_expiration => 300
50             cache_expiration => '5m'
51              
52             This option sets default cache expiration time for keep user data in cache
53              
54             Default: 300 (5 min)
55              
56             =head2 configsection
57              
58             configsection => 'suffitauth'
59             configsection => 'SuffitAuth'
60              
61             This option sets a section name of the config file for define
62             namespace of configuration directives for this plugin
63              
64             Default: 'suffitauth'
65              
66             =head2 expiration
67              
68             expiration => 3600
69             expiration => '1h'
70             expiration => SESSION_EXPIRATION
71              
72             This option performs set a default expiration time of session
73              
74             Default: 3600 secs (1 hour)
75              
76             See L
77              
78             =head2 public_key_file
79              
80             public_key_file => 'auth_public.key'
81              
82             This option sets default public key file location (relative to datadir)
83              
84             Default: 'auth_public.key'
85              
86             =head2 userfile_format
87              
88             userfile_format => 'u.%s.json'
89              
90             This option sets default format of userdata authorization filename
91              
92             Default: 'u.%s.json'
93              
94             =head1 HELPERS
95              
96             This plugin provides the following helpers
97              
98             =head2 suffitauth.authenticate
99              
100             my $auth = $self->suffitauth->authenticate({
101             address => $self->remote_ip($self->app->trustedproxies),
102             method => $self->req->method, # 'ANY'
103             base_url => $self->base_url,
104             referer => $self->req->headers->header("Referer"),
105             username => $username,
106             password => $password,
107             loginpage => 'login', # -- To login-page!!
108             expiration => $remember ? SESSION_EXPIRE_MAX : SESSION_EXPIRATION,
109             realm => "Test zone", # Reserved. Now is not supported
110             options => {}, # Reserved. Now is not supported
111             });
112             if (my $err = $auth->get('/error')) {
113             if (my $location = $auth->get('/location')) { # Redirect
114             $self->flash(message => $err);
115             $self->redirect_to($location); # 'login' -- To login-page!!
116             } elsif ($auth->get('/status') >= 500) { # Fatal server errors
117             $self->reply->error($auth->get('/status'), $auth->get('/code'), $err);
118             } else { # User errors (show on login page)
119             $self->stash(error => $err);
120             return $self->render;
121             }
122             return;
123             }
124              
125             This helper performs authentication backend subprocess and returns
126             result object (L) that contains data structure:
127              
128             {
129             error => '', # Error message
130             status => 200, # HTTP status code
131             code => 'E0000', # The Suffit error code
132             username => $username, # User name
133             referer => $referer, # Referer
134             loginpage => $loginpage, # Login page for redirects (location)
135             location => undef, # Location URL for redirects
136             }
137              
138             This method is typically called in a handler responsible for the authentication and authorization
139             process, such as `login`. The call is typically made before a session is established, for eg.:
140              
141             # Set session
142             $self->session(
143             username => $username,
144             remember => $remember ? 1 : 0,
145             logged => time,
146             $remember ? (expiration => SESSION_EXPIRE_MAX) : (),
147             );
148             $self->flash(message => 'Thanks for logging in.');
149              
150             # Go to protected page (/root)
151             $self->redirect_to('root');
152              
153             =head2 suffitauth.authorize
154              
155             my $auth = $self->suffitauth->authorize({
156             referer => $referer,
157             username => $username,
158             loginpage => 'login', # -- To login-page!!
159             options => {}, # Reserved. Now is not supported
160             });
161             if (my $err = $auth->get('/error')) {
162             if (my $location = $auth->get('/location')) {
163             $self->flash(message => $err);
164             $self->redirect_to($location); # 'login' -- To login-page!!
165             } else {
166             $self->reply->error($auth->get('/status'), $auth->get('/code'), $err);
167             }
168             return;
169             }
170              
171             This helper performs authorization backend subprocess and returns
172             result object (L) that contains data structure:
173              
174             {
175             error => '', # Error message
176             status => 200, # HTTP status code
177             code => 'E0000', # The Suffit error code
178             username => $username, # User name
179             referer => $referer, # Referer
180             loginpage => $loginpage, # Login page for redirects (location)
181             location => undef, # Location URL for redirects
182             user => { # User data
183             address => "127.0.0.1", # User (client) IP address
184             base => "http://localhost:8080", # Base URL of request
185             comment => "No comments", # Comment
186             email => 'test@example.com', # Email address
187             email_md5 => "a84450...366", # MD5 hash of email address
188             method => "ANY", # Current method of request
189             name => "Bob Smith", # Full user name
190             path => "/", # Current query-path of request
191             role => "Regular user", # User role
192             status => true, # User status in JSON::PP::Boolean notation
193             uid => 1, # User ID
194             username => $username, # User name
195             },
196             }
197              
198             The 'user' is structure that describes found user. For eg.:
199              
200             {
201             "address": "127.0.0.1",
202             "base": "http://localhost:8473",
203             "code": "E0000",
204             "email": "foo@example.com",
205             "email_md5": "b48def645758b95537d4424c84d1a9ff",
206             "expires": 1700490101,
207             "groups": [
208             "wheel"
209             ],
210             "method": "ANY",
211             "name": "Anon Anonymous",
212             "path": "/",
213             "role": "System Administratot",
214             "status": true,
215             "uid": 1,
216             "username": "admin"
217             }
218              
219             Typically, this method is called in the handler responsible for session accounting (logged_in).
220             The call is accompanied by staking of the authorization data into the corresponding
221             project templates, for example:
222              
223             # Stash user data
224             $self->stash(
225             username => $username,
226             name => $auth->get('/user/name') // 'Anonymous',
227             email_md5=> $auth->get('/user/email_md5') // '',
228             role => $auth->get('/user/role') // 'User',
229             user => $auth->get('/user') || {},
230             );
231              
232              
233             =head2 suffitauth.client
234              
235             my $client = $self->suffitauth->client;
236              
237             Returns authorization client
238              
239             See L
240              
241             =head2 suffitauth.init
242              
243             my $init = $self->suffitauth->init;
244              
245             This method returns the init object (L)
246             that contains data of initialization:
247              
248             {
249             error => '...', # Error message
250             status => 500, # HTTP status code
251             code => 'E7000', # The Suffit error code
252             }
253              
254             For example (in your controller):
255              
256             # Check init status
257             my $init = $self->suffitauth->init;
258             if (my $err = $init->get('/error')) {
259             $self->reply->error($init->get('/status'),
260             $init->get('/code'), $err);
261             return;
262             }
263              
264             =head2 suffitauth.options
265              
266             my $options = $self->suffitauth->options;
267              
268             Returns authorization plugin options as hashref
269              
270             =head2 suffitauth.unauthorize
271              
272             my $auth = $self->suffitauth->unauthorize(username => $username);
273             if (my $err = $auth->get('/error')) {
274             $self->reply->error($authdata->get('/status'), $authdata->get('/code'), $err);
275             }
276              
277             This helper performs unauthorize process - remove userdata file from disk and returns
278             result object (L) that contains data structure:
279              
280             {
281             error => '', # Error message
282             status => 200, # HTTP status code
283             code => 'E0000', # The Suffit error code
284             username => $username, # User name
285             }
286              
287             This method is typically called in the handler responsible for the logout process.
288             The call usually occurs before the redirect to the `login` page, for example:
289              
290             # Remove session
291             $self->session(expires => 1);
292              
293             # Unauthorize
294             if (my $username = $self->session('username')) {
295             my $authdata = $self->suffitauth->unauthorize(username => $username);
296             if (my $err = $authdata->get('/error')) {
297             $self->reply->error($authdata->get('/status'), $authdata->get('/code'), $err);
298             }
299             }
300              
301             # To login-page!
302             $self->redirect_to('login');
303              
304             =head1 METHODS
305              
306             Internal methods
307              
308             =head2 register
309              
310             This method register the plugin and helpers in L application
311              
312             =head1 ERROR CODES
313              
314             =over 8
315              
316             =item B -- General errors
317              
318             E01xx, E02xx, E03xx, E04xx and E05xx are reserved as HTTP errors
319              
320             E0000 Ok
321             E0100 Continue
322             E0200 OK
323             E0300 Multiple Choices
324             E0400 Bad Request
325             E0500 Internal Server Error
326              
327             =item B -- API errors
328              
329             See L
330              
331             =item B -- Database errors
332              
333             See L
334              
335             =item B -- SuffitAuth (application) errors
336              
337             B
338              
339             E7000 [403] Access denied
340             E7001 [400] Incorrect username
341             E7002 [503] Can't connect to authorization server
342             E7003 [500] Can't get public key from authorization server
343             E7004 [500] Can't save public key file %s
344             E7005 [*] Authentication error
345             E7006 [*] Authorization error
346             E7007 [500] Can't save file .json
347             E7008 [500] File .json not found or incorrect
348             E7009 [400] Incorrect username (while authorize)
349             E7010 [400] Incorrect username (while unauthorize)
350              
351             B<*> -- this code defines on server side
352              
353             =back
354              
355             =head1 SEE ALSO
356              
357             L, L, L, bundled examples
358              
359             =head1 AUTHOR
360              
361             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
362              
363             =head1 COPYRIGHT
364              
365             Copyright (C) 1998-2026 D&D Corporation
366              
367             =head1 LICENSE
368              
369             This program is distributed under the terms of the Artistic License Version 2.0
370              
371             See the C file or L for details
372              
373             =cut
374              
375 1     1   762 use Mojo::Base 'Mojolicious::Plugin';
  1         14219  
  1         6  
376              
377             our $VERSION = '1.05';
378              
379 1     1   2153 use File::stat;
  1         12358  
  1         89  
380 1     1   639 use Mojo::File qw/path/;
  1         299387  
  1         139  
381 1     1   10 use Mojo::Util qw/encode md5_sum hmac_sha1_sum/;
  1         2  
  1         71  
382 1     1   678 use Mojo::JSON::Pointer;
  1         1230  
  1         8  
383              
384 1     1   737 use Acrux::Util qw/parse_time_offset/;
  1         43223  
  1         134  
385              
386 1     1   804 use WWW::Suffit::Client::V1;
  1         353672  
  1         68  
387 1     1   11 use WWW::Suffit::Const qw/ :session /;
  1         74  
  1         192  
388 1     1   637 use WWW::Suffit::Util qw/json_load json_save/;
  1         5319  
  1         201  
389              
390             use constant {
391 1         4605 SUFFITAUTH_PREFIX => 'suffitauth',
392             PUBLIC_KEY_FILE => 'auth_public.key',
393             USERFILE_FORMAT => 'u.%s.json',
394             CACHE_EXPIRATION => 300, # 5 min
395 1     1   12 };
  1         2  
396              
397             has _options => sub { {} };
398              
399             sub register {
400 0     0 1   my ($plugin, $app, $opts) = @_; # $self = $plugin
401 0   0       $opts //= {};
402 0   0       my $configsection = lc($opts->{configsection} || SUFFITAUTH_PREFIX);
403 0   0       my $expiration = parse_time_offset($opts->{expiration} // SESSION_EXPIRATION);
404 0   0       my $cache_expiration = parse_time_offset($opts->{cache_expiration} // CACHE_EXPIRATION);
405 0   0       my $public_key_file = $opts->{public_key_file} || PUBLIC_KEY_FILE;
406 0 0         $public_key_file = path($app->app->datadir, $public_key_file)->to_string
407             unless path($public_key_file)->is_abs;
408 0           my $now = time();
409              
410             # Options
411 0           $plugin->_options->{ 'configsection' } = $configsection;
412 0           $plugin->_options->{ 'expiration' } = $expiration; # Session & Userdata expiration (3600)
413 0           $plugin->_options->{ 'cache_expiration' } = $cache_expiration; # Cache expiration (300)
414 0           $plugin->_options->{ 'public_key_file' } = $public_key_file;
415 0   0       $plugin->_options->{ 'userfile_format' } = $opts->{userfile_format} || USERFILE_FORMAT;
416 0     0     $app->helper( 'suffitauth.options' => sub { state $opts = $plugin->_options } );
  0            
417              
418             # Auth client (V1)
419             $app->helper('suffitauth.client' => sub {
420 0     0     my $c = shift;
421 0           state $client = WWW::Suffit::Client::V1->new(
422             url => $c->conf->latest("/$configsection/serverurl"),
423             insecure => $c->conf->latest("/$configsection/insecure"),
424             max_redirects => $c->conf->latest("/$configsection/maxredirects"),
425             connect_timeout => $c->conf->latest("/$configsection/connecttimeout"),
426             inactivity_timeout => $c->conf->latest("/$configsection/inactivitytimeout"),
427             request_timeout => $c->conf->latest("/$configsection/requesttimeout"),
428             proxy => $c->conf->latest("/$configsection/proxy"),
429             token => $c->conf->latest("/$configsection/token"),
430             username => $c->conf->latest("/$configsection/username"),
431             password => $c->conf->latest("/$configsection/password"),
432             auth_scheme => $c->conf->latest("/$configsection/authscheme"),
433             );
434 0           });
435              
436             # Auth helpers (methods)
437 0           $app->helper('suffitauth.authenticate'=> \&_authenticate);
438 0           $app->helper('suffitauth.authorize' => \&_authorize);
439 0           $app->helper('suffitauth.unauthorize' => \&_unauthorize);
440              
441             # Initialize auth client
442 0           my %payload = ( # Ok by default
443             error => '', # Error message
444             status => 200, # HTTP status code
445             code => 'E0000', # The Suffit error code
446             );
447              
448             # Check auth client
449 0           my $client = $app->suffitauth->client;
450 0 0         unless ($client->check) {
451 0   0       my $code = $client->res->json("/code") || 'E7002';
452 0   0       $app->log->error(sprintf("[%s] %s: Can't connect to authorization server: %s",
453             $code, $client->code, $client->apierr // 'unknown error'));
454 0           $payload{error} = "Can't connect to authorization server";
455 0           $payload{status} = 503;
456 0           $payload{code} = $code;
457 0     0     return $app->helper('suffitauth.init' => sub { Mojo::JSON::Pointer->new({%payload}) });
  0            
458             }
459              
460             # Get public_key and set it
461 0           my $pkfile = path($public_key_file);
462 0 0 0       $pkfile->remove if $expiration && (-e $public_key_file) && (stat($public_key_file)->mtime + $expiration) < $now; # Remove expired public key file
      0        
463 0 0         if (-e $public_key_file) { # Set public key
464 0           $client->public_key($pkfile->slurp);
465             } else { # No file found - try get from auth server
466 0 0         unless ($client->pubkey(1)) {
467 0   0       my $code = $client->res->json("/code") || 'E7003';
468 0   0       $app->log->error(sprintf("[%s] %s: Can't get public key from authorization server: %s",
469             $code, $client->code, $client->apierr // 'unknown error'));
470 0           $payload{error} = "Can't get public key from authorization server";
471 0           $payload{status} = 500;
472 0           $payload{code} = $code;
473 0     0     return $app->helper('suffitauth.init' => sub { Mojo::JSON::Pointer->new({%payload}) });
  0            
474             }
475              
476             # Save file
477 0           $pkfile->spew($client->public_key);
478 0 0         unless (-e $public_key_file) {
479 0           $app->log->error(sprintf("[E7004] Can't save public key file %s", $public_key_file));
480 0           $payload{error} = sprintf("Can't save public key file %s", $public_key_file);
481 0           $payload{status} = 500;
482 0           $payload{code} = 'E7004';
483 0     0     return $app->helper('suffitauth.init' => sub { Mojo::JSON::Pointer->new({%payload}) });
  0            
484             }
485             }
486              
487             # Ok
488 0     0     return $app->helper('suffitauth.init' => sub { Mojo::JSON::Pointer->new({%payload}) });
  0            
489             }
490             sub _authenticate {
491 0     0     my $self = shift;
492 0 0         my %args = scalar(@_) ? scalar(@_) % 2 ? ref($_[0]) eq 'HASH' ? (%{$_[0]}) : () : (@_) : ();
  0 0          
    0          
493 0           my $cache = $self->app->cache;
494 0           my $now = time();
495 0   0       my $username = $args{username} || '';
496 0   0       my $password = $args{password} // '';
497 0 0         $password = encode('UTF-8', $password) if length $password; # chars to bytes
498 0   0       my $referer = $args{referer} // $self->req->headers->header("Referer") // '';
      0        
499 0   0       my $loginpage = $args{loginpage} // '';
500 0   0       my $expiration = parse_time_offset($args{expiration} || 0) || $self->suffitauth->options->{expiration} || 0;
501 0   0       my $address = $args{address} || $self->remote_ip($self->app->trustedproxies);
502 0           my %payload = ( # Ok by default
503             error => '', # Error message
504             status => 200, # HTTP status code
505             code => 'E0000', # The Suffit error code
506             username => $username, # User name
507             referer => $referer, # Referer
508             loginpage => $loginpage, # Login page for redirects (location)
509             location => undef, # Location URL for redirects
510             );
511 0           my $fileformat = $self->suffitauth->options->{userfile_format};
512 0           my $json_file = path($self->app->datadir, sprintf($fileformat, $username));
513 0           my $file = $json_file->to_string;
514              
515             # Check username
516 0 0         unless (length $username) {
517 0           $self->log->error("[E7001] Incorrect username");
518 0           $payload{error} = "Incorrect username";
519 0           $payload{status} = 400;
520 0           $payload{code} = 'E7001';
521 0           return Mojo::JSON::Pointer->new({%payload});
522             }
523              
524             # Get user key and file
525 0           my $ustat_key = sprintf("auth.ustat.%s", hmac_sha1_sum(sprintf("%s:%s", encode('UTF-8', $username), $password), $self->app->mysecret));
526 0   0       my $ustat_tm = $cache->get($ustat_key) || 0;
527 0 0 0       if ($expiration && (-e $file) && ($ustat_tm + $expiration) > $now) { # Ok!
      0        
528 0           $self->log->debug(sprintf("$$: User data is still valid. Expired at %s", scalar(localtime($ustat_tm + $expiration))));
529 0           return Mojo::JSON::Pointer->new({%payload});
530             }
531              
532             # Get auth client
533 0           my $client = $self->suffitauth->client;
534              
535             # Authentication
536 0 0         unless ($client->authn($username, $password, $address)) { # Error
537 0   0       my $code = $client->res->json("/code") || 'E7005';
538 0           $self->log->error(sprintf("[%s] %s: %s", $code, $client->code, $client->apierr));
539 0           $payload{error} = $client->apierr;
540 0           $payload{status} = $client->code;
541 0           $payload{code} = $code;
542 0           return Mojo::JSON::Pointer->new({%payload});
543             }
544              
545             # Authorization
546 0 0 0       unless ($client->authz(
      0        
547             $args{method} || $self->req->method || "ANY",
548             $args{base_url} || $self->base_url,
549             { # Error
550             username => $username,
551             address => $address,
552             verbose => 1,
553             })
554             ) {
555 0   0       my $code = $client->res->json("/code") || 'E7006';
556 0           $self->log->error(sprintf("[%s] %s: %s", $code, $client->code, $client->apierr));
557 0           $payload{error} = $client->apierr;
558 0           $payload{status} = $client->code;
559 0           $payload{code} = $code;
560 0           return Mojo::JSON::Pointer->new({%payload});
561             }
562              
563             # Save json file
564 0           json_save($file, $client->res->json);
565 0 0         unless (-e $file) {
566 0           $self->log->error(sprintf("[E7007] Can't save file %s", $file));
567 0           $payload{error} = sprintf("Can't save file DATADIR/$fileformat", $username);
568 0           $payload{status} = 500;
569 0           $payload{code} = 'E7007';
570 0           return Mojo::JSON::Pointer->new({%payload});
571             }
572              
573             # Fixed to cache
574 0           $cache->set($ustat_key, $now);
575              
576             # Ok
577 0           return Mojo::JSON::Pointer->new({%payload});
578             }
579             sub _authorize {
580 0     0     my $self = shift;
581 0 0         my %args = scalar(@_) ? scalar(@_) % 2 ? ref($_[0]) eq 'HASH' ? (%{$_[0]}) : () : (@_) : ();
  0 0          
    0          
582 0   0       my $username = $args{username} || '';
583 0   0       my $referer = $args{referer} // $self->req->headers->header("Referer") // '';
      0        
584 0   0       my $loginpage = $args{loginpage} // '';
585 0           my %payload = ( # Ok by default
586             error => '', # Error message
587             status => 200, # HTTP status code
588             code => 'E0000', # The Suffit error code
589             username => $username, # User name
590             referer => $referer, # Referer
591             loginpage => $loginpage, # Login page for redirects (location)
592             location => undef, # Location URL for redirects
593             user => { # User data with required fields (defaults)
594             status => \0, # User status
595             uid => 0, # User ID
596             username => $username, # User name
597             name => $username, # Full name
598             role => "", # User role
599             email => "", # Email address
600             email_md5 => "", # MD5 of email address
601             comment => "", # Comment
602             },
603             );
604              
605             # Check username
606 0 0         unless (length $username) {
607 0           $self->log->error("[E7009] Incorrect username");
608 0           $payload{error} = "Incorrect username";
609 0           $payload{status} = 400;
610 0           $payload{code} = 'E7009';
611 0           return Mojo::JSON::Pointer->new({%payload});
612             }
613              
614             # Get user file name
615 0           my $fileformat = $self->suffitauth->options->{userfile_format};
616 0           my $file = path($self->app->datadir, sprintf($fileformat, $username))->to_string;
617              
618             # Get user data from cache first
619 0           my $cache = $self->app->cache;
620 0           my $user_cache_key = sprintf("auth.userdata.%s", $username);
621 0           my $user_data = $cache->get($user_cache_key);
622              
623             # Load user data from file
624 0 0         unless ($user_data) {
625 0 0         $user_data = -e $file ? json_load($file) : {};
626              
627             # Set loaded user data to cache
628 0           $cache->set($user_cache_key, $user_data, $self->suffitauth->options->{cache_expiration});
629             }
630              
631             # Check user data
632 0 0         unless ($user_data->{uid}) {
633 0           $self->log->error(sprintf("[E7008] File %s not found or incorrect", $file));
634 0           $payload{error} = sprintf("File DATADIR/$fileformat not found or incorrect", $username);
635 0           $payload{status} = 500;
636 0           $payload{code} = 'E7008';
637 0           return Mojo::JSON::Pointer->new({%payload});
638             }
639              
640             # Ok
641 0           $payload{user} = {%{$user_data}}; # Set user data to pyload hash
  0            
642 0           return Mojo::JSON::Pointer->new({%payload});
643             }
644             sub _unauthorize {
645 0     0     my $self = shift;
646 0 0         my %args = scalar(@_) ? scalar(@_) % 2 ? ref($_[0]) eq 'HASH' ? (%{$_[0]}) : () : (@_) : ();
  0 0          
    0          
647 0   0       my $username = $args{username} || '';
648              
649             # Initialize auth client
650 0           my %payload = ( # Ok by default
651             error => '', # Error message
652             status => 200, # HTTP status code
653             code => 'E0000', # The Suffit error code
654             username => $username,
655             );
656              
657             # Check username
658 0 0         unless (length $username) {
659 0           $self->log->error("[E7010] Incorrect username");
660 0           $payload{error} = "Incorrect username";
661 0           $payload{status} = 400;
662 0           $payload{code} = 'E7010';
663 0           return Mojo::JSON::Pointer->new({%payload});
664             }
665              
666             # Remove user data from cache first
667 0           my $cache = $self->app->cache;
668 0           my $user_cache_key = sprintf("auth.userdata.%s", $username);
669 0           $cache->del($user_cache_key);
670              
671             # Get user file name
672 0           my $fileformat = $self->suffitauth->options->{userfile_format};
673 0           my $file = path($self->app->datadir, sprintf($fileformat, $username))->to_string;
674              
675             # Remove userdata file
676 0 0         path($file)->remove if -e $file;
677              
678             # Ok
679 0           return Mojo::JSON::Pointer->new({%payload});
680             }
681              
682             1;
683              
684             __END__