aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2016-05-21 13:05:20 -0400
committerPaul Duncan <pabs@pablotron.org>2016-05-21 13:05:20 -0400
commitb1d1a7c6c5c13c1496fa87a0eddaf4e724ecb299 (patch)
tree9ab1bac834199321fa6d3cb8568def02ed200cc6
parent59e64495121447c988d6aef243b7b3c17cb5f483 (diff)
downloadguff-b1d1a7c6c5c13c1496fa87a0eddaf4e724ecb299.tar.bz2
guff-b1d1a7c6c5c13c1496fa87a0eddaf4e724ecb299.zip
add csrf protection to login page
-rw-r--r--src/guff.cr58
-rw-r--r--src/views/login-page.ecr6
2 files changed, 64 insertions, 0 deletions
diff --git a/src/guff.cr b/src/guff.cr
index 8046af7..b610e96 100644
--- a/src/guff.cr
+++ b/src/guff.cr
@@ -239,6 +239,53 @@ module Guff
r
end
end
+
+ class CSRFModel < Model
+ getter :minutes
+
+ def initialize(context : Context)
+ super(context)
+ @cache = {} of String => Int64
+
+ # expire form after 5 minutes
+ # TODO: make this configurable
+ @minutes = 5
+ end
+
+ def create_token
+ remove_expired_tokens
+ p @cache
+
+ # generate and cache new token
+ r = SecureRandom.hex(16)
+ @cache[r] = Time.now.epoch + 60 * @minutes
+
+ # return token
+ r
+ end
+
+ def use_token(id : String)
+ remove_expired_tokens
+
+ if @cache.has_key?(id)
+ # remove token, return success
+ @cache.delete(id)
+ true
+ else
+ # return failure
+ false
+ end
+ end
+
+ private def remove_expired_tokens
+ now = Time.now.epoch
+
+ # remove expired entries
+ @cache.delete_if do |key, val|
+ val < now
+ end
+ end
+ end
end
class ModelSet
@@ -249,6 +296,7 @@ module Guff
define_model_set_getters({
user: Models::UserModel,
session: Models::SessionModel,
+ csrf: Models::CSRFModel,
})
end
@@ -383,6 +431,10 @@ module Guff
super(context)
end
+ def get_csrf_token
+ @context.models.csrf.create_token
+ end
+
ECR.def_to_s("src/views/login-page.ecr")
end
@@ -595,10 +647,16 @@ module Guff
raise "missing login parameters" unless %w{
username
password
+ csrf_token
}.all? do |key|
params.has_key?(key) && params[key].size > 0
end
+ # check csrf token
+ unless @context.models.csrf.use_token(params["csrf_token"])
+ raise "invalid csrf token"
+ end
+
# try login
user_id = @context.models.user.login(
params["username"],
diff --git a/src/views/login-page.ecr b/src/views/login-page.ecr
index 9f2082a..e0404ce 100644
--- a/src/views/login-page.ecr
+++ b/src/views/login-page.ecr
@@ -75,6 +75,12 @@
Log In
</button>
</div><!-- form-group -->
+
+ <input
+ type='hidden'
+ name='csrf_token'
+ value='<%= h(get_csrf_token) %>'
+ />
</form>
</div><!-- panel-body -->
</div><!-- panel -->