package HTML::FormFu::Element::RequestToken; use strict; use base 'HTML::FormFu::Element::Text'; use Class::C3; use HTML::FormFu::Util qw( process_attrs ); use Carp qw( croak ); __PACKAGE__->mk_item_accessors(qw(expiration_time session_key context)); *default = \&value; sub new { my $self = shift->next::method(@_); $self->field_type('hidden'); $self->session_key('__token'); $self->context('context'); $self->name('_token'); $self->expiration_time(3600); $self->constraints( [qw(RequestToken Required)] ); return $self; } sub value { my $self = shift; if (@_) { $self->{value} = shift; return $self; } if ( !defined $self->{value} ) { return $self->{value} = $self->get_token; } return $self->{value}; } sub verify_token { my ($self) = @_; my $form = $self->form; croak "verify_token() can only be called if form is submitted" if !$form->submitted; my $field_name = $self->name; croak "verify_token() can only be called after \$form->process() has completed" if !$form->valid( $field_name ); return $self->remove_token( $form->param_value( $field_name ) ); } sub remove_token { my ( $self, $token ) = @_; my $c = $self->form->stash->{ $self->context }; my @token; my $found = 0; for ( @{ $c->session->{ $self->session_key } || [] } ) { if ( $_->[0] ne $token ) { push( @token, $_ ); } else { $found = 1; } } $c->session->{ $self->session_key } = \@token; return $found; } sub expire_token { my ($self) = @_; my $c = $self->form->stash->{ $self->context }; my @token; for ( @{ $c->session->{ $self->session_key } || [] } ) { push( @token, $_ ) if ( $_->[1] > time ); } $c->session->{ $self->session_key } = \@token; } sub get_token { my ($self) = @_; my $token; my $c = $self->form->stash->{ $self->context }; my @chars = ( 'a' ... 'z', 0 .. 9 ); $token .= $chars[ int( rand() * 36 ) ] for ( 0 .. 15 ); $c->session->{ $self->session_key } ||= []; push @{ $c->session->{ $self->session_key } }, [ $token, time + $self->expiration_time ]; $self->expire_token; return $token; } 1; __END__ =head1 NAME HTML::FormFu::Element::RequestToken - Hidden text field which contains a unique token =head1 SYNOPSIS my $e = $form->element( { type => 'Token' } ); my $p = $form->element( { plugin => 'Token' } ); =head1 DESCRIPTION This field can prevent CSRF attacks. It contains a random token. After submission the token is checked with the token which is stored in the session of the current user. See L for a convenient way how to use it. =head1 ATTRIBUTES =head2 context Value of the stash key for the Catalyst context object (C<< $c >>). Defaults to C. =head2 expiration_time Time to life for a token in seconds. Defaults to C<3600>. =head2 session_key Session key which is used to store the tokens. Defaults to C<__token>. =head1 METHODS =head2 expire_token This method looks in the session for expired tokens and removes them. =head2 get_token Generates a new token and stores it in the stash. =head2 remove_token Removes a specific token from the session. Returns C<1> if the key was found. C<0> otherwise. =head2 verify_token Checks whether a given token is already in the session. If it exists it is removed and C returns C<1>. C<0> otherwise. =head1 SEE ALSO L, L, L L =head1 AUTHOR Moritz Onken, C =head1 LICENSE This library is free software, you can redistribute it and/or modify it under the same terms as Perl itself.