jeremyevans-rodauth-b53f402/Rakefile 0000664 0000000 0000000 00000015414 15157255142 0017516 0 ustar 00root root 0000000 0000000 require "rake"
require "rake/clean"
CLEAN.include ["rodauth-*.gem", "coverage", "www/public/rdoc", "www/public/*.html"]
# Packaging
desc "Build rodauth gem"
task :package=>[:clean] do |p|
sh %{#{FileUtils::RUBY} -S gem build rodauth.gemspec}
end
### RDoc
desc "Generate rdoc"
task :website_rdoc do
rdoc_dir = "www/public/rdoc"
rdoc_opts = ["--line-numbers", "--inline-source", '--title', 'Rodauth: Authentication and Account Management Framework for Rack Applications']
begin
gem 'hanna'
rdoc_opts.concat(['-f', 'hanna'])
rescue Gem::LoadError
end
rdoc_opts.concat(['--main', 'README.rdoc', "-o", rdoc_dir])
rdoc_opts.concat(%w"README.rdoc CHANGELOG doc/CHANGELOG.old MIT-LICENSE" + Dir["lib/**/*.rb"] + Dir["doc/**/*.rdoc"] + Dir['doc/release_notes/*.txt'])
FileUtils.rm_rf(rdoc_dir)
require "rdoc"
RDoc::RDoc.new.document(rdoc_opts)
end
desc "Check configuration method documentation"
task :check_method_doc do
docs = {}
Dir["doc/*.rdoc"].sort.each do |f|
meths = File.binread(f).split("\n").grep(/\A(\w+[!?]?(\([^\)]+\))?) :: /).map{|line| line.split(/( :: |\()/, 2)[0]}.sort
docs[File.basename(f).sub(/\.rdoc\z/, '')] = meths unless meths.empty?
end
require "rack/version"
require './lib/rodauth'
docs.each do |f, doc_meths|
require "./lib/rodauth/features/#{f}"
feature = Rodauth::FEATURES[f.to_sym]
meths = (feature.auth_methods + feature.auth_value_methods + feature.auth_private_methods).map(&:to_s).sort
unless (undocumented_meths = meths - doc_meths).empty?
puts "#{f} undocumented methods: #{undocumented_meths.join(', ')}"
end
unless (bad_doc_meths = doc_meths - meths).empty?
puts "#{f} documented methods that don't exist: #{bad_doc_meths.join(', ')}"
end
end
puts "#{docs.values.flatten.length} total documented configuration methods"
end
# Specs
adapters = if RUBY_ENGINE == 'jruby'
{:mysql=>'jdbc:mysql', :mssql=>'jdbc:jtds:sqlserver', :postgres=>'jdbc:postgresql'}
else
{:mysql=>'mysql2', :mssql=>'tinytds', :postgres=>'postgres'}
end
desc "Run specs"
task :default=>:spec
spec = proc do |env|
env.each{|k,v| ENV[k] = v}
sh "#{FileUtils::RUBY} #{"-w" if RUBY_VERSION >= '3'} #{'-W:strict_unused_block' if RUBY_VERSION >= '3.4'} spec/all.rb"
env.each{|k,v| ENV.delete(k)}
end
desc "Run specs on PostgreSQL"
task "spec" do
spec.call({})
end
desc "Run specs with method visibility checking"
task "spec_vis" do
spec.call('CHECK_METHOD_VISIBILITY'=>'1')
end
desc "Run specs with coverage"
task "spec_cov" do
spec.call('COVERAGE'=>'1')
end
desc "Run specs with Rack::Lint"
task "spec_lint" do
spec.call('LINT'=>'1')
end
desc "Setup database used for testing on PostgreSQL"
task :db_setup_postgres do
sh 'psql -U postgres -c "CREATE USER rodauth_test PASSWORD \'rodauth_test\'"'
sh 'psql -U postgres -c "CREATE USER rodauth_test_password PASSWORD \'rodauth_test\'"'
sh 'createdb -U postgres -O rodauth_test rodauth_test'
sh 'psql -U postgres -c "CREATE EXTENSION citext" rodauth_test'
sh 'psql -U postgres -c "CREATE EXTENSION pgcrypto" rodauth_test'
$: << 'lib'
require 'sequel'
Sequel.extension :migration
Sequel.connect("#{adapters[:postgres]}:///rodauth_test?user=rodauth_test&password=rodauth_test") do |db|
Sequel::Migrator.run(db, 'spec/migrate')
end
sh 'psql -U postgres -c "GRANT CREATE ON SCHEMA public TO rodauth_test_password" rodauth_test'
Sequel.connect("#{adapters[:postgres]}:///rodauth_test?user=rodauth_test_password&password=rodauth_test") do |db|
Sequel::Migrator.run(db, 'spec/migrate_password', :table=>'schema_info_password')
end
sh 'psql -U postgres -c "REVOKE CREATE ON SCHEMA public FROM rodauth_test_password" rodauth_test'
end
desc "Teardown database used for testing on MySQL"
task :db_teardown_postgres do
sh 'dropdb -U postgres rodauth_test'
sh 'dropuser -U postgres rodauth_test_password'
sh 'dropuser -U postgres rodauth_test'
end
desc "Setup database used for testing on MySQL"
task :db_setup_mysql do
sh 'mysql --user=root -p mysql < spec/sql/mysql_setup.sql'
$: << 'lib'
require 'sequel'
Sequel.extension :migration
Sequel.connect("#{adapters[:mysql]}:///rodauth_test?user=rodauth_test_password&password=rodauth_test") do |db|
Sequel::Migrator.run(db, 'spec/migrate')
Sequel::Migrator.run(db, 'spec/migrate_password', :table=>'schema_info_password')
end
end
desc "Teardown database used for testing on MySQL"
task :db_teardown_mysql do
sh 'mysql --user=root -p mysql < spec/sql/mysql_teardown.sql'
end
desc "Setup database used for testing on Microsoft SQL Server"
task :db_setup_mssql do
sh 'sqlcmd -E -e -b -r1 -i spec\\sql\\mssql_setup.sql'
$: << 'lib'
require 'sequel'
Sequel.extension :migration
sep = ';' if RUBY_ENGINE == 'jruby'
Sequel.connect("#{adapters[:mssql]}://localhost/rodauth_test#{sep || '?'}user=rodauth_test_password#{sep || '&'}password=Rodauth1.") do |db|
Sequel::Migrator.run(db, 'spec/migrate')
Sequel::Migrator.run(db, 'spec/migrate_password', :table=>'schema_info_password')
end
end
desc "Teardown database used for testing on Microsoft SQL Server"
task :db_teardown_mssql do
sh 'sqlcmd -E -e -b -r1 -i spec\\sql\\mssql_teardown.sql'
end
desc "Run specs on MySQL"
task :spec_mysql do
spec.call('RODAUTH_SPEC_DB'=>"#{adapters[:mysql]}://localhost/rodauth_test?user=rodauth_test&password=rodauth_test")
end
task :spec_ci do
mysql_host = "localhost"
pg_database = "rodauth_test" unless ENV["DEFAULT_DATABASE"]
ENV['LINT'] = '1' if RUBY_VERSION >= '3.0'
if ENV["MYSQL_ROOT_PASSWORD"]
mysql_password = "&password=root"
mysql_host= "127.0.0.1:3306"
end
if RUBY_ENGINE == 'jruby'
pg_db = "jdbc:postgresql://localhost/#{pg_database}?user=postgres&password=postgres"
my_db = "jdbc:mysql://#{mysql_host}/rodauth_test?user=root#{mysql_password}&useSSL=false&allowPublicKeyRetrieval=true"
else
pg_db = "postgres://localhost/#{pg_database}?user=postgres&password=postgres"
my_db = "mysql2://#{mysql_host}/rodauth_test?user=root#{mysql_password}&useSSL=false"
end
require "sequel/core"
Sequel.connect(pg_db) do |db|
db.run 'CREATE EXTENSION citext'
db.run 'CREATE EXTENSION pgcrypto' if ENV['RODAUTH_SPEC_UUID']
end
spec.call('RODAUTH_SPEC_MIGRATE'=>'1', 'RODAUTH_SPEC_DB'=>pg_db)
if RUBY_VERSION >= '2.4'
spec.call('RODAUTH_SPEC_MIGRATE'=>'1', 'RODAUTH_SPEC_DB'=>my_db)
Rake::Task['spec_sqlite'].invoke
end
end
desc "Run specs on SQLite"
task :spec_sqlite do
conn_string = if RUBY_ENGINE == 'jruby'
'jdbc:sqlite::memory:'
else
'sqlite:/'
end
spec.call('RODAUTH_SPEC_MIGRATE'=>'1', 'RODAUTH_SPEC_DB'=>conn_string)
end
### Website
desc "Make local version of website"
task :website_base do
sh %{#{FileUtils::RUBY} -I lib www/make_www.rb}
end
desc "Make local version of website, with rdoc"
task :website => [:website_base, :website_rdoc]
jeremyevans-rodauth-b53f402/SECURITY.md 0000664 0000000 0000000 00000000424 15157255142 0017635 0 ustar 00root root 0000000 0000000 # Security Policy
## Reporting a Vulnerability
Potential security issues can be publicly reported in the same
manner as non-security issues (e.g. on GitHub Issues). However,
if you would like to report them privately, you can report them
via email to code@jeremyevans.net.
jeremyevans-rodauth-b53f402/demo-site/ 0000775 0000000 0000000 00000000000 15157255142 0017732 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/demo-site/config.ru 0000664 0000000 0000000 00000000211 15157255142 0021541 0 ustar 00root root 0000000 0000000 $:.unshift(::File.expand_path('../../lib', __FILE__))
require ::File.expand_path('../rodauth_demo', __FILE__)
run RodauthDemo::App.app
jeremyevans-rodauth-b53f402/demo-site/rodauth_demo.rb 0000664 0000000 0000000 00000005766 15157255142 0022747 0 ustar 00root root 0000000 0000000 require 'roda'
require 'sequel/core'
require 'mail'
require 'securerandom'
module RodauthDemo
class App < Roda
if url = ENV.delete('RODAUTH_DATABASE_URL') || ENV.delete('DATABASE_URL')
DB = Sequel.connect(url)
if DB.adapter_scheme == :postgres && Sequel::Postgres::USES_PG
begin
DB.extension :pg_auto_parameterize
rescue LoadError
end
end
else
DB = Sequel.sqlite
Sequel.extension :migration
Sequel::Migrator.run(DB, File.expand_path('../../spec/migrate_ci', __FILE__))
end
if ENV.delete('RODAUTH_DEMO_LOGGER')
require 'logger'
DB.loggers << Logger.new($stdout)
end
DB.extension :date_arithmetic
DB.freeze
::Mail.defaults do
delivery_method :test
end
opts[:root] = File.dirname(__FILE__)
MAILS = {}
SMS = {}
MUTEX = Mutex.new
plugin :render, :escape=>true
plugin :request_aref, :raise
plugin :hooks
plugin :flash
plugin :common_logger
plugin :route_csrf
plugin :disallow_file_uploads
secret = ENV.delete('RODAUTH_SESSION_SECRET') || SecureRandom.random_bytes(64)
plugin :sessions, :secret=>secret, :key=>'rodauth-demo.session'
plugin :rodauth, :json=>true, :csrf=>:route_csrf do
db DB
enable :change_login, :change_password, :close_account, :create_account,
:lockout, :login, :logout, :remember, :reset_password_verifies_account,
:otp_modify_email, :otp_lockout_email, :recovery_codes, :sms_codes, :disallow_common_passwords,
:disallow_password_reuse, :password_grace_period, :active_sessions, :jwt,
:verify_login_change, :change_password_notify, :confirm_password,
:email_auth
enable :webauthn, :webauthn_login, :webauthn_modify_email if ENV["RODAUTH_WEBAUTHN"]
enable :webauthn_verify_account if ENV["RODAUTH_WEBAUTHN_VERIFY_ACCOUNT"]
enable :webauthn_autofill if ENV["RODAUTH_WEBAUTHN_AUTOFILL"]
max_invalid_logins 2
account_password_hash_column :ph
title_instance_variable :@page_title
only_json? false
jwt_secret(secret)
hmac_secret secret
sms_send do |phone_number, message|
MUTEX.synchronize{SMS[session_value] = "Would have sent the following SMS to #{phone_number}: #{message}"}
end
end
plugin :error_handler do |e|
@page_title = "Internal Server Error"
view :content=>"#{h e.class}: #{h e.message}
#{e.backtrace.map{|line| h line}.join("
")}"
end
plugin :not_found do
@page_title = "File Not Found"
view :content=>""
end
def last_sms_sent
MUTEX.synchronize{SMS.delete(rodauth.session_value)}
end
def last_mail_sent
MUTEX.synchronize{MAILS.delete(rodauth.session_value)}
end
after do
Mail::TestMailer.deliveries.each do |mail|
MUTEX.synchronize{MAILS[rodauth.session_value] = mail}
end
Mail::TestMailer.deliveries.clear
end
route do |r|
check_csrf! unless r.env['CONTENT_TYPE'] =~ /application\/json/
rodauth.load_memory
rodauth.check_active_session
r.rodauth
r.root do
view 'index'
end
end
freeze
end
end
jeremyevans-rodauth-b53f402/demo-site/views/ 0000775 0000000 0000000 00000000000 15157255142 0021067 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/demo-site/views/index.erb 0000664 0000000 0000000 00000003740 15157255142 0022674 0 ustar 00root root 0000000 0000000 <% if rodauth.logged_in? %>
You are logged in as <%= DB[:accounts].where(:id=>rodauth.session_value).get(:email) %>
<% if rodauth.two_factor_authenticated? %>
You have authenticated using multifactor authentication (<%= rodauth.authenticated_by.join(' and ') %>).
<% elsif rodauth.uses_two_factor_authentication? %>
You have logged in via single factor authentication (<%= rodauth.authenticated_by.first %>), but have not authenticated using multifactor authentication.
<% else %>
You have authenticated via <%= rodauth.authenticated_by.first %>
<% end %>
- Change Login
- Change Password
- Close Account
- Change Remember Settings
<% if rodauth.logged_in_via_remember_key? %>
- Confirm Password
<% end %>
<% if rodauth.two_factor_partially_authenticated? %>
- Authenticate Using Additional Factor
<% else %>
- Manage Multifactor Authentication
<% end %>
<% else %>
Overview
This is the demo site for Rodauth. Rodauth is an authentication and account management framework for rack applications, with the following design goals:
- Security: Ship in a maximum security by default configuration
- Simplicity: Allow for easy configuration via a DSL
- Flexibility: Allow for easy overriding of any part of the framework
It's easiest to get started by going to the login page.
This demo site is part of the Rodauth repository, so if you want to know how it works, you can review the source.
<% end %>
jeremyevans-rodauth-b53f402/demo-site/views/layout.erb 0000664 0000000 0000000 00000003345 15157255142 0023103 0 ustar 00root root 0000000 0000000
Rodauth Demo - <%= @page_title %>
<% if sms = last_sms_sent %>
<%= sms %>
<% end %>
<% if flash['notice'] %>
<%= flash['notice'] %>
<% end %>
<% if flash['error'] %>
<%= flash['error'] %>
<% end %>
<%= @page_title %>
<%== yield %>
<% if mail = last_mail_sent %>
Last Email Sent
From: <%= mail.from.join %>
To: <%= mail.to.join %>
Subject: <%= mail.subject %>
<%= mail.body %>
<% end %>
jeremyevans-rodauth-b53f402/dict/ 0000775 0000000 0000000 00000000000 15157255142 0016767 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/dict/top-10_000-passwords.txt 0000664 0000000 0000000 00000225334 15157255142 0023163 0 ustar 00root root 0000000 0000000 123456
password
12345678
qwerty
123456789
12345
1234
111111
1234567
dragon
123123
baseball
abc123
football
monkey
letmein
696969
shadow
master
666666
qwertyuiop
123321
mustang
1234567890
michael
654321
pussy
superman
1qaz2wsx
7777777
fuckyou
121212
000000
qazwsx
123qwe
killer
trustno1
jordan
jennifer
zxcvbnm
asdfgh
hunter
buster
soccer
harley
batman
andrew
tigger
sunshine
iloveyou
fuckme
2000
charlie
robert
thomas
hockey
ranger
daniel
starwars
klaster
112233
george
asshole
computer
michelle
jessica
pepper
1111
zxcvbn
555555
11111111
131313
freedom
777777
pass
fuck
maggie
159753
aaaaaa
ginger
princess
joshua
cheese
amanda
summer
love
ashley
6969
nicole
chelsea
biteme
matthew
access
yankees
987654321
dallas
austin
thunder
taylor
matrix
william
corvette
hello
martin
heather
secret
fucker
merlin
diamond
1234qwer
gfhjkm
hammer
silver
222222
88888888
anthony
justin
test
bailey
q1w2e3r4t5
patrick
internet
scooter
orange
11111
golfer
cookie
richard
samantha
bigdog
guitar
jackson
whatever
mickey
chicken
sparky
snoopy
maverick
phoenix
camaro
sexy
peanut
morgan
welcome
falcon
cowboy
ferrari
samsung
andrea
smokey
steelers
joseph
mercedes
dakota
arsenal
eagles
melissa
boomer
booboo
spider
nascar
monster
tigers
yellow
xxxxxx
123123123
gateway
marina
diablo
bulldog
qwer1234
compaq
purple
hardcore
banana
junior
hannah
123654
porsche
lakers
iceman
money
cowboys
987654
london
tennis
999999
ncc1701
coffee
scooby
0000
miller
boston
q1w2e3r4
fuckoff
brandon
yamaha
chester
mother
forever
johnny
edward
333333
oliver
redsox
player
nikita
knight
fender
barney
midnight
please
brandy
chicago
badboy
iwantu
slayer
rangers
charles
angel
flower
bigdaddy
rabbit
wizard
bigdick
jasper
enter
rachel
chris
steven
winner
adidas
victoria
natasha
1q2w3e4r
jasmine
winter
prince
panties
marine
ghbdtn
fishing
cocacola
casper
james
232323
raiders
888888
marlboro
gandalf
asdfasdf
crystal
87654321
12344321
sexsex
golden
blowme
bigtits
8675309
panther
lauren
angela
bitch
spanky
thx1138
angels
madison
winston
shannon
mike
toyota
blowjob
jordan23
canada
sophie
Password
apples
dick
tiger
razz
123abc
pokemon
qazxsw
55555
qwaszx
muffin
johnson
murphy
cooper
jonathan
liverpoo
david
danielle
159357
jackie
1990
123456a
789456
turtle
horny
abcd1234
scorpion
qazwsxedc
101010
butter
carlos
password1
dennis
slipknot
qwerty123
booger
asdf
1991
black
startrek
12341234
cameron
newyork
rainbow
nathan
john
1992
rocket
viking
redskins
butthead
asdfghjkl
1212
sierra
peaches
gemini
doctor
wilson
sandra
helpme
qwertyui
victor
florida
dolphin
pookie
captain
tucker
blue
liverpool
theman
bandit
dolphins
maddog
packers
jaguar
lovers
nicholas
united
tiffany
maxwell
zzzzzz
nirvana
jeremy
suckit
stupid
porn
monica
elephant
giants
jackass
hotdog
rosebud
success
debbie
mountain
444444
xxxxxxxx
warrior
1q2w3e4r5t
q1w2e3
123456q
albert
metallic
lucky
azerty
7777
shithead
alex
bond007
alexis
1111111
samson
5150
willie
scorpio
bonnie
gators
benjamin
voodoo
driver
dexter
2112
jason
calvin
freddy
212121
creative
12345a
sydney
rush2112
1989
asdfghjk
red123
bubba
4815162342
passw0rd
trouble
gunner
happy
fucking
gordon
legend
jessie
stella
qwert
eminem
arthur
apple
nissan
bullshit
bear
america
1qazxsw2
nothing
parker
4444
rebecca
qweqwe
garfield
01012011
beavis
69696969
jack
asdasd
december
2222
102030
252525
11223344
magic
apollo
skippy
315475
girls
kitten
golf
copper
braves
shelby
godzilla
beaver
fred
tomcat
august
buddy
airborne
1993
1988
lifehack
qqqqqq
brooklyn
animal
platinum
phantom
online
xavier
darkness
blink182
power
fish
green
789456123
voyager
police
travis
12qwaszx
heaven
snowball
lover
abcdef
00000
pakistan
007007
walter
playboy
blazer
cricket
sniper
hooters
donkey
willow
loveme
saturn
therock
redwings
bigboy
pumpkin
trinity
williams
tits
nintendo
digital
destiny
topgun
runner
marvin
guinness
chance
bubbles
testing
fire
november
minecraft
asdf1234
lasvegas
sergey
broncos
cartman
private
celtic
birdie
little
cassie
babygirl
donald
beatles
1313
dickhead
family
12121212
school
louise
gabriel
eclipse
fluffy
147258369
lol123
explorer
beer
nelson
flyers
spencer
scott
lovely
gibson
doggie
cherry
andrey
snickers
buffalo
pantera
metallica
member
carter
qwertyu
peter
alexande
steve
bronco
paradise
goober
5555
samuel
montana
mexico
dreams
michigan
cock
carolina
yankee
friends
magnum
surfer
poopoo
maximus
genius
cool
vampire
lacrosse
asd123
aaaa
christin
kimberly
speedy
sharon
carmen
111222
kristina
sammy
racing
ou812
sabrina
horses
0987654321
qwerty1
pimpin
baby
stalker
enigma
147147
star
poohbear
boobies
147258
simple
bollocks
12345q
marcus
brian
1987
qweasdzxc
drowssap
hahaha
caroline
barbara
dave
viper
drummer
action
einstein
bitches
genesis
hello1
scotty
friend
forest
010203
hotrod
google
vanessa
spitfire
badger
maryjane
friday
alaska
1232323q
tester
jester
jake
champion
billy
147852
rock
hawaii
badass
chevy
420420
walker
stephen
eagle1
bill
1986
october
gregory
svetlana
pamela
1984
music
shorty
westside
stanley
diesel
courtney
242424
kevin
porno
hitman
boobs
mark
12345qwert
reddog
frank
qwe123
popcorn
patricia
aaaaaaaa
1969
teresa
mozart
buddha
anderson
paul
melanie
abcdefg
security
lucky1
lizard
denise
3333
a12345
123789
ruslan
stargate
simpsons
scarface
eagle
123456789a
thumper
olivia
naruto
1234554321
general
cherokee
a123456
vincent
Usuckballz1
spooky
qweasd
cumshot
free
frankie
douglas
death
1980
loveyou
kitty
kelly
veronica
suzuki
semperfi
penguin
mercury
liberty
spirit
scotland
natalie
marley
vikings
system
sucker
king
allison
marshall
1979
098765
qwerty12
hummer
adrian
1985
vfhbyf
sandman
rocky
leslie
antonio
98765432
4321
softball
passion
mnbvcxz
bastard
passport
horney
rascal
howard
franklin
bigred
assman
alexander
homer
redrum
jupiter
claudia
55555555
141414
zaq12wsx
shit
patches
nigger
cunt
raider
infinity
andre
54321
galore
college
russia
kawasaki
bishop
77777777
vladimir
money1
freeuser
wildcats
francis
disney
budlight
brittany
1994
00000000
sweet
oksana
honda
domino
bulldogs
brutus
swordfis
norman
monday
jimmy
ironman
ford
fantasy
9999
7654321
PASSWORD
hentai
duncan
cougar
1977
jeffrey
house
dancer
brooke
timothy
super
marines
justice
digger
connor
patriots
karina
202020
molly
everton
tinker
alicia
rasdzv3
poop
pearljam
stinky
naughty
colorado
123123a
water
test123
ncc1701d
motorola
ireland
asdfg
slut
matt
houston
boogie
zombie
accord
vision
bradley
reggie
kermit
froggy
ducati
avalon
6666
9379992
sarah
saints
logitech
chopper
852456
simpson
madonna
juventus
claire
159951
zachary
yfnfif
wolverin
warcraft
hello123
extreme
penis
peekaboo
fireman
eugene
brenda
123654789
russell
panthers
georgia
smith
skyline
jesus
elizabet
spiderma
smooth
pirate
empire
bullet
8888
virginia
valentin
psycho
predator
arizona
134679
mitchell
alyssa
vegeta
titanic
christ
goblue
fylhtq
wolf
mmmmmm
kirill
indian
hiphop
baxter
awesome
people
danger
roland
mookie
741852963
1111111111
dreamer
bambam
arnold
1981
skipper
serega
rolltide
elvis
changeme
simon
1q2w3e
lovelove
fktrcfylh
denver
tommy
mine
loverboy
hobbes
happy1
alison
nemesis
chevelle
cardinal
burton
wanker
picard
151515
tweety
michael1
147852369
12312
xxxx
windows
turkey
456789
1974
vfrcbv
sublime
1975
galina
bobby
newport
manutd
daddy
american
alexandr
1966
victory
rooster
qqq111
madmax
electric
bigcock
a1b2c3
wolfpack
spring
phpbb
lalala
suckme
spiderman
eric
darkside
classic
raptor
123456789q
hendrix
1982
wombat
avatar
alpha
zxc123
crazy
hard
england
brazil
1978
01011980
wildcat
polina
freepass
carrie
99999999
qaz123
holiday
fyfcnfcbz
brother
taurus
shaggy
raymond
maksim
gundam
admin
vagina
pretty
pickle
good
chronic
alabama
airplane
22222222
1976
1029384756
01011
time
sports
ronaldo
pandora
cheyenne
caesar
billybob
bigman
1968
124578
snowman
lawrence
kenneth
horse
france
bondage
perfect
kristen
devils
alpha1
pussycat
kodiak
flowers
1973
01012000
leather
amber
gracie
chocolat
bubba1
catch22
business
2323
1983
cjkysirj
1972
123qweasd
ytrewq
wolves
stingray
ssssss
serenity
ronald
greenday
135790
010101
tiger1
sunset
charlie1
berlin
bbbbbb
171717
panzer
lincoln
katana
firebird
blizzard
a1b2c3d4
white
sterling
redhead
password123
candy
anna
142536
sasha
pyramid
outlaw
hercules
garcia
454545
trevor
teens
maria
kramer
girl
popeye
pontiac
hardon
dude
aaaaa
323232
tarheels
honey
cobra
buddy1
remember
lickme
detroit
clinton
basketball
zeppelin
whynot
swimming
strike
service
pavilion
michele
engineer
dodgers
britney
bobafett
adam
741852
21122112
xxxxx
robbie
miranda
456123
future
darkstar
icecream
connie
1970
jones
hellfire
fisher
fireball
apache
fuckit
blonde
bigmac
abcd
morris
angel1
666999
321321
simone
rockstar
flash
defender
1967
wallace
trooper
oscar
norton
casino
cancer
beauty
weasel
savage
raven
harvey
bowling
246810
wutang
theone
swordfish
stewart
airforce
abcdefgh
nipples
nastya
jenny
hacker
753951
amateur
viktor
srinivas
maxima
lennon
freddie
bluebird
qazqaz
presario
pimp
packard
mouse
looking
lesbian
jeff
cheryl
2001
wrangler
sandy
machine
lights
eatme
control
tattoo
precious
harrison
duke
beach
tornado
tanner
goldfish
catfish
openup
manager
1971
street
Soso123aljg
roscoe
paris
natali
light
julian
jerry
dilbert
dbrnjhbz
chris1
atlanta
xfiles
thailand
sailor
pussies
pervert
lucifer
longhorn
enjoy
dragons
young
target
elaine
dustin
123qweasdzxc
student
madman
lisa
integra
wordpass
prelude
newton
lolita
ladies
hawkeye
corona
bubble
31415926
trigger
spike
katie
iloveu
herman
design
cannon
999999999
video
stealth
shooter
nfnmzyf
hottie
browns
314159
trucks
malibu
bruins
bobcat
barbie
1964
orlando
letmein1
freaky
foobar
cthutq
baller
unicorn
scully
pussy1
potter
cookies
pppppp
philip
gogogo
elena
country
assassin
1010
zaqwsx
testtest
peewee
moose
microsoft
teacher
sweety
stefan
stacey
shotgun
random
laura
hooker
dfvgbh
devildog
chipper
athena
winnie
valentina
pegasus
kristin
fetish
butterfly
woody
swinger
seattle
lonewolf
joker
booty
babydoll
atlantis
tony
powers
polaris
montreal
angelina
77777
tickle
regina
pepsi
gizmo
express
dollar
squirt
shamrock
knicks
hotstuff
balls
transam
stinger
smiley
ryan
redneck
mistress
hjvfirf
cessna
bunny
toshiba
single
piglet
fucked
father
deftones
coyote
castle
cadillac
blaster
valerie
samurai
oicu812
lindsay
jasmin
james1
ficken
blahblah
birthday
1234abcd
01011990
sunday
manson
flipper
asdfghj
181818
wicked
great
daisy
babes
skeeter
reaper
maddie
cavalier
veronika
trucker
qazwsx123
mustang1
goldberg
escort
12345678910
wolfgang
rocks
mylove
mememe
lancer
ibanez
travel
sugar
snake
sister
siemens
savannah
minnie
leonardo
basketba
1963
trumpet
texas
rocky1
galaxy
cristina
aardvark
shelly
hotsex
goldie
fatboy
benson
321654
141627
sweetpea
ronnie
indigo
13131313
spartan
roberto
hesoyam
freeman
freedom1
fredfred
pizza
manchester
lestat
kathleen
hamilton
erotic
blabla
22222
1995
skater
pencil
passwor
larisa
hornet
hamlet
gambit
fuckyou2
alfred
456456
sweetie
marino
lollol
565656
techno
special
renegade
insane
indiana
farmer
drpepper
blondie
bigboobs
272727
1a2b3c
valera
storm
seven
rose
nick
mister
karate
casey
1qaz2wsx3edc
1478963
maiden
julie
curtis
colors
christia
buckeyes
13579
0123456789
toronto
stephani
pioneer
kissme
jungle
jerome
holland
harry
garden
enterpri
dragon1
diamonds
chrissy
bigone
343434
wonder
wetpussy
subaru
smitty
racecar
pascal
morpheus
joanne
irina
indians
impala
hamster
charger
change
bigfoot
babylon
66666666
timber
redman
pornstar
bernie
tomtom
thuglife
millie
buckeye
aaron
virgin
tristan
stormy
rusty
pierre
napoleon
monkey1
highland
chiefs
chandler
catdog
aurora
1965
trfnthbyf
sampson
nipple
dudley
cream
consumer
burger
brandi
welcome1
triumph
joejoe
hunting
dirty
caserta
brown
aragorn
363636
mariah
element
chichi
2121
123qwe123
wrinkle1
smoke
omega
monika
leonard
justme
hobbit
gloria
doggy
chicks
bass
audrey
951753
51505150
11235813
sakura
philips
griffin
butterfl
artist
66666
island
goforit
emerald
elizabeth
anakin
watson
poison
none
italia
callie
bobbob
autumn
andreas
123
sherlock
q12345
pitbull
marathon
kelsey
inside
german
blackie
access14
123asd
zipper
overlord
nadine
marie
basket
trombone
stones
sammie
nugget
naked
kaiser
isabelle
huskers
bomber
barcelona
babylon5
babe
alpine
weed
ultimate
pebbles
nicolas
marion
loser
linda
eddie
wesley
warlock
tyler
goddess
fatcat
energy
david1
bassman
yankees1
whore
trojan
trixie
superfly
kkkkkk
ybrbnf
warren
sophia
sidney
pussys
nicola
campbell
vfvjxrf
singer
shirley
qawsed
paladin
martha
karen
help
harold
geronimo
forget
concrete
191919
westham
soldier
q1w2e3r4t5y6
poiuyt
nikki
mario
juice
jessica1
global
dodger
123454321
webster
titans
tintin
tarzan
sexual
sammy1
portugal
onelove
marcel
manuel
madness
jjjjjj
holly
christy
424242
yvonne
sundance
sex4me
pleasure
logan
danny
wwwwww
truck
spartak
smile
michel
history
Exigen
65432
1234321
sherry
sherman
seminole
rommel
network
ladybug
isabella
holden
harris
germany
fktrctq
cotton
angelo
14789632
sergio
qazxswedc
moon
jesus1
trunks
snakes
sluts
kingkong
bluesky
archie
adgjmptw
911911
112358
sunny
suck
snatch
planet
panama
ncc1701e
mongoose
head
hansolo
desire
alejandr
1123581321
whiskey
waters
teen
party
martina
margaret
january
connect
bluemoon
bianca
andrei
5555555
smiles
nolimit
long
assass
abigail
555666
yomama
rocker
plastic
katrina
ghbdtnbr
ferret
emily
bonehead
blessed
beagle
asasas
abgrtyu
sticky
olga
japan
jamaica
home
hector
dddddd
1961
turbo
stallion
personal
peace
movie
morrison
joanna
geheim
finger
cactus
7895123
susan
super123
spyder
mission
anything
aleksandr
zxcvb
shalom
rhbcnbyf
pickles
passat
natalia
moomoo
jumper
inferno
dietcoke
cumming
cooldude
chuck
christop
million
lollipop
fernando
christian
blue22
bernard
apple1
unreal
spunky
ripper
open
niners
letmein2
flatron
faster
deedee
bertha
april
4128
01012010
werewolf
rubber
punkrock
orion
mulder
missy
larry
giovanni
gggggg
cdtnkfyf
yoyoyo
tottenha
shaved
newman
lindsey
joey
hongkong
freak
daniela
camera
brianna
blackcat
a1234567
1q1q1q
zzzzzzzz
stars
pentium
patton
jamie
hollywoo
florence
biscuit
beetle
andy
always
speed
sailing
phillip
legion
gn56gn56
909090
martini
dream
darren
clifford
2002
stocking
solomon
silvia
pirates
office
monitor
monique
milton
matthew1
maniac
loulou
jackoff
immortal
fossil
dodge
delta
44444444
121314
sylvia
sprite
shadow1
salmon
diana
shasta
patriot
palmer
oxford
nylons
molly1
irish
holmes
curious
asdzxc
1999
makaveli
kiki
kennedy
groovy
foster
drizzt
twister
snapper
sebastia
philly
pacific
jersey
ilovesex
dominic
charlott
carrot
anthony1
africa
111222333
sharks
serena
satan666
maxmax
maurice
jacob
gerald
cosmos
columbia
colleen
cjkywt
cantona
brooks
99999
787878
rodney
nasty
keeper
infantry
frog
french
eternity
dillon
coolio
condor
anton
waterloo
velvet
vanhalen
teddy
skywalke
sheila
sesame
seinfeld
funtime
012345
standard
squirrel
qazwsxed
ninja
kingdom
grendel
ghost
fuckfuck
damien
crimson
boeing
bird
biggie
090909
zaq123
wolverine
wolfman
trains
sweets
sunrise
maxine
legolas
jericho
isabel
foxtrot
anal
shogun
search
robinson
rfrfirf
ravens
privet
penny
musicman
memphis
megadeth
dogs
butt
brownie
oldman
graham
grace
505050
verbatim
support
safety
review
newlife
muscle
herbert
colt45
bottom
2525
1q2w3e4r5t6y
1960
159159
western
twilight
thanks
suzanne
potato
pikachu
murray
master1
marlin
gilbert
getsome
fuckyou1
dima
denis
789789
456852
stone
stardust
seven7
peanuts
obiwan
mollie
licker
kansas
frosty
ball
262626
tarheel
showtime
roman
markus
maestro
lobster
darwin
cindy
chubby
2468
147896325
tanker
surfing
skittles
showme
shaney14
qwerty12345
magic1
goblin
fusion
blades
banshee
alberto
123321123
123098
powder
malcolm
intrepid
garrett
delete
chaos
bruno
1701
tequila
short
sandiego
python
punisher
newpass
iverson
clayton
amadeus
1234567a
stimpy
sooners
preston
poopie
photos
neptune
mirage
harmony
gold
fighter
dingdong
cats
whitney
sucks
slick
rick
ricardo
princes
liquid
helena
daytona
clover
blues
anubis
1996
192837465
starcraft
roxanne
pepsi1
mushroom
eatshit
dagger
cracker
capital
brendan
blackdog
25802580
strider
slapshot
porter
pink
jason1
hershey
gothic
flight
ekaterina
cody
buffy
boss
bananas
aaaaaaa
123698745
1234512345
tracey
miami
kolobok
danni
chargers
cccccc
blue123
bigguy
33333333
0.0.000
warriors
walnut
raistlin
ping
miguel
latino
griffey
green1
gangster
felix
engine
doodle
coltrane
byteme
buck
asdf123
123456z
0007
vertigo
tacobell
shark
portland
penelope
osiris
nymets
nookie
mary
lucky7
lucas
lester
ledzep
gorilla
coco
bugger
bruce
blood
bentley
battle
1a2b3c4d
19841984
12369874
weezer
turner
thegame
stranger
sally
Mailcreated5240
knights
halflife
ffffff
dorothy
dookie
damian
258456
women
trance
qwerasdf
playtime
paradox
monroe
kangaroo
henry
dumbass
dublin
charly
butler
brasil
blade
blackman
bender
baggins
wisdom
tazman
swallow
stuart
scruffy
phoebe
panasonic
Michael
masters
ghjcnj
firefly
derrick
christine
beautiful
auburn
archer
aliens
161616
1122
woody1
wheels
test1
spanking
robin
redred
racerx
postal
parrot
nimrod
meridian
madrid
lonestar
kittycat
hell
goodluck
gangsta
formula
devil
cassidy
camille
buttons
bonjour
bingo
barcelon
allen
98765
898989
303030
2020
0000000
tttttt
tamara
scoobydo
samsam
rjntyjr
richie
qwertz
megaman
luther
jazz
crusader
bollox
123qaz
12312312
102938
window
sprint
sinner
sadie
rulez
quality
pooper
pass123
oakland
misty
lvbnhbq
lady
hannibal
guardian
grizzly
fuckface
finish
discover
collins
catalina
carson
black1
bang
annie
123987
1122334455
wookie
volume
tina
rockon
qwer
molson
marco
californ
angelica
2424
world
william1
stonecol
shemale
shazam
picasso
oracle
moscow
luke
lorenzo
kitkat
johnjohn
janice
gerard
flames
duck
dark
celica
445566
234567
yourmom
topper
stevie
septembe
scarlett
santiago
milano
lowrider
loving
incubus
dogdog
anastasia
1962
123zxc
vacation
tempest
sithlord
scarlet
rebels
ragnarok
prodigy
mobile
keyboard
golfing
english
carlo
anime
545454
19921992
11112222
vfhecz
sobaka
shiloh
penguins
nuttertools
mystery
lorraine
llllll
lawyer
kiss
jeep
gizmodo
elwood
dkflbvbh
987456
6751520
12121
titleist
tardis
tacoma
smoker
shaman
rootbeer
magnolia
julia
juan
hoover
gotcha
dodgeram
creampie
buffett
bridge
aspirine
456654
socrates
photo
parola
nopass
megan
lucy
kenwood
kenny
imagine
forgot
cynthia
blondes
ashton
aezakmi
1234567q
viper1
terry
sabine
redalert
qqqqqqqq
munchkin
monkeys
mersedes
melvin
mallard
lizzie
imperial
honda1
gremlin
gillian
elliott
defiant
dadada
cooler
bond
blueeyes
birdman
bigballs
analsex
753159
zaq1xsw2
xanadu
weather
violet
sergei
sebastian
romeo
research
putter
oooooo
national
lexmark
hotboy
greg
garbage
colombia
chucky
carpet
bobo
bobbie
assfuck
88888
01012001
smokin
shaolin
roger
rammstein
pussy69
katerina
hearts
frogger
freckles
dogg
dixie
claude
caliente
amazon
abcde
1221
wright
willis
spidey
sleepy
sirius
santos
rrrrrr
randy
picture
payton
mason
dusty
director
celeste
broken
trebor
sheena
qazwsxedcrfv
polo
oblivion
mustangs
margarita
letsgo
josh
jimbob
jimbo
janine
jackal
iforgot
hallo
fatass
deadhead
abc12
zxcv1234
willy
stud
slappy
roberts
rescue
porkchop
noodles
nellie
mypass
mikey
marvel
laurie
grateful
fuck_inside
formula1
Dragon
cxfcnmt
bridget
aussie
asterix
a1s2d3f4
23232323
123321q
veritas
spankme
shopping
roller
rogers
queen
peterpan
palace
melinda
martinez
lonely
kristi
justdoit
goodtime
frances
camel
beckham
atomic
alexandra
active
223344
vanilla
thankyou
springer
sommer
Software
sapphire
richmond
printer
ohyeah
massive
lemons
kingston
granny
funfun
evelyn
donnie
deanna
brucelee
bosco
aggies
313131
wayne
thunder1
throat
temple
smudge
qqqq
qawsedrf
plymouth
pacman
myself
mariners
israel
hitler
heather1
faith
Exigent
clancy
chelsea1
353535
282828
123456qwerty
tobias
tatyana
stuff
spectrum
sooner
shitty
sasha1
pooh
pineappl
mandy
labrador
kisses
katrin
kasper
kaktus
harder
eduard
dylan
dead
chloe
astros
1234567890q
10101010
stephanie
satan
hudson
commando
bones
bangkok
amsterdam
1959
webmaster
valley
space
southern
rusty1
punkin
napass
marian
magnus
lesbians
krishna
hungry
hhhhhh
fuckers
fletcher
content
account
906090
thompson
simba
scream
q1q1q1
primus
Passw0rd
mature
ivanov
husker
homerun
esther
ernest
champs
celtics
candyman
bush
boner
asian
aquarius
33333
zxcv
starfish
pics
peugeot
painter
monopoly
lick
infiniti
goodbye
gangbang
fatman
darling
celine
camelot
boat
blackjac
barkley
area51
8J4yE3Uz
789654
19871987
0000000000
vader
shelley
scrappy
sarah1
sailboat
richard1
moloko
method
mama
kyle
kicker
keith
judith
john316
horndog
godsmack
flyboy
emmanuel
drago
cosworth
blake
19891989
writer
usa123
topdog
timmy
speaker
rosemary
pancho
night
melody
lightnin
life
hidden
gator
farside
falcons
desert
chevrole
catherin
carolyn
bowler
anders
666777
369369
yesyes
sabbath
qwerty123456
power1
pete
oscar1
ludwig
jammer
frontier
fallen
dance
bryan
asshole1
amber1
aaa111
123457
01011991
terror
telefon
strong
spartans
sara
odessa
luckydog
frank1
elijah
chang
center
bull
blacks
15426378
132435
vivian
tanya
swingers
stick
snuggles
sanchez
redbull
reality
qwertyuio
qwert123
mandingo
ihateyou
hayden
goose
franco
forrest
double
carol
bohica
bell
beefcake
beatrice
avenger
andrew1
anarchy
963852
1366613
111111111
whocares
scooter1
rbhbkk
matilda
labtec
kevin1
jojo
jesse
hermes
fitness
doberman
dawg
clitoris
camels
5555555555
1957
vulcan
vectra
topcat
theking
skiing
nokia
muppet
moocow
leopard
kelley
ivan
grover
gjkbyf
filter
elvis1
delta1
dannyboy
conrad
children
catcat
bossman
bacon
amelia
alice
2222222
viktoria
valhalla
tricky
terminator
soccer1
ramona
puppy
popopo
oklahoma
ncc1701a
mystic
loveit
looker
latin
laptop
laguna
keystone
iguana
herbie
cupcake
clarence
bunghole
blacky
bennett
bart
19751975
12332
000007
vette
trojans
today
romashka
puppies
possum
pa55word
oakley
moneys
kingpin
golfball
funny
doughboy
dalton
crash
charlotte
carlton
breeze
billie
beast
achilles
tatiana
studio
sterlin
plumber
patrick1
miles
kotenok
homers
gbpltw
gateway1
franky
durango
drake
deeznuts
cowboys1
ccbill
brando
9876543210
zzzz
zxczxc
vkontakte
tyrone
skinny
rookie
qwqwqw
phillies
lespaul
juliet
jeremiah
igor
homer1
dilligaf
caitlin
budman
atlantic
989898
362436
19851985
vfrcbvrf
verona
technics
svetik
stripper
soleil
september
pinkfloy
noodle
metal
maynard
maryland
kentucky
hastings
gang
frederic
engage
eileen
butthole
bone
azsxdc
agent007
474747
19911991
01011985
triton
tractor
somethin
snow
shane
sassy
sabina
russian
porsche9
pistol
justine
hurrican
gopher
deadman
cutter
coolman
command
chase
california
boris
bicycle
bethany
bearbear
babyboy
73501505
123456k
zvezda
vortex
vipers
tuesday
traffic
toto
star69
server
ready
rafael
omega1
nathalie
microlab
killme
jrcfyf
gizmo1
function
freaks
flamingo
enterprise
eleven
doobie
deskjet
cuddles
church
breast
19941994
19781978
1225
01011970
vladik
unknown
truelove
sweden
striker
stoner
sony
SaUn
ranger1
qqqqq
pauline
nebraska
meatball
marilyn
jethro
hammers
gustav
escape
elliot
dogman
chair
brothers
boots
blow
bella
belinda
babies
1414
titties
syracuse
river
polska
pilot
oilers
nofear
military
macdaddy
hawk
diamond1
dddd
danila
central
annette
128500
zxcasd
warhammer
universe
splash
smut
sentinel
rayray
randall
Password1
panda
nevada
mighty
meghan
mayday
manchest
madden
kamikaze
jennie
iloveyo
hustler
hunter1
horny1
handsome
dthjybrf
designer
demon
cheers
cash
cancel
blueblue
bigger
australia
asdfjkl
321654987
1qaz1qaz
1955
1234qwe
01011981
zaphod
ultima
tolkien
Thomas
thekid
tdutybq
summit
select
saint
rockets
rhonda
retard
rebel
ralph
poncho
pokemon1
play
pantyhos
nina
momoney
market
lickit
leader
kong
jenna
jayjay
javier
eatpussy
dracula
dawson
daniil
cartoon
capone
bubbas
789123
19861986
01011986
zxzxzx
wendy
tree
superstar
super1
ssssssss
sonic
sinatra
scottie
sasasa
rush
robert1
rjirfrgbde
reagan
meatloaf
lifetime
jimmy1
jamesbon
houses
hilton
gofish
charmed
bowser
betty
525252
123456789z
1066
woofwoof
Turkey50
santana
rugby
rfnthbyf
miracle
mailman
lansing
kathryn
Jennifer
giant
front242
firefox
check
boxing
bogdan
bizkit
azamat
apollo13
alan
zidane
tracy
tinman
terminal
starbuck
redhot
oregon
memory
lewis
lancelot
illini
grandma
govols
gordon24
giorgi
feet
fatima
crunch
creamy
coke
cabbage
bryant
brandon1
bigmoney
azsxdcfv
3333333
321123
warlord
station
sayang
rotten
rightnow
mojo
models
maradona
lololo
lionking
jarhead
hehehe
gary
fast
exodus
crazybab
conner
charlton
catman
casey1
bonita
arjay
19931993
19901990
1001
100000
sticks
poiuytrewq
peters
passwort
orioles
oranges
marissa
japanese
holyshit
hohoho
gogo
fabian
donna
cutlass
cthulhu
chewie
chacha
bradford
bigtime
aikido
4runner
21212121
150781
wildfire
utopia
sport
sexygirl
rereirf
reebok
raven1
poontang
poodle
movies
microsof
grumpy
eeyore
down
dong
chocolate
chickens
butch
arsenal1
adult
adriana
19831983
zzzzz
volley
tootsie
sparkle
software
sexx
scotch
science
rovers
nnnnnn
mellon
legacy
julius
helen
happyday
fubar
danie
cancun
br0d3r
beverly
beaner
aberdeen
44444
19951995
13243546
123456aa
wilbur
treasure
tomato
theodore
shania
raiders1
natural
kume
kathy
hamburg
gretchen
frisco
ericsson
daddy1
cosmo
condom
comics
coconut
cocks
Check
camilla
bikini
albatros
1Passwor
1958
1919
143143
0.0.0.000
zxcasdqwe
zaqxsw
whisper
vfvekz
tyler1
Sojdlg123aljg
sixers
sexsexsex
rfhbyf
profit
okokok
nancy
mikemike
michaela
memorex
marlene
kristy
jose
jackson1
hope
hailey
fugazi
fright
figaro
excalibu
elvira
dildo
denali
cruise
cooter
cheng
candle
bitch1
attack
armani
anhyeuem
78945612
222333
zenith
walleye
tsunami
trinidad
thomas1
temp
tammy
sultan
steve1
slacker
selena
samiam
revenge
pooppoop
pillow
nobody
kitty1
killer1
jojojo
huskies
greens
greenbay
greatone
fuckin
fortuna
fordf150
first
fashion
fart
emerson
davis
cloud9
china
boob
applepie
alien
963852741
321456
292929
1998
1956
18436572
tasha
stocks
rustam
rfrnec
piccolo
orgasm
milana
marisa
marcos
malaka
lisalisa
kelly1
hithere
harley1
hardrock
flying
fernand
dinosaur
corrado
coleman
clapton
chief
bloody
anfield
636363
420247
332211
voyeur
toby
texas1
surf
steele
running
rastaman
pa55w0rd
oleg
number1
maxell
madeline
keywest
junebug
ingrid
hollywood
hellyeah
hayley
goku
felicia
eeeeee
dicks
dfkthbz
dana
daisy1
columbus
charli
bonsai
billy1
aspire
9999999
987987
50cent
000001
xxxxxxx
wolfie
viagra
vfksirf
vernon
tang
swimmer
subway
stolen
sparta
slutty
skywalker
sean
sausage
rockhard
ricky
positive
nyjets
miriam
melissa1
krista
kipper
kcj9wx5n
jedi
jazzman
hyperion
happy123
gotohell
garage
football1
fingers
february
faggot
easy
dragoon
crazy1
clemson
chanel
canon
bootie
balloon
abc12345
609609609
456321
404040
162534
yosemite
slider
shado
sandro
roadkill
quincy
pedro
mayhem
lion
knopka
kingfish
jerkoff
hopper
everest
ddddddd
damnit
cunts
chevy1
cheetah
chaser
billyboy
bigbird
bbbb
789987
1qa2ws3ed
1954
135246
123789456
122333
1000
050505
wibble
valeria
tunafish
trident
thor
tekken
tara
starship
slave
saratoga
romance
robotech
rich
rasputin
rangers1
powell
poppop
passwords
p0015123
nwo4life
murder
milena
midget
megapass
lucky13
lolipop
koshka
kenworth
jonjon
jenny1
irish1
hedgehog
guiness
gmoney
ghetto
fortune
emily1
duster
ding
davidson
davids
dammit
dale
crysis
bogart
anaconda
alibaba
airbus
7753191
515151
20102010
200000
123123q
12131415
10203
work
wood
vladislav
vfczyz
tundra
Translator
torres
splinter
spears
richards
rachael
pussie
phoenix1
pearl
monty
lolo
lkjhgf
leelee
karolina
johanna
jensen
helloo
harper
hal9000
fletch
feather
fang
dfkthf
depeche
barsik
789789789
757575
727272
zorro
xtreme
woman
vitalik
vermont
train
theboss
sword
shearer
sanders
railroad
qwer123
pupsik
pornos
pippen
pingpong
nikola
nguyen
music1
magicman
killbill
kickass
kenshin
katie1
juggalo
jayhawk
java
grapes
fritz
drew
divine
cyclops
critter
coucou
cecilia
bristol
bigsexy
allsop
9876
1230
01011989
wrestlin
twisted
trout
tommyboy
stefano
song
skydive
sherwood
passpass
pass1234
onlyme
malina
majestic
macross
lillian
heart
guest
gabrie
fuckthis
freeporn
dinamo
deborah
crawford
clipper
city
better
bears
bangbang
asdasdasd
artemis
angie
admiral
2003
020202
yousuck
xbox360
werner
vector
usmc
umbrella
tool
strange
sparks
spank
smelly
small
salvador
sabres
rupert
ramses
presto
pompey
operator
nudist
ne1469
minime
matador
love69
kendall
jordan1
jeanette
hooter
hansen
gunners
gonzo
gggggggg
fktrcfylhf
facial
deepthroat
daniel1
dang
cruiser
cinnamon
cigars
chico
chester1
carl
caramel
calico
broadway
batman1
baddog
778899
2128506
123456r
0420
01011988
z1x2c3
wassup
wally
vh5150
underdog
thesims
thecat
sunnyday
snoopdog
sandy1
pooter
multiplelo
magick
library
kungfu
kirsten
kimber
jean
jasmine1
hotshot
gringo
fowler
emma
duchess
damage
cyclone
Computer
chong
chemical
chainsaw
caveman
catherine
carrera
canadian
buster1
brighton
back
australi
animals
alliance
albion
969696
555777
19721972
19691969
1024
trisha
theresa
supersta
steph
static
snowboar
sex123
scratch
retired
rambler
r2d2c3po
quantum
passme
over
newbie
mybaby
musica
misfit
mechanic
mattie
mathew
mamapapa
looser
jabroni
isaiah
heyhey
hank
hang
golfgolf
ghjcnjnfr
frozen
forfun
fffff
downtown
coolguy
cohiba
christopher
chivas
chicken1
bullseye
boys
bottle
bob123
blueboy
believe
becky
beanie
20002000
yzerman
west
village
vietnam
trader
summer1
stereo
spurs
solnce
smegma
skorpion
saturday
samara
safari
renault
rctybz
peterson
paper
meredith
marc
louis
lkjhgfdsa
ktyjxrf
kill
kids
jjjj
ivanova
hotred
goalie
fishes
eastside
cypress
cyber
credit
brad
blackhaw
beastie
banker
backdoor
again
192837
112211
westwood
venus
steeler
spawn
sneakers
snapple
snake1
sims
sharky
sexxxx
seeker
scania
sapper
route66
Robert
q123456
Passwor1
mnbvcx
mirror
maureen
marino13
jamesbond
jade
horizon
haha
getmoney
flounder
fiesta
europa
direct
dean
compute
chrono
chad
boomboom
bobby1
bing
beerbeer
apple123
andres
8888888
777888
333666
1357
12345z
030303
01011987
01011984
wolf359
whitey
undertaker
topher
tommy1
tabitha
stroke
staples
sinclair
silence
scout
scanner
samsung1
rain
poetry
pisces
phil
peter1
packer
outkast
nike
moneyman
mmmmmmmm
ming
marianne
magpie
love123
kahuna
jokers
jjjjjjjj
groucho
goodman
gargoyle
fuckher
florian
federico
droopy
dorian
donuts
ddddd
cinder
buttman
benny
barry
amsterda
alfa
656565
1x2zkg8w
19881988
19741974
zerocool
walrus
walmart
vfvfgfgf
user
typhoon
test1234
studly
Shadow
sexy69
sadie1
rtyuehe
rosie
qwert1
nipper
maximum
klingon
jess
idontknow
heidi
hahahaha
gggg
fucku2
floppy
flash1
fghtkm
erotica
erik
doodoo
dharma
deniska
deacon
daphne
daewoo
dada
charley
cambiami
bimmer
bike
bigbear
alucard
absolut
a123456789
4121
19731973
070707
03082006
02071986
vfhufhbnf
sinbad
secret1
second
seamus
renee
redfish
rabota
pudding
pppppppp
patty
paint
ocean
number
nature
motherlode
micron
maxx
massimo
losers
lokomotiv
ling
kristine
kostya
korn
goldstar
gegcbr
floyd
fallout
dawn
custom
christina
chrisbln
button
bonkers
bogey
belle
bbbbb
barber
audia4
america1
abraham
585858
414141
336699
20012001
12345678q
0123
whitesox
whatsup
usnavy
tuan
titty
titanium
thursday
thirteen
tazmania
steel
starfire
sparrow
skidoo
senior
reading
qwerqwer
qazwsx12
peyton
panasoni
paintbal
newcastl
marius
italian
hotpussy
holly1
goliath
giuseppe
frodo
fresh
buckshot
bounce
babyblue
attitude
answer
90210
575757
10203040
1012
01011910
ybrjkfq
wasser
tyson
Superman
sunflowe
steam
ssss
sound
solution
snoop
shou
shawn
sasuke
rules
royals
rivers
respect
poppy
phillips
olivier
moose1
mondeo
mmmm
knickers
hoosier
greece
grant
godfather
freeze
europe
erica
doogie
danzig
dalejr
contact
clarinet
champ
briana
bluedog
backup
assholes
allmine
aaliyah
12345679
100100
zigzag
whisky
weaver
truman
tomorrow
tight
theend
start
southpark
sersolution
roberta
rhfcjnrf
qwerty1234
quartz
premier
paintball
montgom240
mommy
mittens
micheal
maggot
loco
laurel
lamont
karma
journey
johannes
intruder
insert
hairy
hacked
groove
gesperrt
francois
focus
felipe
eternal
edwards
doug
dollars
dkflbckfd
dfktynbyf
demons
deejay
cubbies
christie
celeron
cat123
carbon
callaway
bucket
albina
2004
19821982
19811981
1515
12qw34er
123qwerty
123aaa
10101
1007
080808
zeus
warthog
tights
simona
shun
salamander
resident
reefer
racer
quattro
public
poseidon
pianoman
nonono
michell
mellow
luis
jillian
havefun
gunnar
goofy
futbol
fucku
eduardo
diehard
dian
chuckles
carla
carina
avalanch
artur
allstar
abc1234
abby
4545
1q2w3e4r5
125125
123451
ziggy
yumyum
working
what
wang
wagner
volvo
ufkbyf
twinkle
susanne
superman1
sunshin
strip
searay
rockford
radio
qwertyqwerty
proxy
prophet
ou8122
oasis
mylife
monke
monaco
meowmeow
meathead
Master
leanne
kang
joyjoy
joker1
filthy
emmitt
craig
cornell
changed
cbr600
builder
budweise
boobie
bobobo
biggles
bigass
bertie
amanda1
a1s2d3
784512
767676
235689
1953
19411945
14725836
11223
01091989
01011992
zero
vegas
twins
turbo1
triangle
thongs
thanatos
sting
starman
spike1
smokes
shai
sexyman
sex
scuba
runescape
phish
pepper1
padres
nitram
nickel
napster
lord
jewels
jeanne
gretzky
great1
gladiator
crjhgbjy
chuang
chou
blossom
bean
barefoot
alina
787898
567890
5551212
25252525
02071982
zxcvbnm1
zhong
woohoo
welder
viewsonic
venice
usarmy
trial
traveler
together
team
tango
swords
starter
sputnik
spongebob
slinky
rover
ripken
rasta
prissy
pinhead
papa
pants
original
mustard
more
mohammed
mian
medicine
mazafaka
lance
juliette
james007
hawkeyes
goodboy
gong
footbal
feng
derek
deeznutz
dante
combat
cicero
chun
cerberus
beretta
bengals
beaches
3232
135792468
12345qwe
01234567
01011975
zxasqw12
xxx123
xander
will
watcher
thedog
terrapin
stoney
stacy
something
shang
secure
rooney
rodman
redwing
quan
pony
pobeda
pissing
philippe
overkill
monalisa
mishka
lions
lionel
leonid
krystal
kosmos
jessic
jane
illusion
hoosiers
hayabusa
greene
gfhjkm123
games
francesc
enter1
confused
cobra1
clevelan
cedric
carole
busted
bonbon
barrett
banane
badgirl
antoine
7779311
311311
2345
187187
123456s
123456654321
1005
0987
01011993
zippy
zhei
vinnie
tttttttt
stunner
stoned
smoking
smeghead
sacred
redwood
Pussy1
moonlight
momomo
mimi
megatron
massage
looney
johnboy
janet
jagger
jacob1
hurley
hong
hihihi
helmet
heckfy
hambone
gollum
gaston
f**k
death1
Charlie
chao
cfitymrf
casanova
brent
boricua
blackjack
blablabla
bigmike
bermuda
bbbbbbbb
bayern
amazing
aleksey
717171
12301230
zheng
yoyo
wildman
tracker
syncmaster
sascha
rhiannon
reader
queens
qing
purdue
pool
poochie
poker
petra
person
orchid
nuts
nice
lola
lightning
leng
lang
lambert
kashmir
jill
idiot
honey1
fisting
fester
eraser
diao
delphi
dddddddd
cubswin
cong
claudio
clark
chip
buzzard
buzz
butts
brewster
bravo
bookworm
blessing
benfica
because
babybaby
aleksandra
6666666
1997
19961996
19791979
1717
1213
02091987
02021987
xiao
wild
valencia
trapper
tongue
thegreat
sancho
really
rainman
piper
peng
peach
passwd
packers1
newpass6
neng
mouse1
motley
morning
midway
Michelle
miao
maste
marin
kaylee
justin1
hokies
health
glory
five
dutchess
dogfood
comet
clouds
cloud
charles1
buddah
bacardi
astrid
alphabet
adams
19801980
147369
12qwas
02081988
02051986
02041986
02011985
01011977
xuan
vedder
valeri
teng
stumpy
squash
snapon
site
ruan
roadrunn
rjycnfynby
rhtdtlrj
rambo
pizzas
paula
novell
mortgage
misha
menace
maxim
lori
kool
hanna
gsxr750
goldwing
frisky
famous
dodge1
dbrnjh
christmas
cheese1
century
candice
booker
beamer
assword
army
angus
andromeda
adrienne
676767
543210
2010
1369
12345678a
12011987
02101985
02031986
02021988
zhuang
zhou
wrestling
tinkerbell
thumbs
thedude
teddybea
sssss
sonics
sinister
shannon1
satana
sang
salomon
remote
qazzaq
playing
piao
pacers
onetime
nong
nikolay
motherfucker
mortimer
misery
madison1
luan
lovesex
look
Jessica
handyman
hampton
gromit
ghostrider
doghouse
deluxe
clown
chunky
chuai
cgfhnfr
brewer
boxster
balloons
adults
a1a1a1
794613
654123
24682468
2005
1492
1020
1017
02061985
02011987
*****
zhun
ying
yang
windsor
wedding
wareagle
svoboda
supreme
stalin
sponge
simon1
roadking
ripple
realmadrid
qiao
PolniyPizdec0211
pissoff
peacock
norway
nokia6300
ninjas
misty1
medusa
medical
maryann
marika
madina
logan1
lilly
laser
killers
jiang
jaybird
jammin
intel
idontkno
huai
harry1
goaway
gameover
dino
destroy
deng
collin
claymore
chicago1
cheater
chai
bunny1
blackbir
bigbutt
bcfields
athens
antoni
abcd123
686868
369963
1357924680
12qw12
1236987
111333
02091986
02021986
01011983
000111
zhuai
yoda
xiang
wrestle
whiskers
valkyrie
toon
tong
ting
talisman
starcraf
sporting
spaceman
southpar
smiths
skate
shell
seng
saleen
ruby
reng
redline
rancid
pepe
optimus
nova
mohamed
meister
marcia
lipstick
kittykat
jktymrf
jenn
jayden
inuyasha
higgins
guai
gonavy
face
eureka
dutch
darkman
courage
cocaine
circus
cheeks
camper
br549
bagira
babyface
7uGd5HIp2J
5050
1qaz2ws
123321a
02081987
02081984
02061986
02021984
01011982
zhai
xiong
willia
vvvvvv
venera
unique
tian
sveta
strength
stories
squall
secrets
seahawks
sauron
ripley
riley
recovery
qweqweqwe
qiong
puddin
playstation
pinky
phone
penny1
nude
mitch
milkman
mermaid
max123
maria1
lust
loaded
lighter
lexus
leavemealone
just4me
jiong
jing
jamie1
india
hardcock
gobucks
gawker
fytxrf
fuzzy
florida1
flexible
eleanor
dragonball
doudou
cinema
checkers
charlene
ceng
buffy1
brian1
beautifu
baseball1
ashlee
adonis
adam12
434343
02031984
02021985
xxxpass
toledo
thedoors
templar
sullivan
stanford
shei
sander
rolling
qqqqqqq
pussey
pothead
pippin
nimbus
niao
mustafa
monte
mollydog
modena
mmmmm
michae
meng
mango
mamama
lynn
love12
kissing
keegan
jockey
illinois
ib6ub9
hotbox
hippie
hill
ghblehjr
gamecube
ferris
diggler
crow
circle
chuo
chinook
charity
carmel
caravan
cannabis
cameltoe
buddie
bright
bitchass
bert
beowulf
bartman
asia
armagedon
ariana
alexalex
alenka
ABC123
987456321
373737
2580
21031988
123qq123
12345t
1234567890a
123455
02081989
02011986
01020304
01011999
xyz123
xerxes
wraith
wishbone
warning
todd
ticket
three
subzero
shuang
rong
rider
quest
qiang
pppp
pian
petrov
otto
nuan
ning
myname
matthews
martine
mandarin
magical
latinas
lalalala
kotaku
jjjjj
jeffery
jameson
iamgod
hellos
hassan
Harley
godfathe
geng
gabriela
foryou
ffffffff
divorce
darius
chui
breasts
bluefish
binladen
bigtit
anne
alexia
2727
19771977
19761976
02061989
02041984
zhui
zappa
yfnfkmz
weng
tricia
tottenham
tiberius
teddybear
spinner
spice
spectre
solo
silverad
silly
shuo
sherri
samtron
poland
poiuy
pickup
pdtplf
paloma
ntktajy
northern
nasty1
musashi
missy1
microphone
meat
manman
lucille
lotus
letter
kendra
iomega
hootie
forward
elite
electron
electra
duan
DRAGON
dotcom
dirtbike
dianne
desiree
deadpool
darrell
cosmic
common
chrome
cathy
carpedie
bilbo
bella1
beemer
bearcat
bank
ashley1
asdfzxcv
amateurs
allan
absolute
50spanks
147963
120676
1123
02021983
zang
virtual
vampires
vadim
tulips
sweet1
suan
spread
spanish
some
slapper
skylar
shiner
sheng
shanghai
sanfran
ramones
property
pheonix
password2
pablo
othello
orange1
nuggets
netscape
ludmila
lost
liang
kakashka
kaitlyn
iscool
huang
hillary
high
hhhh
heater
hawaiian
guang
grease
gfhjkmgfhjkm
gfhjkm1
fyutkbyf
finance
farley
dogshit
digital1
crack
counter
corsair
company
colonel
claudi
carolin
caprice
caligula
bulls
blackout
beatle
beans
banzai
banner
artem
9562876
5656
1945
159632
15151515
123456qw
1234567891
02051983
02041983
02031987
02021989
z1x2c3v4
xing
vSjasnel12
twenty
toolman
thing
testpass
stretch
stonecold
soulmate
sonny
snuffy
shutup
shuai
shao
rhino
q2w3e4r5
polly
poipoi
pierce
piano
pavlov
pang
nicole1
millions
marsha
lineage2
liao
lemon
kuai
keller
jimmie
jiao
gregor
ggggg
game
fuckyo
fuckoff1
friendly
fgtkmcby
evan
edgar
dolores
doitnow
dfcbkbq
criminal
coldbeer
chuckie
chimera
chan
ccccc
cccc
cards
capslock
cang
bullfrog
bonjovi
bobdylan
beth
berger
barker
balance
badman
bacchus
babylove
argentina
annabell
akira
646464
15975
1223
11221122
1022
02081986
02041988
02041987
02041982
02011988
zong
zhang
yummy
yeahbaby
vasilisa
temp123
tank
slim
skyler
silent
sergeant
reynolds
qazwsx1
PUSSY
pasword
nomore
noelle
nicol
newyork1
mullet
monarch
merlot
mantis
mancity
magazine
llllllll
kinder
kilroy
katherine
jayhawks
jackpot
ipswich
hack
fishing1
fight
ebony
dragon12
dog123
dipshit
crusher
chippy
canyon
bigbig
bamboo
athlon
alisha
abnormal
a11111
2469
12365
1011
09876543
02101984
02081985
02071984
02011980
010180
01011979
zhuo
zaraza
wg8e3wjf
triple
tototo
theater
teddy1
syzygy
susana
sonoma
slavik
shitface
sheba
sexyboy
screen
salasana
rufus
Richard
reds
rebecca1
pussyman
pringles
preacher
park
oceans
niang
momo
misfits
mikey1
media
manowar
mack
kayla
jump
jorda
hondas
hollow
here
heineken
halifax
gatorade
gabriell
ferrari1
fergie
female
eldorado
eagles1
cygnus
coolness
colton
ciccio
cheech
card
boom
blaze
bhbirf
BASEBALL
barton
655321
1818
14141414
123465
1224
1211
111111a
02021982
zhao
wings
warner
vsegda
tripod
tiao
thunderb
telephon
tdutybz
talon
speedo
specialk
shepherd
shadows
samsun
redbird
race
promise
persik
patience
paranoid
orient
monster1
missouri
mets
mazda
masamune
martin1
marker
march
manning
mamamama
licking
lesley
laurence
jezebel
jetski
hopeless
hooper
homeboy
hole
heynow
forum
foot
ffff
farscape
estrella
entropy
eastwood
dwight
dragonba
door
dododo
deutsch
crystal1
corleone
cobalt
chopin
chevrolet
cattle
carlitos
buttercu
butcher
bushido
buddyboy
blond
bingo1
becker
baron
augusta
alex123
998877
24242424
12365478
02061988
02031985
??????
zuan
yfcntymrf
wowwow
winston1
vfibyf
ventura
titten
tiburon
thoma
thelma
stroker
snooker
smokie
slippery
shui
shock
seadoo
sandwich
records
rang
puffy
piramida
orion1
napoli
nang
mouth
monkey12
millwall
mexican
meme
maxxxx
magician
leon
lala
lakota
jenkins
jackson5
insomnia
harvard
HARLEY
hardware
giorgio
ginger1
george1
gator1
fountain
fastball
exotic
elizaveta
dialog
davide
channel
castro
bunnies
borussia
asddsa
andromed
alfredo
alejandro
7007
69696
4417
3131
258852
1952
147741
1234asdf
02081982
02051982
zzzzzzz
zeng
zalupa
yong
windsurf
wildcard
weird
violin
universal
sunflower
suicide
strawberry
stepan
sphinx
someone
sassy1
romano
reddevil
raquel
rachel1
pornporn
polopolo
pluto
plasma
pinkfloyd
panther1
north
milo
maxime
matteo
malone
major
mail
lulu
ltybcrf
lena
lassie
july
jiggaman
jelly
islander
inspiron
hopeful
heng
hans
green123
gore
gooner
goirish
gadget
freeway
fergus
eeeee
diego
dickie
deep
danny1
cuan
cristian
conover
civic
Buster
bombers
bird33
bigfish
bigblue
bian
beng
beacon
barnes
astro
artemka
annika
anita
Andrew
747474
484848
464646
369258
225588
1z2x3c
1a2s3d4f
123456qwe
02061980
02031982
02011984
zaqxswcde
wrench
washington
violetta
tuning
trainer
tootie
store
spurs1
sporty
sowhat
sophi
smashing
sleeper
slave1
sexysexy
seeking
sam123
robotics
rjhjktdf
reckless
pulsar
project
placebo
paddle
oooo
nightmare
nanook
married
linda1
lilian
lazarus
kuang
knockers
killkill
keng
katherin
Jordan
jellybea
jayson
iloveme
hunt
hothot
homerj
hhhhhhhh
helene
haggis
goat
ganesh
gandalf1
fulham
force
dynasty
drakon
download
doomsday
dieter
devil666
desmond
darklord
daemon
dabears
cramps
cougars
clowns
classics
citizen
cigar
chrysler
carlito
candace
bruno1
browning
brodie
bolton
biao
barbados
aubrey
arlene
arcadia
amigo
abstr
9293709b13
737373
4444444
4242
369852
20202020
1qa2ws
1Pussy
1947
1234560
1112
1000000
02091983
02061987
01081989
zephyr
yugioh
yjdsqgfhjkm
woofer
wanted
volcom
verizon
tripper
toaster
tipper
tigger1
tartar
superb
stiffy
spock
soprano
snowboard
sexxxy
senator
scrabble
santafe
sally1
sahara
romero
rhjrjlbk
reload
ramsey
rainbow6
qazwsxedc123
poopy
pharmacy
obelix
normal
nevermind
mordor
mclaren
mariposa
mari
manuela
mallory
magelan
lovebug
lips
kokoko
jakejake
insanity
iceberg
hughes
hookup
hockey1
hamish
graphics
geoffrey
firewall
fandango
ernie
dottie
doofus
donovan
domain
digimon
darryl
darlene
dancing
county
chloe1
chantal
burrito
bummer
bubba69
brett
bounty
bigcat
bessie
basset
augustus
ashleigh
878787
3434
321321321
12051988
111qqq
1023
1013
05051987
02101989
02101987
02071987
02071980
02041985
titan
thong
sweetnes
stanislav
sssssss
snappy
shanti
shanna
shan
script
scorpio1
RuleZ
rochelle
rebel1
radiohea
q1q2q3
puss
pumpkins
puffin
onetwo
oatmeal
nutmeg
ninja1
nichole
mobydick
marine1
mang
lover1
longjohn
lindros
killjoy
kfhbcf
karen1
jingle
jacques
iverson3
istanbul
iiiiii
howdy
hover
hjccbz
highheel
happiness
guitar1
ghosts
georg
geneva
gamecock
fraser
faithful
dundee
dell
creature
creation
corey
concorde
cleo
cdtnbr
carmex2
budapest
bronze
brains
blue12
battery
attila
arrow
anthrax
aloha
383838
19711971
1948
134679852
123qw
123000
02091984
02091981
02091980
02061983
02041981
01011900
zhjckfd
zazaza
wingman
windmill
wifey
webhompas
watch
thisisit
tech
submit
stress
spongebo
silver1
senators
scott1
sausages
radical
qwer12
ppppp
pixies
pineapple
piazza
patrice
officer
nygiants
nikitos
nigga
nextel
moses
moonbeam
mihail
MICHAEL
meagan
marcello
maksimka
loveless
lottie
lollypop
laurent
latina
kris
kleopatra
kkkk
kirsty
katarina
kamila
jets
iiii
icehouse
hooligan
gertrude
fullmoon
fuckinside
fishin
everett
erin
dynamite
dupont
dogcat
dogboy
diane
corolla
citadel
buttfuck
bulldog1
broker
brittney
boozer
banger
aviation
almond
aaron1
78945
616161
426hemi
333777
22041987
2008
20022002
153624
1121
111111q
05051985
02081977
02071988
02051988
02051987
02041979
zander
wwww
webmaste
webber
taylor1
taxman
sucking
stylus
spoon
spiker
simmons
sergi
sairam
royal
ramrod
radiohead
popper
platypus
pippo
pepito
pavel
monkeybo
Michael1
master12
marty
kjkszpj
kidrock
judy
juanita
joshua1
jacobs
idunno
icu812
hubert
heritage
guyver
gunther
Good123654
ghost1
getout
gameboy
format
festival
evolution
epsilon
enrico
electro
dynamo
duckie
drive
dolphin1
ctrhtn
cthtuf
cobain
club
chilly
charter
celeb
cccccccc
caught
cascade
carnage
bunker
boxers
boxer
bombay
bigboss
bigben
beerman
baggio
asdf12
arrows
aptiva
a1a2a3
a12345678
626262
26061987
1616
15051981
08031986
060606
02061984
02061982
02051989
02051984
02031981
woodland
whiteout
visa
vanguard
towers
tiny
tigger2
temppass
super12
stop
stevens
softail
sheriff
robot
reddwarf
pussy123
praise
pistons
patric
partner
niceguy
morgan1
model
mars
mariana
manolo
mankind
lumber
krusty
kittens
kirby
june
johann
jared
imation
henry1
heat
gobears
forsaken
Football
fiction
ferguson
edison
earnhard
dwayne
dogger
diver
delight
dandan
dalshe
cross
cottage
coolcool
coach
camila
callum
busty
british
biology
beta
beardog
baldwin
alone
albany
airwolf
9876543
987123
7894561230
786786
535353
21031987
1949
13041988
1234qw
123456l
1215
111000
11051987
10011986
06061986
02091985
02021981
02021979
01031988
vjcrdf
uranus
tiger123
summer99
state
starstar
squeeze
spikes
snowflak
slamdunk
sinned
shocker
season
santa
sanity
salome
saiyan
renata
redrose
queenie
puppet
popo
playboy1
pecker
paulie
oliver1
ohshit
norwich
news
namaste
muscles
mortal
michael2
mephisto
mandy1
magnet
longbow
llll
living
lithium
komodo
kkkkkkkk
kjrjvjnbd
killer12
kellie
julie1
jarvis
iloveyou2
holidays
highway
havana
harvest
harrypotter
gorgeous
giraffe
garion
frost
fishman
erika
earth
dusty1
dudedude
demo
deer
concord
colnago
clit
choice
chillin
bumper
blam
bitter
bdsm
basebal
barron
baker
arturo
annie1
andersen
amerika
aladin
abbott
81fukkc
5678
135791
1002
02101986
02081983
02041989
02011989
01011978
zzzxxx
zxcvbnm123
yyyyyy
yuan
yolanda
winners
welcom
volkswag
vera
ursula
ultra
toffee
toejam
theatre
switch
superma
Stone55
solitude
sissy
sharp
scoobydoo
romans
roadster
punk
presiden
pool6123
playstat
pipeline
pinball
peepee
paulina
ozzy
nutter
nights
niceass
mypassword
mydick
milan
medic
mazdarx7
mason1
marlon
mama123
lemonade
krasotka
koroleva
karin
jennife
itsme
isaac
irishman
hookem
hewlett
hawaii50
habibi
guitars
grande
glacier
gagging
gabriel1
freefree
francesco
food
flyfish
fabric
edward1
dolly
destin
delilah
defense
codered
cobras
climber
cindy1
christma
chipmunk
chef
brigitte
bowwow
bigblock
bergkamp
bearcats
baba
altima
74108520
45M2DO5BS
30051985
258258
24061986
22021989
21011989
20061988
1z2x3c4v
14061991
13041987
123456m
12021988
11081989
03041991
02071981
02031979
02021976
01061990
01011960
yvette
yankees2
wireless
werder
wasted
visual
trust
tiffany1
stratus
steffi
stasik
starligh
sigma
rubble
ROBERT
register
reflex
redfox
record
qwerty7
premium
prayer
players
pallmall
nurses
nikki1
nascar24
mudvayne
moritz
moreno
moondog
monsters
micro
mickey1
mckenzie
mazda626
manila
madcat
louie
loud
krypton
kitchen
kisskiss
kate
jubilee
impact
Horny
hellboy
groups
goten
gonzalez
gilles
gidget
gene
gbhfvblf
freebird
federal
fantasia
dogbert
deeper
dayton
comanche
cocker
choochoo
chambers
borabora
bmw325
blast
ballin
asdfgh01
alissa
alessandro
airport
abrakadabra
7777777777
635241
494949
420000
23456789
23041987
19701970
1951
18011987
172839
1235
123456789s
1125
1102
1031
07071987
02091989
02071989
02071983
02021973
02011981
01121986
01071986
0101
zodiac
yogibear
word
water1
wasabi
wapbbs
wanderer
vintage
viktoriya
varvara
upyours
undertak
underground
undead
umpire
tropical
tiger2
threesom
there
sunfire
sparky1
snoopy1
smart
slowhand
sheridan
sensei
savanna
rudy
redsox1
ramirez
prowler
postman
porno1
pocket
pelican
nfytxrf
nation
mykids
mygirl
moskva
mike123
Master1
marianna
maggie1
maggi
live
landon
lamer
kissmyass
keenan
just4fun
julien
juicy
JORDAN
jimjim
hornets
hammond
hallie
glenn
ghjcnjgfhjkm
gasman
FOOTBALL
flanker
fishhead
firefire
fidelio
fatty
excalibur
enterme
emilia
ellie
eeee
diving
dindom
descent
daniele
dallas1
customer
contest
compass
comfort
comedy
cocksuck
close
clay
chriss
chiara
cameron1
calgary
cabron
bologna
berkeley
andyod22
alexey
achtung
45678
3636
28041987
25081988
24011985
20111986
19651965
1941
19101987
19061987
1812
14111986
13031987
123ewq
123456123
12121990
112112
10071987
10031988
02101988
02081980
02021990
01091987
01041985
01011995
zebra
zanzibar
waffle
training
teenage
sweetness
sutton
sushi
suckers
spam
south
sneaky
sisters
shinobi
shibby
sexy1
rockies
presley
president
pizza1
piggy
password12
olesya
nitro
motion
milk
medion
markiz
lovelife
longdong
lenny
larry1
kirk
johndeer
jefferso
james123
jackjack
ijrjkfl
hotone
heroes
gypsy
foxy
fishbone
fischer
fenway
eddie1
eastern
easter
drummer1
Dragon1
Daniel
coventry
corndog
compton
chilli
chase1
catwoman
booster
avenue
armada
987321
818181
606060
5454
28021992
25800852
22011988
19971997
1776
17051988
14021985
13061986
12121985
11061985
10101986
10051987
10011990
09051945
08121986
04041991
03041986
02101983
02101981
02031989
02031980
01121988
wwwwwww
virgil
troy
torpedo
toilet
tatarin
survivor
sundevil
stubby
straight
spotty
slater
skip
sheba1
runaway
revolver
qwerty11
qweasd123
parol
paradigm
older
nudes
nonenone
moore
mildred
michaels
lowell
knock
klaste
junkie
jimbo1
hotties
hollie
gryphon
gravity
grandpa
ghjuhfvvf
frogman
freesex
foreve
felix1
fairlane
everlast
ethan
eggman
easton
denmark
deadly
cyborg
create
corinne
cisco
chick
chestnut
bruiser
broncos1
bobdole
azazaz
antelope
anastasiya
456456456
415263
30041986
29071983
29051989
29011985
28021990
28011987
27061988
25121987
25031987
24680
22021986
21031990
20091991
20031987
196969
19681968
1946
17061988
16051989
16051987
1210
11051990
100500
08051990
05051989
04041988
02051980
02051976
02041980
02031977
02011983
01061986
01041988
01011994
0000007
zxcasdqwe123
washburn
vfitymrf
troll
tranny
tonight
thecure
studman
spikey
soccer12
soccer10
smirnoff
slick1
skyhawk
skinner
shrimp
shakira
sekret
seagull
score
sasha_007
rrrrrrrr
ross
rollins
reptile
razor
qwert12345
pumpkin1
porsche1
playa
notused
noname123
newcastle
never
nana
MUSTANG
minerva
megan1
marseille
marjorie
mamamia
malachi
lilith
letmei
lane
lambda
krissy
kojak
kimball
keepout
karachi
kalina
justus
joel
joe123
jerry1
irinka
hurricane
honolulu
holycow
hitachi
highbury
hhhhh
hannah1
hall
guess
glass
gilligan
giggles
flores
fabie
eeeeeeee
dungeon
drifter
dogface
dimas
dentist
death666
costello
castor
bronson
brain
bolitas
boating
benben
baritone
bailey1
badgers
austin1
astra
asimov
asdqwe
armand
anthon
amorcit
797979
4200
31011987
3030
30031988
3000gt
224466
22071986
21101986
21051991
20091988
2009
20051988
19661966
18091985
18061990
15101986
15051990
15011987
13121985
12qw12qw
1234123
1204
12031987
12031985
11121986
1025
1003
08081988
08031985
03031986
02101979
02071979
02071978
02051985
02051978
02051973
02041975
02041974
02031988
02011982
01031989
01011974
zoloto
zippo
wwwwwwww
w_pass
wildwood
wildbill
transit
superior
styles
stryker
string
stream
stefanie
slugger
skillet
sidekick
show
shawna
sf49ers
Salsero
rosario
remingto
redeye
redbaron
question
quasar
ppppppp
popova
physics
papers
palermo
options
mothers
moonligh
mischief
ministry
minemine
messiah
mentor
megane
mazda6
marti
marble
leroy
laura1
lantern
Kordell1
koko
knuckles
khan
kerouac
kelvin
jorge
joebob
jewel
iforget
Hunter
house1
horace
hilary
grand
gordo
glock
georgie
George
fuckhead
freefall
films
fantomas
extra
ellen
elcamino
doors
diaper
datsun
coldplay
clippers
chandra
carpente
carman
capricorn
calimero
boytoy
boiler
bluesman
bluebell
bitchy
bigpimp
bigbang
biatch
Baseball
audi
astral
armstron
angelika
angel123
abcabc
999666
868686
3x7PxR
357357
30041987
27081990
26031988
258369
25091987
25041988
24111989
23021986
22041988
22031984
21051988
17011987
16121987
15021985
142857
14021986
13021990
12345qw
123456ru
1124
10101990
10041986
07091990
02051981
01031985
01021990
******
zildjian
yfnfkb
yeah
WP2003WP
vitamin
villa
valentine
trinitro
torino
tigge
thewho
thethe
tbone
swinging
sonia
sonata
smoke1
sluggo
sleep
simba1
shamus
sexxy
sevens
rober
rfvfcenhf
redhat
quentin
qazws
pufunga7782
priest
pizdec
pigeon
pebble
palmtree
oxygen
nostromo
nikolai
mmmmmmm
mahler
lorena
lopez
lineage
korova
kokomo
kinky
kimmie
kieran
jsbach
johngalt
isabell
impreza
iloveyou1
iiiii
huge
fuck123
franc
foxylady
fishfish
fearless
evil
entry
enforcer
emilie
duffman
ducks
dominik
david123
cutiepie
coolcat
cookie1
conway
citroen
chinese
cheshire
cherries
chapman
changes
carver
capricor
book
blueball
blowfish
benoit
Beast1
aramis
anchor
741963
654654
57chevy
5252
357159
345678
31031988
25091990
25011990
24111987
23031990
22061988
21011991
21011988
1942
19283746
19031985
19011989
18091986
17111985
16051988
15071987
145236
14081985
132456
13071984
1231
12081985
1201
11021985
10071988
09021988
05061990
02051972
02041978
02031983
01091985
01031984
010191
01012009
yamahar1
wormix
whistler
wertyu
warez
vjqgfhjkm
versace
universa
taco
sugar1
strawber
stacie
sprinter
spencer1
sonyfuck
smokey1
slimshady
skibum
series
screamer
sales
roswell
roses
report
rampage
qwedsa
q11111
program
Princess
petrova
patrol
papito
papillon
paco
oooooooo
mother1
mick
Maverick
marcius2
magneto
macman
luck
lalakers
lakeside
krolik
kings
kille
kernel
kent
junior1
jules
jermaine
jaguars
honeybee
hola
highlander
helper
hejsan
hate
hardone
gustavo
grinch
gratis
goth
glamour
ghbywtccf
ghbdtn123
elefant
earthlink
draven
dmitriy
dkflbr
dimples
cygnusx1
cold
cococo
clyde
cleopatr
choke
chelse
cecile
casper1
carnival
cardiff
buddy123
bruce1
bootys
bookie
birddog
bigbob
bestbuy
assasin
arkansas
anastasi
alberta
addict
acmilan
7896321
30081984
258963
25101988
23051985
23041986
23021989
22121987
22091988
22071987
22021988
2006
20052005
19051987
15041988
15011985
14021990
14011986
13051987
13011988
13011987
12345s
12061988
12041988
12041986
11111q
11071988
11031988
10081989
08081986
07071990
07071977
05071984
04041983
03021986
02091988
02081976
02051977
02031978
01071987
01041987
01011976
zack
zachary1
yoyoma
wrestler
weston
wealth
wallet
vjkjrj
vendetta
twiggy
twelve
turnip
tribal
tommie
tkbpfdtnf
thecrow
test12
terminat
telephone
synergy
style
spud
smackdow
slammer
sexgod
seabee
schalke
sanford
sandrine
salope
rusty2
right
repair
referee
ratman
radar
qwert40
qwe123qwe
prozac
portal
polish
Patrick
passes
otis
oreo
option
opendoor
nuclear
navy
nautilus
nancy1
mustang6
murzik
mopar
monty1
Misfit99
mental
medved
marseill
magpies
magellan
limited
Letmein1
lemmein
leedsutd
larissa
kikiki
jumbo
jonny
jamess
jackass1
install
hounddog
holes
hetfield
heidi1
harlem
gymnast
gtnhjdbx
godlike
glow
gideon
ghhh47hj7649
flip
flame
fkbyjxrf
fenris
excite
espresso
ernesto
dontknow
dogpound
dinner
diablo2
dejavu
conan
complete
cole
chocha
chips
chevys
cayman
breanna
borders
blue32
blanco
bismillah
biker
bennie
benito
azazel
ashle
arianna
argentin
antonia
alanis
advent
acura
858585
4040
333444
30041985
29071985
29061990
27071987
27061985
27041990
26031990
24031988
23051990
2211
22011986
21061986
20121989
20092009
20091986
20081991
20041988
20041986
1qwerty
19671967
1950
19121989
19061990
18101987
18051988
18041986
18021984
17101986
17061989
17041991
16021990
15071988
15071986
14101987
135798642
13061987
1234zxcv
12321
1214
12071989
1129
11121985
11061991
10121987
101101
10101985
10031987
100200
09041987
09031988
06041988
05071988
03081989
02071985
02071975
0123456
01051989
01041992
01041990
zarina
woodie
whiteboy
white1
waterboy
volkov
vlad
virus
vikings1
viewsoni
vbkfirf
trans
terefon
swedish
squeak
spanner
spanker
sixpack
seymour
sexxx
serpent
samira
roma
rogue
robocop
robins
real
Qwerty1
qazxcv
q2w3e4
punch
pinky1
perry
peppe
penguin1
Password123
pain
optimist
onion
noway
nomad
nine
morton
moonshin
money12
modern
mcdonald
mario1
maple
loveya
love1
loretta
lookout
loki
lllll
llamas
limewire
konstantin
k.lvbkf
keisha
jones1
jonathon
johndoe
johncena
john123
janelle
intercourse
hugo
hopkins
harddick
glasgow
gladiato
gambler
galant
gagged
fortress
factory
expert
emperor
eight
django
dinara
devo
daniels
crusty
cowgirl
clutch
clarissa
cevthrb
ccccccc
capetown
candy1
camero
camaross
callisto
butters
bigpoppa
bigones
bigdawg
best
beater
asgard
angelus
amigos
amand
alexandre
9999999999
8989
875421
30011985
29051985
2626
26061985
25111987
25071990
22081986
22061989
21061985
20082008
20021988
1a2s3d
19981998
16051985
15111988
15051985
15021990
147896
14041988
123567
12345qwerty
12121988
12051990
12051986
12041990
11091989
11051986
11051984
1008
10061986
0815
06081987
06021987
04041990
02081981
02061977
02041977
02031975
01121987
01061988
01031986
01021989
01021988
wolfpac
wert
vienna
venture
vehpbr
vampir
university
tuna
trucking
trip
trees
transfer
tower
tophat
tomahawk
timosha
timeout
tenchi
tabasco
sunny1
suckmydick
suburban
stratfor
steaua
spiral
simsim
shadow12
screw
schmidt
rough
rockie
reilly
reggae
quebec
private1
printing
pentagon
pearson
peachy
notebook
noname
nokian73
myrtle
munch
moron
matthias
mariya
marijuan
mandrake
mamacita
malice
links
lekker
lback
larkin
ksusha
kkkkk
kestrel
kayleigh
inter
insight
hotgirls
hoops
hellokitty
hallo123
gotmilk
googoo
funstuff
fredrick
firefigh
finland
fanny
eggplant
eating
dogwood
doggies
dfktynby
derparol
data
damon
cvthnm
cuervo
coming
clock
cleopatra
clarke
cheddar
cbr900rr
carroll
canucks
buste
bukkake
boyboy
bowman
bimbo
bighead
bball
barselona
aspen
asdqwe123
around
aries
americ
almighty
adgjmp
addison
absolutely
aaasss
4ever
357951
29061989
28051987
27081986
25061985
25011986
24091986
24061988
24031990
21081987
21041992
20031991
2001112
19061985
18111987
18021988
17071989
17031987
16051990
15021986
14031988
14021987
14011989
1220
1205
120120
111999
111777
1115
1114
11011990
1027
10011983
09021989
07051990
06051986
05091988
05081988
04061986
04041985
03041980
02101976
02071976
02061976
02011975
01031983
zasada
wyoming
wendy1
washingt
warrior1
vickie
vader1
uuuuuu
username
tupac
Trustno1
tinkerbe
suckdick
streets
strap
storm1
stinker
sterva
southpaw
solaris
sloppy
sexylady
sandie
roofer
rocknrol
rico
rfhnjirf
QWERTY
qqqqq1
punker
progress
platon
Phoenix
Phoeni
peeper
pastor
paolo
page
obsidian
nirvana1
nineinch
nbvjatq
navigator
native
money123
modelsne
minimoni
millenium
max333
maveric
matthe
marriage
marquis
markie
marines1
marijuana
margie
little1
lfybbk
klizma
kimkim
kfgjxrf
joshu
jktxrf
jennaj
irishka
irene
ilove
hunte
htubcnhfwbz
hottest
heinrich
happy2
hanson
handball
greedy
goodie
golfer1
gocubs
gerrard
gabber
fktyrf
facebook
eskimo
elway7
dylan1
dominion
domingo
dogbone
default
darkangel
cumslut
cumcum
cricket1
coral
coors
chris123
charon
challeng
canuck
call
calibra
buceta
bubba123
bricks
bozo
blues1
bluejays
berry
beech
awful
april1
antonina
antares
another
andrea1
amore
alena
aileen
a1234
996633
556677
5329
5201314
3006
28051986
28021985
27031989
26021987
25101989
25061986
25041985
25011985
24061987
23021985
23011985
223322
22121986
22121983
22081983
22071989
22061987
22061941
22041986
22021985
21021985
2007
20031988
1qaz
199999
19101990
19071988
19071986
18061985
18051990
17071985
16111990
16061986
16011989
15081991
15051987
14071987
13031986
123qwer
1235789
123459
1227
1226
12101988
12081984
12071987
1200
11121987
11081987
11071985
11011991
1101
1004
08071987
08061987
05061986
04061991
03111987
03071987
02091976
02081979
02041976
02031973
02021991
02021980
02021971
zouzou
yaya
wxcvbn
wolfen
wives
wingnut
whatwhat
Welcome1
wanking
VQsaBLPzLa
truth
tracer
trace
theforce
terrell
sylveste
susanna
stephane
stephan
spoons
spence
sixty
sheepdog
services
sawyer
sandr
saigon
rudolf
rodeo
roadrunner
rimmer
ricard
republic
redskin
Ranger
ranch
proton
post
pigpen
peggy
paris1
paramedi
ou8123
nevets
nazgul
mizzou
midnite
metroid
Matthew
masterbate
margarit
loser1
lolol
lloyd
kronos
kiteboy
junk
joyce
jomama
joemama
ilikepie
hung
homework
hattrick
hardball
guido
goodgirl
globus
funky
friendster
flipflop
flicks
fender1
falcon1
f00tball
evolutio
dukeduke
disco
devon
derf
decker
davies
cucumber
cnfybckfd
clifton
chiquita
castillo
cars
capecod
cafc91
brown1
brand
bomb
boater
bledsoe
bigdicks
bbbbbbb
barley
barfly
ballet
azzer
azert
asians
angelic
ambers
alcohol
6996
5424
393939
31121990
30121987
29121987
29111989
29081990
29081985
29051990
27272727
27091985
27031987
26031987
26031984
24051990
23061990
22061990
22041985
22031991
22021990
21111985
21041985
20021986
19071990
19051986
19011987
17171717
17061986
17041987
16101987
16031990
159357a
15091987
15081988
15071985
15011986
14101988
14071988
14051990
14021983
132465
13111990
12121987
12121982
12061986
12011989
11111987
11081990
10111986
10031991
09090909
08051987
08041986
05051990
04081987
04051988
03061987
03031993
03031988
02101980
02101977
02091977
02091975
02061979
02051975
01081990
01061987
01011971
wiseguy
weed420
tosser
toriamos
toolbox
toocool
tomas
thedon
tender
taekwondo
starwar
start1
sprout
sonyericsson
slimshad
skateboard
shonuf
shoes
sheep
shag
ring
riccardo
rfntymrf
redcar
qwe321
qqqwww
proview
prospect
persona
penetration
peaches1
peace1
olympus
oberon
nokia6233
nightwish
munich
morales
mone
mohawk
merlin1
Mercedes
mega
maxwell1
mash4077
marcelo
mann
mad
macbeth
LOVE
loren
longer
lobo
leeds
lakewood
kurt
krokodil
kolbasa
kerstin
jenifer
hott
hello12
hairball
gthcbr
grin
grandam
gotribe
ghbrjk
ggggggg
FUCKYOU
fuck69
footjob
flasher
females
fellow
explore
evangelion
egghead
dudeman
doubled
doris
dolemite
dirty1
devin
delmar
delfin
David
daddyo
cromwell
cowboy1
closer
cheeky
ceasar
cassandr
camden
cabernet
burns
bugs
budweiser
boxcar
boulder
biggun
beloved
belmont
beezer
beaker
Batman
bastards
bahamut
azertyui
awnyce
auggie
aolsucks
allegro
963963
852852
515000
45454545
31011990
29011987
28071986
28021986
27051987
27011988
26051988
26041991
26041986
25011993
24121986
24061992
24021991
24011990
23051986
23021988
23011990
21121986
21111990
21071989
20071986
20051985
20011989
1943
19111987
19091988
18041990
18021986
18011986
17101987
17091987
17021985
17011990
16061985
1598753
15051986
14881488
14121989
14081988
14071986
13111984
122112
12121989
12101985
12051985
111213
11071986
1103
11011987
10293847
101112
10081985
10061987
10041983
0911
07091982
07081986
06061987
06041987
06031983
04091986
03071986
03051987
03051986
03031990
03011987
02101978
02091973
02081974
02071977
02071971
0192837465
01051988
01051986
01011973
?????
zxcv123
zxasqw
yyyy
yessir
wordup
wizards
werty
watford
Victoria
vauxhall
vancouve
tuscl
trailer
touching
tokiohotel
suslik
supernov
steffen
spider1
speakers
spartan1
sofia
signal
sigmachi
shen
sheeba
sexo
sambo
salami
roger1
rocknroll
rockin
road
reserve
rated
rainyday
q123456789
purpl
puppydog
power123
poiuytre
pointer
pimping
phialpha
penthous
pavement
outside
odyssey
nthvbyfnjh
norbert
nnnnnnnn
mutant
Mustang
mulligan
mississippi
mingus
Merlin
magic32
lonesome
liliana
lighting
lara
ksenia
koolaid
kolokol
klondike
kkkkkkk
kiwi
kazantip
junio
jewish
jajaja
jaime
jaeger
irving
ironmaiden
iriska
homemade
herewego
helmut
hatred
harald
gonzales
goldfing
gohome
gerbil
genesis1
fyfnjkbq
freee
forgetit
foolish
flamengo
finally
favorite6
exchange
enternow
emilio
eeeeeee
dougie
dodgers1
deniro
delaware
deaths
darkange
commande
comein
cement
catcher
cashmone
burn
buffet
breaker
brandy1
bordeaux
books
bongo
blue99
blaine
birgit
billabon
benessere
banan
awesome1
asdffdsa
archange
annmarie
ambrosia
ambrose
alleycat
all4one
alchemy
aceace
aaaaaaaaaa
777999
43214321
369258147
31121988
31121987
30061987
30011986
2fast4u
29041985
28121984
28061986
28041992
28031982
27111985
27021991
26111985
26101986
26091986
26031986
25021988
24111990
24101986
24071987
24011987
23051991
23051987
23031987
222777
22071983
22051986
21101989
21071987
21051986
20081986
20061986
20031986
20021985
20011988
19641964
19111986
19101986
19021990
18051987
18031991
18021987
16111982
16011987
15111984
15091988
15061988
15031988
15021983
14021989
14011988
14011987
12348765
12345qaz
1234566
12111990
12091988
12051989
12051987
12031988
12021985
12011985
11111986
11091984
1109
11071989
1016
10071985
10061984
10041990
10031989
10011988
06071983
05021988
03041987
02091982
02091971
02061974
02051990
02051979
02011990
01051990
010390
01021985
youtube
yasmin
woodstoc
wonderful
wildone
widget
whiplash
ukraine
tyson1
twinkie
trouble1
treetop
tigers1
their
testing1
tarpon
tantra
summer69
stickman
stafford
spooge
spliff
speedway
somerset
smoothie
siobhan
shuttle
shodan
SHADOW
selina
segblue2
sebring
scheisse
Samantha
rrrr
roll
riders
revolution
redbone
reason
rasmus
randy1
rainbows
pumper
pornking
point
ploppy
pimpdadd
payday
pasadena
p0o9i8u7
opennow
nittany
newark
navyseal
nautica
monic
mikael
metall
Marlboro
manfred
macleod
luna
luca
longhair
lokiloki
lkjhgfds
lefty
lakers1
kittys
killa
kenobi
karine
kamasutra
juliana
joseph1
jenjen
jello
interne
houdini
gsxr1000
grass
gotham
goodday
gianni
getting
gannibal
gamma
flower2
fishon
Fabie
evgeniy
drums
dingo
daylight
dabomb
cornwall
cocksucker
climax
catnip
carebear
camber
butkus
bootsy
blue42
auto
austin31
auditt
ariel
alice1
algebra
advance
adrenalin
888999
789654123
777333
5Wr2i7H8
4567
3ip76k2
32167
31031987
30111987
30071986
30061983
30051989
30041991
28071987
28051990
28051985
27041985
26071987
26061986
26051986
25121985
25051985
24081988
24041988
24031987
24021988
23skidoo
23121986
23091987
23071985
23061992
22111985
22091986
22081991
22071990
22061985
21081985
21071992
21021987
20101988
20061984
20051989
20041990
1Dragon
19091990
19031987
18121984
18081988
18061991
18041991
18011988
17061991
17021987
16031988
16021987
15091989
15081990
15071983
15041987
14091990
14081990
14041992
14041987
14031989
13081985
13021987
123qwert
12345qwer
12345abc
123456t
123456789m
1212121212
12081983
12021991
111112
11101986
11081988
11061989
11041991
11011989
1018
1015
10121986
10121985
10101989
10041991
09091986
09081988
09051986
08071988
08011986
07101987
07071985
0660
06061985
06011988
05031991
05021987
04061984
04051985
02101973
02061981
02061972
02041973
02011979
01101987
01051985
01021987
workout
wonderboy
winter1
wetter
werdna
vvvv
voyager1
vagabond
trustme
toonarmy
timtim
Tigger
thrasher
terra
swoosh
supra
stigmata
stayout
status
square
sperma
smackdown
sixty9
sexybabe
sergbest
senna
scuba1
scrapper
samoht
sammy123
salem
rugger
royalty
rivera
ringo
restart
reginald
readers
raleigh
rainbow1
rage
prosper
pitch
pictures
petunia
peterbil
perfect1
patrici
pantera1
pancake
p4ssw0rd
outback
norris
normandy
nevermore
needles
nathan1
nataly
narnia
musical
mooney
michal
maxdog
MASTER
madmad
m123456
lumina
luckyone
luciano
linkin
lillie
leigh
kirkland
kahlua
junkmail
Joshua
josephin
Jordan23
johnson1
jocelyn
jeannie
javelin
inlove
honor
holein1
harbor
grisha
gina
gatit
futurama
firenze
fireblad
fellatio
esquire
errors
emmett
elvisp
drum
driller
dragonfl
dragon69
dingle
davinci
crackers
corwin
compaq1
collie
christa
checker
cartoons
buttercup
bungle
budgie
boomer1
body
blue1234
biit
bigguns
barry1
audio
atticus
atlas
Anthony
angus1
Anai
alisa
alex12
aikman
abacab
951357
7894
4711
321678
31101987
31051985
30121986
30091989
30031992
30031986
30011987
29061988
29061985
29031988
28061988
27061983
27031986
27021990
26101987
26071989
26071986
25081986
25061987
25051987
25041991
24101989
24071991
23111987
23091986
23051983
23031986
2222222222
22121989
22071991
22051991
22011985
21121985
21031985
20121988
20121986
20061990
20051987
1q2q3q
1944
19091983
19061992
1905
19021991
18121987
18121983
18111986
16121986
16091987
16071991
16071987
15111989
15031990
14041986
13121983
13101987
13091984
13071990
1245
12345m
1234568
123456789qwe
1234567899
1234561
1228
12211221
12121991
12121986
12101990
12101984
12091991
1209
12081988
12071990
12071988
115599
11111a
11041990
1028
10081990
10081983
10071990
10061989
10011992
09111987
09081985
08121987
08111984
08101986
08051989
07091988
07081987
07071988
07071984
07071982
07051987
06031992
05111986
05051991
05031990
05011987
04111988
04061987
04041987
040404
02081973
02061978
02031991
02031990
02011976
01071984
01041980
01021992
zaqwsxcde
yyyyyyyy
worthy
woowoo
wind
William
warhamme
walton
vodka
venom
velocity
treble
tralala
tigercat
tarakan
sunlight
streaming
starr
sonysony
smart1
skylark
sites
shower
sheldon
seneca
sedona
scamper
sand
sabrina1
romantic
rockwell
rabbits
q1234567
puzzle
protect
poker1
plato
plastics
pinnacle
peppers
pathetic
patch
pancakes
ottawa
ooooo
offshore
octopus
nounours
nokia1
neville
ncc74656
natasha1
nastia
mynameis
motor
motocros
middle
met2002
meow
meliss
medina
meadow
matty
masterp
manga
lucia
loose
linden
lhfrjy
letsdoit
leopold
lawson
larson
laddie
ladder
kristian
kittie
jughead
joecool
jimmys
iklo
honeys
hoffman
hiking
hello2
heels
harrier
hansol
haley
granada
gofast
fyutkjxtr
frogs
francisc
four
fields
farm
faith1
fabio
dreamcas
dragster
doggy1
dirt
dicky
destiny1
deputy
delpiero
dbnfkbr
dakota1
daisydog
cyprus
cutie
cupoi
colonial
colin
clovis
cirrus
chewy
chessie
chelle
caster
cannibal
candyass
camping
cable
bynthytn
byebye
buzzer
burnout
burner
bumbum
bumble
briggs
brest
boyz
bowtie
bootsie
bmwbmw
blanche
blanca
bigbooty
baylor
base
azertyuiop
austria
asd222
armando
ariane
amstel
amethyst
airman
afrika
adelina
acidburn
7734
741741
66613666
44332211
31071990
31051993
30051987
30011990
29091987
29061986
29011982
2828
28101986
28081990
28081986
28011988
27111989
27031992
27021992
26081986
25081985
25031991
25031983
24121987
24091991
23111989
23091989
23091985
23061989
22091991
22071985
22071984
22061984
22051989
22051987
22031986
22011992
21061988
21031984
20071988
20061983
20041985
1qazzaq1
1qazxsw23edc
19991999
19061991
18101985
18051989
18031988
18021992
18011985
17051990
17051989
17051987
17021989
16091988
16081986
16061988
16061987
15121987
15091985
15081986
15061985
15011983
14101986
1357911
13071987
13061985
13021985
123456qqq
123456789d
1234509876
12131213
12111991
12111985
12081990
12081987
12071991
1207
120689
1120
11071987
11051988
1104
11031983
10091984
10071989
10071986
10061985
10051990
10041987
10031993
10031990
09091988
09051987
09041986
08081990
08081989
08021990
07101984
07071989
07041987
07031989
07021991
06061981
06021986
05121990
05061988
05031987
04071988
04071986
04041986
03101991
03091983
03051988
03041983
03031992
02081970
02061971
02051970
02041972
02031974
02021978
0202
02011977
01121990
01091992
01081992
01081985
01011972
007bond
zapper
vipergts
vfntvfnbrf
vfndtq
tujhrf
tripleh
track
THOMAS
thierry
thebear
systems
supernova
stone1
stephen1
stang
stan
spot
sparkles
soul
snowbird
snicker
slonik
slayer1
sixsix
singapor
shauna
scissors
savior
samm
rumble
rrrrr
robin1
renato
redstar
raphael
q1w2e3r
pressure
poptart
playball
pizzaman
pinetree
phyllis
pathfind
papamama
panter
pandas
panda1
pajero
pacino
orchard
olive
nightmar
nico
Mustang1
mooses
montrose
montecar
montag
melrose
masterbating
maserati
marshal
makaka
macmac
mackie
lockdown
liverpool1
link
lemans
leinad
lagnaf
kingking
killer123
kaboom
jeter2
jeremy1
jeepster
jabber
itisme
italy
ilovegod
idefix
howell
hores
HIZIAD
hewitt
hellsing
Heather
gonzo1
golden1
GEORGE
generic
gatsby
fujitsu
frodo1
frederik
forlife
fitter
feelgood
fallon
escalade
enters
emil
eleonora
earl
dummy
donner
dominiqu
dnsadm
dickens
deville
delldell
daughter
contract
contra
conquest
compact
christi
chill
chavez
chaos1
chains
casio
carrots
building
buffalo1
brennan
boubou
bonner
blubber
blacklab
behappy
barbar
bambi
babycake
aprilia
ANDREW
allgood
alive
adriano
808080
7777777a
777666
31121986
31121985
31051991
31051987
30121988
30121985
30101988
30061988
29041988
27091991
26121989
26061989
26031991
25111991
25031984
25021986
24121989
24121988
24101990
24101984
24071992
24051989
24041986
23091991
23061987
23041988
23021992
23021983
22111988
22091990
22091984
22051988
21111986
21101988
21101987
21091989
21051990
21021989
20101987
20071984
20051983
20031990
20031985
20011983
1passwor
19111985
19081987
19051983
19041985
18121990
18121985
18121812
18091987
17121985
17111987
17071987
17071986
17061987
17041986
17041985
16121991
16101986
16041988
16041985
16031986
16021988
16011986
15121983
15101991
15061984
15011988
14091987
14061988
14051983
13101992
13101988
13101982
13071989
13071985
13061991
13051990
13031989
123456n
1234567890-
123450
1216
12101989
1208
12071984
12061987
12041991
12031990
12021984
1117
11091986
11091985
11081986
1026
10101988
10101980
10091986
10091985
10081987
10051988
10021987
10021986
09041985
09031987
08041985
08031987
07061988
07041989
07021980
06011982
05121988
05061989
05051986
04031991
03071985
03061986
03061985
03031987
03031984
03011991
02111987
02061990
02011971
01091988
01071990
01061983
01051980
01022010
000777
000123
young1
yamato
winona
winner1
whatthe
weiner
weekend
volleyba
volcano
virginie
videos
vegitto
uptown
tycoon
treefrog
trauma
town
toast
titts
these
therock1
tetsuo
tennesse
tanya1
success1
stupid1
stockton
stock
stellar
springs
spoiled
someday
skinhead
sick
shyshy
shojou
shampoo
sexman
sex69
saskia
Sandra
s123456
russel
rudeboy
rollin
ridge
ride
rfgecnf
qwqwqwqw
pushkin
puck
probes
pong
playmate
planes
piercing
phat
pearls
password9
painting
nineball
navajo
napalm
mohammad
miller1
matchbox
marie1
mariam
mamas
malish
maison
logger
locks
lister
lfitymrf
legos
lander
laetitia
kenken
kane
johnny5
jjjjjjj
jesper
jerk
jellybean
jeeper
jakarta
instant
ilikeit
icecube
hotass
hogtied
having
harman
hanuman
hair
hacking
gumby
gramma
GOLF
goldeneye
gladys
furball
fuckme2
franks
fick
fduecn
farmboy
eunice
erection
entrance
elisabet
elements
eclipse1
eatmenow
duane
dooley
dome
doktor
dimitri
dental
delaney
Dallas
cyrano
cubs
crappy
cloudy
clips
cliff
clemente
charlie2
cassandra
cashmoney
camil
burning
buckley
booyah
boobear
bonanza
bobmarley
bleach
bedford
bathing
baracuda
antony
ananas
alinka
alcatraz
aisan
5000
49ers
334455
31051982
30051988
30051986
29111988
29051992
29041989
29031990
28121989
28071985
28021983
27111990
27071988
26071984
26061991
26021992
26011990
26011986
25091991
25091989
25081989
25071987
25071985
25071983
25051988
25051980
25041987
25021985
24101991
24101988
24071990
24061985
24041985
24041984
23456
23111986
23101987
23041991
23031983
22071992
22071988
21121989
21111989
21111983
21101983
21041991
21041987
21031986
21021990
21021988
20081990
20061991
20061987
20032003
20031992
1qw23er4
1q1q1q1q
1Master
19121988
19081986
19071989
19041986
18111983
18071990
18071989
18071986
18031986
17121987
17091985
17071990
17051983
16091990
15081989
15071990
15051992
15051989
15031991
15011990
14031986
13091988
13091987
13091986
13081986
13071982
13051986
13041989
13021991
1269
123890
1234rewq
12345r
1231234
12111984
12091986
12081993
12071992
1206
12021990
111555
11111991
11091990
11061987
11061986
11061984
11041985
11031986
1030
1029
1014
101091m
10041984
10031980
10011980
09051984
08071985
07081984
07041988
06101989
06061988
06041984
05091987
05081992
05081986
05071985
05041985
04111991
04071987
04021990
03091988
03061988
03041989
03041984
03031991
02091978
01071988
01061992
01041993
01041983
01031981
0069
zyjxrf
xian
wizard1
winger
wilder
welkom
wearing
weare138
vanessa1
usmarine
unlock
thumb
this
tasha1
talks
talbot
summers
sucked
storage
sqdwfe
socce
sniffing
smirnov
shovel
shopper
shady
semper
screwy
schatz
samanth
salman
rugby1
rjhjkm
rita
rfhfylfi
retire
ratboy
rachelle
qwerasdfzxcv
purple1
prince1
pookey
picks
perkins
patches1
password99
oyster
olenka
nympho
nikolas
neon
muslim
muhammad
morrowind
monk
missie
mierda
mercede
melina
maximo
matrix1
Martin
mariner
mantle
mammoth
mallrats
madcow
macintos
macaroni
lunchbox
lucas1
london1
lilbit
leoleo
KILLER
kerry
kcchiefs
juniper
jonas
jazzy
istheman
implants
hyundai
hfytnrb
herring
grunt
grimace
granite
grace1
gotenks
glasses
giggle
ghjcnbnenrf
garnet
gabriele
gabby
fosters
forever1
fluff
Fktrcfylh
finder
experienced
dunlop
duffer
driven
dragonballz
draco
downer
douche
doom
discus
darina
daman
daisey
clement
chouchou
cheerleaers
Charles
charisma
celebrity
cardinals
captain1
caca
c2h5oh
bubbles1
brook
brady
jeremyevans-rodauth-b53f402/doc/ 0000775 0000000 0000000 00000000000 15157255142 0016611 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/doc/CHANGELOG.old 0000664 0000000 0000000 00000052176 15157255142 0020613 0 ustar 00root root 0000000 0000000 === 1.23.0 (2020-03-06)
* Remove specs from the gem to reduce gem size by over 20% (jeremyevans)
* Make rodauth.authenticated? return true on OTP setup page (jeremyevans) (#68)
* Display link to email auth request form when user has entered login and incorrect password if using email_auth feature (janko) (#65)
* Add *_path and *_url methods for all *_route methods (janko) (#64)
* Add send_email configuration method for configuring how email is sent (janko) (#63)
=== 1.22.0 (2019-10-29)
* Add jwt_cors feature to handle Cross-Origin Resource Sharing when using the jwt feature (jeremyevans)
* Add space before newline after links in email, fixing issues with some webmail providers with broken autolinkers (jeremyevans)
=== 1.21.0 (2019-07-24)
* Support rotp 5.1 in the otp feature (jeremyevans)
* Log user out when locking out OTP account if no fallback options available (jeremyevans)
=== 1.20.0 (2019-06-07)
* Support rotp 5 in the otp feature (jeremyevans)
* Add jwt_refresh feature to allow shorter lived JWTs with a refresh token for creating new JWTs (allavena, jeremyevans) (#28)
* Fix disallow_password_reuse feature when account_password_hash_column is not set and verify_account feature is not used (cptaffe) (#59)
* Rename no_matching_email_auth_key_message to no_matching_email_auth_key_error_flash for consistency (jeremyevans)
* Rename no_matching_verify_login_change_key_message to no_matching_verify_login_change_key_error_flash for consistency (jeremyevans)
* Rename attempt_to_login_to_unverified_account_notice_message to attempt_to_login_to_unverified_account_error_flash for consistency (jeremyevans)
* Rename attempt_to_create_unverified_account_notice_message to attempt_to_create_unverified_account_error_flash for consistency (jeremyevans)
* Rename no_matching_verify_account_key_message to no_matching_verify_account_key_error_flash for consistency (jeremyevans)
* Rename no_matching_unlock_account_key_message to no_matching_unlock_account_key_error_flash for consistency (jeremyevans)
* Rename no_matching_reset_password_key_message to no_matching_reset_password_key_error_flash for consistency (jeremyevans)
* Add otp_keys_use_hmac? and otp_setup_raw_param configuration methods to the otp feature for configuring use of HMACs with OTP authentication (jeremyevans)
* Do not set a previous account password before password has been set when using disallow_password_reuse with verify_account_set_password? (jeremyevans)
* Add allow_raw_single_session_key? to single_session feature to allow raw single single session tokens, for graceful transition (jeremyevans)
* Add raw_remember_token_deadline to remember feature to allow raw remember tokens before given deadline, for graceful transition (jeremyevans)
* Add allow_raw_email_token? configuration method to email_base feature to allow raw tokens when email_token_hmac_secret is set, for graceful transition (jeremyevans)
* Add hmac_secret configuration method, used for additional security using HMACs (jeremyevans)
* Use urlsafe base64 for new token keys on Ruby 1.8 (jeremyevans)
* Add login_input_type configuration method for setting the input type for login inputs (jeremyevans)
* Add formatted_field_error configuration method for formatting error messages (jeremyevans)
* Add field_error_attributes configuration method for configuring attributes for fields with errors (jeremyevans)
* Add field_attributes configuration method for configuring attributes for specific fields (jeremyevans)
* Add default_field_attributes configuration method to set default attributes for all input fields (jeremyevans)
* Make error handling accessible by default using aria-invalid and aria-describedby attributes (jeremyevans)
* Add mark_input_fields_as_required? configuration method for whether inputs should use the required attribute (jeremyevans)
* Add input_field_error_message_class configuration method for the CSS class used for error messages (jeremyevans)
* Wrap all error messages in a span so they can be styled (jeremyevans)
* Add input_field_error_class configuration method for customizing CSS class to use for inputs with errors (jeremyevans)
* Add input_field_label_suffix configuration method for suffixing all input labels, useful for labeling fields as required (jeremyevans)
* Add verify_account_resend_explanatory_text configuration method to verify_account feature for configuring text (jeremyevans)
* Add unlock_account_explanatory_text and unlock_account_request_explanatory_text configuration methods to lockout feature for configuring text (jeremyevans)
* Add reset_password_explanatory_text configuration method to reset_password feature for configuring text (jeremyevans)
* Add otp_provisioning_uri_label and otp_secret_label configuration methods to otp feature for configuring labels displayed during OTP setup (jeremyevans)
* Add add_recovery_codes_heading configuration method to recovery_codes feature for configuring heading text (jeremyevans)
* Use define_method instead of instance_exec for route dispatching for better performance (jeremyevans)
* Add already_an_account_with_this_login_message configuration method (1gor) (#54)
=== 1.19.1 (2018-11-16)
* Support rotp 4 in the otp feature (jeremyevans)
=== 1.19.0 (2018-11-16)
* Avoid unneeded database queries in the two factor authentication support (jeremyevans)
* Add {before,after}_verify_login_change_email configuration methods, called around sending the verify login change email (jeremyevans)
* Add after_account_lockout configuration method, called after locking out an account (jeremyevans)
* Add default_post_email_redirect configuration method, setting default for all redirects after emailing when not logged in (jeremyevans)
* Gracefully handle failure when new login is already taken in the verify_login_change feature (jeremyevans)
* Support optional email rate limiting in the lockout, reset password, and verify account features (jeremyevans)
* Make MySQL rodauth_get_salt function handle accounts without password hashes (jeremyevans)
* Add email_auth feature, for authentication using links sent via email (jeremyevans)
* Deprecate before_otp_authentication_route, users should switch to before_otp_auth_route (jeremyevans)
* Add use_multi_phase_login? configuration method to login feature, separating login entry from password entry (jeremyevans)
* Don't disable use of date_arithmetic extension on !MySQL when using lockout, remember, or reset password features (jeremyevans)
=== 1.18.0 (2018-07-18)
* Add confirm_password_redirect_session_key configuration method to confirm_password feature (jeremyevans)
* Work with Roda sessions plugin, using string keys for session information if that is used (jeremyevans)
* Add flash_error_key and flash_notice_key configuration for setting keys used in flash (jeremyevans)
=== 1.17.0 (2018-06-11)
* Support Roda route_csrf plugin for request-specific CSRF tokens (jeremyevans)
=== 1.16.0 (2018-03-09)
* Add disallow_common_passwords feature, for disallowing the usage of the most common passwords (jeremyevans)
* Remove calling request [] method to get request param values, as it is deprecated in the current version of rack (jeremyevans)
=== 1.15.0 (2018-01-29)
* Add create_account_set_password? and verify_account_set_password? methods to delay setting password until account verification (jeremyevans)
=== 1.14.0 (2017-12-19)
* Don't allow unlocking expired accounts when using account_expiration and lockout features (jeremyevans)
* Don't allow resetting passwords for expired accounts when using account_expiration and reset_password features (jeremyevans)
* Add change_password_notify feature for emailing when user uses change password feature (jeremyevans)
=== 1.13.0 (2017-11-21)
* Add json_response_body(hash) configuration method to jwt feature (jeremyevans)
* Support invalid_previous_password_message configuration method in change_password feature (jeremyevans)
* Use custom error statuses if only_json? and json_response_custom_error_status? are true even if request isn't in json format (jeremyevans)
* Add cache_templates configuration method for disabling caching of templates (adam12, jeremyevans) (#46)
=== 1.12.0 (2017-10-03)
* [SECURITY] Clear expired password reset key for account before retrieving password reset key (chanks, jeremyevans) (#43)
* Update migrations to work with Sequel 5 (jeremyevans)
* Add require_http_basic_auth configuration method to http_basic_auth feature (jeremyevans) (#41)
* Support passing :search_path option to Rodauth.create_database_authentication_functions when using PostgreSQL (jeremyevans)
* Support passing options to Rodauth.{create,drop}_database_previous_password_check_functions (jeremyevans)
* Support passing options to Rodauth.drop_database_authentication_functions (jeremyevans)
=== 1.11.0 (2017-04-24)
* Add login_required_error_status, and use it in the jwt feature when custom error statuses are allowed (jeremyevans)
* Deal better with time differences between the database and application servers in the password_expiration plugin (jeremyevans)
* Add rodauth.valid_jwt? method for checking if a valid JWT was submitted with the request (jeremyevans)
=== 1.10.0 (2017-03-23)
* Add Internals Guide (jeremyevans)
* Set FeatureConfiguration instances to constants, just like Feature instances (jeremyevans)
* When reopening rodauth configuration in roda subclass, automatically subclass rodauth configuration so it doesn't modify superclass (jeremyevans)
* Add verify_login_change feature as an alternative to verify_change_login, where the change doesn't take affect until after verification (jeremyevans) (#31)
* Add login_failed_reset_password_request_form for customizing the HTML used for the request password request form on login failures (jeremyevans)
* Make reset password request form available without requiring a login attempt, and provide a login field in that case (jeremyevans) (#30)
* Make resending verify account email request form available without requiring a login/account creation attempt, and provide a login field in that case (jeremyevans) (#30)
* Fix resending verify account email when attempting to create a new account with same login as unverified account when using verify_account_grace_period feature (jeremyevans) (#30)
* Fix precompile_rodauth_templates usage with reset_password feature (jeremyevans)
=== 1.9.0 (2017-02-22)
* Make reset-password use existing password reset key if one is present (jeremyevans) (#26)
* Add Roda.precompile_rodauth_templates method, useful to save memory when forking, or when chrooting (jeremyevans)
=== 1.8.0 (2017-01-06)
* Add json_response_custom_error_status? option to jwt feature to use specific 4xx statuses instead of 400 (jeremyevans)
* Use 4xx error statuses for errors, instead of using a 200 success status (jeremyevans)
=== 1.7.0 (2016-11-22)
* Make reset password, unlock account, and verify account pages not leak keys to external servers via Referer header (jeremyevans)
=== 1.6.0 (2016-10-24)
* Add http_basic_auth feature (TiagoCardoso1983, jeremyevans) (#12)
* Move login hooks from login feature to base, to be usable by other features (jeremyevans)
* Make reset_password feature not attempt to render a template in json-only mode (jeremyevans) (#11)
* Memoize jwt_payload in jwt feature, as it may be called more than once (mwpastore) (#10)
* Add jwt_decode_opts configuration method to jwt feature, for specifying options to JWT.decode, allowing for JWT claim verification (mwpastore, jeremyevans) (#9)
* Add jwt_session_hash configuration method to jwt feature, for modifying the session information stored in the JWT hash, allowing for setting JWT claims (mwpastore, jeremyevans) (#9)
* Add jwt_session_key configuration method to jwt feature, for nesting the session under a key in the JWT, avoiding reserve claim names (mwpastore, jeremyevans) (#9)
* Add jwt_symbolize_deeply? configuration method to jwt feature, for symbolizing nested keys in session hash when using JWT (mwpastore) (#9)
=== 1.5.0 (2016-09-22)
* Return error instead of raising exception in the jwt feature if an invalid jwt format is submitted in the Authorization header (jeremyevans)
* Add jwt_authorization_remove configuration method to jwt feature, for regexp to remove from Authorization header before JWT processing (jeremyevans)
* Add jwt_authorization_ignore configuration method to jwt feature, for regexp to skip processing of JWTs in Authorization header (jeremyevans)
* Add json_accept_regexp configuration method to jwt feature, for the regexp used to match against the Accept header (jeremyevans)
* Add use_jwt? configuration method to jwt feature, for whether to use the JWT token or rack session for authentication information (jeremyevans)
* Add jwt_check_accept? configuration method to jwt feature, to return 406 error if Accept header is present and json is not accepted (jeremyevans)
* Add json_response_content_type configuration method to jwt feature, for the content type to set for json responses, default to application/json (jeremyevans)
* Add json_request_content_type_regexp configuration method to the jwt feature, for the regexp that recognize a request as a json request (jeremyevans)
* Add session_jwt method to the jwt feature, which returns a string for the encoded JWT for the current session (jeremyevans)
* If the only_json? setting is true, return a 400 error if the request content type to a rodauth endpoint is not json (jeremyevans)
* The only_json? setting in the jwt feature is now only true by default if :json=>:only plugin option was used (jeremyevans)
* Don't have jwt feature break if HTTP Basic/Digest authentication is used (jeremyevans)
* Add template_opts configuration method, for overriding view/method options (jeremyevans)
=== 1.4.0 (2016-08-18)
* Add update_password_hash feature, for updating the password hash when the hash cost changes (jeremyevans)
=== 1.3.0 (2016-07-19)
* Add login_maximum_length, defaulting to 255 (jeremyevans)
=== 1.2.0 (2016-06-15)
* Add otp_drift configuration method to otp plugin, setting number of seconds of allowed drift (jeremyevans)
* Don't allow setting passwords containing the ASCII NUL character, as bcrypt truncates at that point (jeremyevans) (#4)
=== 1.1.0 (2016-05-13)
* Support :csrf=>false and :flash=>false plugin options (jeremyevans)
=== 1.0.0 (2016-04-15)
* Remove invalid remember cookies to prevent unnecessary future database checks (jeremyevans)
* Extend remember deadline in cookie in addition to database (jeremyevans)
* Make tokens work with string account ids (jeremyevans)
* Add verify_change_login feature for requiring account reverification on login changes (jeremyevans)
* Set correct cookie expiration in the remember feature (jeremyevans)
* Split confirm_password feature from remember feature (jeremyevans)
* Add verify_account_grace_period feature, for allowing logins into unverified accounts for a certain period after creation (jeremyevans)
* Move login/password requirements settings to login password requirements base feature (jeremyevans)
* Add session_expiration feature, expiring sessions based on inactivity and max lifetime checks (jeremyevans)
* Add password_grace_period feature, for not requiring password entry if password was recently entered (jeremyevans)
* Make create/verify account autologin true by default (jeremyevans)
* Optimize routing using a hash table, disallow per-request routes (jeremyevans)
* Add ability to turn off login/password confirmations (jeremyevans)
* Don't allow changing login to the same as the current login (jeremyevans)
* Only allow requesting account unlocks if the account is current locked out (jeremyevans)
* Use separate routes for unlock account/reset password/verify account requests (jeremyevans)
* Use separate routes for confirming passwords and changing remember settings (jeremyevans)
* Add JWT feature for JSON API support using JWT tokens (jeremyevans)
* Add account_select configuration option for setting which columns to select from accounts_table (jeremyevans)
* Execute get_block and post_block in the Rodauth::Auth instance scope (jeremyevans)
* Store field errors in the rodauth object instead of instance variables in the Roda scope (jeremyevans)
* Add rodauth.redirect to abstract redirection code (jeremyevans)
* Only use flash notices for successful requests, other requests that redirect now use an error flash (jeremyevans)
* The before_* configuration methods now run directly before making the related database changes (jeremyevans)
* Before hooks run before routes now use before_*_route instead of before_* configuration methods (jeremyevans)
* Add token_separator configuration method to replace the default of _ (jeremyevans)
* Rename account_id_value to account_id (jeremyevans)
* Rename account_id to account_id_column and account_session_id to account_session_column (jeremyevans)
* Make skip_status_checks? default to true unless loading verify_account or close_account features (jeremyevans)
* Replace account_model with accounts_table and db, removing use of Sequel models (jeremyevans)
* Extract shared email-related code into email_base feature (jeremyevans)
* Add auth_class_eval to configuration block for adding custom methods (jeremyevans)
* Add configuration_module_eval to feature definitions for adding custom configuration methods (jeremyevans)
* Allow close_account feature to optionally delete accounts (jeremyevans)
* Make close_account feature work when skipping status checks or when using account_password_hash_column (jeremyevans)
* Add sms_codes feature, for codes received via SMS that can be used if TOTP authentication is not available (jeremyevans)
* Attempt to handle unique constraint violations raised in race conditions where possible (jeremyevans)
* Add _before and _after internal methods, make ununderscored methods only for users (jeremyevans)
* Add single_session feature, for only allowing a single active session per account (jeremyevans)
* Add account_expiration feature, for disallowing access to accounts after an amount of time since last login/activity (jeremyevans)
* Check account status in rodauth.load_memory in remember plugin (jeremyevans)
* Use csrf plugin automatically, depend on Roda >=2.6.0 (jeremyevans)
* Make bcrypt and mail development dependencies instead of runtime dependencies in the gem (jeremyevans)
* Add password_expiration feature, requiring users to change their password after a given amount of time (jeremyevans)
* Add disallow_password_reuse feature, checking that a new password doesn't match previous passwords (jeremyevans)
* Add password_complexity feature, allowing more sophisticated password complexity checks (jeremyevans)
* Add rodauth.remember_param and .remember_confirm_param for overriding parameter names (jeremyevans)
* Check that new password is not the same as existing password in change password and reset password features (jeremyevans)
* Add rodauth.login_meets_requirements? for checking if a login is valid, by default a valid email address (jeremyevans)
* Allow unlock account to optionally require the user's current password (jeremyevans)
* Add support for running on Microsoft SQL Server with database functions for authentication (jeremyevans)
* Make change password, change login, and close account require the user's current password by default (jeremyevans)
* Add rodauth.csrf_tag to make it easy to replace the CSRF tag implementation (jeremyevans)
* Switch unlock_account_autologin? to be true by default (jeremyevans)
* Add rodauth.authenticated? and .require_authentication (jeremyevans)
* Add recovery_codes feature, for single use codes that can be used if TOTP authentication is not available (jeremyevans)
* Add otp feature, for 2 factor authentication via TOTP (jeremyevans)
* Add support for running on MySQL with database functions for authentication (jeremyevans)
* Add *_interval and set_deadline_values? methods for setting deadline intervals on a per-request basis (jeremyevans)
* Add remember_deadline_column method for overriding the column used for storing the deadline (jeremyevans)
* Add rodauth/migrations file for DRYing up the database function creation (jeremyevans)
* Add Rodauth.version for getting the version (jeremyevans)
* External features should now be requirable via rodauth/features/feature_name instead of roda/plugins/rodauth/feature_name (jeremyevans)
* Make Rodauth top level module instead of under Roda::RodaPlugins (jeremyevans)
* Require mail at configure time instead of run time if using a feature that sends email, use require_mail? false to disable (jeremyevans)
* Require bcrypt at configure time instead of run time, use require_bcrypt? false to disable (jeremyevans)
* Always require securerandom (jeremyevans)
* Make remember, password reset, and lockout features work on non-PostgreSQL databases (jeremyevans)
* Support authentication without database functions when password hashes are stored in separate table (jeremyevans)
* Remove overriding of route/get/post blocks (jeremyevans)
* Make lockout feature work on databases not supporting UPDATE RETURNING (jeremyevans)
* Add timing safe comparison of tokens (jeremyevans)
=== 0.10.0 (2016-02-17)
* Retrieve salt from database and compute hash client side, instead of computing hash on server (jeremyevans)
=== 0.9.1 (2015-08-13)
* Don't use csrf plugin automatically (jeremyevans)
=== 0.9.0 (2015-08-12)
* Initial public release
jeremyevans-rodauth-b53f402/doc/account_expiration.rdoc 0000664 0000000 0000000 00000005066 15157255142 0023367 0 ustar 00root root 0000000 0000000 = Documentation for Account Expiration Feature
The account expiration feature disallows access to accounts after
a configurable amount of time since the last login or activity
(default: 180 days since last login). By default, this feature
does not track activity times as that can slow things down, but if
you want to record activity times, you can do so by adding the
following code to your routing block:
rodauth.update_last_activity
Note that it only makes sense to do this if you are also expiring
accounts based on last activity instead of last login, via the
+expire_account_on_last_activity?+ configuration setting.
Note that this feature does not support the reenabling of expired
accounts, that is something you would have to implement yourself,
if you need such a feature.
== Auth Value Methods
account_activity_expired_column :: The column in the +account_activity_table+ storing the expiration timestamp.
account_activity_id_column :: The column in the +account_activity_table+ storing the account id.
account_activity_last_activity_column :: The column in the +account_activity_table+ storing the last activity timestamp.
account_activity_last_login_column :: The column in the +account_activity_table+ storing the last login timestamp.
account_activity_table :: The database table use for storing account login/activity/expiration timestamps.
account_expiration_error_flash :: The flash error to show when attempting to login to an account that has expired.
account_expiration_redirect :: Where to redirect after attempting to login to an account that has expired.
expire_account_after :: How long in seconds from last login or activity until an account is considered expired.
expire_account_on_last_activity? :: Whether to use the last activity timestamp when checking an account for expiration. By default, this is false and it uses the last login timestamp.
== Auth Methods
account_expired? :: Whether the current account has expired.
account_expired_at :: The expiration timestamp for the current account, nil if the account hasn't been expired.
after_account_expiration :: Run arbitrary code after account expiration.
last_account_activity_at :: The last activity timestamp for the current account, nil if the account hasn't had activity recorded yet.
last_account_login_at :: The last login timestamp for the current account, nil if the account hasn't had a login recorded yet.
set_expired :: Set the current account as having expired.
update_last_activity :: Update the last activity timestamp for the account.
update_last_login :: Update the last login timestamp for the account.
jeremyevans-rodauth-b53f402/doc/active_sessions.rdoc 0000664 0000000 0000000 00000010503 15157255142 0022662 0 ustar 00root root 0000000 0000000 = Documentation for Active Sessions Feature
The active sessions feature stores an id for each session in a
database table whenever a user logs in to the system. In your
routing block, you can check that the session id given is
still listed as an active session:
rodauth.check_active_session
On logout, the session id is removed from the database table,
so attempts to reuse the session id after that will fail.
Additionally, this supports an option on logout to globally
logout all sessions, which removes all active session ids for
the account from the database table.
In addition to removing sessions on logout, this also by default
supports session inactivity deadlines (based on time since last
use) and session lifetime deadlines (based on time since session
creation). To prevent the sessions table from growing
indefinitely, sessions that are passed either deadline are
removed when checking if the current session is active.
This depends on the logout feature.
== Auth Value Methods
active_sessions_account_id_column :: The column in the +active_sessions_table+ containing the account id.
active_sessions_created_at_column :: The column in the +active_sessions_table+ containing the time of session creation.
active_sessions_error_flash :: The flash error to display if the current session is no longer active.
active_sessions_last_use_column :: The column in the +active_sessions_table+ containing the time the session was last used.
active_sessions_redirect :: Where to redirect if the current session is no longer active.
active_sessions_session_id_column :: The column in the +active_sessions_table+ containing the session_id.
active_sessions_table :: The database table storing active session keys.
global_logout_label :: The label for the global logout checkbox on the logout page.
global_logout_param :: The parameter name for the global logout checkbox on the logout page.
inactive_session_error_status :: The error status to use when a JSON request is made and the session is no longer active, 401 by default.
session_id_session_key :: The session key name to use for storing the session id.
session_inactivity_deadline :: The number of seconds since last use after which the session will be considered expired (1 day by default). Can be set to nil to not check session inactivity.
session_lifetime_deadline :: The number of seconds since session creation after which the session will be considered expired (30 days by default). Can be set to nil to not check session lifetimes.
update_current_session? :: Whether the update current session with +active_sessions_update_hash+. By default returns true if +session_inactivity_deadline+ is set.
== Auth Methods
active_sessions_insert_hash :: The hash to insert into the +active_sessions_table+.
active_sessions_key :: The active session key for the current account.
active_sessions_update_hash :: The hash to update the currently active session when +update_current_session?+ is true. By default updates last use to current time.
add_active_session :: Create a session id for the session and populate the session and add the session id to the database.
currently_active_session? :: Whether the session is currently active, by checking the database table.
handle_duplicate_active_session_id(exception) :: How to handle the case where a duplicate session id for the account is inserted into the table. Does nothing by default. This should only be called if the random number generator is broken.
no_longer_active_session :: What action to take if +rodauth.check_active_session+ is called and the session is no longer active.
remove_active_session(session_id) :: Removes the active session matching the given session ID from the database. Useful for implementing session revoking.
remove_all_active_sessions :: Remove all active sessions for the account from the database, used for global logouts and when closing accounts.
remove_all_active_sessions_except_for(session_id) :: Remove all active sessions for the account from the database, except for the session id given.
remove_all_active_sessions_except_current :: Remove all active sessions for the account from the database, except for the current session.
remove_current_session :: Remove current session from the database, used for regular logouts.
remove_inactive_sessions :: Remove inactive sessions from the database, run before checking for whether the current session is active.
jeremyevans-rodauth-b53f402/doc/argon2.rdoc 0000664 0000000 0000000 00000005761 15157255142 0020663 0 ustar 00root root 0000000 0000000 = Documentation for Argon2 Feature
The argon2 feature adds the ability to replace the bcrypt password hash
algorithm with argon2 (specifically, argon2id). Argon2 is an alternative to
bcrypt that offers the ability to be memory-hard. However, argon2 is weaker
than bcrypt for interactive login environments (e.g. password check times
under a second), so for the vast majority of web applications, using the
argon2 feature will weaken the application's security. You should not use
the argon2 feature unless the usage of argon2 is required or you are a
cryptographer and understand why argon2 would be better than bcrypt for your
application.
If you are using this feature with Rodauth's database authentication functions,
you need to make sure that the database authentication functions are configured
to support argon2 in addition to bcrypt. You can do this by passing the
+:argon2+ option when calling the method to define the database functions.
In this example, +DB+ should be your Sequel::Database object:
require 'rodauth/migrations'
# If the functions are already defined and you are not using PostgreSQL,
# you need to drop the existing functions.
Rodauth.drop_database_authentication_functions(DB)
# If you are using the disallow_password_reuse feature, also drop the
# database functions related to that if not using PostgreSQL:
Rodauth.drop_database_previous_password_check_functions(DB)
# Define new functions that support argon2:
Rodauth.create_database_authentication_functions(DB, argon2: true)
# If you are using the disallow_password_reuse feature, also define
# new functions that support argon2 for that:
Rodauth.create_database_previous_password_check_functions(DB, argon2: true)
The argon2 feature provides the ability to allow for a gradual migration
from transitioning from bcrypt to argon2 and vice-versa, if you are using the
update_password_hash feature.
Argon2 is more configurable than bcrypt in terms of password hash cost
specification. Instead of specifying the password_hash_cost value as
an integer, you must specify the password hash cost as a hash, such as
({t_cost: 2, m_cost: 16}).
If you are using the argon2 feature and if you have no bcrypt passwords in
your database, you should use require_bcrypt? false in your
Rodauth configuration to prevent loading the bcrypt library, which will save
memory.
== Auth Value Methods
argon2_old_secret :: The previous secret key used as input at hashing time, used for argon2_secret rotation. In order to rotate the argon2_secret, you must also use the update_password_hash feature, and rotation will not be finished until all users have logged in using the new secret.
argon2_secret :: A secret key used as input at hashing time, folded into the value of the hash.
use_argon2? :: Whether to use the argon2 password hash algorithm for new passwords (true by default). The only reason to set this to false is if you have existing passwords using argon2 that you want to support, but want to use bcrypt for new passwords.
jeremyevans-rodauth-b53f402/doc/audit_logging.rdoc 0000664 0000000 0000000 00000004676 15157255142 0022313 0 ustar 00root root 0000000 0000000 = Documentation for Audit Logging Feature
The audit logging feature adds audit logging of rodauth actions to a
database table. It ties into the after hook processing used by all
features so that all features that use after hooks automatically
support audit logging.
In addition to the configuration methods defined below, the audit
logging feature also offers two additional configuration methods
for action specific audit log messages and metadata,
+audit_log_message_for+ and +audit_log_metadata_for+. These
methods take the action symbol and either take a value or a
block that returns a value to use for the message and metadata
for that action:
audit_log_message_for :login, "I have logged in"
audit_log_metadata_for :logout, 'Uses'=>'JSON Metadata'
audit_log_message_for :login_failure do
"Login failure on domain #{request.host}"
end
audit_log_metadata_for :login_failure do
{'ip'=>request.ip}
end
To skip audit logging for a particular action, you can set the
log message for the action to nil.
== Auth Value Methods
audit_logging_account_id_column :: The id column in the +audit_logging_table+, should be a foreign key referencing the accounts table.
audit_logging_message_column :: The message column in the +audit_logging_table+, containing the log message.
audit_logging_metadata_column :: The metadata column in the +audit_logging_table+, storing metadata for the log (if any).
audit_logging_table :: The name of the audit logging table.
audit_log_metadata_default :: The default metadata to use for logs that do not have custom metadata specified by +audit_log_metadata_for+.
== Auth Methods
add_audit_log(account_id, action) :: Add an appropriate audit log entry for the account id and action.
audit_log_insert_hash(account_id, action) :: A hash to use when inserting into the +audit_logging_table+.
audit_log_message(action) :: The log message to use when logging the action, by default using +audit_log_message_for+ and +audit_log_message_default+.
audit_log_message_default(action) :: The log message to use when logging the action for logs that do not have custom metadata specified by +audit_log_message_for+
audit_log_metadata(action) :: The metadata to use when logging the action, by default using +audit_log_metadata_for+ and +audit_log_metadata_default+.
serialize_audit_log_metadata(metadata) :: Serialize the metadata for insertion into the database. By default, this converts the metadata using +to_json+, unless the metadata is nil.
jeremyevans-rodauth-b53f402/doc/base.rdoc 0000664 0000000 0000000 00000033615 15157255142 0020404 0 ustar 00root root 0000000 0000000 = Documentation for Base Feature
The base feature is automatically loaded when you use Rodauth. It contains
shared functionality that is used by multiple features.
== Auth Value Methods
=== Most Commonly Used
account_password_hash_column :: Set if the password hash column is in the same table as the login. If this is set, Rodauth will check the password hash in ruby. This is often used if you are replacing a legacy authentication system with Rodauth.
accounts_table :: The database table containing the accounts.
base_url :: The base URL to use, used when construct absolute links. It is recommended to set this if the application can be reached using arbitrary Host headers, as otherwise it is possible for an attacker to control the value.
db :: The Sequel::Database object used for database access.
domain :: The domain to use, required by some other features. It is recommended to set this if the application can be reached using arbitrary Host headers, as otherwise it is possible for an attacker to control the value.
hmac_secret :: This sets the secret to use for all of Rodauth's HMACs. This is not set by default, in which case Rodauth does not use HMACs for additional security. However, it is highly recommended that you set this, and some features require it.
mark_input_fields_as_required? :: Whether input fields should be marked as required, so browsers will not allow submission without filling out the field (default: true).
prefix :: The routing prefix used for Rodauth routes. If you are calling in a routing subtree, this should be set to the root path of the subtree. This should include a leading slash if set, but not a trailing slash.
require_bcrypt? :: Set to false to not require bcrypt, useful if using custom authentication or when using the argon2 feature without existing bcrypt password hashes.
session_key :: The key in the session hash storing the primary key of the logged in account.
session_key_prefix :: The string that will be prepended to the default value for all session keys.
skip_status_checks? :: Whether status checks should be skipped for accounts. Defaults to true unless enabling the verify_account or close_account features.
title_instance_variable :: The instance variable to set in the Roda scope with the page title. The layout should use this instance variable if available to set the title of the page. You can use +set_title+ if setting the page title is not done through an instance variable.
=== Other
account_id_column :: The primary key column of the +accounts_table+.
account_open_status_value :: The integer representing open accounts.
account_select :: An array of columns to select from +accounts_table+. By default, selects all columns in the table.
account_status_column :: The status id column in the +accounts_table+.
account_unverified_status_value :: The integer representing unverified accounts.
authenticated_by_session_key :: The key in the session hash storing an array of methods used to authenticate.
autocomplete_for_field?(param) :: Whether to use an autocomplete attribute for the given parameter, defaults to +mark_input_fields_with_autocomplete?+.
autologin_type_session_key :: The key in the session hash storing the type of autologin method used, if autologin was used to authenticate.
cache_templates :: Whether to cache templates. True by default. It may be worth switching this to false in development if you are using your own templates instead of the templates provided by Rodauth.
check_csrf? :: Whether Rodauth should use Roda's +check_csrf!+ method for checking CSRF tokens before dispatching to Rodauth routes, true by default.
check_csrf_opts :: Options to pass to Roda's +check_csrf!+ if Rodauth calls it before dispatching.
check_csrf_block :: Proc for block to pass to Roda's +check_csrf!+ if Rodauth calls it before dispatching.
clear_tokens(reason) :: Called when there is an account change, clears tokens for the account. By examining the reason symbol you can get different behavior per action, but the default behavior is clear all tokens whenever there is an account. Clearing actions/reasons are +:reset_password+, +:verify_account+, +:change_login+, +:unlock_account+, and +close_account+. Tokens are cleared for the following features +reset_password+, +verify_account+, +verify_login_change+, +jwt_refresh+, +remember+, +email_auth+, +single_session+, and +active_sessions+.
convert_token_id_to_integer? :: Whether token ids should be converted to a valid 64-bit integer value. If not set, defaults to true if +account_id_column+ uses an integer type, and false otherwise.
default_field_attributes :: The default attributes to use for input field tags, if field_attributes returns nil for the field.
default_redirect :: Where to redirect after most successful actions.
field_attributes(field) :: The attributes to use for the input field tags for the given field (parameter name).
field_error_attributes(field) :: The attributes to use for the input field tags for the given field (parameter name), if the input has an error.
flash_error_key :: The flash key to use for error messages (default: +:error+ or 'error' depending on session support for symbols).
flash_notice_key :: The flash key to use for notice messages (default: +:notice+ or 'notice' depending on session support for symbols).
formatted_field_error(field, error) :: HTML to use for error messages for the field (parameter name), if the field has an error. By default, uses a span tag for the error message.
hmac_old_secret :: This sets the previous secret used for Rodauth's HMACs, to allow for secret rotation.
hook_action(hook_type, action) :: Arbitrary action to take on all hook processing, with hook type being +:before+ or +:after+, and action being symbol for related action.
input_field_error_class :: The CSS class to use for input fields with errors. Can be a space separated string for multiple CSS classes.
input_field_error_message_class :: The CSS class to use for error messages. Can be a space separated string for multiple CSS classes.
input_field_label_suffix :: The suffix to use for all labels. Useful for noting that the fields are required.
inputmode_for_field?(param) :: Whether to use an inputmode attribute for the given parameter, defaults to mark_input_fields_with_inputmode?.
invalid_field_error_status :: The response status to use for invalid field value errors, 422 by default.
invalid_key_error_status :: The response status to use for invalid key codes, 401 by default.
invalid_password_error_status :: The response status to use for invalid passwords, 401 by default.
invalid_password_message :: The error message to display when a given password doesn't match the stored password hash.
lockout_error_status :: The response status to use a login is attempted to an account that is locked out, 403 by default.
login_column :: The login column in the +accounts_table+.
login_input_type :: The input type to use for logins. Defaults to email if login column is email and text otherwise.
login_label :: The label to use for logins.
login_param :: The parameter name to use for logins.
login_required_error_status :: The response status to return when a login is required and you are not logged in, if not redirecting, 401 by default
login_uses_email? :: Whether the login field uses email, used to set the type of the login field as well as the autocomplete setting.
mark_input_fields_with_autocomplete? :: Whether input fields should be marked with autocomplete attribute appropriate for the field, true by default.
mark_input_fields_with_inputmode? :: Whether input fields should be marked with inputmode attribute appropriate for the field, true by default.
max_param_bytesize :: The maximum bytesize allowed for submitted parameters, 1024 by default. Use nil for no limit.
modifications_require_password? :: Whether making changes to an account requires the user reinputing their password. True by default if the account has a password.
no_matching_login_error_status :: The response status to use when the login is not in the database, 401 by default.
no_matching_login_message :: The error message to display when the login used is not in the database.
password_hash_column :: The password hash column in the +password_hash_table+.
password_hash_id_column :: The account id column in the +password_hash_table+.
password_hash_table :: The table storing the password hashes.
password_label :: The label to use for passwords.
password_param :: The parameter name to use for passwords.
require_login_error_flash :: The flash error to display when accessing a page that requires a login, when you are not logged in.
require_login_redirect :: A redirect to the login page.
set_deadline_values? :: Whether deadline values should be set. True by default on MySQL, as that doesn't support default values that are not constant. Can be set to true on other databases if you want to vary the value based on a request parameter.
strftime_format :: The format to pass to Time#strftime when formatting timestamps to display to the user, '%F %T' by default.
template_opts :: Any template options to pass to view/render. This can be used to set a custom layout, for example.
token_separator :: The string used to separate account id from the random key in links.
unmatched_field_error_status :: The response status to use when two field values should match but do not, 422 by default.
unopen_account_error_status :: The response status to use when trying to login to an account that isn't open, 403 by default.
use_database_authentication_functions? :: Whether to use functions to do authentication. True by default on PostgreSQL, MySQL, and Microsoft SQL Server, false otherwise.
use_date_arithmetic? :: Whether the date_arithmetic extension should be loaded into the database. Defaults to whether deadline values should be set.
use_request_specific_csrf_tokens? :: Whether to use request-specific CSRF tokens. True if the default CSRF setting are used.
use_template_fixed_locals? :: Whether to specify fixed locals for rodauth templates. True by default, should only be set to false if overriding the templates and having them accept different local variables.
== Auth Methods
account_from_id(id, status_id=nil) :: Retrieve the account hash for the given account id and status.
account_from_login(login) :: Retrieve the account hash related to the given login or nil if no login matches.
account_from_session :: Retrieve the account hash related to the currently logged in session.
account_id :: The primary key value of the current account.
account_session_value :: The primary value of the current account to store in the session when logging in.
after_login :: Run arbitrary code after a successful login.
after_login_failure :: Run arbitrary code after a login failure due to an invalid password.
already_logged_in :: What action to take if you are already logged in and attempt to access a page that only makes sense if you are not logged in.
around_rodauth(&block) :: Run arbitrary code around handling any rodauth route. Call super(&block) for Rodauth to handle the action.
authenticated? :: Whether the user has been authenticated. If multifactor authentication has been enabled for the account, this is true only if the session is multifactor authenticated.
before_login :: Run arbitrary code after password has been checked, but before updating the session.
before_login_attempt :: Run arbitrary code after an account has been located, but before the password has been checked.
before_rodauth :: Run arbitrary code before handling any rodauth route, but after CSRF checks if Rodauth is doing CSRF checks.
check_csrf :: Checks CSRF token using Roda's +check_csrf!+ method.
clear_session :: Clears the current session.
convert_token_id(id) :: Convert the token id string to an appropriate object to use for the token id (or return +nil+ to signal an invalid token id). By default, converts to a 64-bit signed integer if +convert_token_id_to_integer?+ is true.
csrf_tag(path=request.path) :: The HTML fragment containing the CSRF tag to use, if any.
function_name(name) :: The name of the database function to call. It's passed either :rodauth_get_salt or :rodauth_valid_password_hash.
logged_in? :: Whether the current session is logged in.
login_required :: Action to take when a login is required to access the page and the user is not logged in.
normalize_login(login) :: How to normalize the submitted login parameter value, returns the argument by default.
null_byte_parameter_value(key, value) :: The value to use for the parameter if the parameter includes an ASCII NUL byte ("\0"), nil by default to ignore the parameter.
open_account? :: Whether the current account is an open account (not closed or unverified).
over_max_bytesize_param_value(key, value) :: The value to use for the parameter if the parameter is over the maximum allowed bytesize, nil by default to ignore the parameter.
password_match?(password) :: Check whether the given password matches the stored password hash.
random_key :: A randomly generated string, used for creating tokens.
redirect(path) :: Redirect the request to the given path.
session_value :: The value for session_key in the current session.
set_error_flash(message) :: Set the current error flash to the given message.
set_error_reason(reason) :: You can override this method to customize handling of specific error types (does nothing by default). Each separate error type has a separate reason symbol, you can see the {list of error reason symbols}[rdoc-ref:doc/error_reasons.rdoc].
set_notice_flash(message) :: Set the next notice flash to the given message.
set_notice_now_flash(message) :: Set the current notice flash to the given message.
set_redirect_error_flash(message) :: Set the next error flash to the given message.
set_title(title) :: Set the title of the page to the given title.
translate(key, default_value) :: Return a translated version for the key (uses the default value by default).
unverified_account_message :: The message to use when attempting to login to an unverified account.
update_session :: Clear the session, then set the session key to the primary key of the current account.
jeremyevans-rodauth-b53f402/doc/change_login.rdoc 0000664 0000000 0000000 00000003002 15157255142 0022072 0 ustar 00root root 0000000 0000000 = Documentation for Change Login Feature
The change login feature implements a form that a user can use to
change their login.
== Auth Value Methods
change_login_additional_form_tags :: HTML fragment containing additional form tags to use on the change login form.
change_login_button :: The text to use for the change login button.
change_login_error_flash :: The flash error to show for an unsuccessful login change.
change_login_notice_flash :: The flash notice to show after a successful login change.
change_login_page_title :: The page title to use on the change login form.
change_login_redirect :: Where to redirect after a successful login change.
change_login_requires_password? :: Whether a password is required when changing logins.
change_login_route :: The route to the change login action. Defaults to +change-login+.
same_as_current_login_message :: The error message to display if using the same value as the current login when changing the login.
== Auth Methods
after_change_login :: Run arbitrary code after successful login change.
before_change_login :: Run arbitrary code before changing a login.
before_change_login_route :: Run arbitrary code before handling a change login route.
change_login(login) :: Change the users login to the given login, or return nil/false if the login cannot be changed to the given login.
change_login_response :: Return a response after a successful login change. By default, redirects to +change_login_redirect+.
change_login_view :: The HTML to use for the change login form.
jeremyevans-rodauth-b53f402/doc/change_password.rdoc 0000664 0000000 0000000 00000003156 15157255142 0022636 0 ustar 00root root 0000000 0000000 = Documentation for Change Password Feature
The change password feature implements a form that a user can use to
change their password.
== Auth Value Methods
change_password_additional_form_tags :: HTML fragment containing additional form tags to use on the change password form.
change_password_button :: The text to use for the change password button.
change_password_error_flash :: The flash error to show for an unsuccessful password change.
change_password_notice_flash :: The flash notice to show after a successful password change.
change_password_page_title :: The page title to use on the change password form.
change_password_redirect :: Where to redirect after a successful password change.
change_password_requires_password? :: Whether a password is required when changing passwords.
change_password_route :: The route to the change password action. Defaults to +change-password+.
invalid_previous_password_message :: The message to use when the previous password was incorrect. Defaults to +invalid_password_message+.
new_password_label :: The label to use for the new password.
new_password_param :: The parameter name to use for new passwords.
== Auth Methods
after_change_password :: Run arbitrary code after successful password change.
before_change_password :: Run arbitrary code before changing the password for an account.
before_change_password_route :: Run arbitrary code before handling a change password route.
change_password_response :: Return a response after a successful password change. By default, redirects to +change_password_redirect+.
change_password_view :: The HTML to use for the change password form.
jeremyevans-rodauth-b53f402/doc/change_password_notify.rdoc 0000664 0000000 0000000 00000001010 15157255142 0024211 0 ustar 00root root 0000000 0000000 = Documentation for Change Password Notify Feature
The change password notify feature emails the user when their password
is changed using the change password feature.
== Auth Value Methods
password_changed_email_body :: Body to use for the password changed emails
password_changed_email_subject :: Subject to use for the password changed emails
== Auth Methods
create_password_changed_email :: A Mail::Message for the password changed email to send.
send_password_changed_email :: Send the password changed email.
jeremyevans-rodauth-b53f402/doc/close_account.rdoc 0000664 0000000 0000000 00000003155 15157255142 0022307 0 ustar 00root root 0000000 0000000 = Documentation for Close Account Feature
The close account feature allows users to close their accounts.
== Auth Value Methods
account_closed_status_value :: The integer representing closed accounts.
close_account_additional_form_tags :: HTML fragment containing additional form tags to use on the close account form.
close_account_button :: The text to use for the close account button.
close_account_error_flash :: The flash error to show if there is an error closing the account.
close_account_notice_flash :: The flash notice to show after closing the account.
close_account_page_title :: The page title to use on the close account form.
close_account_redirect :: Where to redirect after closing the account.
close_account_requires_password? :: Whether a password is required when closing accounts.
close_account_route :: The route to the close account action. Defaults to +close-account+.
delete_account_on_close? :: Whether to delete the account when closing it, default value is to use +skip_status_checks?+.
== Auth Methods
after_close_account :: Run arbitrary code after closing the account.
before_close_account :: Run arbitrary code before closing an account.
before_close_account_route :: Run arbitrary code before handling a close account route.
close_account :: Close the account, by default setting the account status to closed.
close_account_response :: Return a response after successfully closing the account . By default, redirects to +close_account_redirect+.
close_account_view :: The HTML to use for the close account form.
delete_account :: If +delete_account_on_close?+ is true, delete the account when closing it.
jeremyevans-rodauth-b53f402/doc/confirm_password.rdoc 0000664 0000000 0000000 00000004451 15157255142 0023045 0 ustar 00root root 0000000 0000000 = Documentation for Confirm Password Feature
The confirm password feature allows you to redirect users to a page to
confirm their password.
When confirming passwords, if authenticated via autologin, a remember token,
or an email_auth token, switches the authentication type from that login
method to password.
== Auth Value Methods
confirm_password_additional_form_tags :: HTML fragment containing additional form tags to use on the confirm password form.
confirm_password_button :: The text to use for the confirm password button.
confirm_password_error_flash :: The flash error to show if password confirmation is unsuccessful.
confirm_password_link_text :: The text to use for the link from the two factor auth page.
confirm_password_notice_flash :: The flash notice to show after password confirmed successful.
confirm_password_page_title :: The page title to use on the confirm password form.
confirm_password_redirect :: Where to redirect after successful password confirmation. By default, uses session[confirm_password_redirect_session_key] if set, allowing an easy way to redirect back to the page requesting password confirmation.
confirm_password_redirect_session_key :: The session key used to check for the confirm_password_redirect.
confirm_password_route :: The route to the confirm password form. Defaults to +confirm-password+.
password_authentication_required_error_flash :: The flash error to show if going to a page requiring password confirmation.
password_authentication_required_error_status :: The response status to use if going to a page requiring password confirmation, 401 by default.
password_authentication_required_redirect :: Where to redirect when going to a page requiring password confirmation.
== Auth Methods
after_confirm_password :: Run arbitrary code after successful confirmation of password.
before_confirm_password :: Run arbitrary code before setting that the password has been confirmed.
before_confirm_password_route :: Run arbitrary code before handling the password confirmation route.
confirm_password :: Update the session to reflect the password has been confirmed.
confirm_password_response :: Return a response after successful password confirmation. By default, redirects to +confirm_password_redirect+.
confirm_password_view :: The HTML to use for the confirm password form.
jeremyevans-rodauth-b53f402/doc/create_account.rdoc 0000664 0000000 0000000 00000003733 15157255142 0022447 0 ustar 00root root 0000000 0000000 = Documentation for Create Account Feature
The create account feature allows users to create new accounts.
== Auth Value Methods
create_account_additional_form_tags :: HTML fragment containing additional form tags to use on the create account form.
create_account_button :: The text to use for the create account button.
create_account_error_flash :: The flash error to show for unsuccessful account creation.
create_account_notice_flash :: The flash notice to show after successful account creation.
create_account_page_title :: The page title to use on the create account form.
create_account_redirect :: Where to redirect after creating the account.
create_account_route :: The route to the create account action. Defaults to +create-account+.
create_account_set_password? :: Whether to ask for a password to be set on the create account form. Defaults to true if not verifying accounts. If set to false, an alternative method to set the password should be used (assuming you want to allow password authentication).
== Auth Methods
after_create_account :: Run arbitrary code after creating the account.
before_create_account :: Run arbitrary code before creating the account.
before_create_account_route :: Run arbitrary code before handling a create account route.
create_account_autologin? :: Whether to autologin the user upon successful account creation, true by default unless verifying accounts.
create_account_link_text :: The text to use for a link to the create account form.
create_account_response :: Return a response after successful account creation. By default, redirects to +create_account_redirect+.
create_account_view :: The HTML to use for the create account form.
new_account(login) :: Instantiate a new account hash for the given login, without saving it.
save_account :: Insert the account into the database, or return nil/false if that was not successful.
set_new_account_password :: Set the password for a new account if +account_password_hash_column+ is set, without saving.
jeremyevans-rodauth-b53f402/doc/disallow_common_passwords.rdoc 0000664 0000000 0000000 00000002625 15157255142 0024762 0 ustar 00root root 0000000 0000000 = Documentation for Disallow Common Passwords Feature
The disallow common passwords feature disallows setting of a password
that matches one of the most common passwords. By default, a list of
10,000 of the most common passwords is used, but you can supply your
own file. Using a larger list is recommended, but Rodauth doesn't
ship with a larger list to avoid bloating the size of the gem.
== Auth Value Methods
most_common_passwords :: An object that responds to +include?+ which will return true if the password given is one of the most common passwords. Useful for custom password sets where they are not stored in files and kept in memory.
most_common_passwords_file :: The path to the file containing the most common passwords, which are not allowed to be used for new passwords. Defaults to a list of 10,000 most common passwords that ships with Rodauth. Can be set to nil/false if you do not want to to load common passwords from a file.
password_is_one_of_the_most_common_message :: The error message fragment to display if the given password matches one of the most common passwords.
== Auth Methods
password_one_of_most_common?(password) :: This can be used to override the default check for whether the given password is contained in the most_common_passwords_file. This method may be useful when using very large password databases where you don't want to keep the list of most common passwords in memory.
jeremyevans-rodauth-b53f402/doc/disallow_password_reuse.rdoc 0000664 0000000 0000000 00000003343 15157255142 0024430 0 ustar 00root root 0000000 0000000 = Documentation for Disallow Password Reuse Feature
The disallow password reuse feature disallows setting of a password
that matches a number of previous passwords (6 by default).
On databases where Rodauth supports the use of database authentication
functions, Rodauth also supports the use of database functions for checking
previous passwords, so previous password hashes enjoy the same database
security as current password hashes.
It is not recommended to use this feature unless you have a policy that
requires it. This will significantly slow down setting a new password
due to the need to check all of the previous stored passwords. Additionally,
storing previous passwords means that if attackers can get access to the
the database, they can get the previous stored passwords in addition to the
current password.
== Auth Value Methods
password_same_as_previous_password_message :: The error message fragment to display if the given password is the same as a previous password.
previous_password_account_id_column :: The column in the +previous_password_hash_table+ that stores the account id.
previous_password_hash_column :: The column in the +previous_password_hash_table+ that stores the password hash.
previous_password_hash_table :: The table storing previous password hashes.
previous_password_id_column :: The column in the +previous_password_hash_table+ that stores the autoincrementing primary key.
previous_passwords_to_check :: The number of previous password hashes to store and check.
== Auth Methods
add_previous_password_hash(hash) :: Add the given hash to the list of previous hashes for the current account.
password_doesnt_match_previous_password?(password) :: Whether the password given matches any of the previous passwords.
jeremyevans-rodauth-b53f402/doc/email_auth.rdoc 0000664 0000000 0000000 00000011532 15157255142 0021574 0 ustar 00root root 0000000 0000000 = Documentation for Email Auth Feature
The email auth feature implements passwordless login using links sent via email. It is
similar to the reset password feature, except you don't need to update
a password, or even have a password to login. It depends on the login and
email_base features.
== Auth Value Methods
email_auth_additional_form_tags :: HTML fragment containing additional form tags to use on the email auth login form.
email_auth_deadline_column :: The column name in the +email_auth_table+ storing the deadline after which the token will be ignored.
email_auth_deadline_interval :: The amount of time for which to allow users to use email auth keys, 1 day by default. Only used if set_deadline_values? is true.
email_auth_email_last_sent_column :: The email auth last sent column in the +email_auth_table+, storing the last time the email was sent. Set to nil to always send an email when requested.
email_auth_email_recently_sent_error_flash :: The flash error to show if not sending an email auth email because another was sent recently.
email_auth_email_recently_sent_redirect :: Where to redirect after not sending an email auth email because another was sent recently.
email_auth_email_sent_notice_flash :: The flash notice to show after an email auth email has been sent.
email_auth_email_sent_redirect :: Where to redirect after sending an email auth email.
email_auth_email_subject :: The subject to use for email auth emails.
email_auth_error_flash :: The flash error to show if unable to login using email authentication.
email_auth_id_column :: The id column in the +email_auth_table+, should be a foreign key referencing the accounts table.
email_auth_key_column :: The email auth key/token column in the +email_auth_table+.
email_auth_key_param :: The parameter name to use for the email auth key.
email_auth_page_title :: The page title to use on the email auth form.
email_auth_request_additional_form_tags :: HTML fragment containing additional form tags to use on the email auth request form.
email_auth_request_button :: The text to use for the email auth request button.
email_auth_request_error_flash :: The flash error to show if not able to send an email auth email.
email_auth_request_route :: The route to the email auth request action. Defaults to +email-auth-request+.
email_auth_route :: The route to the email auth action. Defaults to +email-auth+.
email_auth_session_key :: The key in the session to hold the email auth key temporarily.
email_auth_skip_resend_email_within :: The number of seconds required before sending another email auth email, 5 minutes by default.
email_auth_table :: The name of the table storing email auth keys.
force_email_auth? :: Whether email auth should be forced for the account. False by default, which results in email auth only be used automatically if the account does not have a password.
no_matching_email_auth_key_error_flash :: The flash error message to show if attempting to access the email auth form with an invalid key.
== Auth Methods
account_from_email_auth_key(key) :: Retrieve the account using the given email auth key, or return nil if no account matches.
after_email_auth_request :: Run arbitrary code after sending the email auth email.
before_email_auth_request :: Run arbitrary code before sending the email auth email.
before_email_auth_request_route :: Run arbitrary code before handling an email auth request route.
before_email_auth_route :: Run arbitrary code before handling an email auth route.
create_email_auth_email :: A Mail::Message for the email auth email.
create_email_auth_key :: Add the email auth key data to the database.
email_auth_email_body :: The body to use for the email auth email.
email_auth_email_link :: The link to the email auth form in the email auth email.
email_auth_email_sent_response :: Return a response after successfully sending an email auth email. By default, redirects to +email_auth_email_sent_redirect+.
email_auth_key_insert_hash :: The hash to insert into the +email_auth_table+.
email_auth_key_value :: The email auth key for the current account.
email_auth_request_form :: The HTML to use for a form to request an email auth email, shown on the login page after the user submits their login, if +force_email_auth?+ is false and email authentication is not the only possible for of authentication for the user.
email_auth_view :: The HTML to use for the email auth form.
get_email_auth_email_last_sent :: Get the last time an email auth email is sent, or nil if there is no last sent time.
get_email_auth_key(id) :: Get the email auth key for the given account id from the database.
remove_email_auth_key :: Remove the email auth key for the current account, run after successful email auth.
send_email_auth_email :: Send the email auth email.
set_email_auth_email_last_sent :: Set the last time an email auth email is sent. This is only called if there is a previous email auth token still active.
jeremyevans-rodauth-b53f402/doc/email_base.rdoc 0000664 0000000 0000000 00000002361 15157255142 0021545 0 ustar 00root root 0000000 0000000 = Documentation for Email Base Feature
The email base feature is automatically loaded when you use a Rodauth feature
that requires sending emails.
== Auth Value Methods
allow_raw_email_token? :: When +hmac_secret+ is used, this allows the use of the raw token. This should only be set to true temporarily during a transition period from using raw tokens to using HMACed tokens. After the transition period, this should not be set, as setting this to true removes the security that HMACed tokens add.
default_post_email_redirect :: Where to redirect after sending an email. This is the default redirect location for all redirects after an email is sent when the account is not logged in. Also includes cases where an email is not sent due to rate limiting.
email_from :: The from address to use for emails sent by Rodauth.
email_subject_prefix :: The prefix to use for email subjects
require_mail? :: Set to false to not require mail, useful if using a different library for sending email.
== Auth Methods
create_email(subject, body) :: Return a Mail::Message instance with the given subject and body.
email_to :: The email address to send emails to, by default the login of the current account.
send_email(email) :: Deliver a given Mail::Message instance.
jeremyevans-rodauth-b53f402/doc/error_reasons.rdoc 0000664 0000000 0000000 00000004505 15157255142 0022351 0 ustar 00root root 0000000 0000000 = Error Reasons
Rodauth allows for customizing response status codes and error
messages for each type of error. However, in some cases, the
response status code is too coarse for desired error handling
by the application (since many error types use the same status
code), and using the error message is too fragile since it may
be translated.
For this reason, Rodauth associates a fine grained reason for
each type of error. If an error occurs in Rodauth, it will
call the +set_error_reason+ method with a symbol for the
specific type of error. By default, this method does not do
anything, but you can use the +set_error_reason+ configuration
method to customize the error handling.
These are the currently supported error type symbols that
Rodauth will call +set_error_reason+ with:
* :account_locked_out
* :already_an_account_with_this_login
* :already_an_unverified_account_with_this_login
* :duplicate_webauthn_id
* :inactive_session
* :invalid_email_auth_key
* :invalid_otp_auth_code
* :invalid_otp_secret
* :invalid_password
* :invalid_password_pattern
* :invalid_phone_number
* :invalid_previous_password
* :invalid_recovery_code
* :invalid_remember_param
* :invalid_reset_password_key
* :invalid_sms_code
* :invalid_sms_confirmation_code
* :invalid_unlock_account_key
* :invalid_verify_account_key
* :invalid_verify_login_change_key
* :invalid_webauthn_auth_param
* :invalid_webauthn_id
* :invalid_webauthn_remove_param
* :invalid_webauthn_setup_param
* :invalid_webauthn_sign_count
* :login_not_valid_email
* :login_required
* :login_too_long
* :login_too_many_bytes
* :login_too_short
* :logins_do_not_match
* :no_current_sms_code
* :no_matching_login
* :not_enough_character_groups_in_password
* :otp_locked_out
* :password_authentication_required
* :password_contains_null_byte
* :password_does_not_meet_requirements
* :password_in_dictionary
* :password_is_one_of_the_most_common
* :password_same_as_previous_password
* :password_too_long
* :password_too_many_bytes
* :password_too_short
* :passwords_do_not_match
* :same_as_current_login
* :same_as_existing_password
* :session_expired
* :sms_already_setup
* :sms_locked_out
* :sms_needs_confirmation
* :sms_not_setup
* :too_many_repeating_characters_in_password
* :two_factor_already_authenticated
* :two_factor_need_authentication
* :two_factor_not_setup
* :unverified_account
* :webauthn_not_setup
jeremyevans-rodauth-b53f402/doc/guides/ 0000775 0000000 0000000 00000000000 15157255142 0020071 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/doc/guides/admin_activation.rdoc 0000664 0000000 0000000 00000003401 15157255142 0024251 0 ustar 00root root 0000000 0000000 = Require account verification by admin
There are scenarios in which, instead of allowing the user to verify they have
access to the email for the account, you may want to have an admin or moderator
approve new accounts manually. One way this can be achieved by sending the
account verification email to the admin:
plugin :rodauth do
enable :login, :logout, :verify_account, :reset_password
# Send account verification email to the admin
email_to do
if account[account_status_column] == account_unverified_status_value
"admin@myapp.com"
else
super()
end
end
# Do not ask for password when creating or verifying account
verify_account_set_password? false
create_account_set_password? false
# Adjust the account verification email subject and body
verify_account_email_subject "New User Awaiting Admin Approval"
verify_account_email_body do
"The user #{account[login_column]} has created an account. Click here to approve it: #{verify_account_email_link}."
end
# Display this message to the user after they've created their account
verify_account_email_sent_notice_flash "Your account has been created and is awaiting approval"
# Prevent the admin from being logged in after confirming the account
verify_account_autologin? false
verify_account_notice_flash "The account has been approved"
# Send a reset password email after verifying the account.
# This allows the user to choose the password for the account,
# and also makes sure the user can only log in if they have
# access to the email address for the account.
after_verify_account do
generate_reset_password_key_value
create_reset_password_key
send_reset_password_email
end
end
jeremyevans-rodauth-b53f402/doc/guides/already_authenticated.rdoc 0000664 0000000 0000000 00000000643 15157255142 0025270 0 ustar 00root root 0000000 0000000 = Skip login page if already authenticated
In some cases it may be useful to skip login/registration pages when the user
is already logged in. This can be achieved as follows. Note that this only
matters if the user manually navigates to the login or create account pages.
plugin :rodauth do
# Redirect logged in users to the wherever login redirects to
already_logged_in { redirect login_redirect }
end
jeremyevans-rodauth-b53f402/doc/guides/alternative_login.rdoc 0000664 0000000 0000000 00000003216 15157255142 0024452 0 ustar 00root root 0000000 0000000 = Use a non-email login
Rodauth's by default uses email addresses for identifying users, since that is
the most common form of identifier currently. In some cases, you might want
to allow logging in via alternative identifiers, such as a username. In this
case, it is best to choose a different column name for the login, such as
+:username+. Among other things, this also makes it so that the login field
does not expect an email address to be provided.
plugin :rodauth do
enable :login, :logout
login_column :username
end
Note that Rodauth features that require sending email need an email address, and
that defaults to the value of the login column. If you have both a username and
an email for an account, you can have the login column be the user, and use the
value of the email column for the email address.
plugin :rodauth do
enable :login, :logout, :reset_password
login_column :username
email_to do
account[:email]
end
end
An alternative approach would be to accept a login and automatically change it
to an email address. If you have a +username+ field on the +accounts+ table,
then you can configure Rodauth to allow entering a username instead of email
during login. See the {Adding new registration field}[rdoc-ref:doc/guides/registration_field.rdoc]
guide for instructions on requiring add an additional field during registration.
plugin :rodauth do
enable :login, :logout
account_from_login do |login|
# handle the case when login parameter is a username
unless login.include?("@")
login = db[:accounts].where(username: login).get(:email)
end
super(login)
end
end
jeremyevans-rodauth-b53f402/doc/guides/case_insensitive_login.rdoc 0000664 0000000 0000000 00000001164 15157255142 0025467 0 ustar 00root root 0000000 0000000 = Case insensitive logins
If your database schema doesn't support case insensitive logins, you can tell
Rodauth to automatically lowercase login param values during authentication and
persistence via the +normalize_login+ configuration option:
normalize_login(&:downcase)
Of the four database types Rodauth officially supports (PostgreSQL, MySQL,
Microsoft SQL Server, and SQLite), only SQLite does not support a case
insensitive column for storing logins by default. However, other databases could
be configured to not use a case insensitive column for logins by default, in
which case you would want to use this setting.
jeremyevans-rodauth-b53f402/doc/guides/change_table_and_column_names.rdoc 0000664 0000000 0000000 00000001536 15157255142 0026725 0 ustar 00root root 0000000 0000000 = Change table and column names
All tables that Rodauth uses will have a configuration method that ends with
+_table+ for configuring the table name. For example, if you store user accounts
in the +users+ table instead of +accounts+ table, you can use the following
in your configuration:
accounts_table :users
All columns that Rodauth uses will have a configuration method that ends with
+_column+ for configuring the column name. For example, if you are storing the
login for accounts in the +login+ column instead of the +email+ column, you
can use the following in your configuration:
login_column :login
Please see the documentation for Rodauth features for the names of the
configuration methods that you can use. You can see the default values for
the tables and columns in the {"Creating tables" section of the README}[rdoc-ref:README.rdoc].
jeremyevans-rodauth-b53f402/doc/guides/create_account_programmatically.rdoc 0000664 0000000 0000000 00000002146 15157255142 0027353 0 ustar 00root root 0000000 0000000 = Create an account record programmatically
In some scenarios you might want to create an account records programmatically,
for example in your tests.
If you're storing passwords in a separate table, you can create an account
records as follows:
account_id = DB[:accounts].insert(
email: "name@example.com",
status_id: 2, # verified
)
DB[:account_password_hashes].insert(
id: account_id,
password_hash: BCrypt::Password.create("secret").to_s,
)
If the password is stored in a column in the accounts table:
account_id = DB[:accounts].insert(
email: "name@example.com",
password_hash: BCrypt::Password.create("secret").to_s,
status_id: 2, # verified
)
If you are creating accounts in your tests, you probably want to use
the +:cost+ option, otherwise you will have very slow tests:
account_id = DB[:accounts].insert(
email: "name@example.com",
status_id: 2, # verified
)
DB[:account_password_hashes].insert(
id: account_id,
password_hash: BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST).to_s,
)
jeremyevans-rodauth-b53f402/doc/guides/delay_password.rdoc 0000664 0000000 0000000 00000001744 15157255142 0023770 0 ustar 00root root 0000000 0000000 = Set password when verifying account
If you want to request less information from the user on registration, you can
ask the user to set their password only when they verify their account:
plugin :rodauth do
enable :login, :logout, :verify_account
verify_account_set_password? true
end
Note that this is already the default behaviour when verify account feature is
loaded, but it's not when verify account grace period is used, because it would
prevent the account from logging in during the grace period. You can work around
this by automatically remembering their login during account creation using the
remember feature. Be aware that remembering accounts has effects beyond the
verification period, and this would only allow automatic logins from the browser
that created the account.
plugin :rodauth do
enable :login, :logout, :verify_account_grace_period, :remember
verify_account_set_password? true
after_create_account do
remember_login
end
end
jeremyevans-rodauth-b53f402/doc/guides/email_only.rdoc 0000664 0000000 0000000 00000001033 15157255142 0023067 0 ustar 00root root 0000000 0000000 = Allow only email authentication
When using the email authentication feature, you can avoid other authentication
mechanisms entirely as follows:
plugin :rodauth do
enable :login, :email_auth, :create_account, :verify_account
create_account_set_password? false
verify_account_set_password? false
force_email_auth? true
end
With this configuration, users won't be required to enter a password on
registration, and on login the email authentication link will automatically be
sent after the email address is entered.
jeremyevans-rodauth-b53f402/doc/guides/email_requirements.rdoc 0000664 0000000 0000000 00000001667 15157255142 0024646 0 ustar 00root root 0000000 0000000 = Customize email requirements
By default, Rodauth requires emails to have at least 3 characters and at most
255 bytes. You can modify the minimum and maximum length:
plugin :rodauth do
enable :login, :logout, :create_account
# Require emails to have at least 5 characters
login_minimum_length 5
# Don't allow emails longer than 100 characters
login_maximum_length 100
# Don't allow emails larger than 200 bytes
login_maximum_bytes 200
end
You can also override email address validation, and do more advanced email
checks, such as checking whether the email address exists using the
{Truemail}[https://github.com/truemail-rb/truemail] gem:
require "truemail"
Truemail.configure do |config|
config.verifier_email = "verifier@example.com"
end
plugin :rodauth do
enable :login, :logout, :create_account
login_valid_email? do |email|
super(email) && Truemail.valid?(email)
end
end
jeremyevans-rodauth-b53f402/doc/guides/i18n.rdoc 0000664 0000000 0000000 00000001757 15157255142 0021533 0 ustar 00root root 0000000 0000000 = Translate with i18n gem
Rodauth allows transforming user-facing text configuration such as flash
messages, validation errors, labels etc. via the +translate+ configuration
method. This method receives a name of a configuration along with its default
value, and is expected to return the result text.
You can use this to perform translations using the
{i18n gem}[https://github.com/ruby-i18n/i18n]:
plugin :rodauth do
enable :login, :logout, :reset_password
translate do |key, default|
I18n.translate("rodauth.#{key}") || default
end
end
Your translation file may then look something like this:
en:
rodauth:
login_notice_flash: "You have been signed in"
require_login_error_flash: "Login is required for accessing this page"
no_matching_login_message: "user with this email address doesn't exist"
reset_password_email_subject: "Password Reset Instructions"
Alternatively, you can use the
{rodauth-i18n}[https://github.com/janko/rodauth-i18n] gem.
jeremyevans-rodauth-b53f402/doc/guides/internals.rdoc 0000664 0000000 0000000 00000026460 15157255142 0022751 0 ustar 00root root 0000000 0000000 = Rodauth Internals
Rodauth's implementation heavily uses metaprogramming in order to DRY up the codebase, which can be a little intimidating to developers who are not familiar with the codebase. This guide explains how Rodauth is built, which should make the internals easier to understand.
== Object Model
First, let's talk about the basic parts of Rodauth.
=== Rodauth::Auth
Rodauth::Auth is the core of rodauth. If a user calls +rodauth+ inside their Roda application, they get a Rodauth::Auth subclass instance. Rodauth's configuration DSL is designed to build a Rodauth::Auth subclass appropriate to the application, by loading only the features that are needed, and overriding defaults as appropriate.
=== Rodauth::Configuration
Inside the block you pass to plugin :rodauth, +self+ is an instance of this class. This class is mostly empty, as most of Rodauth is implemented as separate features, and the configuration for each feature is loaded as a separate module into this instance.
=== Rodauth::Feature
Each of the parts of rodauth that you can use is going to be a separate feature. Rodauth::Feature is a Module subclass, and every rodauth feature you load is an instance of this class, which is included in the Rodauth::Auth subclass used by the Roda application. Rodauth::Feature has many methods designed to make building Rodauth features easier by defining methods in the Rodauth::Feature instance.
=== Rodauth::FeatureConfiguration
Just as each feature is a module included in the Rodauth::Auth subclass for the application, each feature also contains a configuration module that is an instance of Rodauth::FeatureConfiguration (also a module subclass). For each feature you load into the Rodauth configuration, the Rodauth::Configuration instance is extended with the feature's Rodauth::FeatureConfiguration instance, which is what makes the feature's configuration methods available inside the plugin :rodauth block. This is why you need to enable the features in Rodauth before configuring them.
== Object Model Example
Here's some commented output hopefully showing the relation between the different parts
Roda.plugin :rodauth do
self # => # (instance)
auth # => Rodauth::Auth subclass
singleton_class.ancestors # => [#> (singleton class of self),
# Rodauth::FeatureConfiguration::Base (instance of Rodauth::FeatureConfiguration),
# Rodauth::Configuration,
# ...]
auth.ancestors # => [Rodauth::Auth subclass,
# Rodauth::Base (instance of Rodauth::Feature),
# Rodauth::Auth,
# ...]
enable :login
singleton_class.ancestors # => [#> (singleton class of self),
# Rodauth::FeatureConfiguration::Login (instance of Rodauth::FeatureConfiguration),
# Rodauth::FeatureConfiguration::Base (instance of Rodauth::FeatureConfiguration),
# Rodauth::Configuration,
# ...]
auth.ancestors # => [Rodauth::Auth subclass,
# Rodauth::Login (instance of Rodauth::Feature),
# Rodauth::Base (instance of Rodauth::Feature),
# Rodauth::Auth,
# ...]
end
Roda.rodauth # => Rodauth::Auth subclass
Roda.rodauth.ancestors # => [Rodauth::Auth subclass,
# Rodauth::Login (instance of Rodauth::Feature),
# Rodauth::Base (instance of Rodauth::Feature),
# Rodauth::Auth,
# ...]
Roda.route do |r|
rodauth # => Rodauth::Auth subclass instance
end
== Feature Creation Example
Here's a heavily commented example showing what is going on inside a Rodauth feature.
module Rodauth
# Feature.define takes a symbol, specifying the name of the feature. This
# is the same symbol you would pass to enable when loading the feature into
# the Rodauth configuration. Feature is a module subclass, and Feature.define
# is a class method that creates an instance of Feature (a module) and executes
# the block in the context of the Feature instance.
#
# The second argument is optional, and sets the Feature instance and related
# FeatureConfiguration instance to a constant in the Rodauth namespace, which
# makes it easier to locate via inspect.
Feature.define(:foo, :Foo) do
# Inside this block, self is an instance of Feature. As this instance of
# Feature will be included in the Rodauth::Auth subclass instance if
# the feature is loaded into the rodauth configuration, methods you define
# in this block (via def or define_method) will be callable on any
# rodauth object if this feature is loaded into the rodauth configuration.
# Feature has many instance methods that define methods in the Feature
# instance. This is one of those methods, which sets the text of the notice
# flash, shown after successful submission of the form. It's basically
# equivalent to executing this code in the feature:
#
# def foo_notice_flash
# "It worked!"
# end
#
# while also adding a method to the configuration which does:
#
# def foo_notice_flash(v=nil, &block)
# block ||= proc{v}
# @auth.class_eval do
# define_method(:foo_notice_flash, &block)
# end
# end
#
# This is what easily allows you to modify any part of Rodauth during
# configuration. The Rodauth::Auth subclass has the default behavior
# added via a method in an included module (the Feature instance), and the
# Rodauth::Configuration instance has a method that when called defines
# a method in the Rodauth::Auth subclass itself, which will take precedence
# over the default method, which defined in the included Feature instance.
notice_flash "It worked!"
# The rest of these method calls are fairly similar to notice_flash.
# This defines the foo_error_flash method, for the error flash message to
# show if the form submission wasn't successful.
error_flash "There was an error"
# This defines the foo_view method to use template 'foo.str' in the templates
# folder, and set the title of the page to 'Foo'.
view 'foo', 'Foo'
# This defines the foo_additional_form_tags method, which would generally be called
# inside the foo.str template.
additional_form_tags
# This defines the foo_button method, for the text to use on the submit button
# for the form in foo.str.
button 'Submit'
# This defines the foo_redirect method, for where to redirect after successful submission
# of the form.
redirect
# This defines the before_foo method, called before performing the foo action.
before
# This defines the after_foo method, called after successfully performing the foo action.
after
# This defines a loaded_templates method that calls super and adds 'foo' as one of the
# templates. This is necessary for precompilation of templates to work.
loaded_templates ['foo']
# This defines the following methods related to sending email:
#
# * foo_email_subject: uses given subject
# * foo_email_body: renders foo-email template
# * create_foo_email: creates Mail::Message using subject and body
# * send_foo_email: sends created email
#
# The foo-email template should be included in the loaded_templates call to make sure
# template precompilation works.
email :foo, 'Foo Subject'
# auth_value_method is a generic method that takes two arguments, a method to define
# and a default value. It is similar to the methods above, except that it allows
# arbitrary method names. The notice_flash, error_flash, button, and additional_form_tags
# methods are actually defined in terms of this method.
#
# So this particular method defines a foo_error_status method that will return 401 by
# default, but also adds a configuration method that allows you to override the default.
auth_value_method :foo_error_status, 401
# This is similar to auth_value_method, but it only adds the configuration method.
# Using this should only be done if you have defining the method in the feature
# separately (see below).
auth_value_methods :foo_bar
# This is similar to auth_value_methods, but it changes the configuration method so that
# a block is required and you cannot provide an argument. This is used for the cases
# where a statically defined value would never make sense, such as when any correct
# behavior would depend on accessing request-specific information.
auth_methods :foo
# route defines a route used for the feature. This is the code that will be executed
# if a user goes to /foo in the Roda app.
route do |r|
# Inside the block, you are in the context of the Rodauth::Auth subclass instance.
# r is the Roda::RodaRequest subclass instance, just as it would be for a Roda
# route block.
# route adds a before_foo_route method that by default does nothing. It also
# adds a configuration method that you can call to set behavior that will be
# executed before routing.
before_foo_route
# Just like in Roda, r.get is called for GET requests
r.get do
# This will render a view to the user, using the foo.erb template from the
# templates directory (unless the user has overridden it), inside the Roda
# application's layout.
foo_view
end
# Just like in Roda, r.post is called for POST requests
r.post do
# This is called before performing the foo action
before_foo
# This assumes foo returns false or nil on failure, or otherwise on
# success.
if foo
# In general, Rodauth only calls after_foo if foo is successful.
after_foo
# Successful form submission will usually set the notice flash,
# the redirect to the appropriate page.
set_notice_flash foo_notice_flash
redirect foo_redirect
else
# Unsuccessful form subsmission will usually set the error flash,
# the redisplay the page so that the submission can be fixed.
set_error_flash foo_error_flash
foo_view
end
end
end
# This is the default behavior for the foo method, if a user doesn't
# call the foo method inside the configuration block.
def foo
# Do Something
end
# This is the default behavior for the foo_bar method, if a user doesn't
# call the foo_bar method inside the configuration block.
def foo_bar
42
end
end
end
jeremyevans-rodauth-b53f402/doc/guides/links.rdoc 0000664 0000000 0000000 00000000712 15157255142 0022062 0 ustar 00root root 0000000 0000000 = Display authentication links
You can retrieve a relative URL to any Rodauth action by calling the
corresponding *_path method on the Rodauth instance:
Sign in
Sign up
For absolute URLs instead of paths, you can use the *_url methods:
Sign in
Sign up
jeremyevans-rodauth-b53f402/doc/guides/login_return.rdoc 0000664 0000000 0000000 00000002367 15157255142 0023461 0 ustar 00root root 0000000 0000000 = Redirect to original page after login
When the user attempts to open a page that requires authentication, Rodauth
redirects them to the login page. It can be useful to redirect them back to
the page they originally requested after successful login. Similarly, you
can do this for pages requiring multifactor authentication.
plugin :rodauth do
enable :login, :logout, :otp
# Have successful login redirect back to originally requested page
login_return_to_requested_location? true
# Have successful multifactor authentication redirect back to
# originally requested page
two_factor_auth_return_to_requested_location? true
end
You can manually set which page to redirect after login or multifactor
authentication, though it is questionable whether the user will desire
this behavior compared to the default.
route do |r|
r.rodauth
# Return the last visited path after login
if rodauth.logged_in?
# Return to the last visited page after multifactor authentication
unless rodauth.two_factor_authenticated?
session[rodauth.two_factor_auth_redirect_session_key] = request.fullpath
end
else
session[rodauth.login_redirect_session_key] = request.fullpath
end
# rest of routes
end
jeremyevans-rodauth-b53f402/doc/guides/migrate_password_hash_algorithm.rdoc 0000664 0000000 0000000 00000001144 15157255142 0027365 0 ustar 00root root 0000000 0000000 = Migrate users passwords from bcrypt to argon2 or back
If you are currently using the default bcrypt password hash algorithm, and want to
gradually migrate to the argon2 password hash algorithm, you can use both the argon2
and update_password_hash features:
plugin :rodauth do
enable :login, :update_password_hash, :argon2
end
When a user with a current bcrypt password hash next successfully uses their
password, their password hash will be migrated to argon2.
If for some reason you want to migrate back from argon2 to bcrypt, you can set
use_argon2? false in your Rodauth configuration.
jeremyevans-rodauth-b53f402/doc/guides/password_column.rdoc 0000664 0000000 0000000 00000001457 15157255142 0024170 0 ustar 00root root 0000000 0000000 = Store password hash in accounts table
By default, Rodauth stores the password hash in a separate
+account_password_hashes+ table. This makes it a lot less likely that the
password hashes will be leaked, especially if you use Rodauth's default
approach of using database functions for checking the hashes.
However, if you have reasons for storing the password hashes in +accounts+
table that outweigh the security benefits of Rodauth's default approach,
Rodauth supports that.
To do this, add the password hash column to the +accounts+ table:
alter_table :accounts do
add_column :password_hash, String
end
And then tell Rodauth to use it:
plugin :rodauth do
enable :login, :logout
# Use the password_hash column in the accounts table
account_password_hash_column :password_hash
end
jeremyevans-rodauth-b53f402/doc/guides/password_confirmation.rdoc 0000664 0000000 0000000 00000002305 15157255142 0025354 0 ustar 00root root 0000000 0000000 = Require password confirmation for certain actions
You might want to require the user to enter their password before accessing
sensitive sections of the app. This functionality is provided by the confirm
password feature, which accompanied with the password grace period feature will
remember the entered password for a period of time:
plugin :rodauth do
enable :confirm_password, :password_grace_period
# Remember the password for 1 hour
password_grace_period 60*60
end
route do |r|
r.rodauth
r.is 'some-action' do
# Require password authentication if the password has not been
# input recently.
rodauth.require_password_authentication
# ...
end
end
You can also do this for Rodauth actions that normally require a password.
Which essentially moves the password confirmation into a separate step, as
Rodauth's behavior with the password grace period feature is to ask for the
password on the same form.
plugin :rodauth do
enable :confirm_password, :password_grace_period, :change_login, :change_password
before_change_login_route { require_password_authentication }
before_change_password_route { require_password_authentication }
end
jeremyevans-rodauth-b53f402/doc/guides/password_requirements.rdoc 0000664 0000000 0000000 00000003116 15157255142 0025410 0 ustar 00root root 0000000 0000000 = Customize password requirements
By default, Rodauth requires passwords to have at least 6 characters. You can
modify the minimum and maximum length:
plugin :rodauth do
enable :login, :logout, :create_account
# Require passwords to have at least 8 characters
password_minimum_length 8
# Don't allow passwords to be too long, to prevent long password DoS attacks
password_maximum_length 64
end
You can use the {disallow common passwords feature}[rdoc-ref:doc/disallow_common_passwords.rdoc]
to prevent the usage of common passwords (the most common 10,000 by default).
You can use additional complexity checks on passwords via the {password
complexity feature}[rdoc-ref:doc/password_complexity.rdoc], though most of
those complexity checks are no longer considered modern security best
practices and are likely to decrease overall security.
If you want complete control over whether passwords meet requirements, you
can use the password_meets_requirements? configuration method.
plugin :rodauth do
enable :login, :logout, :create_account
password_meets_requirements? do |password|
super(password) && password_complex_enough?(password)
end
auth_class_eval do
# If password doesn't pass custom validation, add field error with error
# reason, and return false.
def password_complex_enough?(password)
return true if password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/)
set_password_requirement_error_message(:password_simple, "requires one number and one special character")
false
end
end
end
jeremyevans-rodauth-b53f402/doc/guides/paths.rdoc 0000664 0000000 0000000 00000002564 15157255142 0022070 0 ustar 00root root 0000000 0000000 = Change route path
You can change the URL path of any Rodauth route by overriding the
corresponding *_route method:
plugin :rodauth do
enable :login, :logout, :create_account, :reset_password
# Change login route to "/signin"
login_route "signin"
# Change redirect when login is required to "/signin"
require_login_redirect { login_path }
# Change create account route to "/register"
create_account_route "register"
# Change password reset request route to "/reset-password/request"
reset_password_request_route "reset-password/request"
end
If you want to add a prefix to all Rodauth routes, you should use the +prefix+
setting:
plugin :rodauth do
enable :login, :logout
# Use /auth prefix to each Rodauth route
prefix "/auth"
end
route do |r|
r.on "auth" do
# Serve Rodauth routes under the /auth branch of the routing tree
r.rodauth
end
# ...
end
There are cases where you may want to disable certain routes. For example, you
may want to enable the create_account feature to allow creating admins, but
only make it possible programmatically via internal requests. In this case,
you should set the corresponding *_route method to +nil+:
plugin :rodauth, name: :admin do
enable :create_account
# disable the /create-account route
create_account_route nil
end
jeremyevans-rodauth-b53f402/doc/guides/query_params.rdoc 0000664 0000000 0000000 00000000451 15157255142 0023452 0 ustar 00root root 0000000 0000000 = Pass query parameters to auth URLs
The *_path and *_url methods allow passing additional query parameters:
rodauth.create_account_path(type: "seller")
#=> "/create-account?type=seller"
rodauth.login_url(type: "operator")
#=> "https//example.com/login?type=operator"
jeremyevans-rodauth-b53f402/doc/guides/redirects.rdoc 0000664 0000000 0000000 00000001022 15157255142 0022721 0 ustar 00root root 0000000 0000000 = Change redirect destination
You can change the redirect destination for any Rodauth action by overriding
the corresponding *_redirect method:
plugin :rodauth do
enable :login, :logout, :create_account, :reset_password
# Redirect to "/dashboard" after login
login_redirect "/dashboard"
# Redirect to wherever login redirects to after creating account
create_account_redirect { login_redirect }
# Redirect to login page after password reset
reset_password_redirect { login_path }
end
jeremyevans-rodauth-b53f402/doc/guides/registration_field.rdoc 0000664 0000000 0000000 00000004144 15157255142 0024622 0 ustar 00root root 0000000 0000000 = Add new field during account creation
The create account form only handles login and password parameters by
default. However, you might want to ask for additional information during
account creation, such as requiring the user to also enter their full name
or their company's name.
== A) Accounts table
Let's assume you wanted to wanted to store the additional field(s) directly on
the +accounts+ table:
alter_table :accounts do
add_column :name, String
end
You need to override the create-account template, which by default in
Rodauth you can do by adding a create-account.erb template in your
Roda +views+ directory.
Once you've added the create-account.erb template, and had it include
a field for the +name+, you can handle the submission of that field in a before
create account hook:
plugin :rodauth do
enable :login, :logout, :create_account
before_create_account do
# Validate presence of the name field. This example checks that the was field was submitted
# and is not empty, but you may may want to have application specific checks.
if param("name").empty?
throw_error_status(422, "name", "must be present")
end
# Assign the new field to the account record
account[:name] = param("name")
end
end
== B) Separate table
Alternatively, you can store the additional field(s) in separate table, for
example:
create_table :account_names do
foreign_key :account_id, :accounts, primary_key: true, type: :Bignum
String :name, null: false
end
You can then handle the new submitted field as follows:
plugin :rodauth do
enable :login, :logout, :create_account
before_create_account do
# Validate presence of the name field
throw_error_status(422, "name", "must be present") if param("name").empty?
end
after_create_account do
# Create the associated record
db[:account_names].insert(account_id: account[:id], name: param("name"))
end
after_close_account do
# Delete the associated record
db[:account_names].where(account_id: account[:id]).delete
end
end
jeremyevans-rodauth-b53f402/doc/guides/render_confirmation.rdoc 0000664 0000000 0000000 00000001730 15157255142 0024772 0 ustar 00root root 0000000 0000000 = Render confirmation view
Most Rodauth actions redirect and display a flash notice after they're successfully performed. However, in some cases you may wish to render a view confirming that the action was successful, for nicer user experience.
For example, when the user creates an account, you might render a page with a call to action to verify their account. Assuming you've created an +account_created+ view template alongside your other Rodauth templates, you can configure the following:
after_create_account do
# render "account_created" view template with page title of "Account created!"
return_response view("account_created", "Account created!")
end
Similarly, when the user has requested a password reset, you can render a page telling them to check their email:
after_reset_password_request do
# render "password_reset_sent" view template with page title of "Password sent!"
return_response view("password_reset_sent", "Password sent!")
end
jeremyevans-rodauth-b53f402/doc/guides/require_mfa.rdoc 0000664 0000000 0000000 00000001614 15157255142 0023243 0 ustar 00root root 0000000 0000000 = Require multifactor authentication after login
You may want to require multifactor authentication on login for people
that have multifactor authentication set up. The +require_authentication+
Rodauth method works for pages that require an authenticated user, but not for
pages where authentication is optional.
You can set this up as follows:
plugin :rodauth do
enable :login, :logout, :otp
# If you don't want to show an error message when redirecting
# to the multifactor authentication page.
two_factor_need_authentication_error_flash nil
# Display the same flash message after multifactor
# authentication than is displayed after login
two_factor_auth_notice_flash { login_notice_flash }
end
route do |r|
r.rodauth
if rodauth.logged_in? && rodauth.two_factor_authentication_setup?
rodauth.require_two_factor_authenticated
end
# ...
end
jeremyevans-rodauth-b53f402/doc/guides/reset_password_autologin.rdoc 0000664 0000000 0000000 00000001175 15157255142 0026073 0 ustar 00root root 0000000 0000000 = Autologin after password reset
When the user resets their password, by default they are not automatically
logged in. You can change this behaviour and login the user automatically
after password reset.
plugin :rodauth do
enable :login, :logout, :reset_password
reset_password_autologin? true
end
Similarly, when the verify login change feature is used, the user is not
automatically logged in after verifying the login change. You can configure
Rodauth to automatically log the user in in this case:
plugin :rodauth do
enable :login, :logout, :verify_login_change
verify_login_change_autologin? true
end
jeremyevans-rodauth-b53f402/doc/guides/share_configuration.rdoc 0000664 0000000 0000000 00000001713 15157255142 0024775 0 ustar 00root root 0000000 0000000 = Share configuration via inheritance
If you have multiple configurations that needs to share some amount of
authentication behaviour, you can do so through inheritance. For example:
require "rodauth"
class RodauthBase < Rodauth::Auth
configure do
# common authentication configuration
end
end
class RodauthMain < RodauthBase # inherit common configuration
configure do
# main-specific authentication configuration
end
end
class RodauthAdmin < RodauthBase # inherit common configuration
configure do
# admin-specific authentication configuration
end
end
class RodauthApp < Roda
plugin :rodauth, auth_class: RodauthMain
plugin :rodauth, auth_class: RodauthAdmin, name: :admin
# ...
end
However, when doing this, you need to be careful that you do not use a
configuration method in a superclass, and then load a feature in a subclass
that overrides the configuration you set in the superclass.
jeremyevans-rodauth-b53f402/doc/guides/status_column.rdoc 0000664 0000000 0000000 00000002047 15157255142 0023645 0 ustar 00root root 0000000 0000000 = Store account status in a text column
By default, Rodauth recommends using a separate table for account statuses, and
linking them via foreign keys. This is useful as it achieves an enum-like
behaviour, where the database ensures a constrained set of status values.
However, if you use a testing environment that starts with a blank database,
and don't want to fix your testing environment to support real foreign keys,
you can configure Rodauth to store the account status in a text column.
Doing so results in problems if a text value you do not expect gets stored
in the column. We can mitigate the problems by using a CHECK constraint
on the column.
create_table :accounts do
# ...
String :status, null: false, default: "verified",
check: {status: %w'unverified verified closed'}
end
Then we can configure Rodauth to support this.
plugin :rodauth do
# ...
account_status_column :status
account_unverified_status_value "unverified"
account_open_status_value "verified"
account_closed_status_value "closed"
end
jeremyevans-rodauth-b53f402/doc/guides/totp_or_recovery.rdoc 0000664 0000000 0000000 00000001010 15157255142 0024336 0 ustar 00root root 0000000 0000000 = Allow recovery code on TOTP code field
If using the otp feature, for convenience you might want to allow
the user to enter the recovery code into the TOTP code field, instead
of requiring they use the separate recovery codes form. You can
implement this using the following configuration:
plugin :rodauth do
enable :login, :logout, :otp, :recovery_codes
before_otp_auth_route do
if recovery_code_match?(param(otp_auth_param))
two_factor_authenticate("recovery_code")
end
end
end
jeremyevans-rodauth-b53f402/doc/http_basic_auth.rdoc 0000664 0000000 0000000 00000001476 15157255142 0022633 0 ustar 00root root 0000000 0000000 = Documentation for HTTP Basic Auth Feature
The HTTP basic auth feature allows logins using HTTP basic authentication,
described in RFC 1945.
In your routing block, you can require HTTP basic authentication via:
rodauth.require_http_basic_auth
If you want to allow HTTP basic authentication but not require it, you can
call:
rodauth.http_basic_auth
== Auth Value Methods
http_basic_auth_realm :: The realm to return in the WWW-Authenticate header.
require_http_basic_auth? :: If true, when +rodauth.require_login+ or +rodauth.require_authentication+ is used, return a 401 status page if basic auth has not been provided, instead of redirecting to the login page. If false, +rodauth.require_login+ or +rodauth.require_authentication+ will check for HTTP basic authentication if not already logged in. False by default.
jeremyevans-rodauth-b53f402/doc/internal_request.rdoc 0000664 0000000 0000000 00000044450 15157255142 0023055 0 ustar 00root root 0000000 0000000 = Documentation for Internal Request Feature
The internal request feature allows interacting with Rodauth by
calling methods, and is expected to be used mostly for administrative
purposes. It allows for things like an changing a login or password
for an existing user, without requiring that the user login to the
system. The reason the feature is named +internal_request+ is that
it internally submits requests to Rodauth, which are handled almost
identically to how actual web requests will be handled by Rodauth.
The general form of calling these methods is:
App.rodauth.internal_request_method(hash)
Where +App+ is the Roda class, and +internal_request_method+ is the
method you are calling. For example:
App.rodauth.change_password(account_id: 1, password: 'foobar')
Will change the password for the account with id 1 to +foobar+.
All internal request methods support the following options. For
internal requests that require an existing account, you should
generally use one of the two following options:
:account_id :: The id of the account to be considered as logged in when the internal request is submitted (most internal requests require a logged in account). This value is assumed to represent an existing account, the database is not checked to confirm that.
:account_login :: The login of the account to be considered as logged in when the internal request is submitted (most internal requests require a login). This will query the database to determine the account's id before submitting the request. If there is no non-closed account for the login, this will raise an exception.
There are additional options available, that you should only use
if you have special requirements:
:authenticated_by :: The array of strings to use for how the internal request's session was authenticated.
:env :: A hash to merge into the internal request environment hash. Keys given will override default values, so you will probably have problems if you directly use an existing request environment.
:session :: A hash for the session to use.
:params :: A hash of custom parameters.
All remaining options are considered parameters. Using the
previous example:
App.rodauth.change_password(account_id: 1, password: 'foobar')
The password: 'foobar' part means that the parameters
for the request will be {rodauth.password_param => 'foobar'},
where +rodauth.password_param+ is the value of +password_param+ in
your Rodauth configuration (this defaults to "password").
Passing any options not mentioned above that are not valid Rodauth
parameters will result in a warning.
== Configuration
In general, the configuration for internal requests is almost
the same as for regular requests. There are some minor changes
for easier usability. +modifications_require_password?+ (and
similar methods for requiring password),
+require_login_confirmation?+, and +require_password_confirmation?+
are set to false. In general, the caller of the method should not
be able to determine the user's password, and there is no point
in requiring parameter confirmation when calling the method
directly.
You can override the configuration for internal requests by using
the +internal_request_configuration+ configuration method. For
example, you can set the minimum length for logins to be 15
for normal requests, but only 3 for internal requests:
plugin :rodauth do
enable :create_account, :internal_request
login_minimum_length 15
internal_request_configuration do
login_minimum_length 3
end
end
Another approach for doing this is to call the +internal_request?+
method inside configuration method blocks:
plugin :rodauth do
enable :create_account, :internal_request
login_minimum_length{internal_request? ? 3 : 15}
end
== Return Values and Exceptions
Internal request methods ending in a question mark return true or false.
Most other internal request methods return nil on success, and or raise a
Rodauth::InternalRequestError exception on failure. The exception
message will include the flash message, {the reason for the
failure}[rdoc-ref:doc/error_reasons.rdoc] if available, and any field errors.
This data can also be retrieved via +flash+, +reason+, and +field_errors+
attributes on the exception object.
If an internal request method returns a non-nil value on success,
it will be documented in the Features section below. In such
cases, unless documented below, the methods will still raise a
Rodauth::InternalRequestError exception on failure.
== Domain
While it is a good idea to use the +domain+ configuration method
to force a domain to use, as it can avoid DNS rebinding attacks,
Rodauth can function without it, as it can use the domain of the
request. However, for internal requests, there is no submitted
domain, and Rodauth does not know what to use as the domain. To
avoid potentially using a wrong domain, Rodauth will raise an
Rodauth::InternalRequestError in internal requests if a domain
is needed and has not been configured.
== Features
This section documents the methods that are available for each
feature. You must load that feature and the internal request feature
in order to call the internal request methods for that feature.
Some features support multiple internal request methods, and
each internal request method supported will be documented under
the appropriate subheading.
If the method subheading states it it requires an account, you
must pass the +:account_id+ or +account_login+ option when calling
the method.
If the method subheading states it it requires an account or
a login, you must pass either +:login+, +:account_id+, or
+account_login+ when calling the method.
=== Base
=== account_exists?
The +account_exists?+ method returns whether the account exists
for the given login.
Options:
+:login+ :: (required) The login for the account.
=== account_id_for_login
The +account_id_for_login+ method returns the account id for
the given login. A Rodauth::InternalRequestError is raised
if the login given is not valid.
Options:
+:login+ :: (required) The login for the account.
=== internal_request_eval
The +internal_request_eval+ requires a block and will +instance_eval+
the block the context of an internal request instance. This allows
you full usage of the +Rodauth::Auth+ API inside the request.
Before using this method, you should have a good understanding
of Rodauth's internals and the effects of calling any methods you
are calling inside the block.
The return value of the method will be the return value of the
block, unless one of the methods in the block has set a
different return value.
=== Change Login
==== change_login (requires account)
The +change_login+ method changes the login for the account.
Options:
+:login+ :: (required) The new login for the account. Note that if the +:account_login+ option is provided, that is the current login for the account, not the new login.
=== Change Password
==== change_password (requires account)
The +change_password+ method changes the password for the account.
Options:
+:password+ or +new_password+ :: (required) The new password for the account.
=== Close Account
==== close_account (requires account)
The +close_account+ method closes the account. There is no method
in Rodauth to reopen closed accounts.
=== Create Account
==== create_account
The +create_account+ method creates an account.
Options:
+:login+ :: (required) The login for the created account.
+:password+ :: The password for the created account.
=== Email Auth
==== email_auth_request (requires account or login)
The +email_auth_request+ method requests an email with an
authentication link be sent to the account's email address.
==== email_auth
The +email_auth+ method determines if the given email authentication
key is valid.
This method will return the account id if the authentication key is
valid.
Options:
+:email_auth_key+ :: (required) The email authentication key for the account.
==== valid_email_auth?
The +valid_email_auth?+ method returns whether the given email
authentication key is valid.
Options:
+:email_auth_key+ :: (required) The email authentication key for the account.
=== Lockout
==== lock_account (requires account)
The +lock_account+ method locks an account, even if the account has
not experienced any login failures. This is one method only available
as an internal request.
==== unlock_account_request (requires account or login)
The +unlock_account_request+ method requests an email with an
link to unlock the account be sent to the account's email address.
==== unlock_account
The +unlock_account+ method unlocks the account.
If an +:account_id+ or +:account_login+ option is provided, this
will unlock the account without requiring the unlock account key
value.
Options:
+:unlock_account_key+ :: The unlock account key for the account. This allows unlocking accounts by key, without knowing the account id or login.
=== Login
==== login (requires account or login)
The +login+ method determines if the given password is valid for
the given account.
This method will return the account id if the password is valid.
Options:
+:password+ :: (required) The password for the account.
==== valid_login_and_password? (requires account or login)
The +valid_login_and_password?+ method returns whether the given
password is valid for the given account.
Options:
+:password+ :: (required) The password for the account.
=== OTP
==== otp_setup_params (requires account)
The +otp_setup_params+ method returns a hash with an +:otp_setup+
key, and an +:otp_setup_raw+ key if the Rodauth configuration uses
+hmac_secret+.
The +:otp_setup+ key in the returned hash specifies the OTP secret.
This hash should be merged into the options submitted to the
+otp_setup+ method in order to complete OTP setup.
==== otp_setup (requires account)
The +otp_setup+ method enables OTP multifactor authentication for
the account.
The values in the hash returned by the +otp_setup_params+ hash
must be passed as options to this method.
Additional Options:
+:otp_auth+ :: (required) The current OTP authentication code for the OTP secret.
==== otp_auth (requires account)
The +otp_auth+ method determines if the OTP authentication code is
valid for the account.
Options:
+:otp_auth+ :: (required) The current OTP authentication code for account.
==== valid_otp_auth? (requires account)
The +valid_otp_auth?+ method returns whether the OTP authentication
code is valid for the account.
Options:
+:otp_auth+ :: (required) The current OTP authentication code for account.
==== otp_disable (requires account)
The +otp_disable+ method disables OTP authentication for the account.
=== Recovery Codes
==== recovery_codes (requires account)
The +recovery_codes+ method returns an array of recovery codes for
the account. This array can be empty if no recovery codes are setup.
Options:
+:add_recovery_codes+ :: Generate new recovery codes for the account, up to the configured +recovery_codes_limit+, before returning the codes.
==== recovery_auth (requires account)
The +recovery_auth+ method determines if the recovery authentication
code is valid for the account.
Options:
+:recovery_codes+ :: (required) A valid recovery code for the account. This option sounds like it would take an array of recover codes, but it only takes a single recovery code.
==== valid_recovery_auth? (requires account)
The +valid_recovery_auth?+ method returns whether the recovery
authentication code is valid for the account.
Options:
+:recovery_codes+ :: (required) A valid recovery code for the account. This option sounds like it would take an array of recover codes, but it only takes a single recovery code.
=== Remember
==== remember_setup (requires_account)
The +remember_setup+ method setups up the remember feature for
the account, and returns the cookie value that can be used for
the remember cookie.
==== remember_disable (requires_account)
The +remember_disable+ method disables the remember feature for
the account.
==== account_id_for_remember_key
The +account_id_for_remember_key+ method returns the account id
for the given remember key.
Options:
+:remember+ :: (required) The remember key for the account. This is the same value returned by +remember_setup+.
=== Reset Password
==== reset_password_request (requires account or login)
The +reset_password_request+ method requests an email with an
link to reset the password for the account be sent to the account's
email address.
==== reset_password
The +reset_password+ method resets the password for an account.
This is similar to the +change_password+ method, but requires
that a reset password key has been created for the account, and
removes the key after the password has been reset.
If an +:account_id+ or +:account_login+ option is provided, this
will reset the password for the account without requiring the
reset password key value.
Options:
+:password+ :: (required) The new password for the account.
+:reset_password_key+ :: The reset password key for the account. This allows resetting passwords by key, without knowing the account id or login.
=== SMS Codes
==== sms_setup (requires account)
The +sms_setup+ method sends an SMS message to the given
phone number with a code to setup SMS authentication for
the account.
Options:
+:sms_phone+ :: (required) The phone number to use to setup SMS authentication.
==== sms_confirm (requires account)
The +sms_confirm+ method sets up SMS authentication for
an account, confirming that the SMS authentication code
sent previously was received.
Options:
+:sms_code+ :: (required) The authentication code sent to the user for setting up SMS authentication.
==== sms_request (requires account)
The +sms_setup+ method sends an SMS message to the account's
SMS phone number with an authentication code for two factor
authentication.
==== sms_auth (requires account)
The +sms_auth+ method determines if the SMS authentication code is
valid for the account.
Options:
+:sms_code+ :: (required) The authentication code sent to the user via SMS.
==== valid_sms_auth? (requires account)
The +valid_sms_auth?+ method returns whether the SMS authentication
code is valid for the account.
Options:
+:sms_code+ :: (required) The authentication code sent to the user via SMS.
==== sms_disable (requires account)
The +sms_disable+ method disables SMS authentication for the account.
=== Two Factor Base
==== two_factor_disable (requires_account)
The +two_factor_disable+ method disables all multifactor authentication
for the account.
=== Verify Account
==== verify_account_resend (requires account or login)
The +verify_account_resend+ method resends the account verification email
to the account's email address.
==== verify_account
The +verify_account+ method verifies the account.
to the account's email address.
If an +:account_id+ or +:account_login+ option is provided, this
will verify the account without requiring the verify account key value.
Options:
+:password+ :: The password for the account, if setting up passwords during verification.
+:verify_account_key+ :: The verify account key for the account. This allows verifying accounts by key, without knowing the account id or login.
=== Verify Login Change
==== verify_login_change
The +verify_login_change+ method verifies the login change for the
account.
If an +:account_id+ or +:account_login+ option is provided, this
will verify the account without requiring the verify account key value.
If the +:account_login+ option is provided, it specifies the current
account login, before the change.
Options:
+:verify_login_change_key+ :: The verify login change key for the account. This allows verifying login changes by key, without knowing the account id or login.
=== WebAuthn
==== webauthn_setup_params (requires account)
The +webauthn_setup_params+ method returns a hash with +:webauthn_setup+,
+:webauthn_setup_challenge+ and +:webauthn_setup_challenge_hmac+ keys.
The +:webauthn_setup+ options should be provided to the client for WebAuthn
registration, while +:webauthn_setup_challenge+ and
+webauthn_setup_challenge_hmac+ should be passed to the +webauthn_setup+
method.
==== webauthn_setup (requires account)
The +webauthn_setup+ method creates a WebAuthn credential for the account.
Options:
+:webauthn_setup+ :: The WebAuthn credential provided by the client during registration.
+:webauthn_setup_challenge+ :: The WebAuthn challenge generated for registration.
+:webauthn_setup_challenge_hmac+ :: The HMAC of the WebAuthn challenge generated for registration.
==== webauthn_auth_params (requires account)
The +webauthn_auth_params+ method returns a hash with +:webauthn_auth+,
+:webauthn_auth_challenge+ and +:webauthn_auth_challenge_hmac+ keys.
The +:webauthn_auth+ options should be provided to the client for WebAuthn
authentication, while +:webauthn_auth_challenge+ and
+webauthn_auth_challenge_hmac+ should be passed to the +webauthn_auth+ method.
==== webauthn_auth (requires account)
The +webauthn_auth+ method determines if the given WebAuthn credential is valid
for the account.
Options:
+:webauthn_auth+ :: The WebAuthn credential provided by the client during authentication.
+:webauthn_auth_challenge+ :: The WebAuthn challenge generated for authentication.
+:webauthn_auth_challenge_hmac+ :: The HMAC of the WebAuthn challenge generated for authentication.
==== webauthn_remove (requires account)
The +webauthn_remove+ methods deletes the given WebAuthn credential for the
account.
Options:
+:webauthn_remove+ :: The ID of the WebAuthn credential to delete.
=== WebAuthn Login
==== webauthn_login_params (requires account or login)
The +webauthn_login_params+ method returns a hash with +:webauthn_auth+,
+:webauthn_auth_challenge+ and +:webauthn_auth_challenge_hmac+ keys.
The +:webauthn_auth+ options should be provided to the client for WebAuthn
authentication, while +:webauthn_auth_challenge+ and
+webauthn_auth_challenge_hmac+ should be passed to the +webauthn_login+ method.
==== webauthn_login (requires account or login)
The +webauthn_login+ method determines if the given WebAuthn credential is
valid for the given account.
This method will return the account id if the WebAuthn credential is valid.
Options:
+:webauthn_auth+ :: The WebAuthn credential provided by the client during authentication.
+:webauthn_auth_challenge+ :: The WebAuthn challenge generated for authentication.
+:webauthn_auth_challenge_hmac+ :: The HMAC of the WebAuthn challenge generated for authentication.
=== WebAuthn Autofill
Enabling this feature modifies +webauthn_login_params+ and +webauthn_login+
methods not to require an account or login.
jeremyevans-rodauth-b53f402/doc/json.rdoc 0000664 0000000 0000000 00000007410 15157255142 0020435 0 ustar 00root root 0000000 0000000 = Documentation for JSON Feature
The json feature adds support for JSON API access for all other
features that ship with Rodauth.
When this feature is used, all other features become accessible via a
JSON API. The JSON API uses the POST method for all requests, using
the same parameter names as the features uses. JSON API requests to
Rodauth endpoints that use a method other than POST will result in a
405 Method Not Allowed response.
Responses are returned as JSON hashes. In case of an error, the +error+
entry is set to an error message, and the field-error entry is set to
an array containing the field name and the error message for that field.
Successful requests by default store a +success+ entry with a success
message, though that can be disabled.
The JSON response can be modified at any point by modifying the +json_response+
hash. The following example adds an {error reason}[rdoc-ref:doc/error_reasons.rdoc]
to the JSON response:
set_error_reason do |reason|
json_response[:error_reason] = reason
end
The session state is managed in the rack session (just like in HTML mode), with
CSRF protection being disabled by default for JSON requests. HTML mode is still
available when json: true option is passed to the rodauth plugin. If
you want to only handle JSON requests, set only_json? true in your
rodauth configuration.
If you want token-based authentication sent via the Authorization
header, consider using the jwt feature.
== Auth Value Methods
json_accept_regexp :: The regexp to use to check the Accept header for JSON if +json_check_accept?+ is true.
json_check_accept? :: Whether to check the Accept header to see if the client supports JSON responses, true by default.
json_non_post_error_message :: The error message to use when a JSON non-POST request is sent.
json_not_accepted_error_message :: The error message to display if +json_check_accept?+ is true and the Accept header is present but does not match +json_request_content_type_regexp+.
json_request_content_type_regexp :: The regexp to use to recognize a request as a json request.
json_response_content_type :: The content type to set for json responses, application/json by default.
json_response_custom_error_status? :: Whether to use custom error statuses, instead of always using +json_response_error_status+, true by default, can be set to false for backwards compatibility with Rodauth 1.
json_response_error? :: Whether the current JSON response indicates an error. By default, returns whether +json_response_error_key+ is set.
json_response_error_key :: The JSON result key containing an error message, +error+ by default.
json_response_error_status :: The HTTP status code to use for JSON error responses if not using custom error statuses, 400 by default.
json_response_field_error_key :: The JSON result key containing an field error message, field-error by default.
json_response_success_key :: The JSON result key containing a success message for successful request, if set. +success+ by default.
non_json_request_error_message :: The error message to use when a non-JSON request is sent and +only_json?+ is set.
only_json? :: Whether to have Rodauth only allow JSON requests. True by default if json: :only option was given when loading the plugin. If set, rodauth endpoints will issue an error for non-JSON requests.
use_json? :: Whether to return a JSON response. By default, a JSON response is returned if +only_json?+ is true, or if the request uses a json content type.
== Auth Methods
json_request? :: Whether the current request is a JSON request, looks at the Content-Type request header by default.
json_response_body(hash) :: The body to use for JSON response. By default just converts hash to JSON. Can be used to reformat JSON output in arbitrary ways.
jeremyevans-rodauth-b53f402/doc/jwt.rdoc 0000664 0000000 0000000 00000006711 15157255142 0020273 0 ustar 00root root 0000000 0000000 = Documentation for JWT Feature
The jwt feature adds support for JSON API access for all other features
that ship with Rodauth, using JWT (JSON Web Tokens) to hold the
session information. It depends on the json feature.
In order to use this feature, you have to set the +jwt_secret+ configuration
option with the secret used to cryptographically protect the token.
To use this JSON API, when processing responses for requests to a Rodauth
endpoint, check for the Authorization header, and use the value of the
response Authorization header as the request Authorization header in
future requests, if the response Authorization header is set. If the
response Authorization header is not set, then continue to use the
previous Authorization header.
When using this feature, consider using the json: :only option when
loading the rodauth plugin, if you want Rodauth to only handle
JSON requests. If you don't use the json: :only option, the jwt feature
will probably result in an error if a request to a Rodauth endpoint comes
in with a Content-Type that isn't application/json, unless you also set
only_json? false in your rodauth configuration.
If you would like to check if a valid JWT was submitted with the current
request in your Roda app, you can call the +rodauth.valid_jwt?+ method. If
+rodauth.valid_jwt?+ returns true, the contents of the jwt can be retrieved
from +rodauth.session+.
Logging the session out does not invalidate the previous JWT token by default.
If you would like this behavior, you can use the active_sessions feature, which
stores session identifiers in the database and deletes them when the session
expires. This provides a whitelist approach of revoking JWT tokens.
== Auth Value Methods
invalid_jwt_format_error_message :: The error message to use when a JWT with an invalid format is submitted in the Authorization header.
jwt_algorithm :: The JWT algorithm to use, +HS256+ by default.
jwt_authorization_ignore :: A regexp matched against the Authorization header, which skips JWT processing if it matches. By default, HTTP Basic and Digest authentication are ignored.
jwt_authorization_remove :: A regexp to remove from the Authorization header before processing the JWT. By default, a Bearer prefix is removed.
jwt_decode_opts :: An optional hash to pass to +JWT.decode+. Can be used to set JWT verifiers.
jwt_old_secret :: The previous JWT secret used, to support JWT secret rotation (only supported when using jwt 2.4+). Access to this should be protected the same as a session secret.
jwt_secret :: The JWT secret to use. Access to this should be protected the same as a session secret.
jwt_session_key :: A key to nest the session hash under in the JWT payload. nil by default, for no nesting.
jwt_symbolize_deeply? :: Whether to symbolize the session hash deeply. false by default.
use_jwt? :: Whether to use the JWT in the Authorization header for authentication information. If false, falls back to using the rack session. By default, the Authorization header is used if it is present, if +only_json?+ is true, or if the request uses a json content type.
== Auth Methods
jwt_session_hash :: The session hash used to create the session_jwt. Can be used to set JWT claims.
jwt_token :: Retrieve the JWT token from the request, by default taking it from the Authorization header.
session_jwt :: An encoded JWT for the current session.
set_jwt_token(token) :: Set the JWT token in the response, by default storing it in the Authorization header.
jeremyevans-rodauth-b53f402/doc/jwt_cors.rdoc 0000664 0000000 0000000 00000003466 15157255142 0021325 0 ustar 00root root 0000000 0000000 = Documentation for JWT CORS Feature
The jwt_cors feature adds support for Cross-Origin Resource Sharing
to Rodauth's JSON API.
When this feature is used, CORS requests are handled. This includes
CORS preflight requests, which are required since Rodauth's JSON API
uses the application/json request content type.
This feature depends on the jwt feature.
== Auth Value Methods
jwt_cors_allow_headers :: For allowed CORS-preflight requests, the value returned in the Access-Control-Allow-Headers header (default: 'Content-Type, Authorization, Accept'). This specifies which headers can be included in CORS requests.
jwt_cors_allow_methods :: For allowed CORS-preflight requests, the value returned in the Access-Control-Allow-Methods header (default: 'POST'). This specifies which methods are allowed in CORS requests.
jwt_cors_allow_origin :: Which origins are allowed to perform CORS requests. The default is +false+. This can be a String, Array of Strings, Regexp, or +true+ to allow CORS requests from any domain.
jwt_cors_expose_headers :: For allowed CORS requests, the value returned in the Access-Control-Expose-Headers header (default: 'Authorization'). This specifies which headers the browser is allowed to access from a response to a CORS request.
jwt_cors_max_age :: For allowed CORS-preflight requests, the value returned in the Access-Control-Max-Age header (default: 86400). This specifies how long before the information returned should be considered stale and another CORS preflight request made.
== Auth Methods
jwt_cors_allow? :: Whether the request should be allowed. This is called for all requests for a Rodauth route that include an Origin header. It should return true or false for whether to specially handle the cross-origin request. By default, uses the +jwt_cors_allow_origin+ setting to check the origin.
jeremyevans-rodauth-b53f402/doc/jwt_refresh.rdoc 0000664 0000000 0000000 00000012273 15157255142 0022011 0 ustar 00root root 0000000 0000000 = Documentation for JWT Refresh Feature
The jwt_refresh feature adds support for a database-backed JWT refresh token,
setting a short lifetime on JWT access tokens.
When this feature is used, the access and refresh token are provided
at login in the response body (the access token is still provided in the Authorization
header), and for any subsequent POST to /jwt-refresh.
Note that using the refresh token invalidates the token and creates
a new access token with an updated lifetime. However, it does not invalidate
older access tokens. Older access tokens remain valid until they expire. You
can use the active_sessions feature if you want previous access tokens to be invalid
as soon as the refresh token is used.
You can have multiple active refresh tokens active at a time, since each browser session
will generally use a separate refresh token. If you would like to revoke a refresh token
when logging out, provide the refresh token when submitting the JSON request to logout.
If you would like to remove all refresh tokens for the account when logging out, provide
a value of all as the token value.
When using the refresh token, you must provide a valid access token, as that contains
information about the current session, which is used to create the new access token.
If you change the +allow_refresh_with_expired_jwt_access_token?+ setting to +true+,
an expired but otherwise valid access token will be accepted, and Rodauth will check
that the access token was issued in the same session as the refresh token.
When an account change is made made during a logged in session (such as a login change),
refresh tokens are not automatically invalidated, as Rodauth does not know which refresh
token is being used for the current session. It is recommended that you use the
active_sessions feature if you would like an account change during a logged in session
to invalidate refresh tokens. Technically, this invalides the account tokens for other
sessions and not the refresh tokens, but you need a valid access token to use the
refresh token, so it has the same effect.
This feature depends on the jwt feature.
== Auth Value Methods
allow_refresh_with_expired_jwt_access_token? :: Whether refreshing should be allowed with an expired access token. Default is +false+. You must set an +hmac_secret+ if setting this value to +true+.
expired_jwt_access_token_status :: The HTTP status code to use when a access token (JWT) is expired is submitted in the Authorization header. Default is 400 for backwards compatibility, and it is recommended to set it to 401.
expired_jwt_access_token_message :: The error message to use when a access token (JWT) is expired is submitted in the Authorization header.
jwt_access_token_key :: Name of the key in the response json holding the access token. Default is +access_token+.
jwt_access_token_not_before_period :: How many seconds before the current time will the jwt be considered valid (to account for inaccurate clocks). Default is 5.
jwt_access_token_period :: Validity of an access token in seconds, default is 1800 (30 minutes).
jwt_refresh_route :: The route to the login action. Defaults to jwt-refresh.
jwt_refresh_invalid_token_message :: Error message when the provided refresh token is non existent, invalid or expired.
jwt_refresh_token_account_id_column :: The column name in the +jwt_refresh_token_table+ storing the account id, should be a foreign key referencing the accounts table.
jwt_refresh_token_data_session_key :: The key in the session hash storing random data, for access checking during refresh if +allow_refresh_with_expired_jwt_access_token?+ is set.
jwt_refresh_token_deadline_column :: The column name in the +jwt_refresh_token_table+ storing the deadline after which the refresh token will no longer be valid.
jwt_refresh_token_deadline_interval :: Validity of a refresh token. Default is 14 days.
jwt_refresh_token_hmac_session_key :: The key in the session hash storing the hmac, for access checking during refresh if +allow_refresh_with_expired_jwt_access_token?+ is set.
jwt_refresh_token_id_column :: The column name in the refresh token keys table storing the id of each token (the primary key of the table).
jwt_refresh_token_key :: Name of the key in the response json holding the refresh token. Default is +refresh_token+.
jwt_refresh_token_key_column :: The column name in the +jwt_refresh_token_table+ holding the refresh token key value.
jwt_refresh_token_key_param :: Name of parameter in which the refresh token is provided when requesting a new token. Default is +refresh_token+.
jwt_refresh_token_table :: Name of the table holding refresh token keys.
jwt_refresh_without_access_token_message :: Error message when trying to refresh with providing an access token.
jwt_refresh_without_access_token_status :: The HTTP status code to use when trying to refresh without providing an access token.
== Auth Methods
account_from_refresh_token(token) :: Returns the account hash for the given refresh token.
after_refresh_token :: Hooks for specific processing once the refresh token has been set.
before_jwt_refresh_route :: Run arbitrary code before handling a jwt_refresh route.
before_refresh_token :: Hooks for specific processing before the refresh token is computed.
jeremyevans-rodauth-b53f402/doc/lockout.rdoc 0000664 0000000 0000000 00000015304 15157255142 0021145 0 ustar 00root root 0000000 0000000 = Documentation for Lockout Feature
The lockout feature implements bruteforce protection for accounts.
It depends on the login feature. If a user fails to login due to
a password error more than a given number of times, their account
gets locked out, and they are given an option to request an account
unlock via an email sent to them.
== Auth Value Methods
account_lockouts_deadline_column :: The deadline column in the +account_lockouts_table+, containing the timestamp until which the account is locked out.
account_lockouts_deadline_interval :: The amount of time for which to lock out accounts, 1 day by default. Only used if +set_deadline_values?+ is true.
account_lockouts_email_last_sent_column :: The email last sent column in the +account_lockouts_table+. Set to nil to always send an unlock account email when requested.
account_lockouts_id_column :: The id column in the +account_lockouts_table+, should be a foreign key referencing the accounts table.
account_lockouts_key_column :: The unlock key column in the +account_lockouts_table+.
account_lockouts_table :: The table containing account lockout information.
account_login_failures_id_column :: The id column in the +account_login_failures_table+, should be a foreign key referencing the accounts table.
account_login_failures_number_column :: The column in the +account_login_failures_table+ containing the number of login failures for the account.
account_login_failures_table :: The table containing number of login failures per account.
login_lockout_error_flash :: The flash error to show if there if the account is or becomes locked out after a login attempt.
max_invalid_logins :: The maximum number of failed logins before account lockout. As this feature is just designed for bruteforce protection, this defaults to 100.
no_matching_unlock_account_key_error_flash :: The flash error message to show if attempting to access the unlock account form with an invalid key.
unlock_account_additional_form_tags :: HTML fragment with additional form tags to use on the unlock account form.
unlock_account_autologin? :: Whether to autologin users after successful account unlock. This defaults to true, as otherwise an attacker can prevent an account from logging in by continually locking out their account.
unlock_account_button :: The text to use on the unlock account button.
unlock_account_email_recently_sent_error_flash :: The flash error to show if not sending an unlock account email because another was sent recently.
unlock_account_email_recently_sent_redirect :: Where to redirect after not sending an unlock account email because another was sent recently.
unlock_account_email_subject :: The subject to use for the unlock account email.
unlock_account_error_flash :: The flash error to display upon unsuccessful account unlock.
unlock_account_explanatory_text :: The text to display above the button to unlock an account.
unlock_account_key_param :: The parameter name to use for the unlock account key.
unlock_account_notice_flash :: The flash notice to display upon successful account unlock.
unlock_account_page_title :: The page title to use on the unlock account form.
unlock_account_redirect :: Where to redirect after successful account unlock.
unlock_account_request_additional_form_tags :: HTML fragment with additional form tags to use on the form to request an account unlock.
unlock_account_request_button :: The text to use on the unlock account request button.
unlock_account_request_explanatory_text :: The text to display above the button to request an account unlock.
unlock_account_request_notice_flash :: The flash notice to display upon successful sending of the unlock account email.
unlock_account_request_page_title :: The page title to use on the unlock account request form.
unlock_account_request_redirect :: Where to redirect after the account unlock email is sent.
unlock_account_request_route :: The route to the unlock account request action. Defaults to +unlock-account-request+.
unlock_account_requires_password? :: Whether a password is required when unlocking accounts, false by default. May want to set to true if not allowing password resets.
unlock_account_route :: The route to the unlock account action. Defaults to +unlock-account+.
unlock_account_session_key :: The key in the session to hold the unlock account key temporarily.
unlock_account_skip_resend_email_within :: The number of seconds before sending another unlock account email, if +account_lockouts_email_last_sent_column+ is set.
== Auth Methods
account_from_unlock_key(key) :: Retrieve the account using the given verify account key, or return nil if no account matches.
after_account_lockout :: Run arbitrary code after an account has been locked out.
after_unlock_account :: Run arbitrary code after a successful account unlock.
after_unlock_account_request :: Run arbitrary code after a successful account unlock request.
before_unlock_account :: Run arbitrary code before unlocking an account.
before_unlock_account_request :: Run arbitrary code before sending an account unlock email.
before_unlock_account_request_route :: Run arbitrary code before handling an account unlock request route.
before_unlock_account_route :: Run arbitrary code before handling an unlock account route.
clear_invalid_login_attempts :: Clear any stored login failures or lockouts for the current account.
create_unlock_account_email :: A Mail::Message for the account unlock email to send.
generate_unlock_account_key :: A random string to use for a new unlock account key.
get_unlock_account_email_last_sent :: Get the last time an unlock account email is sent, or nil if there is no last sent time.
get_unlock_account_key :: Retrieve the unlock account key for the current account.
invalid_login_attempted :: Record an invalid login attempt, incrementing the number of login failures, and possibly locking out the account.
locked_out? :: Whether the current account is locked out.
send_unlock_account_email :: Send the account unlock email.
set_unlock_account_email_last_sent :: Set the last time an unlock_account email is sent.
unlock_account :: Unlock the account.
unlock_account_email_body :: The body to use for the unlock account email.
unlock_account_email_link :: The link to the unlock account form to include in the unlock account email.
unlock_account_key :: The unlock account key for the current account.
unlock_account_request_response :: Return a response after successfully requesting an account unlock. By default, redirects to +unlock_account_request_redirect+.
unlock_account_request_view :: The HTML to use for the unlock account request form.
unlock_account_response :: Return a response after successfully unlocking an account. By default, redirects to +unlock_account_redirect+.
unlock_account_view :: The HTML to use for the unlock account form.
jeremyevans-rodauth-b53f402/doc/login.rdoc 0000664 0000000 0000000 00000006420 15157255142 0020574 0 ustar 00root root 0000000 0000000 = Documentation for Login Feature
The login feature implements a login page. It's the most commonly
used feature.
In addition to the auth methods below, it provides a +login+ method that wraps
+login_session+, running login hooks and redirecting to the configured
location.
rodauth.account #=> { id: 123, ... }
rodauth.login('password') # login the current account
== Auth Value Methods
login_additional_form_tags :: HTML fragment containing additional form tags to use on the login form.
login_button :: The text to use for the login button.
login_error_flash :: The flash error to show for an unsuccessful login.
login_error_status :: The response status to use when using an invalid login or password to login, 401 by default.
login_form_footer_links :: An array of entries for links to show on the login page. Each entry is an array of three elements, sort order (integer), link href, and link text.
login_form_footer_links_heading :: A heading to show before the login form footer links.
login_notice_flash :: The flash notice to show after successful login.
login_page_title :: The page title to use on the login form.
login_redirect :: Where to redirect after a successful login.
login_redirect_session_key :: The key in the session hash storing the location to redirect to after successful login.
login_return_to_requested_location? :: Whether to redirect to the originally requested location after successful login when +require_login+ was used, false by default.
login_return_to_requested_location_max_path_size :: The maximum path size in bytes to allow when returning to requested location, 2048 by default to avoid exceeding the 4K cookie size limit
login_route :: The route to the login action. Defaults to +login+.
multi_phase_login_forms :: An array of entries for authentication methods that can be used to login when using multi phase login. Each entry is an array of three elements, sort order (integer), HTML, and method to call if this entry is the only authentication method available (or nil to not call a method).
multi_phase_login_page_title :: The page title to use on the login form after login has been entered when using multi phase login.
need_password_notice_flash :: The flash notice to show during multi phase login after the login has been entered, when requesting the password.
use_multi_phase_login? :: Whether to ask for login first, and only ask for password after asking for the login, false by default unless an alternative login feature such as email_auth or webauthn_login is used.
== Auth Methods
before_login_route :: Run arbitrary code before handling a login route.
login_form_footer :: A message to display after the login form.
login_response :: Return a response after a successful login. By default, redirects to +login_redirect+ (or the requested location if +login_return_to_requested_location?+ is true).
login_return_to_requested_location_path :: If +login_return_to_requested_location?+ is true, the path to use as the requested location. By default, uses the full path of the request for GET requests, and is nil for non-GET requests (in which case the default +login_redirect+ will be used).
login_view :: The HTML to use for the login form.
multi_phase_login_view :: The HTML to use for the login form after login has been entered when using multi phase login.
jeremyevans-rodauth-b53f402/doc/login_password_requirements_base.rdoc 0000664 0000000 0000000 00000010202 15157255142 0026304 0 ustar 00root root 0000000 0000000 = Documentation for Login Password Requirements Base Feature
The login password requirements base feature is automatically loaded when you
use a Rodauth feature that requires setting logins or passwords.
== Auth Value Methods
already_an_account_with_this_login_message :: The error message to display when there already exists an account with the same login.
contains_null_byte_message :: The error message to display when the password contains a null byte (only used if parameters with null bytes are otherwise allowed).
login_confirm_label :: The label to use for login confirmations.
login_confirm_param :: The parameter name to use for login confirmations.
login_does_not_meet_requirements_message :: The error message to display when the login does not meet the requirements you have set.
login_email_regexp :: The regular expression used to validate whether login is a valid email address.
login_maximum_bytes :: The maximum length for logins in bytes, 255 by default.
login_maximum_length :: The maximum length for logins in characters, 255 by default.
login_minimum_length :: The minimum length for logins in characters, 3 by default.
login_not_valid_email_message :: The error message to display when login is not a valid email address.
login_too_long_message :: The error message fragment to show if the login is too long.
login_too_many_bytes_message :: The error message fragment to show if the login has too many bytes.
login_too_short_message :: The error message fragment to show if the login is too short.
logins_do_not_match_message :: The error message to display when login and login confirmation do not match.
password_confirm_label :: The label to use for password confirmations.
password_confirm_param :: The parameter name to use for password confirmations.
password_does_not_meet_requirements_message :: The error message to display when the password does not meet the requirements you have set.
password_hash_cost :: The cost to use for the password hash algorithm. This should be an integer when using bcrypt (the default), and a hash if using argon2 (supported by the argon2 feature).
password_maximum_bytes :: The maximum length for passwords in bytes, nil by default for no limit. bcrypt only uses the first 72 bytes of the password when creating the password hash, so if you are using bcrypt as the password hash function, you may want to set this to 72.
password_maximum_length :: The maximum length for passwords in characters, nil by default for no limit.
password_minimum_length :: The minimum length for passwords in characters, 6 by default.
password_too_long_message :: The error message fragment to show if the password is too long.
password_too_many_bytes_message :: The error message fragment to show if the password is has too many bytes.
password_too_short_message :: The error message fragment to show if the password is too short.
passwords_do_not_match_message :: The error message to display when password and password confirmation do not match.
require_email_address_logins? :: Whether logins need to be valid email addresses, true by default.
require_login_confirmation? :: Whether login confirmations are required when changing logins or creating accounts. True by default if not verifying the account.
require_password_confirmation? :: Whether password confirmations are required when changing/resetting passwords and creating accounts.
same_as_existing_password_message :: The error message to display when a new password is the same as the existing password.
== Auth Methods
login_confirmation_matches?(login, login_confirmation) :: Whether the login matches the login confirmation, does a case insensitive check using +casecmp+ by default.
login_meets_requirements?(login) :: Whether the given login meets the requirements. By default, just checks that the login is a valid email address.
login_valid_email?(login) :: Whether the login is a valid email address.
password_hash(password) :: A hash of the given password.
password_meets_requirements?(password) :: Whether the given password meets the requirements. Can be used to implement complexity requirements for passwords.
set_password(password) :: Set the password for the current account to the given password.
jeremyevans-rodauth-b53f402/doc/logout.rdoc 0000664 0000000 0000000 00000001726 15157255142 0021001 0 ustar 00root root 0000000 0000000 = Documentation for Logout Feature
The logout feature implements a logout button, which clears the session.
It is the simplest feature.
== Auth Value Methods
logout_additional_form_tags :: HTML fragment containing additional form tags to use on the logout form.
logout_button :: The text to use for the logout button.
logout_notice_flash :: The flash notice to show after logout.
logout_page_title :: The page title to use on the logout form.
logout_redirect :: Where to redirect after a logout.
logout_route :: The route to the logout action. Defaults to +logout+.
== Auth Methods
after_logout :: Run arbitrary code after logout.
before_logout :: Run arbitrary code before logout.
before_logout_route :: Run arbitrary code before handling a logout route.
logout :: Log the user out, by default clearing the session.
logout_response :: Return a response after a successful logout. By default, redirects to +logout_redirect+.
logout_view :: The HTML to use for the logout form.
jeremyevans-rodauth-b53f402/doc/otp.rdoc 0000664 0000000 0000000 00000017737 15157255142 0020303 0 ustar 00root root 0000000 0000000 = Documentation for OTP Feature
The otp feature implements multifactor authentication via time-based one-time
passwords (TOTP). It supports setting up TOTP authentication, logging
in with TOTP authentication codes, and disabling TOTP authentication.
The otp feature requires the rotp and rqrcode gems.
== Auth Value Methods
otp_already_setup_error_flash :: The flash error to show if going to the OTP setup page when OTP is already setup.
otp_already_setup_redirect :: Where to redirect if going to the OTP setup page when OTP has already been setup.
otp_auth_additional_form_tags :: HTML fragment containing additional form tags to use on the OTP authentication form.
otp_auth_button :: Text to use for button on OTP authentication form.
otp_auth_error_flash :: The flash error to show if unable to authenticate via OTP.
otp_auth_failures_limit :: The number of allowed OTP authentication failures before locking out.
otp_auth_form_footer :: A footer to display at the bottom of the OTP authentication form.
otp_auth_label :: The label for the OTP authentication code.
otp_auth_link_text :: The text to use for the link from the multifactor auth page.
otp_auth_page_title :: The page title to use on the OTP authentication form.
otp_auth_param :: The parameter name for the OTP authentication code.
otp_auth_route :: The route to the OTP authentication action. Defaults to +otp-auth+.
otp_class :: The class to use for OTP authentication (default: ROTP::TOTP)
otp_digits :: The number of digits to use in OTP authentication codes (rotp's default is 6).
otp_disable_additional_form_tags :: HTML fragment containing additional form tags to use on the form to disable OTP authentication.
otp_disable_button :: The text to use for button on the form to disable OTP authentication.
otp_disable_error_flash :: The flash error to show if unable to disable OTP authentication.
otp_disable_link_text :: The text to use for the disable link from the multifactor manage page.
otp_disable_notice_flash :: The flash notice to show after disabling OTP authentication.
otp_disable_page_title :: The page title to use on the OTP disable form.
otp_disable_redirect :: Where to redirect after disabling OTP authentication.
otp_disable_route :: The route to the OTP disable action. Defaults to +otp-disable+.
otp_drift :: The number of seconds the client and server are allowed to drift apart. The default is 30. Can be set to nil to not allow drift.
otp_interval :: The number of seconds in which to rotate TOTP auth codes (rotp's default is 30).
otp_invalid_auth_code_message :: The error message to show when an invalid OTP authentication code is used.
otp_invalid_secret_message :: The error message to show when an invalid OTP secret is submitted during OTP setup.
otp_issuer :: The issuer to use in the OTP provisioning URL. Defaults to +domain+.
otp_keys_column :: The column in the +otp_keys_table+ containing the OTP secret.
otp_keys_failures_column :: The column in the +otp_keys_table+ containing the number of OTP authentication failures.
otp_keys_id_column :: The column in the +otp_keys_table+ containing the account id.
otp_keys_last_use_column :: The column in +otp_keys_table+ containing the last authentication timestamp.
otp_keys_table :: The table name containing the OTP secrets.
otp_keys_use_hmac? :: Whether to use HMACs for OTP keys. Defaults to whether +hmac_secret+ has been set. Should be set to false if adding +hmac_secret+ to Rodauth where the otp feature is already in use, as otherwise it will render existing OTP keys invalid.
otp_lockout_error_flash :: The flash error show show when OTP authentication has been locked out due to numerous authentication failures.
otp_lockout_redirect :: Where to redirect if going to OTP authentication page and OTP authentication has been locked out.
otp_provisioning_uri_label :: The label used when displaying the OTP provisioning URI during OTP setup.
otp_secret_label :: The label used when displaying the OTP secret during OTP setup.
otp_setup_additional_form_tags :: HTML fragment containing additional form tags when setting up OTP authentication.
otp_setup_button :: Text for the button when setting up OTP authentication.
otp_setup_error_flash :: The flash error to show if OTP authentication setup was not successful.
otp_setup_link_text :: The text to use for the setup link from the multifactor manage page.
otp_setup_notice_flash :: The flash notice to show if OTP authentication setup was successful.
otp_setup_page_title :: The page title to use on the form to setup OTP authentication.
otp_setup_param :: The parameter name used for the OTP secret when setting up OTP authentication.
otp_setup_raw_param :: The parameter name used for the raw OTP secret when setting up OTP authentication, when +otp_keys_use_hmac?+ is true.
otp_setup_redirect :: Where to redirect after successful OTP authentication setup.
otp_setup_route :: The route to the OTP setup action. Defaults to +otp-setup+.
== Auth Methods
after_otp_authentication_failure :: Run arbitrary code after OTP authentication failure.
after_otp_disable :: Run arbitrary code after OTP authentication has been disabled.
after_otp_setup :: Run arbitrary code after OTP authentication has been setup.
before_otp_auth_route :: Run arbitrary code before handling an OTP authentication route.
before_otp_authentication :: Run arbitrary code before OTP authentication.
before_otp_disable :: Run arbitrary code before OTP authentication disabling.
before_otp_disable_route :: Run arbitrary code before handling an OTP authentication disable route.
before_otp_setup :: Run arbitrary code before OTP authentication setup.
before_otp_setup_route :: Run arbitrary code before handling an OTP authentication setup route.
otp :: The object used for verifying OTP authentication attempts.
otp_add_key(secret) :: Add an OTP key for the current account with the given secret.
otp_auth_view :: The HTML to use for the OTP authentication form.
otp_available? :: Whether OTP authentication is ready for use.
otp_disable_response :: Return a response after successfully disabling OTP . By default, redirects to +otp_disable_redirect+.
otp_disable_view :: The HTML to use for the OTP disable form.
otp_exists? :: Whether the current account has setup OTP.
otp_key :: The stored OTP secret for the account.
otp_last_use :: The last time OTP authentication was successful for the account.
otp_locked_out? :: Whether the current account has been locked out of OTP authentication.
otp_new_secret :: A new secret to use when setting up OTP.
otp_provisioning_name :: The provisioning name to use during OTP setup, defaults to the account's email.
otp_provisioning_uri :: The provisioning URI displayed during OTP setup.
otp_qr_code :: The QR code containing the otp_provisioning_uri, by default an SVG image.
otp_record_authentication_failure :: Record an OTP authentication failure.
otp_remove :: Removes all stored OTP data for the current account.
otp_remove_auth_failures :: Removes OTP authentication failures for the current account, used after successful multifactor authentication.
otp_setup_response :: Return a response after successful OTP setup. By default, redirects to +otp_setup_redirect+.
otp_setup_view :: The HTML to use for the form to setup OTP authentication.
otp_tmp_key(secret) :: Set the secret to use for the temporary OTP key, during OTP setup.
otp_update_last_use :: Update the last time OTP authentication was successful for the account. Return true if the authentication should be allowed, or false if it should not be allowed because the last authentication was too recent and indicates the possible reuse of a TOTP authentication code.
otp_valid_code_for_old_secret :: Called when valid OTP authentication is performed using hmac_old_secret. This indicates the OTP needs to be rotated before support for the previous hmac secret value is removed. You can use this to track users who need their OTP rotated, and take appropriate action.
otp_valid_code?(auth_code) :: Whether the given code is the currently valid OTP auth code for the account.
otp_valid_key?(secret) :: Whether the given secret is a valid OTP secret.
jeremyevans-rodauth-b53f402/doc/otp_lockout_email.rdoc 0000664 0000000 0000000 00000004150 15157255142 0023173 0 ustar 00root root 0000000 0000000 = Documentation for OTP Lockout Email Feature
The otp_lockout_email feature emails users when:
* TOTP authentication is locked out
* TOTP authentication is unlocked
* A TOTP unlock attempt has failed
The otp_unlock_email feature depends on the otp_lockout and email_base features.
== Auth Value Methods
otp_locked_out_email_body :: Body to use for the email notifying user that TOTP authentication has been locked out.
otp_locked_out_email_subject :: Subject to use for the email notifying user that TOTP authentication has been locked out.
otp_unlock_failed_email_body :: Body to use for the email notifying user that there has been an unsuccessful attempt to unlock TOTP authentication.
otp_unlock_failed_email_subject :: Subject to use for the email notifying user that there has been an unsuccessful attempt to unlock TOTP authentication.
otp_unlocked_email_body :: Body to use for the email notifying user that TOTP authentication has been unlocked.
otp_unlocked_email_subject :: Subject to use for the email notifying user that TOTP authentication has been unlocked.
send_otp_locked_out_email? :: Whether to send an email when TOTP authentication is locked out.
send_otp_unlock_failed_email? :: Whether to send an email when there has been an unsuccessful attempt to unlock TOTP authentication.
send_otp_unlocked_email? :: Whether to send an email when TOTP authentication is unlocked.
== Auth Methods
create_otp_locked_out_email :: A Mail::Message for the email notifying user that TOTP authentication has been locked out.
create_otp_unlock_failed_email :: A Mail::Message for the email notifying user that there has been an unsuccessful attempt to unlock TOTP authentication.
create_otp_unlocked_email :: A Mail::Message for the email notifying user that TOTP authentication has been unlocked.
send_otp_locked_out_email :: Send the email notifying user that TOTP authentication has been locked out.
send_otp_unlock_failed_email :: Send the email notifying user that there has been an unsuccessful attempt to unlock TOTP authentication.
send_otp_unlocked_email :: Send the email notifying user that TOTP authentication has been unlocked.
jeremyevans-rodauth-b53f402/doc/otp_modify_email.rdoc 0000664 0000000 0000000 00000002145 15157255142 0023004 0 ustar 00root root 0000000 0000000 = Documentation for OTP Modify Email Feature
The otp_modify_email feature emails users when TOTP authentication is setup or disabled.
The otp_modify_email feature depends on the otp and email_base features.
== Auth Value Methods
otp_disabled_email_body :: Body to use for the email notifying user that TOTP authentication has been disabled.
otp_disabled_email_subject :: Subject to use for the email notifying user that TOTP authentication has been disabled.
otp_setup_email_body :: Body to use for the email notifying user that TOTP authentication has been setup.
otp_setup_email_subject :: Subject to use for the email notifying user that TOTP authentication has been setup.
== Auth Methods
create_otp_disabled_email :: A Mail::Message for the email notifying user that TOTP authentication has been disabled.
create_otp_setup_email :: A Mail::Message for the email notifying user that TOTP authentication has been setup.
send_otp_disabled_email :: Send the email notifying user that TOTP authentication has been disabled.
send_otp_setup_email :: Send the email notifying user that TOTP authentication has been setup.
jeremyevans-rodauth-b53f402/doc/otp_unlock.rdoc 0000664 0000000 0000000 00000014763 15157255142 0021652 0 ustar 00root root 0000000 0000000 = Documentation for OTP Unlock Feature
The otp_unlock feature implements unlocking of TOTP authentication after
TOTP authentication. The user must consecutively successfully authenticate
with TOTP multiple times (default: 3) within a given time period (15 minutes
per attempt) in order to unlock TOTP authentication. By requiring
consecutive successful unlocks, with a delay after failure, it is infeasible
to brute force the TOTP unlock process.
The otp_unlock feature depends on the otp feature.
== Auth Value Methods
otp_unlock_additional_form_tags :: HTML fragment containing additional form tags to use on the OTP unlock form.
otp_unlock_auth_deadline_passed_error_flash :: The flash error to show if attempting to unlock OTP after the deadline for submittal has passed.
otp_unlock_auth_deadline_passed_error_status :: The response status to use if attempting to unlock OTP after the deadline for submittal has passed, 403 by default.
otp_unlock_auth_failure_cooldown_seconds :: The number of seconds the user must wait to attempt OTP unlock again after a failed OTP unlock attempt.
otp_unlock_auth_failure_error_flash :: The flash error to show if attempting to unlock OTP using an incorrect authentication code.
otp_unlock_auth_failure_error_status :: The response status to use if attempting to unlock OTP using an incorrect authentication code, 403 by default.
otp_unlock_auth_not_yet_available_error_flash :: The flash error to show if attempting to unlock OTP when doing so is not yet available due to a recent attempt.
otp_unlock_auth_not_yet_available_error_status :: The response status to use if attempting to unlock OTP when doing so is not yet available due to a recent attempt, 403 by default.
otp_unlock_auth_success_notice_flash :: The flash notice to show upon successful unlock authentication, when additional unlock authentication is still needed.
otp_unlock_auths_required :: The number of consecutive successful authentication attempts needed to unlock OTP authentication, 3 by default.
otp_unlock_button :: Text to use for button on OTP unlock form.
otp_unlock_consecutive_successes_label :: Text to show next to the number of consecutive successful authentication attempts the user has already made.
otp_unlock_deadline_seconds :: The number of seconds between a previously successful authentication attempt and the next successful authentication attempt. This defaults to twice the amount of time of the OTP interval (30 seconds) plus twice the amount of allowed drift (30 seconds), for a total of 120 seconds. This is to make sure the same OTP code cannot be used more than one when unlocking.
otp_unlock_form_footer :: A footer to display at the bottom of the OTP unlock form.
otp_unlock_id_column :: The column in the +otp_unlock_table+ containing the account id.
otp_unlock_next_auth_attempt_after_column :: The column in the +otp_unlock_table+ containing a timestamp for when the user can next try an authentication attempt.
otp_unlock_next_auth_attempt_label :: Text to show next to the time when the next unlock authentication attempt will be allowed.
otp_unlock_next_auth_attempt_refresh_label :: Text to show explaining that the page will refresh when the next unlock authentication attempt will be allowed.
otp_unlock_next_auth_deadline_label :: Text to show next to the deadline for unlock authentication.
otp_unlock_not_available_page_title :: The page title to use on the page letting users know they need to wait to unlock OTP authentication.
otp_unlock_not_locked_out_error_flash :: The flash error to show if attempting to access the OTP unlock page when OTP authentication is not locked out.
otp_unlock_not_locked_out_error_status :: The response status to use if attempting to access the OTP unlock page when OTP authentication is not locked out, 403 by default.
otp_unlock_not_locked_out_redirect :: Where to redirect if attempting to access the OTP unlock page when OTP authentication is not locked out.
otp_unlock_num_successes_column :: The column in the +otp_unlock_table+ containing the number of consecutive successful authentications.
otp_unlock_page_title :: The page title to use on the OTP unlock form.
otp_unlock_refresh_tag :: The meta refresh tag HTML to use to force a refresh of the page. This can be overridden to use a different refresh approach, such as a manual refresh. This is no longer used by default.
otp_unlock_required_consecutive_successes_label :: Text to show next to the number of consecutive successful authentication attempts the user is required to make to unlock OTP authentication.
otp_unlock_route :: The route to the OTP unlock action. Defaults to +otp-unlock+.
otp_unlock_table :: The table name containing the OTP unlock information.
otp_unlocked_notice_flash :: The flash notice to show when OTP authentication is successfully fully unlocked.
otp_unlocked_redirect :: Where to redirect when OTP authentication is successfully fully unlocked.
== Auth Methods
after_otp_unlock_auth_failure :: Run arbitrary code after OTP unlock authentication failure.
after_otp_unlock_auth_success :: Run arbitrary code after OTP unlock authentication success.
after_otp_unlock_not_yet_available :: Run arbitrary code when attempting OTP unlock when it is not yet available.
before_otp_unlock_attempt :: Run arbitrary code before checking whether OTP unlock authentication code is valid.
before_otp_unlock_route :: Run arbitrary code before handling an OTP unlock route.
otp_unlock_auth_failure :: Handle a authentication failure when trying to unlock. By default, this sets the number of consecutive successful authentication attempts to 0, and forces a significant delay before the next unlock authentication attempt can be made.
otp_unlock_auth_success :: Handle a authentication failure when trying to unlock. By default, this increments the number of consecutive successful authentication attempts, and imposes a short delay before the next unlock authentication attempt can be made (to ensure the code cannot be reused).
otp_unlock_available? :: Returns whether it is possible to unlock OTP authentication. This assumes that OTP is already locked out.
otp_unlock_deadline_passed? :: Returns whether the deadline to submit an OTP unlock authentication code has passed.
otp_unlock_not_available_set_refresh_header :: Set the Refresh response header for the otp unlock not available page, so it refreshes after an unlock attempt is available.
otp_unlock_not_available_view :: The HTML to use for the page when the OTP unlock form is not yet available due to a recent unlock authentication attempt.
otp_unlock_view :: The HTML to use for the OTP unlock form.
jeremyevans-rodauth-b53f402/doc/password_complexity.rdoc 0000664 0000000 0000000 00000005062 15157255142 0023604 0 ustar 00root root 0000000 0000000 = Documentation for Password Complexity Feature
The password complexity feature implements more sophisticated password
complexity checks. It is not recommended to use this feature unless
you have a policy that requires it, as users that would not choose a
good password in the absence of password complexity requirements are
unlikely to choose a good password if you have password complexity
requirements.
Checks:
* Contains characters in multiple character groups, by default at
least 3 of uppercase letters, lowercase letters, numbers, and
everything else, unless the password is over 11 characters.
* Does not contain any invalid patterns, by default patterns like
+qwerty+, +azerty+, +asdf+, +zxcv+, or number sequences such as +123+.
* Does not contain a certain number of repeating characters, by default 3.
* Is not a dictionary word, after stripping off numbers from the prefix
and suffix and replacing some common numbers/symbols often substituted
for letters, catching things like P@$$w0rd1.
== Auth Value Methods
password_character_groups :: An array of regular expressions representing different character groups.
password_dictionary :: A Array/Hash/Set containing dictionary words, which cannot match the password.
password_dictionary_file :: A file containing dictionary words, which will not be allowed. By default, /usr/share/dict/words if present. Set to false to not use a password dictionary. Note that this is only used during initialization, and cannot refer to request-specific state, unlike most other settings.
password_in_dictionary_message :: The error message fragment to show if the password is derived from a word in a dictionary.
password_invalid_pattern :: A regexp where any match is considered an invalid password. For multiple sequences, use +Regexp.union+.
password_invalid_pattern_message :: The error message fragment to show if the password matches the invalid pattern.
password_max_length_for_groups_check :: The number of characters above which to skip the checks for character groups.
password_max_repeating_characters :: The maximum number of repeating characters allowed.
password_min_groups :: The minimum number of character groups the password has to contain if it is less than +password_max_length_for_groups_check+ characters.
password_not_enough_character_groups_message :: The error message fragment to show if the password does not contain characters from enough character groups.
password_too_many_repeating_characters_message :: The error message fragment to show if the password contains too many repeating characters.
jeremyevans-rodauth-b53f402/doc/password_expiration.rdoc 0000664 0000000 0000000 00000004653 15157255142 0023576 0 ustar 00root root 0000000 0000000 = Documentation for Password Expiration Feature
The password expiration feature requires that users change their
password on login if it has expired (default: every 90 days). You can
force password expiration checks for all logged in users by adding
the following code to your route block:
rodauth.require_current_password
Additionally, you can set a minimum amount of time after a password
is changed until it can be changed again. By default this is not
enabled, but it can be enabled by setting +allow_password_change_after+
to a positive number of seconds.
It is not recommended to use this feature unless you have a policy that
requires it, as password expiration in general results in users choosing
weaker passwords. When asked to change their password, many users choose
a password that is based on their previous password, so forcing password
expiration is in general a net loss from a security perspective.
== Auth Value Methods
allow_password_change_after :: How long in seconds after the last password change until another password change is allowed (always allowed by default).
password_change_needed_redirect :: Where to redirect if a password needs to be changed.
password_changed_at_session_key :: The key in the session storing the timestamp the password was changed at.
password_expiration_changed_at_column :: The column in the +password_expiration_table+ containing the timestamp
password_expiration_default :: If the last password change time for an account cannot be determined, whether to consider the account expired, false by default.
password_expiration_error_flash :: The flash error to display when the account's password has expired and needs to be changed.
password_expiration_id_column :: The column in the +password_expiration_table+ containing the account's id.
password_expiration_table :: The table holding the password last changed timestamps.
password_not_changeable_yet_error_flash :: The flash error to display when not enough time has elapsed since the last password change and an attempt is made to change the password.
password_not_changeable_yet_redirect :: Where to redirect if the password cannot be changed yet.
require_password_change_after :: How long in seconds until a password change is required (90 days by default).
== Auth Methods
password_expired? :: Whether the password has expired for the related account.
update_password_changed_at :: Update the password last changed timestamp for the current account.
jeremyevans-rodauth-b53f402/doc/password_grace_period.rdoc 0000664 0000000 0000000 00000002065 15157255142 0024032 0 ustar 00root root 0000000 0000000 = Documentation for Password Grace Period Feature
The password grace period feature keeps track of the last time the
user entered their password in the session, and doesn't require they reenter their
password for account modifications if they recently entered it correctly.
If you would like to provide extra security before certain routes, you can use
the confirm password feature to require users to reenter their password if they
haven't entered it recently:
rodauth.require_password_authentication
By default, this does not redirect if the session has been authenticated via
password, but with the password_grace_period feature, it also redirects if the
password has not been entered recently.
== Auth Value Methods
last_password_entry_session_key :: The session key in which to store the last password entry time.
password_grace_period :: The number of seconds after a password entry until password reentry is required, 300 by default (5 minutes).
== Auth Methods
password_recently_entered? :: Whether the password has last been entered within the grace period.
jeremyevans-rodauth-b53f402/doc/password_pepper.rdoc 0000664 0000000 0000000 00000004773 15157255142 0022712 0 ustar 00root root 0000000 0000000 = Documentation for Password Pepper Feature
The password pepper feature appends a specified secret string to passwords
before they are hashed. This way, if the password hashes get compromised, an
attacker cannot use them to crack the passwords without also knowing the
pepper.
In the configuration block set the +password_pepper+ with your secret string.
It's recommended for the password pepper to be at last 32 characters long and
randomly generated.
password_pepper ""
If your database already contains password hashes that were created without a
password pepper, these will get automatically updated with a password pepper
next time the user successfully enters their password.
If you're using bcrypt (default), you should set +password_maximum_bytes+ so
that password + pepper don't exceed 72 bytes. This is because bcrypt truncates
passwords longer than 72 bytes, enabling an attacker to crack the pepper if the
password bytesize is unlimited. If you're using argon2, you should probably set
+argon2_secret+ instead of using this feature.
== Pepper Rotation
You can rotate the password pepper as well, just make sure to add the previous
pepper to the +previous_password_peppers+ array. Password hashes using the old
pepper will get automatically updated on the next successful password match.
password_pepper "new pepper"
previous_password_peppers ["old pepper", ""]
The empty string above ensures password hashes without pepper are handled as
well.
Note that each entry in +previous_password_peppers+ will multiply the amount of
possible password checks during login, at least for incorrect passwords.
Additionally, when using this feature with the disallow_password_reuse feature,
the number of passwords checked when changing or resetting a password will be
(previous_password_peppers.length + 1) * previous_passwords_to_check
So if you have 2 entries in +previous_password_peppers+, using the default
value of 6 for +previous_passwords_to_check+, every time a password
is changed, there will be 18 password checks done, which will be quite slow.
== Auth Value Methods
password_pepper :: The secret string appended to passwords before they are hashed.
previous_password_peppers :: An array of password peppers that will be tried on an unsuccessful password match. Defaults to [""], which allows introducing this feature with existing passwords.
password_pepper_update? :: Whether to update password hashes that use a pepper from +previous_password_peppers+ with a new pepper. Defaults to +true+.
jeremyevans-rodauth-b53f402/doc/path_class_methods.rdoc 0000664 0000000 0000000 00000000741 15157255142 0023330 0 ustar 00root root 0000000 0000000 = Documentation for Path Class Methods Feature
The path class methods feature allows for calling the *_path and *_url
methods directly on the class, as opposed to an instance of the class.
In order for the *_url methods to be used, you must use the base_url
configuration so that determining the base URL doesn't depend on the
submitted request, as the request will not be set when using the
class method. Failure to do this will probably result in a NoMethodError
being raised.
jeremyevans-rodauth-b53f402/doc/recovery_codes.rdoc 0000664 0000000 0000000 00000011554 15157255142 0022503 0 ustar 00root root 0000000 0000000 = Documentation for Recovery Codes Feature
The recovery codes feature allows multifactor authentication via single use recovery
codes. It is usually used as a backup if other multifactor authentication methods are
not available or have been locked out, but can be used by itself. It allows
users to view authentication recovery codes as well as regenerate recovery codes.
Access to recovery codes is limited to authenticated sessions only, so users should
be recommended to securely store/preserve a subset of these codes prior to any chance
of them being required due to a missing / lost device.
== Auth Value Methods
add_recovery_codes_redirect :: Where to redirect to add recovery codes if recovery codes are the primary multifactor authentication and have not been setup yet.
add_recovery_codes_button :: Text to use for button on the form to add recovery codes.
add_recovery_codes_error_flash :: The flash error to show when adding recovery codes.
add_recovery_codes_heading :: Text to use for heading above the form to add recovery codes.
add_recovery_codes_page_title :: The page title to use on the add recovery codes form.
add_recovery_codes_param :: The parameter name to use for adding recovery codes.
auto_add_recovery_codes? :: Whether to automatically add recovery codes (or any missing recovery codes) when enabling otp, webauthn, or sms authentication (false by default).
auto_remove_recovery_codes? :: Whether to automatically remove recovery codes when disabling otp, webauthn, or sms authentication and not having one of the other two authentication methods enabled (false by default).
invalid_recovery_code_error_flash :: The flash error to show when an invalid recovery code is used.
invalid_recovery_code_message :: The error message to show when an invalid recovery code is used.
recovery_auth_additional_form_tags :: HTML fragment containing additional form tags when authenticating via a recovery code.
recovery_auth_button :: The text to use for the button when authenticating via a recovery code.
recovery_auth_link_text :: The text to use for the link from the multifactor auth page.
recovery_auth_page_title :: The page title to use on the form to authenticate via a recovery code.
recovery_auth_redirect :: Where to redirect after authenticating via an recovery code.
recovery_auth_route :: The route to the recovery code authentication action. Defaults to +recovery-auth+.
recovery_codes_added_notice_flash :: The flash notice to show when recovery codes were added.
recovery_codes_additional_form_tags :: HTML fragment containing additional form tags when adding recovery codes.
recovery_codes_column :: The column in the +recovery_codes_table+ containing the recovery code.
recovery_codes_id_column :: The column in the +recovery_codes_table+ containing the account id.
recovery_codes_label :: The label for recovery codes.
recovery_codes_limit :: The number of recovery codes to setup.
recovery_codes_link_text :: The text to use for the setup link from the multifactor manage page.
recovery_codes_page_title :: The page title to use on the form to view recovery codes.
recovery_codes_param :: The parameter name for the recovery code.
recovery_codes_primary? :: Whether recovery codes are a primary multifactor authentication type. If not, they cannot be setup unless multifactor authentication is already setup.
recovery_codes_route :: The route to the view recovery codes action. Defaults to +recovery-codes+.
recovery_codes_table :: The table storing the recovery codes.
view_recovery_codes_button :: Text for the button to view recovery codes.
view_recovery_codes_error_flash :: The flash error to show when viewing recovery codes was not successful.
== Auth Methods
add_recovery_code :: Add a recovery code for the given account.
add_recovery_codes_view :: The HTML to use for the add recovery codes form.
after_add_recovery_codes :: Run arbitrary code after adding recovery codes.
before_add_recovery_codes :: Run arbitrary code before adding recovery codes.
before_recovery_auth :: Run arbitrary code before recovery code authentication.
before_recovery_auth_route :: Run arbitrary code before handling recovery code authentication route.
before_recovery_codes_route :: Run arbitrary code before handling view/add recovery codes route.
before_view_recovery_codes :: Run arbitrary code before viewing recovery codes.
can_add_recovery_codes? :: Whether the current account can add more recovery codes.
new_recovery_code :: A new recovery code to insert into the recovery codes table.
recovery_auth_view :: The HTML to use for the form to authenticate via a recovery code.
recovery_code_match?(code) :: Whether the given code matches any of the existing recovery_codes.
recovery_codes :: An array containing all valid recovery codes for the current account.
recovery_codes_available? :: Whether authentication via recovery codes is ready for use.
recovery_codes_view :: The HTML to use for the form to view recovery codes.
jeremyevans-rodauth-b53f402/doc/release_notes/ 0000775 0000000 0000000 00000000000 15157255142 0021441 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/doc/release_notes/1.0.0.txt 0000664 0000000 0000000 00000044624 15157255142 0022650 0 ustar 00root root 0000000 0000000 = Highlights
* Two factor authentication support via TOTP, SMS, and recovery codes
* Support for any database supported by Sequel
* Full security support on PostgreSQL, MySQL, and MSSQL
* Full support for all features via JSON APIs, using JWT tokens
* Support for common IT security policies:
* Password complexity checks
* Disallowing reuse of recent passwords
* Password expiration
* Account expiration
* Session expiration
* Limiting accounts to a single session
= Backwards Compatibility
* Rodauth now defaults to skipping status checks on accounts unless
the verify account or close account features are used. Previously,
skip_status_checks? was false by default regardless of which
features were in use.
* Rodauth no longer uses Sequel::Models for accounts, all database
access is done through Sequel datasets. Users should switch to
using the db, accounts_table, and account_select configuration
methods if needed. The account_model configuration method still
exists for backwards compatibility, but it just warns and calls
those methods.
* The account_id_value configuration method has been renamed to
account_id.
* The account_id and account_status_id configuration methods have
been renamed to account_id_column and account_status_column. This
is more consistent with other features, which use *_column for
column names.
* Before hooks (e.g. before_login) are executed before actions that
change state. Before route hooks (e.g. before_login_route) have
been added and are now called in the same place as the previous
before hooks.
* Rodauth now uses flash errors instead of flash notices if the
message is not specifically a success message. For example,
if a login is required and the user is redirected to a login
page, a flash error is used instead of a flash notice.
* Field errors are now stored in the rodauth object instead of
instance variables in the Roda scope. This will affect you if you
were doing custom overrides of Rodauth's templates and were
expecting errors in instance variables. You can now retrieve a
field error using something like rodauth.field_error('login'), where
the argument is the related parameter name.
* Rodauth now requires bcrypt by default. If you are not using
bcrypt for authentication, you should set the following in your
Rodauth configuration:
require_bcrypt? false
* Rodauth now requires mail by default if using the lockout, reset
password, or verify account features. If you are using a custom
mail library, you should set the following in your Rodauth
configuration:
require_mail? false
* Rodauth now asks for the current password by default on all
account modification forms (such as change password). You can
disable this by setting modifications_require_password? to false.
* In the lockout feature, unlock_account_autologin? is now true by
default. Previously, it was false by default, which left open a
persistent denial of service attack if the account could be locked
out between when the account was unlocked and when the user could
login again.
You can now set unlock_account_requires_password? to true if you
want to check for the current password when unlocking the account.
However, if you are enabling password resets, this doesn't add
any security as anyone controlling the email address could reset
their password before unlocking the account.
* Rodauth now requires that logins are valid email addresses and at
least 3 or more characters by default. You can set
require_email_address_logins? to false to not require email
address logins, and login_minimum_length to set the minimum
length for logins. You can also have custom login requirement
checks by overriding login_meets_requirements?.
* Changing and resetting passwords now checks that the new password
is not the same as the existing password. Similarly, changing
logins now checks that the new login is not the same as the
existing login.
* create_account_autologin? is now true by default unless using the
verify_account feature, and verify_account_autologin? is now
true by default.
* Rodauth features are now stored under lib/rodauth/features instead
of under lib/roda/plugins/rodauth. Additionally, Rodauth features
should now go under the Rodauth namespace instead of the
Roda::RodaPlugins::Rodauth namespace. Also, Rodauth's internal APIs
have changed significantly to make it easier to create features.
Anyone using external Rodauth features needs to update them to
work with the new path structure, namespacing, and APIs.
* The ability to override specific routes in the routing tree has
been removed from Rodauth. Previously, you could use configuration
methods such as login_post_route to override Rodauth's handling of
POST /login. These methods no longer exist. Instead of using them,
you should just override the appropriate route in your routing tree
before calling r.rodauth.
* Rodauth now requires securerandom on initialization. Previously,
it did not require securerandom unless/until it was needed. As
all rack session handlers require securerandom, and all supported
ruby versions support securerandom, this should only affect you if
you are using a custom session handler that does not use
securerandom and your ruby implementation does not support
securerandom.
* Many Rodauth::Auth methods have been made private. Previously most
methods were public as the internal routing blocks were evaluated
in the Roda scope instead of the context of the Rodauth::Auth
object.
Additionally, if the feature defines a private method but you
override it with a configuration method, the overridden method now
remains private.
* The password confirmation part of the remember feature has been
split off into a separate confirm password feature with its own
route, and most of the configuration method names have changed to
reflect this.
* The routes to request an account unlock, request a password reset,
and resend the verify account email have been split into their own
routes, instead of using the same route names and handling requests
differently based on whether certain parameters were submitted.
* Per-request route names are no longer supported due to an
optimization. If you really need per-request route names, please
open an issue and they can be brought back as an option.
* Support for Roda < 2.6 has been dropped.
= New Features
* An OTP feature has been added for 2nd factor authentication via TOTP
(Time-Based One-Time Password, RFC 6238). This allows TOTP setup,
including displaying a QR code that can be scanned via a mobile
phone, authentication via TOTP authentication codes, and disabling
of TOTP authentication.
* An SMS codes feature has been added for backup 2nd factor
authentication via authentication codes sent in SMS messages. This
supports registering a mobile phone number, confirming that you can
receive authentication codes on the mobile phone number, requesting
an SMS authentication code, input of the SMS authentication code,
and disabling of SMS authentication.
As ruby has many different SMS libraries, and robust SMS gateways
generally require payments, Rodauth does not actually send the
SMS messages itself, any user using the SMS codes feature needs to
use the sms_send configuration method:
sms_send do |phone_number, message|
SomeSMSLibrary.send(phone_number, message)
end
* A recovery codes feature has been added for backup 2nd factor
authentication via single-use account recovery codes. This supports
viewing existing recovery codes, as well as generating additional
recovery codes.
* A JWT feature has been added, which adds JSON API support for all
features that ship with Rodauth. By default, authentication data
is stored in JWT tokens that are passed via the Authorization
headers in the request and response.
A POST-only JSON API is used, where submitted parameters should
use the same names as the browser would use, all of which are
configurable using Rodauth's configuration methods. By default,
unsuccessful requests receive a 400 status code with a JSON
object body with "error" and possibly "field-error" entries,
and successful requests receive a 200 status code with an empty
JSON object body.
* A password complexity feature has been added for configurable
password complexity checks, such as:
* Contains characters in multiple character groups (default 3),
unless the password is over a given length (default 11).
* Does not contain common character or number sequences such as
qwerty and 123.
* Does not contain a certain number of repeating characters
(default 3).
* Does not contain a dictionary word, after stripping of numbers
from the start and end of the password, and replacing common
character substitutions (0 for o, $ for s).
* A disallow password reuse feature has been added, which stores
previous password hashes in addition to current passwords hashes,
and does not allow a user to reuse a recent password (by default,
any of their last 6).
Previous password hashes are stored with the same security as the
current password hash, so by default on PostgreSQL, MySQL, and
Microsoft SQL Server, the application's database account does not
have access to read them and must use database functions to
retrieve the salts, compute hashes, and check if the hashes match.
* A password expiration feature has been added, which requires that
users change their password after a given amount of time (default
is 90 days). It also supports not allowing password changes
until a given amount of time after the last password change, to
prevent users from quickly rotating their password back to their
original password if disallowing password reuse.
By default, passwords are only checked for expiration on login.
If you want to check passwords on every access, you can use:
rodauth.require_current_password
at the appropriate point in your routing block. If a password
has expired, the user will be redirected to the change password
form.
* An account expiration feature has been added, which disallows
access to accounts after an amount of time since last login or
activity. The default is to only track login times, and expire
accounts based on their last login time. However, if you allow
long running sessions, this may not provide an accurate picture
of the last time the account was used. If you want to expire
accounts based on last activity, you should set
expire_account_on_last_activity? to true and use:
rodauth.update_last_activity
at the appropriate place in your routing block. This method
is fairly expensive as it requires database access every time
it is called.
* A single session feature has been added, which limits each
account to a single logged in session. Upon any login to
an account, any previous session will no longer be valid. To
make sure that this is enforced, you need to use:
rodauth.check_single_session
at the appropriate place in your routing block. This method
is fairly expensive as it requires database access every time
it is called.
* A session expiration feature has been added, which can
automatically expire sessions based on inactivity (default
30 minutes) and max lifetime (default 1 day) checks. To make
sure that session expiration is enforced, you need to use:
rodauth.check_session_expiration
at the appropriate place in your routing block.
* A password grace period feature has been added, which makes it
so passwords are not needed for account changes if the password
has been entered recently (default 5 minutes).
* A verify account grace period feature has been added, which
automatically logs accounts in on account creation, and allows
them to login without verification for a period of time after
creation (default 1 day). After the time period has expired,
the account cannot log in until it has been verified.
* A verify change login feature has been added, which requires
that accounts that change logins reverify they have access to the
new email address. This depends on the verify account grace
period feature, and allows them to continue to use the account
during the grace period, but after the grace period has expired,
they can no longer log in until the account has been reverified.
= Other Improvements
* All of Rodauth's features should now work on any database that
Sequel supports, and Rodauth is fully tested on PostgreSQL, MySQL,
SQLite, and Microsoft SQL Server. Rodauth's full security support,
which prevents the application database account from accessing
password hashes, is fully tested on PostgreSQL, MySQL, and Microsoft
SQL Server.
* r.rodauth is now O(1) instead of O(N) where N is the number of
rodauth routes.
* Rodauth now uses a timing-safe algorithm for all token comparisons,
avoiding possible timing attacks on tokens.
* Rodauth now supports rodauth.authenticated? method for checking if
the user has been authenticated. If the user has setup two
factor authentication, this checks that the user has been
authenticated via two factors. rodauth.require_authentication has
also been added, which redirects the user to the appropriate
authentication page if they have not been authenticated.
* All of Rodauth's routes for modifying accounts, such as change
password, now require the user be authenticated via two factors if
they have setup two factor authentication.
* You can now disable login/password confirmation by setting
require_login_confirmation? and require_password_confirmation? to
false. This is useful when using the JSON API support, where
confirmation checks would generally be done client side.
* Rodauth now supports a set_deadline_values? method for whether to
set deadline values for tokens explicitly on a per-request basis,
and *_interval configuration methods for how long to set such
deadlines:
set_deadline_values? true
account_lockouts_deadline_interval :days=>2
remember_deadline_interval :days=>60
reset_password_deadline_interval :days=>7
In order for this feature to work, Rodauth will load Sequel's
date_arithmetic extension into the Sequel::Database object it
uses. Note that set_deadline_values? defaults to true on MySQL,
as MySQL does not support non-constant column defaults.
* Rodauth supports more specific password requirement error
messages, showing which specific password requirement was
not met.
* A reset_password_deadline_column method has been added for
overriding the column name used to store the reset password
deadlines.
* Many configuration methods were added to the remember feature
to control the parameter names and labels used. Configuration
methods were also added for flash notices and errors in the
remember feature.
* rodauth.load_memory in the remember feature now checks that the
account is still active. Previously, the remember feature could
be used to log into inactive accounts if the accounts remember
token was not correctly deleted. Additionally, any invalid
tokens in cookies will result in the removal of the cookie.
* When extend_remember_deadline? is used, rodauth.load_memory
correctly extends the deadline to be based on the current
timestamp, and also updates the cookie instead of just updating
the database.
* The close account feature now supports a delete_account_on_close?
option, which will delete accounts after closing them.
* The close account feature now works correctly when skipping
status checks or when using account_password_hash_column.
* A password_hash_id_column has been added for specifying the
account id column in the password hash table.
* A token separator configuration method has been, to override the
default token separator of "_".
* You can now add your own methods easily to the rodauth object
via auth_class_eval:
plugin :rodauth do
enable :login, :logout
after_login do
log('logged in')
end
after_logout do
log('logged out')
end
auth_class_eval do
def log(msg)
LOGGER.info("#{account[:email]} #{msg}")
end
end
end
The auth_class_eval block is evaluated in the context of the
Rodauth::Auth class that the rodauth plugin builds. Methods you
define in this block are then callable on the rodauth object
inside the routing tree block.
* Rodauth now only allows requesting an account unlock if the
account is currently locked out.
* If an account is locked out during login, the appropriate error
message is now displayed immediately, instead of waiting until the
next request.
* Rodauth now does better error handling in the lockout, reset
password and verify account features. Previously, users may have
received 404 errors when using invalid tokens in these features.
* Rodauth now uses separate templates for shared form input fields,
making it easier to override handling of individual fields
without overriding entire templates.
* Rodauth now supports authentication without database functions
when using the recommended schema of storing password hashes
in a separate table. Previously, if database functions were not
used, Rodauth only supported storing password hashes in the same
table as the accounts.
* Creating the database authentication functions that Rodauth uses
can now be done by requiring rodauth/migrations and calling the
Rodauth.create_database_authentication_functions method with the
appropriate Sequel::Database object.
* You no longer need to call super() in before and after hooks.
* Rodauth now handles race conditions related to unique constraint
violations where it is possible to do so. In the cases where it
is not possible to handle the race condition correctly, an
exception will still be raised.
* Non-integer account ids now work correctly in tokens.
* Rodauth now uses frozen string literals by default on ruby 2.3
* The random_key and password_hash_cost default methods have been
made faster by using conditionals to define separate methods,
instead of conditionals inside the methods.
* As Rodauth can now be used in JSON API only mode, the gem
dependencies are limited to roda and sequel. When used outside
of JSON API only mode, it also requires tilt and rack_csrf.
* Rodauth.version has been added for getting the version of
Rodauth in use.
* Travis-CI is now used for continuous integration testing on ruby
1.8.7-2.3.0, JRuby 1.7 (1.8 and 1.9 modes), and JRuby 9.0, using
PostgreSQL, MySQL, and SQLite.
jeremyevans-rodauth-b53f402/doc/release_notes/1.1.0.txt 0000664 0000000 0000000 00000000561 15157255142 0022641 0 ustar 00root root 0000000 0000000 = New Features
* The rodauth plugin now supports :csrf=>false and :flash=>false
options. This will make it so it no longer depends on the csrf
or flash plugins, which is useful when the csrf and flash
functionality is provided via a different approach, such as
when rodauth is being used inside middleware in a Rails
application with the roda-rails library.
jeremyevans-rodauth-b53f402/doc/release_notes/1.10.0.txt 0000664 0000000 0000000 00000005554 15157255142 0022730 0 ustar 00root root 0000000 0000000 = New Features
* A verify_login_change feature has been added. This is designed
as a replacement for the previous verify_change_login feature,
which was problematic as it could result in a user being unable
to access their account if they used an incorrect email when
changing their login.
The verify_login_change feature does not change the user's login
until after the user has confirmed that they can receive email
using the new login.
The verify_login_change feature requires an additional database
table to store information on login changes, so it is not a
drop in replacement for the verify_change_login feature. However,
it is recommended that all users of verify_change_login switch
to verify_login_change.
* If using the reset_password feature, there is now a link on the
login page to a page that will allow you to request a password
reset. Previously you had to attempt to login with the account
in order to request a password reset.
* If using the verify_account feature, there is now a link on the
login page to a page that will allow you to request that the
account verification email be resent. Previously you had to
attempt to login with the account or attempt to create a new
account with the same login in order to get an account verification
email resent.
* If using the reset_password feature, there is now a
login_failed_reset_password_request_form configuration method for
customizing the HTML used for the request password reset form shown
when there is a login failure.
= Improvements
* When using the verify_account_grace_period feature, attempting to
create a new account using the same login as an existing
unverified account now correctly offers the ability to resend the
account verification email.
* The precompile_rodauth_templates method now works with the
reset_password feature.
* When attempting to reopen a rodauth configuration in a subclass
of the Roda class that created the rodauth configuration, a
subclass of the rodauth configuration is now automatically
created. This makes it so changes to the rodauth configuration
in the Roda subclass no longer affect the rodauth configuration
of the superclass.
* The FeatureConfiguration instances for each feature are now
assigned to constants, making inspect output more descriptive.
* An internals guide has been added, which explains the
metaprogramming used to implement Rodauth.
= Backwards Compatibility
* Any external features should start providing two arguments to
Feature.define, with the second argument being the constant
name to use. So instead of:
module Rodauth
Foo = Feature.define(:foo) do
# ...
end
end
switch to:
module Rodauth
Feature.define(:foo, :Foo) do
# ...
end
end
This will ensure that the related FeatureConfiguration instance
is assigned to a constant.
jeremyevans-rodauth-b53f402/doc/release_notes/1.11.0.txt 0000664 0000000 0000000 00000002532 15157255142 0022722 0 ustar 00root root 0000000 0000000 = New Features
* A rodauth.valid_jwt? method has been added, allowing for easy
checking of whether a valid JWT has been submitted. If a valid
JWT has been submitted, the contents of the JWT will be available
in rodauth.session.
* If using the jwt feature with json_response_custom_error_status?
set to true, and going to a page that requires a login when not
logged in, a 401 error status will now be used instead of a 400
error status. You can customize this status using the new
login_required_error_status configuration method.
= Improvements
* Time differences between the database server and the application
server are now handled slightly better in the password_expiration
feature. This mostly affects testing, where sometimes tests would
previously fail if the database server time was ahead of the
application server time when testing whether a password change was
allowed.
* Some methods that were private by default, but public if overridden,
are now public by default. These include update_session and
only_json? in the base feature, and json_request?, jwt_secret, and
use_jwt? in the jwt feature.
= Backwards Compatibility
* The private jwt_payload method in the jwt feature now returns false
instead of redirecting on error. This should not affect the
application unless the method was being called explicitly.
jeremyevans-rodauth-b53f402/doc/release_notes/1.12.0.txt 0000664 0000000 0000000 00000004536 15157255142 0022731 0 ustar 00root root 0000000 0000000 = Security Fix
* The password reset key deadline was previously ignored when
checking for a password reset key. This allowed expired keys to
be used. This problem exists in all previous versions.
The root cause of this issue is that support for deadline checking
was not previously implemented. In previous versions, the deadline
was only used to remove old keys when creating a new key.
Rodauth only allows a single password reset key per account, and
deletes password reset keys when passwords are reset. So if the
user had subsequently generated a different password reset key, or
had already used the password reset key to reset the password,
then they would not be vulnerable. The most likely situation
where there exists a vulnerability due to this issue is:
* A user requests a password reset.
* They do not reset their password or request another
password reset.
* The password reset key deadline expires.
* An attacker gets access to their archived email containing
the password reset link, which they use to reset the
password for the account.
Reporting Details:
* Initially reported on 10/3/2017
* Fixed in repository on 10/3/2017
* Version 1.12.0 released with fix on 10/3/2017
Thanks to Chris Hanks for discovering and reporting this issue
and supplying an initial fix.
= New Features
* The http_basic_auth feature now supports a
require_http_basic_auth configuration method. When set to true,
if authentication is required and the request is not already
authenticated, they will get a 401 response instead of a
redirect to the login page.
* All of the following Rodauth migration methods now support an
options hash:
* Rodauth.drop_database_authentication_functions
* Rodauth.create_database_previous_password_check_functions
* Rodauth.drop_database_previous_password_check_functions
These options allow you to customize the get_salt_name and
valid_hash_name database functions, as well as set the the table
for the previous_password_check_functions.
* A :search_path option is now supported when using the following
Rodauth migration methods on PostgreSQL:
* Rodauth.create_database_authentication_functions
* Rodauth.drop_database_authentication_functions
This sets the search_path to use inside the function. For
backwards compatibility, it defaults to 'public, pg_temp'.
jeremyevans-rodauth-b53f402/doc/release_notes/1.13.0.txt 0000664 0000000 0000000 00000002715 15157255142 0022727 0 ustar 00root root 0000000 0000000 = New Features
* A cache_templates configuration method has been added, which can be
set to false to disable the default caching of templates. The main
time you would want to use this is if you were overriding Rodauth's
templates with your own templates and modifying such templates in
development mode. If that is the case, you may want to use
something like:
cache_templates(ENV['RACK_ENV'] != 'development')
* An invalid_previous_password_message configuration method has been
added to the change_password feature, which overrides the default
invalid_password_message configuration method if the incorrect
previous password is used when changing the password. This is
designed for use when invalid_password_message has been overridden
and the message doesn't make sense in the change password case.
* A json_response_body(hash) configuration method has been added to
the jwt feature, allowing for custom formatting of the JSON
response body. This is called with the hash to use in the
response, and should return a JSON-formatted string. Example:
json_response_body do |hash|
super('status'=>response.status, 'detail'=>hash)
end
= Other Improvements
* In the jwt feature, if json_response_custom_error_status? is set to
true, custom error statuses will be used if only_json? is set to
true, even if the request is not in JSON format. Previously,
custom error statuses were only used if the request was in JSON
format.
jeremyevans-rodauth-b53f402/doc/release_notes/1.14.0.txt 0000664 0000000 0000000 00000001516 15157255142 0022726 0 ustar 00root root 0000000 0000000 = New Features
* A change_password_notify feature has been added, which emails the
user when the change_password feature is used to change their
password. This can alert the user when their password may have
been changed without their knowledge.
= Other Improvements
* When using the account_expiration feature with the reset_password
feature, resetting the passwords for expired accounts is no longer
allowed. Note that the previous behavior isn't considered a
security issue, because even after resetting their password,
expired accounts could not login.
* When using the account_expiration feature with the lockout feature,
unlocking expired accounts is no longer allowed. Note that the
previous behavior isn't considered a security issue, because even
after unlocking the account, expired accounts could not login.
jeremyevans-rodauth-b53f402/doc/release_notes/1.15.0.txt 0000664 0000000 0000000 00000001561 15157255142 0022727 0 ustar 00root root 0000000 0000000 = New Features
* create_account_set_password? and verify_account_set_password?
configuration methods have been added to the create_account and
verify_account features. Setting:
verify_account_set_password? true
in your rodauth configuration will change Rodauth so that instead
of asking for a password on the create account form, it will ask for
a password on the verify account form.
This can fix a possible issue where an attacker creates an account
for a user with a password the attacker knows. If the user clicks
on the link in the verify account email and clicks on the button on
the verify account page, the attacker would have have a verified
account that they know the password to.
By setting verify_account_set_password? to true, you can ensure that
only the user who has access to the email can enter the password for
the account.
jeremyevans-rodauth-b53f402/doc/release_notes/1.16.0.txt 0000664 0000000 0000000 00000002200 15157255142 0022717 0 ustar 00root root 0000000 0000000 = New Features
* A disallow_common_passwords feature has been added. This feature
by default will disallow the 10,000 most common passwords:
enable :disallow_common_passwords
You can supply your own file containing common passwords separated
by newlines ("\n"):
most_common_passwords_file '/path/to/file'
You can also supply a password dictionary directly as any object
that responds to include?:
most_common_passwords some_password_dictionary_object
The reason only the 10,000 most common passwords are used by
default is larger password files would significantly bloat the
size of the gem. Also, because the most common passwords are kept
in memory by default for performance reasons, larger password
files can bloat the memory usage of the process (the
disallow_common_passwords feature should use around 500KB of
memory by default). For very large password dictionaries,
consider using a custom object that does not keep all common
passwords in memory.
= Other Improvements
* Rodauth no longer uses the Rack::Request#[] method to get
parameter values. This method is deprecated in Rack 2.
jeremyevans-rodauth-b53f402/doc/release_notes/1.17.0.txt 0000664 0000000 0000000 00000001576 15157255142 0022737 0 ustar 00root root 0000000 0000000 = Improvements
* Support has been added for using Roda's route_csrf plugin with
request-specific CSRF tokens. When loading the Rodauth into
your Roda app, specify the :csrf=>:route_csrf plugin option
so that Rodauth will load the route_csrf plugin instead of
the csrf plugin.
* The use_request_specific_csrf_tokens? configuration option
has been added, it defaults to true when the the
:csrf=>:route_csrf option is used when loading the plugin.
* If you have custom templates for the reset password request,
unlock account request, or verify account resend link
request, you will have to update them to use the new
request-specific CSRF token feature.
= Backwards Compatibility
* The csrf_tag configuration method now accepts the path as
an optional argument, previously it accepted no arguments.
The optional argument defaults to the path of the current
request.
jeremyevans-rodauth-b53f402/doc/release_notes/1.18.0.txt 0000664 0000000 0000000 00000002044 15157255142 0022727 0 ustar 00root root 0000000 0000000 = New Features
* flash_error_key and flash_notice_key configuration methods have
been added for setting the keys used in the flash hash.
* A confirm_password_redirect_session_key configuration method was
added for configuring the session key used for storing the
confirm password redirect.
= Other Improvements
* Support for the new Roda sessions plugin has been added. Rodauth
now recognizes the :sessions_convert_symbols Roda application option
and will default to using string keys instead of symbol keys for
session and flash values if the application option is set.
= Backwards Compatibility
* If the :sessions_convert_symbols Roda application option is used,
and the jwt feature is used and the jwt_symbolize_deeply?
configuration method is not used, then the session data will not
have the top-level data converted to symbols.
* If the Roda application defines a clear_session method in the scope,
that method is now called by Rodauth to clear the session data. This
is for better integration with the Roda sessions plugin.
jeremyevans-rodauth-b53f402/doc/release_notes/1.19.0.txt 0000664 0000000 0000000 00000011746 15157255142 0022741 0 ustar 00root root 0000000 0000000 = New Features
* An email_auth feature has been added, which allows passwordless
logins using links sent via email. This allows usage without any
password storage. If the user does not have a password, when they
submit their login, they are sent a link via email. If the user
has a password, they have the option of either entering their
password or being sent a link via email.
* A use_multi_phase_login? configuration method has been added. If
this configuration method is set to true, a two-phase login is used,
which the login form only has a field for a user's login. After the
login form has been submitted (assuming there is a valid login), a
form is displayed with a field for the password.
* Optional email rate limiting is now supported in the lockout,
reset_password, and verify_account features, using the following
configuration methods:
* account_lockouts_email_last_sent_column
* reset_password_email_last_sent_column
* verify_account_email_last_sent_column
These methods are nil by default. To enable rate limiting, set
these to a symbol representing the column name in the appropriate
table. The recommended column name is email_last_sent. To use
this feature, you'll have to add this column to the appropriate
tables:
DB.add_column :account_lockouts, :email_last_sent, DateTime
DB.add_column :account_password_reset_keys, :email_last_sent, DateTime,
:null=>false, :default=>Sequel::CURRENT_TIMESTAMP
DB.add_column :account_verification_keys, :email_last_sent, DateTime,
:null=>false, :default=>Sequel::CURRENT_TIMESTAMP
When this support is enabled, by default Rodauth will not send
an email if an email has been sent within the last 5 minutes. You
can change this time period using the following configuration
methods, which take the number of seconds that must have elapsed
before sending another email:
* unlock_account_skip_resend_email_within
* reset_password_skip_resend_email_within
* verify_account_skip_resend_email_within
The new email_auth feature also supports email rate limiting, and
because there are no backwards compatibility issues, the support
is enabled by default.
* An after_account_lockout configuration method has been added,
which is called directly after locking out an account. This can
be useful for audit logging.
* A default_post_email_redirect configuration has been added, which
sets the default for all redirects after emailing if the account
is not currently logged in. Each individual feature that emails
still supports the appropriate *_redirect configuration method
for specifying behavior for that feature.
* A verify_login_change_duplicate_account_redirect configuration
method has been added for where to redirect if a user attempts
a login change where the new proposed login already exists.
* before_verify_login_change_email and after_verify_login_change_email
configuration methods have been added for executing code before
or after the verify login change email is sent.
= Other Improvements
* When using the verify_login_change feature, Rodauth now checks
that the new login is not already taken and fails in a more
graceful manner. Previously, Rodauth would not report an
error when the login change was requested, and would raise an
exception when attempting to verify the login change due to the
violation of a uniqueness constraint.
* Rodauth now avoids unnecessary database queries when using the
two factor authentication support and the following methods:
* authenticated?
* require_authentication
* require_two_factor_setup
* The otp-setup template now looks nicer when using both Bootstrap
3 and 4, especially on small screens such as phones.
* If the database_type was not MySQL, the lockout, remember, and
reset_password features no longer disable the requiring of the
date_arithmetic Sequel extension if another feature that
requires the extension is used.
* On MySQL, the rodauth_get_salt database function definition now
handles accounts without passwords. If you previously added the
database function and want to support accounts without passwords,
then you should drop the function and re-add it via:
Rodauth.drop_database_authentication_functions(DB)
Rodauth.create_database_authentication_functions(DB)
Note that MySQL does not support CREATE OR REPLACE FUNCTION, so
you have to drop the function and then create it, which will
temporarily result in the function not being defined.
= Backwards Compatibility
* The before_otp_authentication_route configuration method is
deprecated, please switch to before_otp_auth_route instead. This
change is made so that before_*_route method names are consistent
with the route name.
* The verify_account_email_sent_redirect configuration method now
defaults to / instead of /login. If you were previously not
setting this configuration method and would like it to default to
/login, you will now have to force the setting:
verify_account_email_sent_redirect '/login'
jeremyevans-rodauth-b53f402/doc/release_notes/1.2.0.txt 0000664 0000000 0000000 00000001406 15157255142 0022641 0 ustar 00root root 0000000 0000000 = New Features
* An otp_drift configuration method has been added to the otp plugin,
which allows you to set the number of seconds of allowed drift. This
makes the otp plugin easier to use if the client and server do not
have good synchronize to the same time source.
= Other Improvements
* Passwords containing the ASCII NUL character "\0" are no longer
allowed, as bcrypt truncates the password at the first NUL
character.
Note that bcrypt only uses the first 72 characters of the password
when constructing the hash, but Rodauth does not enforce a limit
of 72 characters. If you want to enforce a maximum password length
in your application, use the password_meets_requirements?
configuration method with a block and call super inside the block.
jeremyevans-rodauth-b53f402/doc/release_notes/1.20.0.txt 0000664 0000000 0000000 00000017226 15157255142 0022730 0 ustar 00root root 0000000 0000000 = New Features
* An hmac_secret configuration method has been added. If set,
Rodauth will use HMACs for all of the tokens that Rodauth creates.
By using HMACs for the tokens, even if the database storing the
tokens is compromised (e.g. via an SQL injection vulnerability), the
tokens stored in the database will not be usable without knowledge
of the HMAC secret.
The following features are affected by setting the hmac_secret
configuration method:
* email_auth
* lockout
* otp
* remember
* reset_password
* single_session
* verify_account
* verify_login_change
To allow for graceful transition when adding hmac_secret to an
existing Rodauth installation, you can use the
allow_raw_email_token? configuration method to keep allowing
raw tokens. However, you should remove the allow_raw_email_token?
setting after the existing tokens have expired (most tokens expire
after 1 day by default). Verify account tokens do not expire,
but users can request a new verify account token if their token has
expired.
For remember tokens, the raw_remember_token_deadline configuration
method can be used, which will only allow the use of raw remember
tokens before the given deadline, which should be the time in the
future when you want to no longer accept raw remember tokens. You
can remove this configuration method after the deadline has passed.
By default, the deadline should be set to 14 days after the time
you enable hmac_secret, since remember tokens expire in 14 days by
default.
Similarly, in the single_session feature, you can use the
allow_raw_single_session_key? configuration method to allow raw
single session keys.
In the otp feature, you cannot mix HMAC and non-HMAC tokens. If
the hmac_secret setting is enabled and there are any existing
otp tokens already setup, they will stop working. If you are
already using the otp feature and would like to use the hmac_secret
configuration method, you need to set the otp_keys_use_hmac?
configuration method to false unless you want to invalidate all
existing otp tokens.
The hmac_secret configuration is also used during OTP setup
in the otp feature, to ensure that the OTP secrets for two factor
authentication came from the server and were not modified by the
user. If hmac_secret is used, setting up OTP via JSON requires
sending a POST request to the otp-setup route. This request will
fail, but included in the response will be the OTP secret and raw
OTP secret to use. Submitting a POST request including the OTP
secret and raw OTP secret will allow OTP setup to complete.
* A jwt_refresh feature has been added. This uses the jwt feature,
and issuing short-lived JWTs with exp, iat, and nbf claims, with a
database-backed refresh token for issuing another short-lived JWT.
The refresh tokens will automatically use HMACs if the hmac_secret
configuration method is set.
* Rodauth's handling of form errors is now accessible by default.
aria-invalid attributes are now used on all input fields with
errors, and aria-describedby attributes are used to tie the input
fields to the error messages.
* All hard coded strings are now overridable via configuration
methods, with the following configuration methods added:
* lockout feature
* unlock_account_explanatory_text
* unlock_account_request_explanatory_text
* login_password_requirements_base feature
* already_an_account_with_this_login_message
* otp feature
* otp_provisioning_uri_label
* otp_secret_label
* recovery_codes feature
* add_recovery_codes_heading
* reset_password feature
* reset_password_explanatory_text
* verify_account feature
* verify_account_resend_explanatory_text
* The following configuration methods have been added to the base
feature, related to customization of input fields in Rodauth forms:
default_field_attributes :: The default attributes to use for input
field tags, if field_attributes does not
handle the field.
field_attributes(field) :: The attributes to use for input fields
with the given parameter name.
field_error_attributes(field) :: The attributes to use for input
fields with the given parameter
name if the field has an error.
formatted_field_error(field, error) :: HTML to use for the given
parameter name and error
text. Uses a span by
default.
input_field_error_class :: The CSS class to add for input fields
with errors.
input_field_error_message_class :: The CSS class to add for error
message spans.
input_field_label_suffix :: Adds suffix to all input field labels
login_input_type :: The input type to use for login fields.
Defaults to text, but can be set to email,
though that is currently a bad idea if you
want the login fields to have accessible error
handling.
mark_input_fields_as_required? :: Whether to mark all input fields
as required by default. Note that
this is currently a bad idea if
you want the fields to have
accessible error handling.
= Other Improvements
* rotp 5 is now supported in the otp feature. Previous rotp versions
down to rotp 2.1.1 remain supported.
* Performance of Rodauth routes has been improved by using defined
methods instead of instance_exec for route dispatching. Internal
unnecessary uses of instance_exec have also been removed for
performance reasons.
* When the disallow_password_reuse feature is used without the
verify_account feature, and account_password_hash_column
configuration is not used, Rodauth no longer tries to call a method
that does not exist.
* When using the disallow_password_reuse and verify_account features,
with verify_account_set_password? set to true, Rodauth skips adding
an empty password to the list of previous passwords.
* Rodauth now avoids an unnecessary DELETE query in the
disallow_password_reuse feature if there are no previous passwords.
* The otp-auth-code field now has an autocomplete=off attribute.
* On Ruby 1.8, new tokens now use URL safe base64 encoding, instead
of hex encoding. Rodauth has always used URL safe base64 encoding
for new tokens on Ruby 1.9+.
= Backwards Compatibility
* The following configuration methods have been renamed:
* email_auth feature
* no_matching_email_auth_key_message =>
no_matching_email_auth_key_error_flash
* lockout feature
* no_matching_unlock_account_key_message =>
no_matching_unlock_account_key_error_flash
* reset_password feature
* no_matching_reset_password_key_message =>
no_matching_reset_password_key_error_flash
* verify_account feature
* attempt_to_create_unverified_account_notice_message =>
attempt_to_create_unverified_account_error_flash
* attempt_to_login_to_unverified_account_notice_message =>
attempt_to_login_to_unverified_account_error_flash
* no_matching_verify_account_key_message =>
no_matching_verify_account_key_error_flash
* verify_login_change feature
* no_matching_verify_login_change_key_message =>
no_matching_verify_login_change_key_error_flash
Attempts to use the old method at configuration time, or calling
the method on the rodauth object at runtime, will result in a
deprecation warning.
jeremyevans-rodauth-b53f402/doc/release_notes/1.21.0.txt 0000664 0000000 0000000 00000001023 15157255142 0022715 0 ustar 00root root 0000000 0000000 = Improvements
* rotp 5.1 is now supported in the otp feature. Previous rotp
versions down to rotp 2.1.1 remain supported.
* When using the otp feature without the sms or recovery_codes
features, if an account gets locked out from OTP authentication due
to multiple invalid OTP authentication codes, automatically log
them out, and redirect them to the login page. Previously, the
default behavior in this case could be a redirect loop if OTP
authentication is required for the user on the default_redirect
page.
jeremyevans-rodauth-b53f402/doc/release_notes/1.22.0.txt 0000664 0000000 0000000 00000000611 15157255142 0022720 0 ustar 00root root 0000000 0000000 = New Features
* A jwt_cors feature has been added, handling Cross-Origin Resource
Sharing when using the jwt feature, including supporting CORS
preflight requests.
= Other Improvements
* Mail templates that include links (e.g. for verifying accounts),
now add a space after the link and before the newline, fixing
issues with some web mail providers that have broken auto-linkers.
jeremyevans-rodauth-b53f402/doc/release_notes/1.23.0.txt 0000664 0000000 0000000 00000002606 15157255142 0022727 0 ustar 00root root 0000000 0000000 = New Features
* When the email_auth feature is used, the link to request email
authentication is now displayed if the user inputs an incorrect
password. Previously, it was only shown if the user had not
yet entered a password.
* A send_email configuration method has been added, which can be
overridden to customize email delivery (such as logging such
email). The configuration method block accepts a Mail::Message
argument.
* All rodauth.*_route methods that return the name of the route
segment now have rodauth.*_path and rodauth.*_url equivalents,
which return the path and URL for the related routes, respectively.
The rodauth.*_path methods are useful when constructing links to
the related Rodauth pages on the same site, and the rodauth.*_url
methods are useful for constructing link to the Rodauth pages from
other sites or in email.
= Other Improvements
* Specs have been removed from the gem file, reducing gem size by
over 20%.
* rodauth.authenticated? now returns true on the OTP setup page
when using the otp feature. Previously, this method returned
false on the OTP setup page. However, as the user has not yet
setup OTP when viewing this page, they should be considered
fully authenticated, as they would be if they viewed any other
page before setting up OTP. This change probably only affects
cases where the layout uses rodauth.authenticated?.
jeremyevans-rodauth-b53f402/doc/release_notes/1.3.0.txt 0000664 0000000 0000000 00000001606 15157255142 0022644 0 ustar 00root root 0000000 0000000 = New Features
* A login_maximum_length configuration method has been added. This
defaults to 255, and rodauth will now show an error message if a
user tries to create a login longer than this setting.
= Backwards Compatibility
* Rodauth's documentation and test code now use :Bignum instead of
Bignum for database-independent 64-bit integer types. This is
because using Bignum is now deprecated in Sequel as it will stop
working correctly in ruby 2.4+, due to the unification of Fixnum
and Bignum into Integer.
Rodauth's library code does not use either :Bignum or Bignum, but if
you are starting to use Rodauth and are copying the example
migration from Rodauth's documentation, or you are running the
migrations in Rodauth's tests, you now need to use Sequel 4.35.0+.
* Some files related to the hosting of the demo site on Heroku have
been removed from the repository.
jeremyevans-rodauth-b53f402/doc/release_notes/1.4.0.txt 0000664 0000000 0000000 00000000746 15157255142 0022651 0 ustar 00root root 0000000 0000000 = New Features
* A update_password_hash feature has been added, which will update
the password hash for the account whenever the account's current
password hash has a cost different from the currently configured
password hash cost.
This allows you to increase the password hash cost for all
accounts or for certain types of accounts, and have the password
hashes automatically updated to use the new cost the next time the
correct password is provided for the account.
jeremyevans-rodauth-b53f402/doc/release_notes/1.5.0.txt 0000664 0000000 0000000 00000005612 15157255142 0022647 0 ustar 00root root 0000000 0000000 = jwt Feature Additions/Improvements
* JSON format responses now have the response content type set to
application/json.
* The jwt feature now does not break if HTTP Basic or Digest
authentication is used.
* If jwt_check_accept? is true, Rodauth will return a 406 error if
a request Accept header is provided and it does not indicate that
JSON is acceptable.
* Many new configuration methods have been added:
* invalid_jwt_format_error_message: The error message to use when a
JWT with an invalid format is submitted in the Authorization
header.
* json_accept_regexp: The regexp to use to check the Accept header
for JSON if jwt_check_accept? is true.
* json_not_accepted_error_message: The error message to display if
jwt_check_accept? is true and the Accept header is present but
does not match json_request_content_type_regexp.
* json_request_content_type_regexp: The regexp to use to recognize
a request as a json request.
* json_response_content_type: The content type to set for json
responses, application/json by default.
* jwt_authorization_ignore: A regexp matched against the
Authorization header, which skips JWT processing if it matches.
By default, HTTP Basic and Digest authentication are ignored.
* jwt_authorization_remove: A regexp to remove from the
Authorization header before processing the JWT. By default, a
Bearer prefix is removed.
* jwt_check_accept?: Whether to check the Accept header to see if
the client supports JSON responses, false by default for backwards
compatibility.
* session_jwt: An encoded JWT for the current session.
* use_jwt?: Whether to use the JWT in the Authorization header for
authentication information. If false, falls back to using the
rack session. By default, the Authorization header is used if it
is present, if only_json? is true, or if the request uses a json
content type.
= jwt Feature Backwards Compatibility Issues
* The only_json? setting in the jwt feature is now only true by
default if the :json=>:only option was used when loading the
rodauth plugin into the roda app. Previously, it was always true,
but it only was considered in requests to Rodauth endpoints. It
now also is considered in most Rodauth calls, and if true will use
an empty session instead of falling back to the rack session if an
Authorization header is not present.
* Previously, the jwt feature only handled requests where the
request content-type is JSON. It now also handles non-JSON
requests if the Authorization header is present or if only_json?
is true.
* If an invalid JWT format is used in the Authorization header,
Rodauth now returns a 400 error, instead of raising an exception.
= Other Improvements
* A template_opts configuration method has been added, for
overriding the view/render options. One possible use for this is
to specify a non-default layout.
jeremyevans-rodauth-b53f402/doc/release_notes/1.6.0.txt 0000664 0000000 0000000 00000002246 15157255142 0022650 0 ustar 00root root 0000000 0000000 = New Feature
* An http_basic_auth feature has been added, allowing the use of
HTTP Basic Auth to login.
= New Configuration Options for jwt Feature
* jwt_session_hash has been added, for modifying the hash given before
creating the JWT. This can be used for setting JWT claims.
Example:
jwt_session_hash do
super().merge(:exp=>Time.now.to_i + 120)
end
* jwt_decode_opts has been added for specifying additional options to
JWT.decode. Among other things, this allows for JWT claim
verification. Example:
jwt_decode_opts(:verify_expiration=>true)
* jwt_session_key has been added, specifying a key in the JWT that
will be used to store session information, instead of storing
session keys in the root of the JWT. Use of this option can avoid
issues with reserved JWT claim names, and will probably be enabled
by default starting in Rodauth 2.
* jwt_symbolize_deeply? configuration method has been added, for
whether to symbolize nested keys when decoding a JWT session hash.
= Other Improvements
* The reset_password feature no longer attempts to render a template
in json-only mode.
* The jwt_payload method is now memoized by default.
jeremyevans-rodauth-b53f402/doc/release_notes/1.7.0.txt 0000664 0000000 0000000 00000000442 15157255142 0022645 0 ustar 00root root 0000000 0000000 = Improvements
* The reset password, unlock account, and verify account features now
temporarily store the feature-specific keys in the session instead
of keeping them as parameters, which avoids leaking the keys to
asset hosts or other external servers via the HTTP Referer header.
jeremyevans-rodauth-b53f402/doc/release_notes/1.8.0.txt 0000664 0000000 0000000 00000001226 15157255142 0022647 0 ustar 00root root 0000000 0000000 = Improvements
* When using a browser, Rodauth now uses an appropriate 401, 403,
or 422 error status for errors instead of using 200 success status.
Many configuration methods have been added to customize the status
codes used for specific types of errors.
* The json_response_custom_error_status? configuration method
has been added to the jwt feature, which if set to true makes
the jwt feature use the same error status codes for JSON API
requests that it would use for browser requests. For backward
compatibility, the default is to continue to use the 400
error status for all errors in the JSON API, but this will
change in Rodauth 2.
jeremyevans-rodauth-b53f402/doc/release_notes/1.9.0.txt 0000664 0000000 0000000 00000001202 15157255142 0022642 0 ustar 00root root 0000000 0000000 = New Features
* Roda.precompile_rodauth_templates has been added. This method
allows for precompiling the templates that rodauth uses, which
allows for memory saving when using a forking webserver that
preloads the application, and also allows Rodauth to be used
with an application that uses chroot after loading.
= Improvements
* If requesting a password reset link more than once, the same
password reset key will be used. Previously, subsequent
emails after the first request would contain an invalid key,
so if the email for the original request was lost, you could
not generate another key until that key expired.
jeremyevans-rodauth-b53f402/doc/release_notes/2.0.0.txt 0000664 0000000 0000000 00000037755 15157255142 0022660 0 ustar 00root root 0000000 0000000 = New Features
* A webauthn feature has been added, allowing multifactor
authentication using WebAuthn. It allows for registering multiple
WebAuthn authenticators per account, authenticating using
WebAuthn, and removing WebAuthn authenticators. This feature
depends on the webauthn gem.
WebAuthn in browsers requires javascript to work, but Rodauth's
approach has the javascript set hidden form inputs and then use a
standard form submission, making it easy to test applications
using WebAuthn without a full browser, as long as a software
WebAuthn authenticator can be used (the webauthn gem provides
such an authenticator).
* A webauthn_login feature has been added, allowing passwordless
logins using WebAuthn.
* A webauthn_verify_account feature has been added, which requires
setting up a WebAuthn authenticator during account verification.
This allows for setups where WebAuthn is the sole method of
authentication.
* An active_sessions feature has been added, which disallows
session reuse after logout, and allows for a global logout of all
sessions for the account. It also supports inactivity and
lifetime deadlines for sessions. This also integrates with the
jwt_refresh feature to disable JWT access token usage after
logout.
* An audit_logging feature has been added, which logs Rodauth
actions to a database table. This hooks into all of Rodauth's
after_* hooks, and will implement audit logging for all
features that use such hooks.
* The confirm_password feature can now operate as multifactor
authentication if the user has a password but was originally
authenticated using the webauthn_login feature.
* The multifactor authentication support now better handles
multiple multifactor authentication methods. When setting up
multifactor authentication, a page is provided linking to all
enabled multifactor authentication options. When authenticating
via an additional factor, a page is provided linking to all
multifactor authentication options that have been setup and are
available for use. There is also a page to disable all multifactor
authentication methods that have been setup, and revert to single
factor authentication.
To provide a better user experience, if there would only be a
single link on the pages to setup multifactor authentication
or authenticate with an additional factor, the user is redirected
directly to the appropriate page.
* A translate configuration method has been added. This is called
with a translation key and default value for the translation, and
allows for internationalizing Rodauth. All translatable strings
are passed through this method, including flash messages, page
titles, button text, field error messages, and link texts.
* login_return_to_requested_location? and
two_factor_auth_return_to_requested_location? configuration methods
have been added. With these methods set to true, if
rodauth.require_login needs to redirect, it will store the current
page, and after logging in, the user will be redirected back to the
page. Likewise, if rodauth.require_two_factor_authenticated needs
to redirect, it will store the current page, and after multifactor
authentication, the user will be redirected back to the page.
* domain and base_url configuration methods have been added and it is
recommended that applications use them if they can be reached with
arbitrary Host headers. If not set, Rodauth will use information
from the request, which can be provided by an attacker.
* The *_url and *_path methods now accept an optional hash of query
parameters to use.
* Many Rodauth forms will now use appropriate autocomplete and
inputmode attributes on form inputs. You can modify the behavior
using the following configuration methods:
* autocomplete_for_field?
* inputmode_for_field?
* mark_input_fields_with_autocomplete?
* mark_input_fields_with_inputmode?
* An sms_phone_input_type configuration method has been added and
now defaults to tel. Previous, the SMS phone input used a text
type.
* rodauth.require_password_authentication has been added to the
confirm_password_feature, which will redirect to the login page
if not logged in, and will redirect to the confirm password page
if the user was logged in without typing in a password. If the
password_grace_period feature is used, this also redirects if
the password has not been entered recently.
* rodauth.authenticated_by has been added, which is an array of
strings for all methods by which the current session has been
authenticated, or nil if the session has not been authenticated.
* rodauth.possible_authentication_methods has been added, which is
an array of strings for all methods by which the current session
could be authenticated.
* rodauth.autologin_type now returns the type of autologin used if
authenticated using autologin.
* All *_view configuration methods now have *_page_title
configuration methods for setting custom page titles.
= Other Improvements
* The templates Rodauth uses by default are now compatible with
Bootstrap 4, and compatibility with Bootstrap 3 (which Rodauth
previously targeted) has been improved.
* When requesting a password reset, if the user provides an invalid
login, an input for the login is now displayed so the problem
can be corrected.
* When setting up an additional multifactor authentication method,
Rodauth no longer overrides which multifactor authentication method
was used to authenticate the current session.
* When disabling a multifactor authentication method that was not
used to authenticate the current session, the session remains
multifactor authenticated.
* When multiple multifactor authentication methods are setup for
an account, disabling a multifactor authentication method will not
mark the session as not having multifactor authentication enabled.
* When disabling OTP authentication, future calls to
rodauth.otp_exists? will return false instead of true.
* Recovery codes are no longer generated automatically when OTP or
SMS authentication is setup. There is no point generating codes
that the user has not yet viewed, and generating them automatically
will disable automatic redirections in the cases where only one
multifactor authentication method is setup. This can be turned
back on using the auto_add_recovery_codes? configuration method.
* The OTP setup page now displays better on phones and other devices
with small viewports.
* Links and alternative login forms shown on the login page are
now in a specific order and not based on the order in which
features were enabled.
* The link to resend the verify account email is not shown on the
multi-phase login page after the login has been entered if the
account has already been verified.
* The modifications_require_password? configuration method now
defaults to false for accounts that do not have a password.
* Multifactor authentication is no longer allowed using the same
factor type as used for initial authentication. Previously,
no multifactor authentication type could be used for initial
authentication, so this wasn't an issue.
* The verify login change page no longer calls already_logged_in
if the session is already logged in. This method is documented
to only be called on pages that expect not to be already logged
in, and it's common to access the verify login change page
while being logged in, since you need to be logged in to go to
the change login page. The default behavior of already_logged_in
is to do nothing, so this only affects you if you have used the
already_logged_in configuration method.
* If using the email_auth and verify_account_grace_period features
together, do not show email authentication as an option for
unverified accounts during the grace period.
* In the lockout feature, generate the unlock account key before
calling send_unlock_account_email, similar to how key generation
happens in other features that send email. This makes it easier
to override the method.
* Various method visibility issues have been fixed, so that
enabling any feature that ships with Rodauth will not affect
visibility of methods for features already enabled.
* All Rodauth configuration methods (over 1000) are now documented.
= Backwards Compatibility
* The verify_change_login feature has been removed. Users should
switch to the verify_login_change feature, which verifies the
new login works correctly before switching the login.
* For CSRF protection, Roda's route_csrf plugin is now used by
default instead of rack_csrf. This supports request specific
CSRF tokens by default. The :csrf=>:rack_csrf plugin option
can be used to continue using rack_csrf.
Roda's route_csrf allows for per-route checking of the CSRF token,
and support for that is enabled for all Rodauth routes. However,
if you were using Rodauth without explicitly loading rack_csrf,
these changes could remove CSRF support from your application.
You should probably load Roda's route_csrf plugin explicitly and
use it in your Roda routing tree if you want CSRF protection for
non-Rodauth routes. You can use the new check_csrf_opts and
check_csrf_block to customize options to pass to check_csrf!, or
set check_csrf? false to disable calling check_csrf!.
* Email rate limiting is now enabled by default in the lockout,
reset_password, and verify_account features. This requires
adding a column to store the last email sent time to the
related tables, if the tables were created without one:
DB.add_column :account_password_reset_keys, :email_last_sent,
DateTime, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
DB.add_column :account_verification_keys, :email_last_sent,
DateTime, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
DB.add_column :account_lockouts, :email_last_sent, DateTime
Alternatively, you can set the appropriate configuration method
(e.g. verify_account_email_last_sent_column) to nil to disable
rate limiting.
* The http_basic_auth feature has been changed significantly.
You should now call rodauth.http_basic_auth in the routing tree
to load authentication information from the Authorization
request header, similar to how rodauth.load_memory works in the
remember feature.
The require_http_basic_auth configuration method has been renamed
to require_http_basic_auth?. rodauth.require_http_basic_auth?
should now be used to check whether HTTP basic auth is required.
rodauth.require_http_basic_auth now requires that HTTP basic
auth is provided in the request.
To be more backwards compatible, if not already logged in,
rodauth.require_login will load HTTP basic auth information if
available, and will require HTTP basic auth if
require_http_basic_auth? is configured.
* If using the Bootstrap 3/4 compatibility, the forms used are
now standard (vertical) Bootstrap forms. Previously, they were
horizontal forms.
* Most of the strings related to multifactor authentication have
been changed to refer to multifactor authentication instead of
two factor authentication, or changed to refer to a specific
multifactor authentication type (such as TOTP), as appropriate.
* Periods at the end of some default flash messages have been
removed for consistency.
* The remember feature no longer depends on the confirm_password
feature. You must now enable confirm_password separately if you
want to use it.
* Login confirmation is no longer required by default when
verifying accounts or verifying login changes. In both cases,
entering an invalid login causes no problems.
* The otp_drift configuration method now defaults to 30, to allow
30 seconds of drift. The previous setting of nil generally
resulted in usability problems, especially without good clock
synchronization.
* The json_response_custom_error_status? configuration method now
defaults to true, so that custom error statuses are now used by
default, instead of a generic 400 response.
* The jwt_check_accept? configuration method now defaults to true,
so that the request Accept header is checked.
* The verify_account_set_password? configuration method now defaults
to true, so that passwords will be set when verifying accounts
instead of when creating accounts. This prevents issues when
an attacker creates an account with a password they know, if the
user with access to the email address verifies the account.
* The mark_input_fields_as_required? configuration method now defaults
to true. Most of rodauth's input fields are required, and this
provides a nicer experience. However, it may cause accessibility
issues if screen readers do not handle invalid form submissions due
to missing required fields in an accessible manner.
* The login_input_type configuration method now defaults to email if
login_column is :email (the default setting). This can cause
accessibility issues if screen readers do not handle invalid form
submissions due to an invalid login field format in an accessible
manner. It can also break installations that leave login_column
as :email but do not use email addresses for logins.
* The json_response_success_key configuration method now defaults to
success, so success messages are included by default. This can be
set back to nil to not include them.
* The single_session and session_expiration plugin now use a
configurable error status code for JSON requests when the session
has expired, using inactive_session_error_status and
session_expiration_error_status configuration methods,
respectively.
* If you are using the jwt_refresh feature and used the migration
previously recommended in the README, you should mark the account_id
field as NOT NULL and add an index:
DB.alter_table(:account_jwt_refresh_keys) do
set_column_not_null :account_id
add_index :account_id, :name=>:account_jwt_rk_account_id_idx
end
* The otp authentication form no longer shows SMS or recovery code
information on failure. The multifactor authentication page will
have links to SMS or recovery code authentication if they have been
setup, and will redirect or show the appropriate links to those
authentication methods if OTP authentication gets locked out.
* Disabling OTP authentication no longer automatically disables SMS
authentication and recovery codes, and disabling SMS authentication
no longer disables recovery codes. To disable all multifactor
authentication methods at once, the new multifactor authentication
disable page should be used. If you want to revert to the previous
behavior of automatic disabling, override after_otp_disable to
disable SMS and recovery codes, and override after_sms_disable to
disable recovery codes.
* HTML id attributes in the recovery_codes and remember features have
been modified to use - instead of _, for consistency with all other
Rodauth features.
* Ruby 1.8 support has been dropped. The minimum supported version is
now Ruby 1.9.2. Support for versions of Ruby that are no longer
supported by ruby-core may be dropped in future minor releases if
keeping the support becomes a maintenance issue.
* The following configuration methods have been replaced:
* create_account_link -> create_account_link_text
* reset_password_request_link -> reset_password_request_link_text
* verify_account_resend_link -> verify_account_resend_link_text
The new methods take only the text of the link, the path to link
to can already be determined by Rodauth.
* The following configuration methods have been removed:
* account_model
* attempt_to_create_unverified_account_notice_message
* attempt_to_login_to_unverified_account_notice_message
* before_otp_authentication_route
* clear_remembered_session_key
* no_matching_email_auth_key_message
* no_matching_reset_password_key_message
* no_matching_unlock_account_key_message
* no_matching_verify_account_key_message
* no_matching_verify_login_change_key_message
* remembered_session_key
* two_factor_session_key
Most of these methods were already deprecated.
* Route blocks in external Rodauth features must now have an arity
of 1.
jeremyevans-rodauth-b53f402/doc/release_notes/2.1.0.txt 0000664 0000000 0000000 00000002514 15157255142 0022642 0 ustar 00root root 0000000 0000000 = New Features
* A check_csrf configuration method has been added for checking
the CSRF token. This is useful in cases where the CSRF protection
is provided by something other than the Roda route_csrf plugin.
= Other Improvements
* When using the http_basic_auth feature, logged_in? now checks for
Basic authentication if the session is not already authenticated
and Basic authentication has not yet been checked. This increases
compatibility for applications that were using the http_basic_auth
feature in Rodauth 1.
* When creating accounts, the password field now correctly uses the
new-password autocomplete attribute instead of the current-password
autocomplete attribute.
* When using the jwt feature, Rodauth no longer checks CSRF tokens
in requests to Rodauth routes if the request submitted is a JSON
request, includes a JWT, or Rodauth has been configured in JSON-only
mode.
* When using the verify_account_grace_period feature, if there is an
unverified account without a password, do not consider the account
open. Attempting to login into the account in such a case now
shows a message letting the user know to verify the account.
* The json_response_body configuration method is now used consistently
in the jwt feature for all JSON responses. Previously, there were
some cases that did not use it.
jeremyevans-rodauth-b53f402/doc/release_notes/2.10.0.txt 0000664 0000000 0000000 00000004207 15157255142 0022723 0 ustar 00root root 0000000 0000000 = New Features
* An argon2 feature has been added that supports using the argon2
password hashing algorithm instead of the bcrypt password hashing
algorithm. While argon2 does not provide an advantage over bcrypt
if the attacker cannot access the password hashes directly (which
is how Rodauth is recommended to be used), in cases where attackers
can access the password hashes directly, argon2 is thought to be
more difficult or expensive to crack due to requiring more memory
(bcrypt is not a memory-hard password hash algorithm).
If you are using this feature with Rodauth's database authentication
functions, you need to make sure that the database authentication
functions are configured to support argon2 in addition to bcrypt.
You can do this by passing the :argon2 option when calling the
method to define the database functions. In this example, DB should
be your Sequel::Database object (this could be self if used in a
Sequel migration):
require 'rodauth/migrations'
# If the functions are already defined and you are not using PostgreSQL,
# you need to drop the existing functions.
Rodauth.drop_database_authentication_functions(DB)
# If you are using the disallow_password_reuse feature, also drop the
# database functions related to that if you are not using PostgreSQL:
Rodauth.drop_database_previous_password_check_functions(DB)
# Define new functions that support argon2:
Rodauth.create_database_authentication_functions(DB, argon2: true)
# If you are using the disallow_password_reuse feature, also define
# new functions that support argon2 for that:
Rodauth.create_database_previous_password_check_functions(DB, argon2: true)
You can transparently migrate bcrypt password hashes to argon2
password hashes whenever a user successfully uses their password
by using the argon2 feature in combination with the
update_password_hash feature.
= Other Improvements
* Unnecessary queries to determine whether the new password matches
a previous password are now skipped when using the create_account
or verify_account features with the disallow_password_reuse
feature.
jeremyevans-rodauth-b53f402/doc/release_notes/2.11.0.txt 0000664 0000000 0000000 00000002525 15157255142 0022725 0 ustar 00root root 0000000 0000000 = New Features
* An :auth_class rodauth plugin option has been added, allowing a user
to specify a specific Rodauth::Auth subclass to use, instead of
always using a new subclass of Rodauth::Auth. This is designed for
advanced configurations or other frameworks that build on top of
Rodauth, which may want to customize the Rodauth::Auth subclasses to
use.
* Two additional configuration methods have been added for easier
translatability, fixing issues where English text was hardcoded:
* same_as_current_login_message (change_login feature)
* contains_null_byte_message (login_password_requirements_base
feature)
= Other Improvements
* Loading the rodauth plugin multiple times in the same application
with different blocks now works better. The same context is now
shared between the blocks, so you can load features in one block
and call configuration methods added by the feature in the other
block. Previously, you could only call configuration methods in
the block that added the feature, and enabling a feature in a
block that was already enabled in a previous block did not allow
the use of configuration methods related to the feature.
* Passing a block when loading the rodauth plugin is now optional.
* The autocomplete attribute on the reset password form now uses
new-password instead of current-password.
jeremyevans-rodauth-b53f402/doc/release_notes/2.12.0.txt 0000664 0000000 0000000 00000000700 15157255142 0022717 0 ustar 00root root 0000000 0000000 = New Features
* The following configuration methods have been added to the
active_sessions feature:
* active_sessions_insert_hash
* active_sessions_key
* active_sessions_update_hash
* update_current_session?
These methods allow you to control what gets inserted and
updated into the active_sessions_table, and to control
whether to perform updates.
= Other Improvements
* A typo was fixed in the default unlock account email.
jeremyevans-rodauth-b53f402/doc/release_notes/2.13.0.txt 0000664 0000000 0000000 00000001443 15157255142 0022725 0 ustar 00root root 0000000 0000000 = New Features
* A set_error_reason configuration method has been added. This method
is called whenever a error occurs in Rodauth, with a symbol
describing the error. The default implementation of this method does
nothing, it has been added to make it easier for Rodauth users to
implement custom handling for specific error types. See the Rodauth
documentation for this method to see the list of symbols this method
can be called with.
= Other Improvements
* When using active_sessions and jwt_refresh together, and allowing for
expired JWTs when refreshing, you can now call
rodauth.check_active_session before r.rodauth. Previously, this
did not work, and you had to call rodauth.check_active_session
after r.rodauth.
* The default templates now also support Bootstrap 5.
jeremyevans-rodauth-b53f402/doc/release_notes/2.14.0.txt 0000664 0000000 0000000 00000001140 15157255142 0022720 0 ustar 00root root 0000000 0000000 = New Features
* A remembered_session_id method has been added for getting the
account id from a valid remember token, without modifying the
session to log the account in.
= Other Improvements
* The jwt_refresh feature's support for allowing refresh with
an expired access token now works even if the Rodauth
configuration uses an incorrect prefix.
* The internal account_in_unverified_grace_period? method now
returns false if an account has not been loaded and the
session has not been logged in. Previously, calling this
method in such cases would result in an exception being
raised.
jeremyevans-rodauth-b53f402/doc/release_notes/2.15.0.txt 0000664 0000000 0000000 00000004164 15157255142 0022732 0 ustar 00root root 0000000 0000000 = New Features
* An internal_request feature has been added. This feature allows
for interacting with Rodauth by calling methods, instead of having
to use a website or JSON API. This feature is designed primarily
for administrative use, so that administrators can create accounts,
change passwords or logins for accounts, and handle similar actions
without the user of the account being involved.
For example, assuming you've loaded the change_password and
internal_request features, and that your Roda class that
is loading Rodauth is named App, you can change the password
for the account with id 1 using:
App.rodauth.change_password(account_id: 1, password: 'foobar')
The internal request methods are implemented as class methods
on the Rodauth::Auth subclass (the object returned by App.rodauth).
These methods call methods on a subclass of that class specific
to internal requests.
The reason the feature is named internal_request is that these
methods are implemented by submitting a request internally, that is
processed almost exactly the same way as Rodauth would process a
web request.
See the internal_request feature documentation for details on which
internal request methods are available and the options they take.
* A path_class_methods feature has been added, that allows for calling
*_path and *_url as class methods. If you would like to call the
*_url methods as class methods, make sure to use the base_url
configuration method to set the base URL so that it does not require
request-specific information.
* Rodauth::Auth classes now have a configuration_name method that
returns the configuration name associated with the class. They also
have a configuration method that returns the configuration
associated with the class.
* Rodauth::Feature now supports an internal_request_method method for
specifying which methods are supported as internal request methods.
= Other Improvements
* The default base_url configuration method will now use the domain
method to get the domain to use, instead of getting the domain
information directly from the request environment.
jeremyevans-rodauth-b53f402/doc/release_notes/2.16.0.txt 0000664 0000000 0000000 00000001225 15157255142 0022726 0 ustar 00root root 0000000 0000000 = New Features
* Rodauth.lib has been added for using Rodauth purely as a library,
useful in non-web applications:
require 'rodauth'
rodauth = Rodauth.lib do
enable :create_account, :change_password
end
rodauth.create_account(login: 'foo@example.com', password: '...')
rodauth.change_password(account_id: 24601, password: '...')
This is built on top of the internal_request feature, and works by
creating a Roda application with the rodauth plugin, and returning
the related Rodauth::Auth class.
= Other Improvements
* The internal_request feature now works correctly for configurations
where only_json? is set to true.
jeremyevans-rodauth-b53f402/doc/release_notes/2.17.0.txt 0000664 0000000 0000000 00000000513 15157255142 0022726 0 ustar 00root root 0000000 0000000 = Improvements
* The jwt_refresh feature now works for unverified accounts when using
the verify_account_grace_period feature.
* When trying to create an account that already exists but is
unverified, Rodauth now returns a 4xx response.
* When trying to login to an unverified account, Rodauth now returns a
4xx response.
jeremyevans-rodauth-b53f402/doc/release_notes/2.18.0.txt 0000664 0000000 0000000 00000002233 15157255142 0022730 0 ustar 00root root 0000000 0000000 = New Features
* When using the json and multifactor auth features, the JSON API can
now access the multifactor-manage route to get lists of endpoints
for setting up and disabling supported multifactor authentication
methods. The JSON API can now also access the multifactor-auth
route to get a list of endpoints for multifactor authentication for
the currently logged in account.
= Other Improvements
* In the otp feature, the viewbox: true rqrcode option is now used
when creating the QR code. This results in a QR code that is
displayed better and is easier to style. This option only has
an effect when using rqrcode 2+.
* When using the :auth_class option when loading the rodauth plugin,
the configuration name is set in the provided auth class, unless the
auth class already has a configuration name set.
* The example migration now recommends using a partial index on the
email column in cases where the database supports partial indexes.
Previously, it only recommended it on PostgreSQL.
* The argon2 feature now works with argon2 2.1.0. Older versions of
Rodauth work with both earlier and later versions of argon2, but
not 2.1.0.
jeremyevans-rodauth-b53f402/doc/release_notes/2.19.0.txt 0000664 0000000 0000000 00000005362 15157255142 0022737 0 ustar 00root root 0000000 0000000 = New Features
* A login_maximum_bytes configuration method has been added, setting
the maximum bytes allowed in a login. This was added as
login_maximum_length sets the maximum length in characters. It's
possible a different number of maximum bytes than maximum
characters is desired by some applications, and since the database
column size may be enforced in bytes, it's useful to have a check
before trying a database query that would raise an exception. This
default value for login_maximum_bytes is 255, the same as the
default value for login_maximum_length.
A login_too_many_bytes_message configuration method has been added
for customizing the error message if a login has too many bytes.
* password_maximum_length and password_maximum_bytes configuration
methods have been added, specifying the maximum size of passwords
in characters and bytes, respectively. Both configurations default
to nil, meaning no limit, so there is no change in default behavior.
The bcrypt algorithm only uses the first 72 bytes of a password, and
in some environments it may be desirable to reject passwords over
that limit. password_too_long_message and
password_too_many_bytes_message configuration methods have been
added for customizing the error messages used for passwords that are
too long.
Note that in most environments, if you want to support passwords
over 72 bytes and have the entire password be considered, you should
probably use the argon2 feature.
= Other Improvements
* The subclass created by the internal_request feature is now set
to the InternalRequest constant on the superclass, mostly to
make identifying it easier in inspect output.
* Support has been improved for custom Rodauth::Auth subclasses that
load features before the subclass is loaded into Roda, by delaying
the call to post_configure until the subclass is loaded into Roda.
Among other things, this fixes the use of the internal_request
feature in such classes.
* Multi-level inheritance of Rodauth::Auth is now supported. This can
be useful as a way to share custom authentication settings between
multiple Rodauth configurations. However, users of multi-level
inheritance should be careful not to load features in subclasses
that override custom settings in superclasses.
= Other
* Rodauth's primary discussion forum is now GitHub Discussions. The
rodauth Google Group is still available for users who would prefer
to use that instead.
= Backwards Compatibility
* The addition of login_maximum_bytes with a default value of 255 is
backwards incompatible for applications that want to support logins
with multibyte characters where the number of characters in the
login is at or below 255, but the number of bytes is above 255.
jeremyevans-rodauth-b53f402/doc/release_notes/2.2.0.txt 0000664 0000000 0000000 00000003116 15157255142 0022642 0 ustar 00root root 0000000 0000000 = New Features
* When using the jwt_refresh feature, you can remove the current
refresh token when logging out by submitting the refresh token
in the logout request, the same as when submitting the refresh
token to obtain a new refresh token. You can also use a value
of "all" instead of the refresh token to remove all refresh
tokens when logging out.
* A rodauth.otp_last_use method has been added to the otp feature,
allowing you to determine when the otp was last used.
= Other Improvements
* When using multifactor authentication, rodauth.authenticated? and
rodauth.require_authentication now cache values in the session and
do not perform queries every time they are called.
* Many guides for common scenarios have been added to the
documentation. These augment Rodauth's existing comprehensive
feature documentation, which is aimed to be more of a reference
and less of a guide.
* When the verify_account_grace_period and email_auth features are
used with a multifactor authentication feature, and the
verify_account_set_password? configuration method is set to true,
Rodauth no longer raises a NoMethodError when checking if the
session was authenticated.
* In the verify_account feature, if verify_account_email_resend
returns false indicating no email was sent, an error message
is now used, instead of a success message.
* In the password_complexity feature, the password_dictionary
configuration method was previously ignored if the default
password dictionary file existed.
* Rodauth and all features that ship with it now have 100% branch
coverage.
jeremyevans-rodauth-b53f402/doc/release_notes/2.20.0.txt 0000664 0000000 0000000 00000000670 15157255142 0022724 0 ustar 00root root 0000000 0000000 = Improvements
* When using the active_sessions and remember features together,
doing a global logout will automatically remove the remember key for
the account, so the account will no longer be able to automatically
create new sessions using the remember key.
* The default value of webauthn_rp_id now removes the port from the
origin if it exists, since the WebAuthn spec does not allow ports
in the relying party identifier.
jeremyevans-rodauth-b53f402/doc/release_notes/2.21.0.txt 0000664 0000000 0000000 00000002466 15157255142 0022732 0 ustar 00root root 0000000 0000000 = Improvements
* When using the verify_account_grace_period feature, if the grace
period has expired for currently logged in session, require_login
will clear the session and redirect to the login page. This is
implemented by having the unverified_account_session_key store the
time of expiration, as an integer.
* The previously private require_account method is now public. The
method is used internally by Rodauth to check that not only is the
current session logged in, but also that the account related to the
currently logged in session still exists in the database. The only
reason you would want to call require_account instead of
require_authentication is if you want to handle cases where there
can be logged in sessions for accounts that have been deleted.
* Rodauth now avoids an unnecessary bcrypt hash calculation when
updating accounts when using the account_password_hash_column
configuration method.
* When WebAuthn token last use times are displayed, Rodauth now uses a
fixed format of YYYY-MM-DD HH:MM:SS, instead of relying on
Time#to_s. If this presents an problem for your application, please
open an issue and we can add a configuration method to control
the behavior.
* A typo in the default value of global_logout_label in the
active_sessions feature has been fixed.
jeremyevans-rodauth-b53f402/doc/release_notes/2.22.0.txt 0000664 0000000 0000000 00000002701 15157255142 0022723 0 ustar 00root root 0000000 0000000 = New Features
* Rodauth now ignores parameters containing ASCII NUL bytes ("\0") by
default. You can customize this behavior using the
null_byte_parameter_value configuration method.
* A reset_password_notify feature has been added for emailing users
after successful password resets.
* External features can now use the email method inside their
feature definitions to DRY up the creation of email configuration
methods. The email method will setup the following configuration
methods for the feature:
* ${name}_email_subject
* ${name}_email_body
* create_${name}_email
* send_${name}_email
= Other Improvements
* The active_sessions feature now correctly handles logouts for
sessions that were created before the active_sessions feature was
added to the Rodauth configuration.
* The change_password_notify feature now works correctly when using
template precompilation.
* The update_sms method now updates the in-memory sms hash instead of
the in-memory account hash. This only has an effect if you are
using the sms_codes feature and customizing Rodauth to access one
of these hashes after a call to update_sms.
= Backwards Compatibility
* If your application requires the ability to submit values containing
ASCII NUL bytes ("\0") as Rodauth parameters, you should use the
new null_byte_parameter_value configuration method to pass the
value through unchanged:
null_byte_parameter_value do |_, v|
v
end
jeremyevans-rodauth-b53f402/doc/release_notes/2.23.0.txt 0000664 0000000 0000000 00000001164 15157255142 0022726 0 ustar 00root root 0000000 0000000 = Improvements
* The otp feature now uses the :use_path option when rendering QR
codes, resulting in significantly smaller svg images.
* Removing all multifactor authentication methods now removes the fact
that the session was authenticated via SMS, if the user used SMS as
an authentication method for the current session.
* The invalid domain check in the internal_request feature now works
correctly when using the rack master branch.
* The :httponly cookie option is no longer set automatically in the
remember feature if the :http_only cookie option was provided by the
user (rack recognizes both options).
jeremyevans-rodauth-b53f402/doc/release_notes/2.24.0.txt 0000664 0000000 0000000 00000001064 15157255142 0022726 0 ustar 00root root 0000000 0000000 = New Features
* rodauth.otp_available? has been added for checking whether the
account is allowed to authenticate with OTP. It returns true
when the account has setup OTP and OTP use is not locked out.
* rodauth.recovery_codes_available? has been added for checking
whether the account is allowed to authenticate using a recovery
code. It returns true when there are any available recovery
codes for the account to use.
= Other Improvements
* The otp feature no longer includes the tag for svg images,
since that results in invalid HTML.
jeremyevans-rodauth-b53f402/doc/release_notes/2.25.0.txt 0000664 0000000 0000000 00000000547 15157255142 0022734 0 ustar 00root root 0000000 0000000 = New Features
* You can now disable routing to specific routes by calling the
related *_route configuration method with nil or false. The main
reason you would want to do this is if you want to load a feature,
but only want to use it for internal requests (using the
internal_request feature), and not have the feature's routes exposed
to users.
jeremyevans-rodauth-b53f402/doc/release_notes/2.26.0.txt 0000664 0000000 0000000 00000003557 15157255142 0022741 0 ustar 00root root 0000000 0000000 = New Features
* An argon2_secret configuration method has been added to the argon2
feature, supporting argon2's built-in password peppering.
= Other Improvements
* Links are no longer automatically displayed for routes that are
disabled by calling the *_route method with nil.
* The QR code used by the otp feature now uses a white background
instead of a transparent background, fixing issues when the
underlying background is dark.
* Input parameter bytesize is now limited to 1024 bytes by default.
Parameters larger than that will be ignored, as if they weren't
submitted.
* The Rodauth::Auth class for internal request classes now uses the
same configuration name as the class it is based on.
* The session_key_prefix configuration method no longer also prefixes
the keys used in the flash hash.
* The *_path and *_url methods now return nil when the related *_route
method returns nil, indicating the route is disabled.
* A more explicit error message is raised when using a feature that
requires the hmac_secret being set and not setting hmac_secret.
= Backwards Compatibility
* If you are using session_key_prefix and flash messages, you will
probably need to adjust your code to remove the prefix from the
expected flash keys, or manually prefix the flash keys by using
the flash_error_key and flash_notice_key configuration methods.
* The limiting of input parameter bytesizes by default could potentially
break applications that use Rodauth's parameter parsing method to
handle parameters that Rodauth itself doesn't handle. You can use
the max_param_bytesize configuration method to set a larger bytesize,
or use a value of nil with the method for the previous behavior of
no limit. Additionally, to customize the behavior if a parameter
is over the allowed bytesize, you can use the
over_max_bytesize_param_value configuration method.
jeremyevans-rodauth-b53f402/doc/release_notes/2.27.0.txt 0000664 0000000 0000000 00000002642 15157255142 0022734 0 ustar 00root root 0000000 0000000 = Improvements
* Token ids submitting in requests are now converted to integers if
the configuration uses an integer primary key for the accounts
table. If the configuration uses a non-integer primary key for
the accounts table, the convert_token_id configuration method can
be used, which should return the token id converted to the
appropriate type, or nil if the token id is not valid for the type.
This revised handling avoids raising a database error when an
invalid token is submitted.
* The button template can now be overridden in the same way that
other Rodauth templates can be overridden.
* When using the Bootstrap CSS framework, the text field in the
Webauthn setup and auth forms is automatically hidden. The text
field already had a rodauth-hidden class to make it easy to hide
when using other CSS frameworks.
* The email_from and email_to methods are now public instead of
private.
* A nicer error is raised if the Sequel Database object is missing.
* A regression in the TOTP QR output that resulted in the QR codes
being solid black squares has been fixed (this was fixed in
Rodauth 2.26.1).
= Backwards Compatibility
* The webauth_credentials_for_get method in the webauthn feature has
been renamed to webauthn_credentials_for_get for consistency with
other methods. The webauth_credentials_for_get method will still
work until Rodauth 3, but will issue deprecation warnings.
jeremyevans-rodauth-b53f402/doc/release_notes/2.28.0.txt 0000664 0000000 0000000 00000001132 15157255142 0022726 0 ustar 00root root 0000000 0000000 = New Features
* A webauthn_key_insert_hash configuration method has been added when
using the webauthn feature, making it easier to add new columns to
the webauthn key data, such as a custom name for the authenticator.
= Other Improvements
* When using the verify_account_grace_period feature, logged_in? now
returns false for sessions where the grace period has expired.
* When using the internal_request and reset_password features,
submitting an internal request for an invalid login no longer tries
to render a reset password request form.
* The password_hash method is now public.
jeremyevans-rodauth-b53f402/doc/release_notes/2.29.0.txt 0000664 0000000 0000000 00000002176 15157255142 0022740 0 ustar 00root root 0000000 0000000 = New Features
* When using the remember feature, by default, the remember deadline
is extended while logged in, if it hasn't been extended in the last
hour
* An account! method has been added, which will return the hash for
the account if already retrieved, or attempt to retrieve the
account hash using the currently logged in session if not.
Because of the ambiguity in the provenance of the returned account
hash, callers should be careful when using this method.
* A remove_active_session method has been added. You can call this
method with a specific session id, and it will remove the related
active session.
* A render: false plugin option is now support, which will disable
the automatic loading of the render plugin. This should only be
used if you are completely replacing Rodauth's view rendering with
your own.
= Other Improvements
* When logging in when using the active_sessions feature, if there is
a current active session, it is removed before a new active session
is created. This prevents some stale active sessions from remaining
in the database (which would eventually be cleaned up later).
jeremyevans-rodauth-b53f402/doc/release_notes/2.3.0.txt 0000664 0000000 0000000 00000002742 15157255142 0022647 0 ustar 00root root 0000000 0000000 = New Features
* Configuration methods have been added for easier validation of
logins when logins must be valid email addresses (the default):
* login_valid_email?(login) can be used for full control of
determining whether the login is valid.
* login_email_regexp can be used to set the regexp used in the
default login_valid_email? check.
* login_not_valid_email_message can be used to set the field
error message if the login is not a valid email. Previously, this
value was hardcoded and not translatable.
* The {create,drop}_database_authentication_functions now work
correctly with uuid keys on PostgreSQL. All other parts of
Rodauth already worked correctly with uuid keys.
= Other Improvements
* The before_jwt_refresh_route hook is now called before the route
is taken. Previously, the configuration method had no effect.
* rodauth.login can now be used by external code to login the current
account (the account that rodauth.account returns). This should be
passed the authentication type string used to login, such as
password.
* The jwt_refresh route now returns an error for requests where a
valid access token for a logged in session is not provided. You
can use the jwt_refresh_without_access_token_message and
jwt_refresh_without_access_token_status configuration methods
to configure the error response.
* The new refresh token is now available to the after_refresh_token
hook by looking in json_response[jwt_refresh_token_key].
jeremyevans-rodauth-b53f402/doc/release_notes/2.30.0.txt 0000664 0000000 0000000 00000001131 15157255142 0022716 0 ustar 00root root 0000000 0000000 = New Features
* A webauthn_autofill feature has been added to allow autofilling
webauthn credentials during login (also known as conditional
mediation). This allows for easier login using passkeys.
This requires a supported browser and operating system on the
client side to work.
= Other Improvements
* The load_memory method in the remember feature no longer raises
a NoMethodError if the there is a remember cookie, the session is
already logged in, and the account no longer exists. The
load_memory method now removes the remember cookie and clears the
session in that case.
jeremyevans-rodauth-b53f402/doc/release_notes/2.31.0.txt 0000664 0000000 0000000 00000003223 15157255142 0022723 0 ustar 00root root 0000000 0000000 = New Features
* The internal_request feature now supports WebAuthn, using
the following methods:
* With the webauthn feature:
* webauthn_setup_params
* webauthn_setup
* webauthn_auth_params
* webauthn_auth
* webauthn_remove
* With the webauthn_login feature:
* webauthn_login_params
* webauthn_login
* A webauthn_login_user_verification_additional_factor? configuration
method has been added to the webauthn_login feature. By default,
this method returns false. If you configure the method to return
true, and the WebAuthn credential provided specifies that it
verified the user, then this will treat the user verification as
a second factor, so the user will be considered multifactor
authenticated after successful login. You should only set this
method to true if you consider the WebAuthn user verification
strong enough to be a independent factor.
* A json_response_error? configuration method has been added to the
json feature. This should return whether the current response
should be treated as an error by the json feature. By default,
it is true if json_response_error_key is set in the response,
since that is the default place that Rodauth stores errors when
using the json feature.
* A webauthn_invalid_webauthn_id_message configuration method has
been added for customizing the error message used for invalid
WebAuthn IDs.
= Other Improvements
* The argon2 feature now supports setting the Argon2 p_cost if
argon2 2.1+ is installed.
* An :invalid_webauthn_id error reason is now used for invalid
WebAuthn IDs.
* The clear_session method now works as expected for internal
requests.
jeremyevans-rodauth-b53f402/doc/release_notes/2.32.0.txt 0000664 0000000 0000000 00000005733 15157255142 0022734 0 ustar 00root root 0000000 0000000 = New Features
* Rodauth now supports secret rotation using the following
configuration methods:
* hmac_old_secret
* argon2_old_secret (argon2 feature)
* jwt_old_secret (jwt feature)
You can use these methods to specify the previous secret when
rotating secrets. Note that full secret rotation (where you can
remove use of the old secret) may not be simple. Here are some
cases that require additional work:
* Rotating the argon2 secret requires the use of the
update_password_hash feature. You cannot remove the use of
argon2_old_secret unless every user who created a password under
the old secret has logged in after the new secret was added.
Removing the old secret before a user has logged in after the new
secret was added will invalidate the password for the user. Thus,
full rotation of the argon2 secret requires invalidating passwords
for inactive accounts.
* Full rotating of the hmac secret when using the remember feature
requires that all remember cookies created under the previous
secret has been removed. By default, remember cookies expire in
2 weeks, but it is possible to set them much longer.
* Full rotation of the hmac secret when using the verify_account
feature requires invalidating old verify account links, since
verify account links do not have a deadline. However, after old
verify account links have been invalidated, a user can request a
new verify account link, which will work.
* Full rotation of the hmac secret when using the otp feature
requires disabling otp and reenabling otp. The
otp_valid_code_for_old_secret configuration method has been added,
which can be used to handle cases where a user successfully
authenticated via TOTP using the old secret. This can be used
to direct them to a page to remove the TOTP authenticator and
then setup a new TOTP authenticator.
* Many *_response configuration methods have been added, which allow
users to override Rodauth's default behavior in successful cases of
setting a flash notice and then redirecting. Note that using these
configuration methods correctly requires that they halt request
processing. You cannot just have them return a response body. You
can use the return_response method to set the response body and
halt processing.
* An sms_needs_confirmation_notice_flash configuration method has been
added, for setting the flash notice when setting up SMS
authentication. By default, it uses the
sms_needs_confirmation_error_flash value.
= Other Improvements
* The argon2 feature no longer uses the Base64 constant. Previously,
it uses the library without attempting to require the base64 library,
which would break if the base64 library was not already required.
* Rodauth's documentation now recommends against the use of the argon2
feature, because for typical interactive login uses (targetting
sub-200ms response times), argon2 provides significantly worse
security than bcrypt.
jeremyevans-rodauth-b53f402/doc/release_notes/2.33.0.txt 0000664 0000000 0000000 00000001447 15157255142 0022733 0 ustar 00root root 0000000 0000000 = Improvements
* Rodauth no longer accidentally confirms an SMS number upon valid
authentication by an alternative second factor.
* Rodauth now automatically expires SMS confirmation codes after 24
hours by default. You can use the sms_confirm_deadline
configuration method to adjust the deadline. Previously, if an
invalid SMS number was submitted, or the SMS confirm code was never
received, it was not possible to continue SMS setup without
administrative intervention.
* Rodauth no longer overwrites existing primary key values when
inserting new accounts. This fixes cases such as setting account
primary key values to UUIDs before inserting.
* When submitting a request to a valid endpoint with a missing token,
Rodauth now returns an error response instead of a 404 response.
jeremyevans-rodauth-b53f402/doc/release_notes/2.34.0.txt 0000664 0000000 0000000 00000003001 15157255142 0022720 0 ustar 00root root 0000000 0000000 = New Features
* A rodauth.current_route method has been added for returning the route
name symbol (if rodauth is currently handling the route). This makes it
simpler to write code that extends Rodauth and works with
applications that use override the default route names.
* A remove_all_active_sessions_except_for method has been added to the
active_sessions feature, which removes all active sessions for the
current account, except for the session id given.
* A remove_all_active_sessions_except_current method has been added to
the active_sessions feature, which removes all active sessions for
the current account, except for the current session.
= Improvements
* Rodauth now supports overriding webauthn_rp_id in the webauthn
feature.
* When using the login feature, Rodauth now defaults
require_login_redirect to use the path to the login route, instead
of /login.
* When setting up multifactor authentication, Rodauth now handles the
case where account has been deleted, instead of raising an exception.
* When a database connection is not available during startup, Rodauth
now handles that case instead of raising an exception. Note that in
this case, Rodauth cannot automatically setup a conversion of token
ids to integer, since it cannot determine whether the underlying
database column uses an integer type.
* When using WebAuthn 3+, Rodauth no longer defines singleton methods
to work around limitations in WebAuthn. Instead, it uses public
APIs that were added in WebAuthn 3.
jeremyevans-rodauth-b53f402/doc/release_notes/2.35.0.txt 0000664 0000000 0000000 00000001547 15157255142 0022736 0 ustar 00root root 0000000 0000000 = New Features
* A throw_rodauth_error method has been added to make it easier
for external extensions to throw the expected error value without
setting a field error.
= Improvements
* If an account is not currently logged in, but Rodauth knows the
related account id, remove_all_active_sessions and related
methods in the active_sessions plugin will now remove sessions
for the related account.
* When using the internal_request feature and subclasses,
internal_request_configuration blocks in superclasses are now
respected when creating the internal request class for a
subclass. When creating the internal request in the subclass,
this behaves as if all internal_request_configuration blocks
were specified directly in the subclass.
* An ignored block warning on Ruby 3.4 is now avoided by having
Rodauth.load_dependencies accept a block.
jeremyevans-rodauth-b53f402/doc/release_notes/2.36.0.txt 0000664 0000000 0000000 00000002577 15157255142 0022743 0 ustar 00root root 0000000 0000000 = New Features
* An otp_unlock feature has been added, allowing a user to unlock
TOTP authentication with 3 consecutive successful TOTP
authentications. Previously, once TOTP authentication was locked
out, there was no way for the user to unlock it.
Any unsuccessful TOTP authentication during the unlock process
prevents unlocks attempts for a configurable amount of time (15
minutes by default). By default, this limits brute force attempts
to unlock TOTP authentication to less than 10^2 per day, with the
odds of a successful unlock in each attempt being 1 in 10^18.
* An otp_lockout_email feature has been added for emailing the user
when their TOTP authentication has been locked out or unlocked, and
when there has been a failed unlock attempt.
* An otp_modify_email feature has been added for emailing the user
when TOTP authentication has been setup or disabled for their
account.
* A webauthn_modify_email feature has been added for emailing the
user when a WebAuthn authenticator has been added or removed from
their account.
* An account_from_id configuration method has been added for loading
the account with the given account id.
* A strftime_format configuration method has been added for
configuring how Time values are formatted for display to the user.
= Improvements
* The internal_request feature now works with Roda's path_rewriter
plugin.
jeremyevans-rodauth-b53f402/doc/release_notes/2.37.0.txt 0000664 0000000 0000000 00000005377 15157255142 0022745 0 ustar 00root root 0000000 0000000 = New Features
* Both route block scope rodauth and r.rodauth now call
default_rodauth_name on the scope to get the rodauth configuration
to use. This can significantly simplify installations using
multiple rodauth configurations, since you can now define
the logic for which rodauth configuration to use in a single
place, instead of having to specify the non-default configuration
name explicitly in all cases where you want to use it. Here's an
example of possible use:
attr_reader :default_rodauth_name
route do |r|
r.on 'secondary' do
@default_rodauth_name = :secondary
r.rodauth # will use the :secondary configuration
end
r.rodauth # will use the default configuration
end
* A normalize_login configuration method has been added, for
normalizing submitted login parameters. You can use this to
force login parameters to lowercase, which can be useful if storing
the logins in a case-sensitive column (the default on SQLite).
normalize_login(&:downcase)
This can also be used for application-specific normalization.
* A two_factor_partially_authenticated? method has been added, which
allows you to more easily detect the partially authenticated case.
This method returns true if the session is logged in and the related
account has setup two factor authentication, but the session has not
yet been authenticated by multiple factors.
* A webauthn_autofill? configuration method has been added to the
webauthn_autofill feature. This allows for disabling the autofill
UI on the login page, while still keeping other parts of the
webauthn_autofill feature.
* A login_confirmation_matches? configuration method has been added,
allowing you to customize the confirmation comparison, so you can
continue to use a case-sensitive comparison if you would like (the
comparison is now case-insensitive by default, see below).
= Improvements
* The login confirmation comparison is now done in a case-insensitive
manner. Previously, while a case-insensitive column was used in
the database (on most databases), the confirmation comparison used
a case-sensitive comparison.
* The jwt feature will no longer call clear_session on the scope if
the request uses JWTs instead of scope sessions for session storage.
* CSRF protection is no longer enforced for JSON requests when using
the Roda route_csrf plugin. Previously, it was not enforced for
JWT requests, but was enforced for other JSON requests. CORS
restrictions are sufficient to prevent typical CSRF attacks for
JSON requests.
If you would like to continue to enforce CSRF protection for JSON
requests when using Roda's route_csrf plugin:
check_csrf? true
* The size of the gem has been reduced 50% by removing documentation.
jeremyevans-rodauth-b53f402/doc/release_notes/2.38.0.txt 0000664 0000000 0000000 00000003420 15157255142 0022731 0 ustar 00root root 0000000 0000000 = New Features
* Rodauth now automatically supports fixed locals in templates if
using Roda 3.88+ and Tilt 2.6+. This allows you to use the
Roda default_fixed_locals: '()' template option without breaking
Rodauth. If the default fixed locals support breaks your Rodauth
configuration, such as if you are overriding Rodauth templates
and modifying the local variables they accept, you can disable
the use of fixed locals in your Rodauth configuration:
use_template_fixed_locals? false
* Rodauth::ConfigurationError has been added, and issues that
Rodauth believes are configuration errors now use this
exception class.
= Other Improvements
* The following methods are now public:
* has_password?
* email_auth_email_recently_sent?
* unlock_account_email_recently_sent?
* reset_password_email_recently_sent?
* verify_account_email_recently_sent?
This makes it supported to call these methods and use the result
in your own code.
* The verify-account-resend page now works if
verify_account_resend_explanatory_text calls
verify_account_email_recently_sent?. Rodauth does not do that
by default, but if you override
verify_account_resend_explanatory_text to use different text
depending on whether the email was recently sent, direct
navigations to the verify-account-resend page previously failed.
* Rodauth now uses JWT.gem_version to check the JWT gem version, which
works with JWT 2.10.0. JWT 2.10.1 restored the constants Rodauth
used to check the version, but this allows the JWT to remove
such constants again in the future without breaking Rodauth.
= Backwards Compatibility
* The change to use Rodauth::ConfigurationError can break code that
rescued other exception classes, such as ArgumentError,
RuntimeError, or NotImplementedError.
jeremyevans-rodauth-b53f402/doc/release_notes/2.39.0.txt 0000664 0000000 0000000 00000001451 15157255142 0022734 0 ustar 00root root 0000000 0000000 = Improvements
* Rodauth now supports Roda's plain_hash_response_headers plugin on
Rack 3+, by using lowercase response header keys, instead of
relying on Roda's default conversion of response header keys to
lowercase.
* When setting login_return_to_requested_location? to true, by
default, Rodauth will no longer return to the requested location if
it is more than 2048 bytes in size. This is to avoid exceeding the
4K cookie size limit. You can modify this limit using the new
login_return_to_requested_location_max_path_size configuration
method.
* Rodauth now uses JSON.generate instead of JSON.fast_generate to
avoid a deprecation warning in recent json gem versions.
* Rodauth now uses allowed_origins instead of origin when using
WebAuthn 3.4+ to avoid a deprecation warning.
jeremyevans-rodauth-b53f402/doc/release_notes/2.4.0.txt 0000664 0000000 0000000 00000002016 15157255142 0022642 0 ustar 00root root 0000000 0000000 = New Features
* A password_pepper feature has been added. This allows you to use a
secret key (called a pepper) to append to passwords before hashing
and hash checking. Using this approach, if an attacker obtains the
password hash, it is unusable for cracking unless they can also
get access to the pepper.
The password_pepper feature also supports a list of previous peppers
that can be used to implement secret rotation and to support
compatibility with unpeppered passwords.
Rodauth by default uses database functions for password hash
checking on PostgreSQL, MySQL, and Microsoft SQL Server, which in
general provides more security than a password pepper, but both
approaches can be used simultaneously.
* A session_key_prefix configuration method has been added for
prefixing the values of all default session keys. This can be
useful if you are using multiple Rodauth configurations in the same
application and want to make sure the session keys for the separate
configurations do not overlap.
jeremyevans-rodauth-b53f402/doc/release_notes/2.40.0.txt 0000664 0000000 0000000 00000001164 15157255142 0022725 0 ustar 00root root 0000000 0000000 = New Features
* A reset_password_request_for_unverified_account configuration method
is now available. This allows you to configure the behavior if an
unverified account requests a password reset. If the method is not
used, the default behavior remains to show an error for the login
parameter.
= Other Improvements
* In the otp_unlock feature, instead of using a meta refresh tag in
the HTML, a refresh HTTP header is used. This should fix the page
not automatically refreshing in some browsers. You can customize
this behavior by using the
otp_unlock_not_available_set_refresh_header configuration method.
jeremyevans-rodauth-b53f402/doc/release_notes/2.41.0.txt 0000664 0000000 0000000 00000003462 15157255142 0022731 0 ustar 00root root 0000000 0000000 = Improvements
* When making a change to an account (e.g. changing a login), tokens
for the account are now cleared or reset. Previously, if you
requested a password reset, then requested a login change, and then
changed the login, the password reset link would still be valid
after the login change was made, until the password reset token
expired (default: 1 day). If the reason you are chaging your login
is that you suspect your email may be compromised, you probably
wouldn't want the reset password link to still be valid after the
login change.
The following account changes trigger clearing of tokens:
* change login
* close account
* reset password
* unlock account
* verify account
The following account tokens are cleared upon such changes:
* active sessions (other than logged in session)
* email auth
* jwt refresh (if not logged in)
* lockout (updates token if it exists)
* remember (creates and uses new remember token if logged in via
remember token)
* reset password
* single session (if not logged in)
* verify account
* verify login change
This is a more secure default, and it is expected that it will
not negatively affect the vast majority of Rodauth installations.
However, due to Rodauth's very configurable nature, it is possible
it will cause issues for some installations.
= Backwards Compatibility
* If clearing tokens on account change causes problems for your
application, you can revert to clearing tokens only on account
close:
clear_tokens do |reason|
super(reason) if reason == :close_account
end
* If you were calling after_close_account directly to clear tokens,
you should now also call:
clear_tokens(:close_account)
As some token clearing now occurs in clear_tokens and not in
after_close_account.
jeremyevans-rodauth-b53f402/doc/release_notes/2.42.0.txt 0000664 0000000 0000000 00000000336 15157255142 0022727 0 ustar 00root root 0000000 0000000 = Improvements
* Rodauth now avoids mixing string and symbol keys in a hash used to
create a JWT in the jwt_refresh feature. This avoids warnings in
the current json gem, and will avoid errors in json gem version 3+.
jeremyevans-rodauth-b53f402/doc/release_notes/2.43.0.txt 0000664 0000000 0000000 00000000465 15157255142 0022733 0 ustar 00root root 0000000 0000000 = New Features
* A reset_password_verifies_account feature has been added. With this
feature enabled, unverified accounts are allowed to reset their
passwords, and a valid password reset verifies the account, as it
proves ownership of the account's email (just as normal account
verification would).
jeremyevans-rodauth-b53f402/doc/release_notes/2.5.0.txt 0000664 0000000 0000000 00000001627 15157255142 0022652 0 ustar 00root root 0000000 0000000 = New Features
* A login_return_to_requested_location_path configuration method has
been added to the login feature. This controls the path to redirect
to if using login_return_to_requested_location?. By default, this
is the same as the fullpath of the request that required login if
that request was a GET request, and nil if that request was not a
GET request. Previously, the fullpath of that request was used even
if it was not a GET request, which caused problems as browsers use a
GET request for redirects, and it is a bad idea to redirect to a path
that may not handle GET requests.
* A change_login_needs_verification_notice_flash configuration method
has been added to the verify_login_change feature, for allowing
translations when using the feature and not using the
change_login_notice_flash configuration method.
= Other Improvements
* new_password_label is now translatable.
jeremyevans-rodauth-b53f402/doc/release_notes/2.6.0.txt 0000664 0000000 0000000 00000002705 15157255142 0022651 0 ustar 00root root 0000000 0000000 = New Features
* An around_rodauth configuration method has been added, which is
called around all Rodauth actions. This configuration method
is passed a block, and is useful for cases where you want to wrap
Rodauth's handling of the request.
For example, if you had a method named time_block in your Roda scope
that timed block execution and added a response header, you could
time Rodauth actions using something like:
around_rodauth do |&block|
scope.time_block('Rodauth') do
super(&block)
end
end
* The allow_refresh_with_expired_jwt_access_token? configuration has
been added to the jwt_refresh feature, allowing refreshing with an
expired but otherwise valid access token. When using this method,
it is required to have an hmac_secret specified, so that Rodauth
can make sure the access token matches the refresh token.
= Other Improvements
* The javascript for setting up a WebAuthn token has been fixed to
allow it to work correctly if there is already an existing
WebAuthn token for the account.
* The rodauth.setup_account_verification method has been promoted to
public API. You can use this method for automatically sending
account verification emails when automatically creating accounts.
* Rodauth no longer loads the same feature multiple times into a
single configuration. This didn't cause any problems before, but
could result in duplicate entries when looking at the loaded
features.
jeremyevans-rodauth-b53f402/doc/release_notes/2.7.0.txt 0000664 0000000 0000000 00000002562 15157255142 0022653 0 ustar 00root root 0000000 0000000 = New Features
* An auto_remove_recovery_codes? configuration method has been added
to the recovery_codes feature. This will automatically remove
recovery codes when the last multifactor authentication type other
than the recovery codes has been removed.
* The jwt_access_expired_status and expired_jwt_access_token_message
configuration methods have been added to the jwt_refresh feature,
for supporting custom statuses and messages for expired tokens.
= Other Improvements
* Rodauth will no longer attempt to require a feature that has
already been required. Related to this is you can now use a
a custom Rodauth feature without a rodauth/features/*.rb file
in the Ruby library path, as long as you load the feature
manually.
* Rodauth now avoids method redefinition warnings in verbose
warning mode. As Ruby 3 is dropping uninitialized instance
variable warnings, Rodauth will be verbose warning free in
Ruby 3.
= Backwards Compatibility
* The default remember cookie path is now set to '/'. This fixes
usage in the case where rodauth is loaded under a subpath of the
application (which is not the default behavior). Unfortunately,
this change can negatively affect cases where multiple rodauth
configurations are used in separate paths on the same domain.
In these cases, you should now use remember_cookie_options and
include a :path option.
jeremyevans-rodauth-b53f402/doc/release_notes/2.8.0.txt 0000664 0000000 0000000 00000001677 15157255142 0022662 0 ustar 00root root 0000000 0000000 = Improvements
* HttpOnly is now set by default on the remember cookie, so it is no
longer accessible from Javascript. This is a more secure approach
that makes applications using Rodauth's remember feature less
vulnerable in case they are subject to a separate XSS attack.
* When using the jwt feature, rodauth.clear_session now clears the
JWT session even when the Roda sessions plugin was in use. In most
cases, the jwt feature is not used with the Roda sessions plugin,
but in cases where the same application serves as both an JSON API
and as a HTML site, it is possible the two may be used together.
= Backwards Compatibility
* As the default remember cookie :httponly setting is now set to true,
applications using Rodauth that expected to be able to access the
remember cookie from Javascript will no longer work by default.
In these cases, you should now use remember_cookie_options and
include a :httponly=>false option.
jeremyevans-rodauth-b53f402/doc/release_notes/2.9.0.txt 0000664 0000000 0000000 00000001443 15157255142 0022652 0 ustar 00root root 0000000 0000000 = New Features
* A json feature has been extracted from the existing jwt feature.
This feature allows for the same JSON API previously supported
by the JWT feature, but stores the session information in the
Rack session instead of in a separate JWT. This makes it
significantly easier to have certain pages use the JSON API,
and other pages the HTML forms.
= Other Improvements
* If the remember cookie is created in an SSL request, the Secure
flag is added by default, so the cookie will not be transmitted
in non-SSL requests.
= Backwards Compatibility
* Rodauth configurations that use the remember feature and support
requests over both http and https and want to have the remember
cookie transmitted over both should now include :secure=>false in
remember_cookie_options.
jeremyevans-rodauth-b53f402/doc/remember.rdoc 0000664 0000000 0000000 00000013452 15157255142 0021265 0 ustar 00root root 0000000 0000000 = Documentation for Remember Feature
The remember feature allows for token-based autologin for users. Calling
+rodauth.remember_login+ for an authenticated session will create a token for
the current account and store it in a cookie. You can then add the following
code to your routing block to automatically login users from that token if the
session has expired:
rodauth.load_memory
By default, the remember feature just supports a form that the user can use
to change their remember settings for the current browser. They can either
enable remembering for the browser, forget it for the browser, or disable
it completely so that any remembering for other browsers is removed as well.
In some cases, you may want to automatically remember users and not require
users to turn it on manually. If you want to automatically remember users
on login:
after_login do
remember_login
end
The remember feature records which sessions were autologged in via the
remember cookie. If you have sections where you want to add more security,
you can use the confirm password feature to request password authentication
for sessions autologged in via a remember token:
rodauth.require_password_authentication
== Auth Value Methods
extend_remember_deadline? :: Whether to extend the remember token deadline when the user is autologged in via remember token and every +extend_remember_deadline_period+ seconds while logged in.
extend_remember_deadline_period :: The amount of seconds to wait before extending remember token deadline when +extend_remember_deadline?+ is true (3600 by default).
raw_remember_token_deadline :: A deadline before which to allow a raw remember token to be used. Allows for graceful transition for when +hmac_secret+ is first set.
remember_additional_form_tags :: HTML fragment containing additional form tags to use on the change remember setting form.
remember_button :: The text to use for the change remember settings button.
remember_cookie_key :: The cookie name to use for the remember token.
remember_cookie_options :: Any options to set for the remember cookie. By default, the `:path` cookie option is set to `/` and `:httponly` is set to `true`. Also, `:secure` is set to `true` by default if the current request is an HTTPS request.
remember_deadline_column :: The column name in the +remember_table+ storing the deadline after which the token will be ignored.
remember_deadline_extended_session_key :: The session key set if the remember deadline token is being extended.
remember_deadline_interval :: The amount of time for which to remember accounts, 14 days by default. Only used if +set_deadline_values?+ is true.
remember_disable_label :: The label for disabling remembering.
remember_disable_param_value :: The parameter value for disabling remembering.
remember_error_flash :: The flash error to show if there is an error changing a remember setting.
remember_forget_label :: The label for turning off remembering.
remember_forget_param_value :: The parameter value for turning off remembering.
remember_id_column :: The id column in the +remember_table+, should be a foreign key referencing the accounts table.
remember_key_column :: The remember key/token column in the +remember_table+.
remember_notice_flash :: The flash notice to show after remember setting has been updated.
remember_page_title :: The page title to use on the change remember settings form.
remember_param :: The parameter name to use for the remember password settings choice.
remember_period :: The additional time to extend the remember deadline if extending remember deadlines.
remember_redirect :: Where to redirect after changing the remember settings.
remember_remember_label :: The label for turning on remembering.
remember_remember_param_value :: The parameter value for switching on remembering.
remember_route :: The route to the change remember settings action. Defaults to +remember+.
remember_table :: The name of the remember keys table.
== Auth Methods
add_remember_key :: Add a remember key for the current account to the remember keys table.
after_load_memory :: Run arbitrary code after autologging in an account via a remember token.
after_remember :: Run arbitrary code after changing the remember settings.
before_load_memory :: Run arbitrary code before autologging in an account via a remember token.
before_remember :: Run arbitrary code before changing the remember settings.
before_remember_route :: Run arbitrary code before handling the remember route.
disable_remember_login :: Disable the remember key token, clearing the token from the database so future connections with the token will not be recognized.
forget_login :: Forget the current remember token, deleting the related cookie. Other browsers that have the cookie cached can still use it login.
generate_remember_key_value :: A random string to use as the remember key.
get_remember_key :: Retrieve the remember key from the database.
load_memory :: If the remember key cookie is included in the request, and the user is not currently logged in, check the remember keys table and autologin the user if the remember key cookie matches the current remember key for the account. This method needs to be called manually inside the Roda route block to autologin users.
logged_in_via_remember_key? :: Whether the current session was logged in via a remember key.
remembered_session_id :: The session_id which is validly remembered, if any.
remember_key_value :: The current value of the remember key/token.
remember_login :: Set the cookie containing the remember token, so that future sessions will be autologged in.
remember_response :: Return a response after successfully changing remember settings. By default, redirects to +remember_redirect+.
remember_view :: The HTML to use for the change remember settings form.
remove_remember_key(id_value=account_id) :: Delete the related remember key from the database.
jeremyevans-rodauth-b53f402/doc/reset_password.rdoc 0000664 0000000 0000000 00000014225 15157255142 0022532 0 ustar 00root root 0000000 0000000 = Documentation for Reset Password Feature
The reset password feature implements password resets. If the user enters
an invalid password, they will be displayed a form where they can request
a password reset. Submitting that form will send an email containing a
link, and that link will taken them to a password reset form. Depends on
the login feature.
== Auth Value Methods
no_matching_reset_password_key_error_flash :: The flash error message to show if attempting to access the reset password form with an invalid key.
reset_password_additional_form_tags :: HTML fragment containing additional form tags to use on the reset password form.
reset_password_autologin? :: Whether to autologin the user after successfully resetting a password, false by default.
reset_password_button :: The text to use for the reset password button.
reset_password_deadline_column :: The column name in the +reset_password_table+ storing the deadline after which the token will be ignored.
reset_password_deadline_interval :: The amount of time for which to allow users to reset their passwords, 1 day by default. Only used if +set_deadline_values?+ is true.
reset_password_email_last_sent_column :: The email last sent column in the +reset_password_table+. Set to nil to always send a reset password request email when requested.
reset_password_email_recently_sent_error_flash :: The flash error to show if not sending reset password request email because one has been sent recently.
reset_password_email_recently_sent_redirect :: Where to redirect if not sending reset password request email because one has been sent recently.
reset_password_email_sent_notice_flash :: The flash notice to show after a reset password request email has been sent.
reset_password_email_sent_redirect :: Where to redirect after sending a reset password request email.
reset_password_email_subject :: The subject to use for the reset password request email.
reset_password_error_flash :: The flash error to show after resetting a password.
reset_password_explanatory_text :: The text to display above the button to request a password reset.
reset_password_id_column :: The id column in the +reset_password_table+, should be a foreign key referencing the accounts table.
reset_password_key_column :: The reset password key/token column in the +reset_password_table+.
reset_password_key_param :: The parameter name to use for the reset password key.
reset_password_notice_flash :: The flash notice to show after resetting a password.
reset_password_page_title :: The page title to use on the reset password form.
reset_password_redirect :: Where to redirect after resetting a password.
reset_password_request_additional_form_tags :: HTML fragment containing additional form tags to use on the reset password request form.
reset_password_request_button :: The text to use for the reset password request button.
reset_password_request_error_flash :: The flash error to show if not able to send a reset password request email.
reset_password_request_link_text :: The text to use for a link to the page to request a password reset.
reset_password_request_page_title :: The page title to use on the reset password request form.
reset_password_request_route :: The route to the reset password request action. Defaults to +reset-password-request+.
reset_password_route :: The route to the reset password action. Defaults to +reset-password+.
reset_password_session_key :: The key in the session to hold the reset password key temporarily.
reset_password_skip_resend_email_within :: The number of seconds before sending another reset password request email, if +reset_password_email_last_sent_column+ is set.
reset_password_table :: The name of the reset password keys table.
== Auth Methods
account_from_reset_password_key(key) :: Retrieve the account using the given reset password key, or return nil if no account matches.
after_reset_password :: Run arbitrary code after successfully resetting a password.
after_reset_password_request :: Run arbitrary code after sending the reset password request email.
before_reset_password :: Run arbitrary code before resetting a password.
before_reset_password_request :: Run arbitrary code before sending the reset password request email.
before_reset_password_request_route :: Run arbitrary code before handling a reset password request route.
before_reset_password_route :: Run arbitrary code before handling a reset password route.
create_reset_password_email :: A Mail::Message for the reset password request email.
create_reset_password_key :: Add the reset password key data to the database.
get_reset_password_email_last_sent :: Get the last time a reset password request email is sent, or nil if there is no last sent time.
get_reset_password_key(id) :: Get the password reset key for the given account id from the database.
login_failed_reset_password_request_form :: The HTML to use for a form to request a password reset, shown on the login page after the user tries to login with an invalid password.
remove_reset_password_key :: Remove the reset password key for the current account, run after successful password reset.
reset_password_email_body :: The body to use for the reset password request email.
reset_password_email_link :: The link to the reset password form in the reset password request email.
reset_password_email_sent_response :: Return a response after successfully sending a password reset email. By default, redirects to +reset_password_email_sent_redirect+.
reset_password_key_insert_hash :: The hash to insert into the +reset_password_table+.
reset_password_key_value :: The reset password key for the current account.
reset_password_request_for_unverified_account :: What to do if there is a request to reset a password for an unverified account. By default, shows an error for the login parameter.
reset_password_request_view :: The HTML to use for the reset password request form.
reset_password_response :: Return a response after successfully resetting a password. By default, redirects to +reset_password_redirect+.
reset_password_view :: The HTML to use for the reset password form.
send_reset_password_email :: Send the reset password request email.
set_reset_password_email_last_sent :: Set the last time a reset password request email is sent.
jeremyevans-rodauth-b53f402/doc/reset_password_notify.rdoc 0000664 0000000 0000000 00000001404 15157255142 0024115 0 ustar 00root root 0000000 0000000 = Documentation for Reset Password Notify Feature
The reset password notify feature emails the user after the user has
reset their password. The user has already been sent a reset password
email by this point, so they know a password reset was requested, but
this feature allows for confirming that the password reset process
was completed. Depends on the reset_password feature.
== Auth Value Methods
reset_password_notify_email_subject :: The subject to use for the reset password notify email.
reset_password_notify_email_body :: The body to use for the reset password notify email.
== Auth Methods
create_reset_password_notify_email :: A Mail::Message for the reset password notify email.
send_reset_password_notify_email :: Send the reset password notify email.
jeremyevans-rodauth-b53f402/doc/reset_password_verifies_account.rdoc 0000664 0000000 0000000 00000000454 15157255142 0026141 0 ustar 00root root 0000000 0000000 = Documentation for Reset Password Feature
The reset password verifies account feature depends on both the reset
password and verify account features, and makes it so that a valid
password reset implicitly operates as an account verification, since
it proves ownership of the related email address.
jeremyevans-rodauth-b53f402/doc/session_expiration.rdoc 0000664 0000000 0000000 00000003036 15157255142 0023411 0 ustar 00root root 0000000 0000000 = Documentation for Session Expiration Feature
The session expiration feature allows setting an inactivity timeout and a max
lifetime for sessions. When this feature is used, you should use
+rodauth.check_session_expiration+ at the top (or other appropriate place)
in your routing tree.
route do |r|
rodauth.check_session_expiration
r.rodauth
# ...
end
When checking session expiration, if the last activity was more than the
inactivity timeout, or the session was created more the maximum lifetime
ago, the session is cleared, and the user is redirected to the login page.
== Auth Value Methods
max_session_lifetime :: The maximum number of seconds since session creation that sessions will be valid for, regardless of session activity. 86400 by default (1 day).
session_created_session_key :: The session key storing the session creation timestamp.
session_expiration_default :: Whether to expire sessions that don't have the created at or last activity at timestamps set, true by default.
session_expiration_error_flash :: The flash error to show if a session expires.
session_expiration_error_status :: The error status to use when a JSON request is made and the session has expired, 401 by default.
session_expiration_redirect :: Where to redirect if a session expires.
session_inactivity_timeout :: The maximum number of seconds allowed since the last activity before the session will be considered invalid. 1800 by default (30 minutes).
session_last_activity_session_key :: The session key storing the last session activity timestamp.
jeremyevans-rodauth-b53f402/doc/single_session.rdoc 0000664 0000000 0000000 00000004306 15157255142 0022511 0 ustar 00root root 0000000 0000000 = Documentation for Single Session Feature
The single session feature stores the key for the session in a
database table whenever a user logs in to the system. In your
routing block, you can check that the session key given matches
the stored key by doing:
rodauth.check_single_session
It is not recommended to use this feature unless you
have a policy that requires it. Many users find it useful to
be able to have multiple concurrent sessions, and restricting
this ability does not make things more secure. You can use the
active_sessions feature for something with similar behavior but
that allows for concurrent sessions.
One of the side benefits with this feature is that
logouts reset the single session key, so attempts to reuse
the previous session after logout no longer work.
== Auth Value Methods
allow_raw_single_session_key? :: Whether to allow a raw single session key to be accepted, should only be enabled for graceful transition when +hmac_secret+ is first set.
inactive_session_error_status :: The error status to use when a JSON request is made and the session is no longer active, 401 by default.
single_session_error_flash :: The flash error to display if the current session is no longer the active session for the account.
single_session_id_column :: The column in the +single_session_table+ containing the account id.
single_session_key_column :: The column in the +single_session_table+ containing the single session key.
single_session_redirect :: Where to redirect if the current session is no longer the active session for the account.
single_session_session_key :: The session key name to use for storing the single session key.
single_session_table :: The database table storing single session keys.
== Auth Methods
currently_active_session? :: Whether the current session is the active session for the user.
no_longer_active_session :: The action to take if the current session is no longer the active session for the user.
reset_single_session_key :: Reset the single session key for the user, by default to a new random key.
update_single_session_key :: Update the single session key in the current session and in the database, reflecting that the current session is the active session for the user.
jeremyevans-rodauth-b53f402/doc/sms_codes.rdoc 0000664 0000000 0000000 00000030253 15157255142 0021444 0 ustar 00root root 0000000 0000000 = Documentation for SMS Codes Feature
The sms codes feature allows for multifactor authentication via codes provided via
SMS messages. It is usually used as a backup if other multifactor authentication is not available
or has been locked out, but it can be used as the primary multifactor authentication method.
This feature allows users to register their mobile phone number with the system, confirm that
they can receive SMS messages on the mobile phone number they have registered, request
SMS authentication codes, authenticate via SMS codes, and disable SMS authentication.
While this feature sets up all of the infrastructure needed to support SMS authentication,
it doesn't handle sending SMS messages itself. There are many ruby libraries that send
SMS messages, and you can choose which one to use. When using this feature, you must
use the +sms_send+ configuration method and send the SMS using whatever SMS library
you prefer:
sms_send do |phone_number, message|
# ...
end
== Auth Value Methods
no_current_sms_code_error_flash :: The flash error to show when going to the SMS authentication page and no current SMS authentication code is available.
sms :: A hash of SMS information for the user, if SMS authentication has been setup.
sms_already_setup_error_flash :: The flash error to show when going to a page to setup SMS authentication if SMS authentication has already been setup.
sms_already_setup_error_status :: The response status to use when going to a page to setup SMS authentication if SMS authentication has already been setup, 403 by default.
sms_already_setup_redirect :: Where to redirect when going to a page to setup SMS authentication if SMS authentication has already been setup.
sms_auth_additional_form_tags :: HTML fragment containing additional form tags when authenticating via SMS.
sms_auth_button :: Text to use for button on the form to authenticate via SMS.
sms_auth_code_length :: The length of SMS authentication codes, 6 by default.
sms_auth_link_text :: The text to use for the link from the multifactor auth page.
sms_auth_page_title :: The page title to use on the form to authenticate via SMS code.
sms_auth_redirect :: Where to redirect if SMS authentication is needed.
sms_auth_route :: The route to the SMS authentication action. Defaults to +sms-auth+.
sms_code_allowed_seconds :: The number of seconds after an SMS authentication is sent until it is no longer valid, 300 seconds by default.
sms_code_column :: The column in the +sms_codes_table+ containing the currently valid SMS authentication/confirmation code.
sms_code_label :: The label for SMS codes.
sms_code_param :: The parameter name for SMS codes.
sms_codes_primary? :: Whether SMS codes are a primary multifactor authentication method. If not, they cannot be setup unless multifactor authentication has already been setup.
sms_codes_table :: The name of the table storing SMS code data.
sms_confirm_additional_form_tags :: HTML fragment containing additional form tags when confirming SMS setup.
sms_confirm_button :: Text to use for button on the form to confirm SMS setup.
sms_confirm_code_length :: The length of SMS confirmation codes, 12 by default, as there is no lockout.
sms_confirm_deadline :: The number of seconds before an SMS confirmation code expires (86400 seconds by default).
sms_confirm_notice_flash :: The flash notice to show when SMS authentication setup has been confirmed.
sms_confirm_page_title :: The page title to use on the form to authenticate via SMS code.
sms_confirm_redirect :: Where to redirect after SMS authentication setup has been confirmed.
sms_confirm_route :: The route to the SMS setup confirmation action. Defaults to +sms-confirm+.
sms_disable_additional_form_tags :: HTML fragment containing additional form tags when disabling SMS authentication.
sms_disable_button :: Text to use for button on the form to disable SMS authentication.
sms_disable_error_flash :: The flash error to show when disabling SMS authentication fails.
sms_disable_link_text :: The text to use for the remove link from the multifactor manage page.
sms_disable_notice_flash :: The flash notice to show when SMS authentication has been successfully disabled.
sms_disable_page_title :: The page title to use on the form to disable SMS authentication.
sms_disable_redirect :: Where to redirect after SMS authentication has been disabled.
sms_disable_route :: The route to the SMS authentication disable action. Defaults to +sms-disable+.
sms_failure_limit :: The number of failures until SMS authentication is locked out.
sms_failures_column :: The column in the +sms_codes_table+ containing the number of SMS authentication failures since the last successful authentication.
sms_id_column :: The column in the +sms_codes_table+ containing the account id.
sms_invalid_code_error_flash :: The flash error to show when an invalid SMS authentication code is used.
sms_invalid_code_message :: The error message to show when an invalid SMS code is used.
sms_invalid_confirmation_code_error_flash :: The flash error to show when an invalid SMS confirmation code is used.
sms_invalid_phone_message :: The error message to show when an invalid SMS phone number is used.
sms_issued_at_column :: The column in the +sms_codes_table+ containing the time the SMS code was issued.
sms_lockout_error_flash :: The flash error to show when SMS authentication has been locked out due to repeated failures.
sms_lockout_redirect :: Where to redirect after SMS authentication has been locked out.
sms_needs_confirmation_notice_flash :: The flash notice to show on SMS authentication pages when SMS authentication setup needs confirmation (uses +sms_needs_confirmation_error_flash+ by default).
sms_needs_confirmation_error_flash :: The flash error to show on SMS authentication pages when SMS authentication setup needs confirmation.
sms_needs_confirmation_error_status :: The response status to use on SMS authentication pages when SMS authentication setup needs confirmation, 403 by default.
sms_needs_confirmation_redirect :: Where to redirect after SMS setup, when confirmation is required.
sms_needs_setup_redirect :: Where to redirect if going to an SMS authentication page when SMS authentication has not been setup.
sms_not_setup_error_flash :: The flash error to show when on SMS authentication pages when SMS authentication has not yet been setup.
sms_phone_column :: The column in the +sms_codes_table+ containing the phone number to which to send SMS messages.
sms_phone_input_type :: The input type to use for SMS phone numbers, tel by default.
sms_phone_label :: The label for SMS phone numbers.
sms_phone_min_length :: The minimum length of phone numbers allowed for SMS authentication, 7 by default.
sms_phone_param :: The parameter name for SMS phone numbers.
sms_request_additional_form_tags :: HTML fragment containing additional form tags when requesting an SMS authentication code.
sms_request_button :: Text to use for button on the form to request an SMS authentication code.
sms_request_notice_flash :: The flash notice to show when an SMS authentication code is requested.
sms_request_page_title :: The page title to use on the form to request an SMS authentication code.
sms_request_redirect :: Where to redirect after requesting an SMS authentication code.
sms_request_route :: The route to the SMS authentication code request action. Defaults to +sms-request+.
sms_setup_additional_form_tags :: HTML fragment containing additional form tags when setting up SMS authentication.
sms_setup_button :: Text to use for button on the form to setup SMS authentication.
sms_setup_error_flash :: The flash error to show when setting up SMS authentication fails.
sms_setup_link_text :: The text to use for the setup link from the multifactor manage page.
sms_setup_page_title :: The page title to use on the form to setup SMS authentication.
sms_setup_route :: The route to the SMS authentication setup action. Defaults to +sms-setup+.
== Auth Methods
after_sms_confirm :: Run arbitrary code after successful SMS authentication confirmation.
after_sms_disable :: Run arbitrary code after disabling SMS authentication.
after_sms_failure :: Run arbitrary code after SMS authentication failure.
after_sms_request :: Run arbitrary code after SMS authentication code request.
after_sms_setup :: Run arbitrary code after SMS authentication setup.
before_sms_auth :: Run arbitrary code before SMS authentication.
before_sms_auth_route :: Run arbitrary code before handling SMS authentication route.
before_sms_confirm :: Run arbitrary code before SMS confirmation.
before_sms_confirm_route :: Run arbitrary code before handling SMS confirmation route.
before_sms_disable :: Run arbitrary code before disabling SMS authentication.
before_sms_disable_route :: Run arbitrary code before handling SMS disable route.
before_sms_request :: Run arbitrary code before sending SMS code.
before_sms_request_route :: Run arbitrary code before handling SMS request route.
before_sms_setup :: Run arbitrary code before setting up SMS authentication.
before_sms_setup_route :: Run arbitrary code before handling SMS setup route.
sms_auth_message(code) :: The SMS message to use for the given authentication code.
sms_auth_view :: The HTML to use for the form to authenticate via SMS code.
sms_available? :: Whether SMS authentication is ready for use.
sms_code_issued_at :: The timestamp the current SMS code was issued at.
sms_code_match?(code) :: Whether there is an active SMS authentication code for the current account and the given code matches it.
sms_confirm_message(code) :: The SMS message to use for the given confirmation code.
sms_confirm_response :: Return a response after successfully confirming SMS code during SMS setup. By default, redirects to +sms_confirm_redirect+.
sms_confirm_view :: The HTML to use for the form to authenticate via SMS code.
sms_confirmation_match?(code) :: Whether there is an active SMS confirmation code for the current account and the given code matches it.
sms_current_auth? :: Whether there is a active SMS authentication code for the current account.
sms_disable :: Action to take to disable SMS authentication for the account.
sms_disable_response :: Return a response after successfully disabling SMS. By default, redirects to +sms_disable_redirect+.
sms_disable_view :: The HTML to use for the form to disable SMS authentication.
sms_failures :: The number of SMS authentication failures since the last successfully SMS authentication for this account.
sms_locked_out? :: Whether SMS authentication has been locked out for the current account.
sms_needs_confirmation? :: Whether SMS authentication has been setup but not confirmed for the current account.
sms_needs_confirmation_response :: Return a response after successfully providing SMS number during SMS setup. By default, redirects to +sms_needs_confirmation_redirect+.
sms_new_auth_code :: A new SMS authentication code that can be used for the account.
sms_new_confirm_code :: A new SMS confirmation code that can be used for the account.
sms_normalize_phone(phone) :: A normalized version of the given phone number, by default removing everything except 0-9.
sms_record_failure :: Record an SMS authentication failure for the current account.
sms_remove_expired_confirm_code :: Remove an expired SMS confirm code, allowing setup of a new sms confirm code.
sms_remove_failures :: Reset the SMS authentication failure counter for the current account, used after a successful multifactor authentication.
sms_request_response :: Return a response after a successful SMS request during SMS authentication. By default, redirects to +sms_auth_redirect+.
sms_request_view :: The HTML to use for the form to request an SMS authentication code.
sms_send(phone, message) :: Send the given message to the given phone number via SMS. By default a NotImplementedError is raised, this is the only method that must be overridden.
sms_set_code(code) :: Set the SMS authentication code for the current account to the given code. The code can be nil to specify that no SMS authentication code is currently valid.
sms_setup :: Setup SMS authentication for the current account.
sms_setup? :: Whether SMS authentication has been setup and confirmed for the current account.
sms_setup_view :: The HTML to use for the form to setup SMS authentication.
sms_valid_phone?(phone) :: Whether the given phone number is a valid phone number.
jeremyevans-rodauth-b53f402/doc/two_factor_base.rdoc 0000664 0000000 0000000 00000016541 15157255142 0022632 0 ustar 00root root 0000000 0000000 = Documentation for Two Factor Base Feature
The two_factor_base feature implements shared functionality for the other
multifactor authentication features.
To handle multiple and potentially different multifactor authentication setups
per user, this feature implements disambiguation pages for multifactor
authentication and manage. If only a single multifactor authentication is
available to setup, the manage page will redirect to the appropriate page.
Likewise, if only a single multifactor authentication method is available,
the authentication page will redirect to the appropriate page. Otherwise,
the authentication and manage pages will show links to the available pages.
Additionally, there is a separate page for disabling all multifactor
authentication methods and reverting to single factor authentication,
so users do not have to disable each multifactor authentication method
individually.
== Auth Value Methods
two_factor_already_authenticated_error_flash :: The flash error to show if going to a multifactor authentication page when already multifactor authenticated.
two_factor_already_authenticated_error_status :: The response status to use if going to a multifactor authentication page when already multifactor authenticated, 403 by default.
two_factor_already_authenticated_redirect :: Where to redirect if going to a multifactor authentication page when already multifactor authenticated.
two_factor_auth_notice_flash :: The flash notice to show after a successful multifactor authentication.
two_factor_auth_page_title :: The page title to use on the page linking to other multifactor authentication pages.
two_factor_auth_redirect :: Where to redirect after a successful multifactor authentication.
two_factor_auth_redirect_session_key :: The key in the session hash storing the location to redirect to after successful multifactor authentication.
two_factor_auth_required_redirect :: Where to redirect if going to a page requiring multifactor authentication when not multifactor authenticated (the multifactor auth page by default).
two_factor_auth_return_to_requested_location? :: Whether to redirect to the originally requested location after successful multifactor authentication when +require_two_factor_authenticated+ was used, false by default.
two_factor_auth_route :: The route to the multifactor authentication page. Defaults to +multifactor-auth+.
two_factor_disable_additional_form_tags :: HTML fragment containing additional form tags when disabling all multifactor authentication.
two_factor_disable_button :: Text to use for button on the form to disable all multifactor authentication.
two_factor_disable_error_flash :: The flash error to show if unable to disable all multifactor authentication.
two_factor_disable_link_text :: The text to use for the link to disable all multifactor authentication from the multifactor manage page.
two_factor_disable_notice_flash :: The flash notice to show after a successfully disabling all multifactor authentication.
two_factor_disable_page_title :: The page title to use on the page for disabling all multifactor authentication.
two_factor_disable_redirect :: Where to redirect after a successfully disabling all multifactor authentication.
two_factor_disable_route :: The route to the page to disable all multifactor authentication. Defaults to +multifactor-disable+.
two_factor_manage_page_title :: The page title to use on the page linking to other multifactor setup and remove pages.
two_factor_manage_route :: The route to the page to manage multifactor authentication. Defaults to +multifactor-manage+.
two_factor_modifications_require_password? :: Whether modifications to multifactor authentication require the inputing the user's password.
two_factor_need_authentication_error_flash :: The flash error to show if going to a page that requires multifactor authentication when not authenticated.
two_factor_need_authentication_error_status :: The response status to use if going to a page that requires multifactor authentication when not authenticated, 401 by default.
two_factor_need_setup_redirect :: Where to redirect if going to a multifactor authentication page when multifactor authentication has not been setup (the multifactor manage page by default).
two_factor_not_setup_error_flash :: The flash error to show if going to a multifactor authentication page when multifactor authentication has not been setup.
two_factor_not_setup_error_status :: The response status to use if going to a multifactor authentication page when multifactor authentication has not been setup, 403 by default.
two_factor_remove_heading :: The HTML to use above the remove links on the multifactor manage page.
two_factor_setup_heading :: The HTML to use above the setup links on the multifactor manage page.
two_factor_setup_session_key :: The session key used for storing whether multifactor authentication has been setup for the current account.
== Auth Methods
after_two_factor_authentication :: Any actions to take after successful multifactor authentication.
after_two_factor_disable :: Any actions to take after successful disabling of all multifactor authentication.
before_two_factor_auth_route :: Run arbitrary code before handling the multifactor auth route.
before_two_factor_disable :: Any actions to take before disabling of all multifactor authentication.
before_two_factor_disable_route :: Run arbitrary code before handling the multifactor disable route.
before_two_factor_manage_route :: Run arbitrary code before handling the multifactor manage route.
two_factor_auth_links :: An array of entries for links to show on the multifactor auth page. Each entry is an array of three elements, sort order (integer), link href, and link text.
two_factor_auth_response :: Return a response after successful multifactor authentication. By default, redirects to +two_factor_auth_redirect+ (or the requested location if +two_factor_auth_return_to_requested_location?+ is true).
two_factor_auth_view :: The HTML to use for the page linking to other multifactor authentication pages.
two_factor_authenticated? :: Whether the current session has already been multifactor authenticated.
two_factor_disable_response :: Return a response after successfully disabling multifactor authentication. By default, redirects to +two_factor_disable_redirect+.
two_factor_disable_view :: The HTML to use for the page for disabling all multifactor authentication.
two_factor_manage_view :: The HTML to use for the page linking to other multifactor setup and remove pages.
two_factor_remove :: Any action to take to remove multifactor authentication, called when closing accounts.
two_factor_remove_auth_failures :: Any action to take to remove multifactor authentication failures, called after a successful multifactor authentication.
two_factor_remove_links :: An array of entries for remove links to show on the multifactor manage page. Each entry is an array of three elements, sort order (integer), link href, and link text.
two_factor_remove_session :: What actions to take to remove multifactor authentication status from the session, called when disabling multifactor authentication when authenticated using the factor being removed.
two_factor_setup_links :: An array of entries for setup links to show on the multifactor manage page. Each entry is an array of three elements, sort order (integer), link href, and link text.
two_factor_update_session(type) :: How to update the session to reflect a successful multifactor authentication.
jeremyevans-rodauth-b53f402/doc/update_password_hash.rdoc 0000664 0000000 0000000 00000000573 15157255142 0023676 0 ustar 00root root 0000000 0000000 = Documentation for Update Password Hash Feature
The update password hash feature updates the hash for the password whenever
the hash cost changes. For example, if you have a cost of 8, and later
increase the cost to 10, anytime the user authenticates correctly with
their password, their password hash will change from one that uses a cost
of 8 to one that uses a cost of 10.
jeremyevans-rodauth-b53f402/doc/verify_account.rdoc 0000664 0000000 0000000 00000014250 15157255142 0022504 0 ustar 00root root 0000000 0000000 = Documentation for Verify Account Feature
The verify account feature implements account verification after account
creation. After account creation, users are sent an email containing
a link to verify the account. Users cannot login to the account until
after verifying the account. Depends on the login and create account features.
== Auth Value Methods
attempt_to_create_unverified_account_error_flash :: The flash error message to show when attempting to create an account awaiting verification.
attempt_to_login_to_unverified_account_error_flash :: The flash error message to show when attempting to login to an account awaiting verification.
no_matching_verify_account_key_error_flash :: The flash error message to show when an invalid verify account key is used.
resend_verify_account_page_title :: The page title to use on page requesting resending the verify account email.
verify_account_additional_form_tags :: HTML fragment containing additional form tags to use on the verify account form.
verify_account_autologin? :: Whether to autologin the user after successful account verification, true by default.
verify_account_button :: The text to use for the verify account button.
verify_account_email_last_sent_column :: The email last sent column in the +verify_account_table+. Set to nil to always send a verify account email when requested.
verify_account_email_recently_sent_error_flash :: The flash error to show if not sending verify account email because one has been sent recently.
verify_account_email_recently_sent_redirect :: Where to redirect if not sending verify account email because one has been sent recently.
verify_account_email_sent_notice_flash :: The flash notice to set after sending the verify account email.
verify_account_email_sent_redirect :: Where to redirect after sending the verify account email.
verify_account_email_subject :: The subject to use for the verify account email.
verify_account_error_flash :: The flash error to show if no matching key is submitted when verifying an account.
verify_account_id_column :: The id column in the +verify_account_table+, should be a foreign key referencing the accounts table.
verify_account_key_column :: The verify account key/token column in the +verify_account_table+.
verify_account_key_param :: The parameter name to use for the verify account key.
verify_account_notice_flash :: The flash notice to show after verifying the account.
verify_account_page_title :: The page title to use on the verify account form.
verify_account_redirect :: Where to redirect after verifying the account.
verify_account_resend_additional_form_tags :: HTML fragment containing additional form tags to use on the page requesting resending the verify account email.
verify_account_resend_button :: The text to use for the verify account resend button.
verify_account_resend_error_flash :: The flash error to show if unable to resend a verify account email.
verify_account_resend_explanatory_text :: The text to display above the button to resend the verify account email.
verify_account_resend_link_text :: The text to use for a link to the page to request the account verification email be resent.
verify_account_resend_route :: The route to the verify account resend action. Defaults to +verify-account-resend+.
verify_account_route :: The route to the verify account action. Defaults to +verify-account+.
verify_account_session_key :: The key in the session to hold the verify account key temporarily.
verify_account_set_password? :: Whether to ask for a password to be set on the verify account form. True by default. If set to false, will ask for password when creating the account instead of when verifying.
verify_account_skip_resend_email_within :: The number of seconds before sending another verify account email, if +verify_account_email_last_sent_column+ is set.
verify_account_table :: The name of the verify account keys table.
== Auth Methods
account_from_verify_account_key(key) :: Retrieve the account using the given verify account key, or return nil if no account matches.
after_verify_account :: Run arbitrary code after verifying the account.
after_verify_account_email_resend :: Run arbitrary code after resending a verify account email.
allow_resending_verify_account_email? :: Whether to allow sending the verify account email for the account, true by default only if the account has not been verified.
before_verify_account :: Run arbitrary code before verifying the account.
before_verify_account_email_resend :: Run arbitrary code before resending a verify account email.
before_verify_account_resend_route :: Run arbitrary code before handling a verify account resend route.
before_verify_account_route :: Run arbitrary code before handling a verify account route.
create_verify_account_email :: A Mail::Message for the verify account email.
create_verify_account_key :: Add the verify account key data to the database.
get_verify_account_email_last_sent :: Get the last time a verify account email is sent, or nil if there is no last sent time.
get_verify_account_key(id) :: Get the verify account key for the given account id from the database.
remove_verify_account_key :: Remove the verify account key for the current account, run after successful account verification.
resend_verify_account_view :: The HTML to use for page requesting resending the verify account email.
send_verify_account_email :: Send the verify account email.
set_verify_account_email_last_sent :: Set the last time a verify account email is sent.
verify_account :: Verify the account by changing the status from unverified to open.
verify_account_email_body :: The body to use for the verify account email.
verify_account_email_link :: The link to the verify account form in the verify account email.
verify_account_email_sent_response :: Return a response after successfully sending an verify account email. By default, redirects to +verify_account_email_sent_redirect+.
verify_account_key_insert_hash :: The hash to insert into the +verify_account_table+.
verify_account_key_value :: The value of the verify account key.
verify_account_response :: Return a response after successfully verifying an account. By default, redirects to +verify_account_redirect+.
verify_account_view :: The HTML to use for the verify account form.
jeremyevans-rodauth-b53f402/doc/verify_account_grace_period.rdoc 0000664 0000000 0000000 00000002147 15157255142 0025211 0 ustar 00root root 0000000 0000000 = Documentation for Verify Account Grace Period Feature
The verify account grace period feature allows users to login for
a given period of time (1 day by default) before their account is
verified. Depends on the verify account feature. This switches
the +verify_account_set_password?+ to false so that user can login
with a password during the grace period.
== Auth Value Methods
unverified_account_session_key :: The session key set if the logged in account has not been unverified.
unverified_change_login_error_flash :: The flash error to show when an unverified accounts accesses a change login route.
unverified_change_login_redirect :: Where to redirect when an unverified accounts accesses a change login route.
verification_requested_at_column :: The column in the +verify_account_table+ table that holds the verification requested timestamp.
verify_account_grace_period :: The amount of seconds after an account creation that a user will be able to login without verifying (86400 by default).
== Auth Methods
account_in_unverified_grace_period? :: Whether the current account is in an unverified grace period.
jeremyevans-rodauth-b53f402/doc/verify_login_change.rdoc 0000664 0000000 0000000 00000012156 15157255142 0023470 0 ustar 00root root 0000000 0000000 = Documentation for Verify Login Change Feature
The verify login change feature implements verification of login
changes. With this feature, login changes do not take effect
until after the user has verified the new login. Until the new
login has been verified, the old login continues to work.
Any time you use the verify account and change login features together,
you should probably use this, otherwise it is trivial for users to work
around account verification by creating an account with an email address
they control, and the changing the login to an email address they don't
control. Depends on the change login and email base features.
== Auth Value Methods
no_matching_verify_login_change_key_error_flash :: The flash error message to show when an invalid verify login change key is used.
change_login_needs_verification_notice_flash :: The flash notice to show after changing a login when using this feature, if +change_login_notice_flash+ is not overridden.
verify_login_change_additional_form_tags :: HTML fragment containing additional form tags to use on the verify login change form.
verify_login_change_autologin? :: Whether to autologin the user after successful login change verification, false by default.
verify_login_change_button :: The text to use for the verify login change button.
verify_login_change_deadline_column :: The column name in the +verify_login_change_table+ storing the deadline after which the token will be ignored.
verify_login_change_deadline_interval :: The amount of time for which to allow users to verify login changes, 1 day by default.
verify_login_change_duplicate_account_error_flash :: The flash error message to show when attempting to verify a login change when the login is already taken.
verify_login_change_duplicate_account_redirect :: Where to redirect if not changing a login during verification because the new login is already taken.
verify_login_change_email_subject :: The subject to use for the verify login change email.
verify_login_change_error_flash :: The flash error to show if no matching key is submitted when verifying login change.
verify_login_change_id_column :: The id column in the +verify_login_change_table+, should be a foreign key referencing the accounts table.
verify_login_change_key_column :: The verify login change key/token column in the +verify_login_change_table+.
verify_login_change_key_param :: The parameter name to use for the verify login change key.
verify_login_change_login_column :: The login column in the +verify_login_change_table+, containing the new login.
verify_login_change_notice_flash :: The flash notice to show after verifying the login change.
verify_login_change_page_title :: The page title to use on the verify login change form.
verify_login_change_redirect :: Where to redirect after verifying the login change.
verify_login_change_route :: The route to the verify login change action. Defaults to +verify-login-change+.
verify_login_change_session_key :: The key in the session to hold the verify login change key temporarily.
verify_login_change_table :: The name of the verify login change keys table.
== Auth Methods
account_from_verify_login_change_key(key) :: Retrieve the account using the given verify account key, or return nil if no account matches. Should also override verify_login_change_new_login if overriding this method.
after_verify_login_change :: Run arbitrary code after verifying the login change.
after_verify_login_change_email :: Run arbitrary code after sending verify login change email.
before_verify_login_change :: Run arbitrary code before verifying the login change.
before_verify_login_change_email :: Run arbitrary code before sending verify login change email.
before_verify_login_change_route :: Run arbitrary code before handling a verify login change route.
create_verify_login_change_email(login) :: A Mail::Message for the verify login change email.
create_verify_login_change_key(login) :: Add the verify login change key data to the database.
get_verify_login_change_login_and_key(id) :: Get the verify login change login and key for the given account id from the database.
remove_verify_login_change_key :: Remove the verify login change key for the current account, run after successful login change verification.
send_verify_login_change_email(login) :: Send the verify login change email.
verify_login_change :: Change the login for the given account to the new login.
verify_login_change_email_body :: The body to use for the verify login change email.
verify_login_change_email_link :: The link to the verify login change form in the verify login change email.
verify_login_change_key_insert_hash(login) :: The hash to insert into the +verify_login_change_table+.
verify_login_change_key_value :: The value of the verify login change key.
verify_login_change_new_login :: The new login to use when the login change is verified.
verify_login_change_old_login :: The old login to display in the verify login change email.
verify_login_change_response :: Return a response after successfully verifying a login change. By default, redirects to +verify_login_change_redirect+.
verify_login_change_view :: The HTML to use for the verify login change form.
jeremyevans-rodauth-b53f402/doc/webauthn.rdoc 0000664 0000000 0000000 00000026033 15157255142 0021303 0 ustar 00root root 0000000 0000000 = Documentation for WebAuthn Feature
The webauthn feature implements multifactor authentication via WebAuthn.
It supports registering WebAuthn authenticators, using them for
multifactor authentication, and removing WebAuthn authenticators.
This feature supports multiple WebAuthn authenticators per user,
and users are encouraged to have multiple WebAuthn authenticators
so that they have a backup if one is not available.
WebAuthn authentication requires javascript to work in
browsers, for the browser to communicate with the authenticator.
This feature offers routes that return the appropriate javascript.
However, the javascript works by setting a hidden form field and
using normal form submission. This allows testing the feature
without using javascript. See Rodauth's tests for how testing
without javascript works.
The webauthn feature requires the webauthn gem.
== Auth Value Methods
authenticated_webauthn_id_session_key :: The session key used for storing which WebAuthn ID was used during authentication.
webauthn_attestation :: The value of the WebAuthn attestation option when registering a new WebAuthn authenticator.
webauthn_auth_additional_form_tags :: HTML fragment containing additional form tags when authenticating via WebAuthn.
webauthn_auth_button :: Text to use for button on the form to authenticate via WebAuthn.
webauthn_auth_challenge_hmac_param :: The parameter name for the HMAC of the WebAuthn challenge during authentication.
webauthn_auth_challenge_param :: The parameter name for the WebAuthn challenge during authentication.
webauthn_auth_error_flash :: The flash error to show if unable to authenticate via WebAuthn.
webauthn_auth_js :: The javascript code to execute on the page to authenticate via WebAuthn.
webauthn_auth_js_route :: The route to the webauthn auth javascript file.
webauthn_auth_link_text :: The text to use for the link from the multifactor auth page.
webauthn_auth_page_title :: The page title to use on the page for authenticating via WebAuthn.
webauthn_auth_param :: The parameter name for the WebAuthn authentication data.
webauthn_auth_route :: The route to the webauthn auth action.
webauthn_auth_timeout :: The number of milliseconds to wait when authenticating using a WebAuthn authenticator.
webauthn_authenticator_selection :: The value of the WebAuthn authenticatorSelection option when registering a new WebAuthn authenticator.
webauthn_duplicate_webauthn_id_message :: The error message to when there is an attempt to insert a duplicate WebAuthn authenticator.
webauthn_extensions :: The value of the WebAuthn extensions option when registering a new WebAuthn authenticator or authenticating via WebAuthn.
webauthn_invalid_auth_param_message :: The error message to show when invalid or missing WebAuthn authentication data is provided.
webauthn_invalid_remove_param_message :: The error message to show when invalid WebAuthn ID is provided when removing a WebAuthn authenticator.
webauthn_invalid_setup_param_message :: The error message to show when invalid or missing WebAuthn registration data is provided.
webauthn_invalid_sign_count_message :: The error message to when there is an attempt to authenticate with WebAuthn authenticator with an invalid sign count.
webauthn_js_host :: The protocol and domain if using a separate host for the WebAuthn setup and auth javascript files.
webauthn_keys_account_id_column :: The column in the +webauthn_keys_table+ containing the account id.
webauthn_keys_last_use_column :: The column in the +webauthn_keys_table+ containing the last time the WebAuthn credential was used.
webauthn_keys_public_key_column :: The column in the +webauthn_keys_table+ containing the public key for the WebAuthn credential.
webauthn_keys_sign_count_column :: The column in the +webauthn_keys_table+ containing the sign count for the WebAuthn credential.
webauthn_keys_table :: The table name containing the WebAuthn public keys.
webauthn_keys_webauthn_id_column :: The column in the +webauthn_keys_table+ containing the WebAuthn ID for the WebAuthn credential.
webauthn_not_setup_error_flash :: The flash error to show if going to the WebAuthn authentication page without having registered a WebAuthn authenticator.
webauthn_not_setup_error_status :: The status code to use if going to the WebAuthn authentication page without having registered a WebAuthn authenticator.
webauthn_origin :: The origin to use when verifying a WebAuthn authenticator.
webauthn_remove_additional_form_tags :: HTML fragment containing additional form tags when removing an existing WebAuthn authenticator.
webauthn_remove_button :: Text to use for button on the form to remove an existing WebAuthn authenticator.
webauthn_remove_error_flash :: The flash error to show if unable to remove an existing WebAuthn authenticator.
webauthn_remove_link_text :: The text to use for the remove link from the multifactor manage page.
webauthn_remove_notice_flash :: The flash notice to show after removing an existing WebAuthn authenticator.
webauthn_remove_page_title :: The page title to use on the page for removing an existing WebAuthn authenticator.
webauthn_remove_param :: The parameter name for the WebAuthn ID to remove.
webauthn_remove_redirect :: Where to redirect after successfully removing an existing WebAuthn authenticator.
webauthn_remove_route :: The route to the webauthn remove action.
webauthn_rp_id :: The relying party ID to use when registering a WebAuthn authenticator or authenticating via WebAuthn.
webauthn_rp_name :: The relying party name to use when registering a WebAuthn authenticator.
webauthn_setup_additional_form_tags :: HTML fragment containing additional form tags when registering a new WebAuthn authenticator.
webauthn_setup_button :: Text to use for button on the form to register a new WebAuthn authenticator.
webauthn_setup_challenge_hmac_param :: The parameter name for the HMAC of the WebAuthn challenge during registration.
webauthn_setup_challenge_param :: The parameter name for the WebAuthn challenge during registration.
webauthn_setup_error_flash :: The flash error to show if unable to register a new WebAuthn authenticator.
webauthn_setup_js :: The javascript code to execute on the page to register a new WebAuthn credential.
webauthn_setup_js_route :: The route to the webauthn setup javascript file.
webauthn_setup_link_text :: The text to use for the setup link from the multifactor manage page.
webauthn_setup_notice_flash :: The flash notice to show after registering a new WebAuthn authenticator.
webauthn_setup_page_title :: The page title to use on the page for registering a new WebAuthn authenticator.
webauthn_setup_param :: The parameter name for the WebAuthn registration data.
webauthn_setup_redirect :: Where to redirect after successfully registering a new WebAuthn authenticator.
webauthn_setup_timeout :: The number of milliseconds to wait when registering a new WebAuthn authenticator.
webauthn_setup_route :: The route to the webauthn setup action.
webauthn_user_ids_account_id_column :: The column in the +webauthn_user_ids_table+ containing the account id.
webauthn_user_ids_table :: The table name containing the WebAuthn user IDs.
webauthn_user_ids_webauthn_id_column :: The column in the +webauthn_user_ids_table+ containing the accounts WebAuthn user ID.
webauthn_user_verification :: The value of the WebAuthn userVerification option when registering a new WebAuthn authenticator.
== Auth Methods
account_webauthn_ids :: An array of WebAuthn IDs for registered WebAuthn credentials for the current account.
account_webauthn_usage :: A hash mapping WebAuthn IDs to the time of their last use for registered WebAuthn credentials for the current account.
account_webauthn_user_id :: The WebAuthn User ID for the current account.
add_webauthn_credential(webauthn_credential) :: Register the given WebAuthn credential to current account.
after_webauthn_auth_failure :: Any actions to take after a WebAuthn authentication failure.
after_webauthn_remove :: Any actions to take after removing an existing WebAuthn authenticator.
after_webauthn_setup :: Any actions to take after registering a new WebAuthn authenticator.
authenticated_webauthn_id :: The WebAuthn ID for the credential used to authenticate via WebAuthn for the current session.
before_webauthn_auth :: Any actions to take before authenticating via WebAuthn.
before_webauthn_auth_js_route :: Run arbitrary code before handling a webauthn auth javascript route.
before_webauthn_auth_route :: Run arbitrary code before handling a webauthn auth route.
before_webauthn_remove :: Any actions to take before removing an existing WebAuthn authenticator.
before_webauthn_remove_route :: Run arbitrary code before handling a webauthn remove route.
before_webauthn_setup :: Any actions to take before registering a new WebAuthn authenticator.
before_webauthn_setup_js_route :: Run arbitrary code before handling a webauthn setup javascript route.
before_webauthn_setup_route :: Run arbitrary code before handling a webauthn setup route.
handle_webauthn_sign_count_verification_error :: What actions to take if there is an invalid sign count when authenticating. The default results in an error, but overriding without calling super will result in successful WebAuthn authentication.
new_webauthn_credential :: WebAuthn credential options to provide to the client during WebAuthn registration.
remove_all_webauthn_keys_and_user_ids :: Remove all WebAuthn credentials and the WebAuthn user ID from the current account.
remove_webauthn_key(webauthn_id) :: Remove the WebAuthn credential with the given WebAuthn ID from the current account.
valid_new_webauthn_credential?(webauthn_credential) :: Check wheck the WebAuthn credential provided by the client during registration is valid.
valid_webauthn_credential_auth?(webauthn_credential) :: Check wheck the WebAuthn credential provided by the client during authentication is valid.
webauthn_auth_js_path :: The path to the WebAuthn authentication javascript.
webauthn_auth_view :: The HTML to use for the page for authenticating via WebAuthn.
webauthn_credential_options_for_get :: WebAuthn credential options to provide to the client during WebAuthn authentication.
webauthn_key_insert_hash(webauthn_credential) :: The hash to insert into the +webauthn_keys_table+.
webauthn_remove_authenticated_session :: Remove the authenticated WebAuthn ID, used when removing the WebAuthn credential with the ID after authenticating with it.
webauthn_remove_response :: Return a response after successfully removing a WebAuthn authenticator. By default, redirects to +webauthn_remove_redirect+.
webauthn_remove_view :: The HTML to use for the page for removing an existing WebAuthn authenticator.
webauthn_setup_js_path :: The path to the WebAuthn registration javascript.
webauthn_setup_response :: Return a response after successfully setting up a WebAuthn authenticator. By default, redirects to +webauthn_setup_redirect+.
webauthn_setup_view :: The HTML to use for the page for registering a new WebAuthn authenticator.
webauthn_update_session(webauthn_id) :: Set the authenticated WebAuthn ID after authenticating via WebAuthn.
webauthn_user_name :: The user name to use when registering a new WebAuthn credential, the user's email by default.
jeremyevans-rodauth-b53f402/doc/webauthn_autofill.rdoc 0000664 0000000 0000000 00000001653 15157255142 0023203 0 ustar 00root root 0000000 0000000 = Documentation for WebAuthn Autofill Feature
The webauthn_autofill feature enables autofill UI (aka "conditional mediation")
for WebAuthn credentials, logging the user in on selection. It depends on the
webauthn_login feature.
This feature allows generating WebAuthn credential options and submitting a
WebAuthn login request without providing a login, which can be used
independently from the autofill UI.
== Auth Value Methods
webauthn_autofill? :: Whether to activate the autofill UI on the login page.
webauthn_autofill_js :: The javascript code to execute on the login page to enable autofill UI.
webauthn_autofill_js_route :: The route to the webauthn autofill javascript file.
webauthn_invalid_webauthn_id_message :: The error message to show when provided WebAuthn ID wasn't found in the database.
== Auth Methods
before_webauthn_autofill_js_route :: Run arbitrary code before handling a webauthn autofill javascript route.
jeremyevans-rodauth-b53f402/doc/webauthn_login.rdoc 0000664 0000000 0000000 00000002014 15157255142 0022464 0 ustar 00root root 0000000 0000000 = Documentation for WebAuthn Login Feature
The webauthn_login feature implements passwordless authentication via
WebAuthn. It depends on the login and webauthn features.
== Auth Value Methods
webauthn_login_user_verification_additional_factor? :: Whether passwordless login via WebAuthn should consider user verification as 2nd factor when using multifactor authentication, false by default. Setting this to true means that the app trusts the user verification done by the authenticator is strong enough to be considered an additional factor.
webauthn_login_error_flash :: The flash error to show if there is a failure during passwordless login via WebAuthn.
webauthn_login_failure_redirect :: Whether to redirect if there is a failure during passwordless login via WebAuthn.
webauthn_login_route :: The route to the webauthn login action.
== Auth Methods
before_webauthn_login :: Any actions to take before passwordless login via WebAuthn.
before_webauthn_login_route :: Run arbitrary code before handling a webauthn login route.
jeremyevans-rodauth-b53f402/doc/webauthn_modify_email.rdoc 0000664 0000000 0000000 00000002733 15157255142 0024022 0 ustar 00root root 0000000 0000000 = Documentation for WebAuthn Modify Email Feature
The webauthn_modify_email feature emails users when a WebAuthn authenticator is added to or removed from their account.
The webauthn_modify_email feature depends on the webauthn and email_base features.
== Auth Value Methods
webauthn_authenticator_added_email_body :: Body to use for the email notifying user that a WebAuthn authenticator has been added to their account.
webauthn_authenticator_added_email_subject :: Subject to use for the email notifying user that a WebAuthn authenticator has been added to their account.
webauthn_authenticator_removed_email_body :: Body to use for the email notifying user that a WebAuthn authenticator has been removed from their account.
webauthn_authenticator_removed_email_subject :: Subject to use for the email notifying user that a WebAuthn authenticator has been removed from their account.
== Auth Methods
create_webauthn_authenticator_added_email :: A Mail::Message for the email notifying user that a WebAuthn authenticator has been added to their account.
create_webauthn_authenticator_removed_email :: A Mail::Message for the email notifying user that a WebAuthn authenticator has been removed from their account.
send_webauthn_authenticator_added_email :: Send the email notifying user that a WebAuthn authenticator has been added to their account.
send_webauthn_authenticator_removed_email :: Send the email notifying user that a WebAuthn authenticator has been removed from their account.
jeremyevans-rodauth-b53f402/doc/webauthn_verify_account.rdoc 0000664 0000000 0000000 00000000741 15157255142 0024401 0 ustar 00root root 0000000 0000000 = Documentation for WebAuthn Verify Account Feature
The webauthn_verify_account feature implements setting up an WebAuthn authenticator
during the account verification process, and making such setup
a requirement for account verification. By default, it disables
asking for a password during account creation and verification,
allowing for completely passwordless designs, where the only
authentication option is WebAuthn. It depends on the verify_account
and webauthn features.
jeremyevans-rodauth-b53f402/javascript/ 0000775 0000000 0000000 00000000000 15157255142 0020212 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/javascript/webauthn_auth.js 0000664 0000000 0000000 00000003541 15157255142 0023411 0 ustar 00root root 0000000 0000000 (function() {
var pack = function(v) { return btoa(String.fromCharCode.apply(null, new Uint8Array(v))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); };
var unpack = function(v) { return Uint8Array.from(atob(v.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); };
var element = document.getElementById('webauthn-auth-form');
var f = function(e) {
//console.log(e);
e.preventDefault();
if (navigator.credentials) {
var opts = JSON.parse(element.getAttribute("data-credential-options"));
opts.challenge = unpack(opts.challenge);
opts.allowCredentials.forEach(function(cred) { cred.id = unpack(cred.id); });
//console.log(opts);
navigator.credentials.get({publicKey: opts}).
then(function(cred){
//console.log(cred);
//window.cred = cred
var rawId = pack(cred.rawId);
var authValue = {
type: cred.type,
id: rawId,
rawId: rawId,
response: {
authenticatorData: pack(cred.response.authenticatorData),
clientDataJSON: pack(cred.response.clientDataJSON),
signature: pack(cred.response.signature)
}
};
if (cred.response.userHandle) {
authValue.response.userHandle = pack(cred.response.userHandle);
}
document.getElementById('webauthn-auth').value = JSON.stringify(authValue);
element.removeEventListener("submit", f);
element.submit();
}).
catch(function(e){document.getElementById('webauthn-auth-button').innerHTML = "Error authenticating using WebAuthn: " + e});
} else {
document.getElementById('webauthn-auth-button').innerHTML = "WebAuthn not supported by browser, or browser has disabled it on this page";
}
};
element.addEventListener("submit", f);
})();
jeremyevans-rodauth-b53f402/javascript/webauthn_autofill.js 0000664 0000000 0000000 00000002670 15157255142 0024271 0 ustar 00root root 0000000 0000000 (function() {
var pack = function(v) { return btoa(String.fromCharCode.apply(null, new Uint8Array(v))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); };
var unpack = function(v) { return Uint8Array.from(atob(v.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); };
var element = document.getElementById('webauthn-login-form');
if (!window.PublicKeyCredential || !PublicKeyCredential.isConditionalMediationAvailable) return;
PublicKeyCredential.isConditionalMediationAvailable().then(function(available) {
if (!available) return;
var opts = JSON.parse(element.getAttribute("data-credential-options"));
opts.challenge = unpack(opts.challenge);
opts.allowCredentials.forEach(function(cred) { cred.id = unpack(cred.id); });
navigator.credentials.get({mediation: "conditional", publicKey: opts}).then(function(cred) {
var rawId = pack(cred.rawId);
var authValue = {
type: cred.type,
id: rawId,
rawId: rawId,
response: {
authenticatorData: pack(cred.response.authenticatorData),
clientDataJSON: pack(cred.response.clientDataJSON),
signature: pack(cred.response.signature)
}
};
if (cred.response.userHandle) {
authValue.response.userHandle = pack(cred.response.userHandle);
}
document.getElementById('webauthn-auth').value = JSON.stringify(authValue);
element.submit();
});
});
})();
jeremyevans-rodauth-b53f402/javascript/webauthn_setup.js 0000664 0000000 0000000 00000003272 15157255142 0023611 0 ustar 00root root 0000000 0000000 (function() {
var pack = function(v) { return btoa(String.fromCharCode.apply(null, new Uint8Array(v))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); };
var unpack = function(v) { return Uint8Array.from(atob(v.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); };
var element = document.getElementById('webauthn-setup-form');
var f = function(e) {
//console.log(e);
e.preventDefault();
if (navigator.credentials) {
var opts = JSON.parse(element.getAttribute("data-credential-options"));
opts.challenge = unpack(opts.challenge);
opts.user.id = unpack(opts.user.id);
opts.excludeCredentials.forEach(function(cred) { cred.id = unpack(cred.id); });
//console.log(opts);
navigator.credentials.create({publicKey: opts}).
then(function(cred){
//console.log(cred);
//window.cred = cred
var rawId = pack(cred.rawId);
document.getElementById('webauthn-setup').value = JSON.stringify({
type: cred.type,
id: rawId,
rawId: rawId,
response: {
attestationObject: pack(cred.response.attestationObject),
clientDataJSON: pack(cred.response.clientDataJSON)
}
});
element.removeEventListener("submit", f);
element.submit();
}).
catch(function(e){document.getElementById('webauthn-setup-button').innerHTML = "Error creating public key in authenticator: " + e});
} else {
document.getElementById('webauthn-setup-button').innerHTML = "WebAuthn not supported by browser, or browser has disabled it on this page";
}
};
element.addEventListener("submit", f);
})();
jeremyevans-rodauth-b53f402/lib/ 0000775 0000000 0000000 00000000000 15157255142 0016612 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/lib/roda/ 0000775 0000000 0000000 00000000000 15157255142 0017537 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/lib/roda/plugins/ 0000775 0000000 0000000 00000000000 15157255142 0021220 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/lib/roda/plugins/rodauth.rb 0000664 0000000 0000000 00000000166 15157255142 0023216 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require_relative '../../rodauth'
Roda::RodaPlugins.register_plugin(:rodauth, Rodauth)
jeremyevans-rodauth-b53f402/lib/rodauth.rb 0000664 0000000 0000000 00000027641 15157255142 0020617 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'securerandom'
module Rodauth
class ConfigurationError < StandardError; end
def self.lib(opts={}, &block)
require 'roda'
c = Class.new(Roda)
c.plugin(:rodauth, opts) do
enable :internal_request
instance_exec(&block)
end
c.freeze
c.rodauth
end
def self.load_dependencies(app, opts={}, &_)
json_opt = opts.fetch(:json, app.opts[:rodauth_json])
if json_opt
app.plugin :json
app.plugin :json_parser
end
unless json_opt == :only
unless opts[:render] == false
require 'tilt/string'
app.plugin :render
end
case opts.fetch(:csrf, app.opts[:rodauth_csrf])
when false
# nothing
when :rack_csrf
# :nocov:
app.plugin :csrf
# :nocov:
else
app.plugin :route_csrf
end
app.plugin :flash unless opts[:flash] == false
app.plugin :h
end
end
def self.configure(app, opts={}, &block)
json_opt = app.opts[:rodauth_json] = opts.fetch(:json, app.opts[:rodauth_json])
csrf = app.opts[:rodauth_csrf] = opts.fetch(:csrf, app.opts[:rodauth_csrf])
app.opts[:rodauth_route_csrf] = case csrf
when false, :rack_csrf
false
else
json_opt != :only
end
auth_class = (app.opts[:rodauths] ||= {})[opts[:name]] ||= opts[:auth_class] || Class.new(Auth)
if !auth_class.roda_class
auth_class.roda_class = app
elsif auth_class.roda_class != app
auth_class = app.opts[:rodauths][opts[:name]] = Class.new(auth_class)
auth_class.roda_class = app
end
auth_class.class_eval{@configuration_name = opts[:name] unless defined?(@configuration_name)}
auth_class.configure(&block) if block
auth_class.allocate.post_configure if auth_class.method_defined?(:post_configure)
end
FEATURES = {}
class FeatureConfiguration < Module
def def_configuration_methods(feature)
private_methods = feature.private_instance_methods.map(&:to_sym)
priv = proc{|m| private_methods.include?(m)}
feature.auth_methods.each{|m| def_auth_method(m, priv[m])}
feature.auth_value_methods.each{|m| def_auth_value_method(m, priv[m])}
feature.auth_private_methods.each{|m| def_auth_private_method(m)}
end
private
def def_auth_method(meth, priv)
define_method(meth) do |&block|
@auth.send(:define_method, meth, &block)
@auth.send(:private, meth) if priv
@auth.send(:alias_method, meth, meth)
end
end
def def_auth_private_method(meth)
umeth = :"_#{meth}"
define_method(meth) do |&block|
@auth.send(:define_method, umeth, &block)
@auth.send(:private, umeth)
@auth.send(:alias_method, umeth, umeth)
end
end
def def_auth_value_method(meth, priv)
define_method(meth) do |v=nil, &block|
block ||= proc{v}
@auth.send(:define_method, meth, &block)
@auth.send(:private, meth) if priv
@auth.send(:alias_method, meth, meth)
end
end
end
class Feature < Module
[:auth, :auth_value, :auth_private].each do |meth|
name = :"#{meth}_methods"
define_method(name) do |*v|
iv = :"@#{name}"
existing = instance_variable_get(iv) || []
if v.empty?
existing
else
instance_variable_set(iv, existing + v)
end
end
end
attr_accessor :feature_name
attr_accessor :dependencies
attr_accessor :routes
attr_accessor :configuration
attr_reader :internal_request_methods
def route(name=feature_name, default=name.to_s.tr('_', '-'), &block)
route_meth = :"#{name}_route"
auth_value_method route_meth, default
define_method(:"#{name}_path"){|opts={}| route_path(send(route_meth), opts) if send(route_meth)}
define_method(:"#{name}_url"){|opts={}| route_url(send(route_meth), opts) if send(route_meth)}
handle_meth = :"handle_#{name}"
internal_handle_meth = :"_#{handle_meth}"
before route_meth
define_method(internal_handle_meth, &block)
define_method(handle_meth) do
request.is send(route_meth) do
@current_route = name
check_csrf if check_csrf?
_around_rodauth do
before_rodauth
send(internal_handle_meth, request)
end
end
end
routes << handle_meth
end
def self.define(name, constant=nil, &block)
feature = new
feature.dependencies = []
feature.routes = []
feature.feature_name = name
configuration = feature.configuration = FeatureConfiguration.new
feature.module_eval(&block)
configuration.def_configuration_methods(feature)
# :nocov:
if constant
# :nocov:
Rodauth.const_set(constant, feature)
Rodauth::FeatureConfiguration.const_set(constant, configuration)
end
FEATURES[name] = feature
end
def internal_request_method(name=feature_name)
(@internal_request_methods ||= []) << name
end
def configuration_module_eval(&block)
configuration.module_eval(&block)
end
if RUBY_VERSION >= '2.5'
DEPRECATED_ARGS = [{:uplevel=>1}]
else
# :nocov:
DEPRECATED_ARGS = []
# :nocov:
end
def def_deprecated_alias(new, old)
configuration_module_eval do
define_method(old) do |*a, &block|
warn("Deprecated #{old} method used during configuration, switch to using #{new}", *DEPRECATED_ARGS)
send(new, *a, &block)
end
end
define_method(old) do
warn("Deprecated #{old} method called at runtime, switch to using #{new}", *DEPRECATED_ARGS)
send(new)
end
end
DEFAULT_REDIRECT_BLOCK = proc{default_redirect}
def redirect(name=feature_name, &block)
meth = :"#{name}_redirect"
block ||= DEFAULT_REDIRECT_BLOCK
define_method(meth, &block)
auth_value_methods meth
end
def view(page, title, name=feature_name)
meth = :"#{name}_view"
title_meth = :"#{name}_page_title"
translatable_method(title_meth, title)
define_method(meth) do
view(page, send(title_meth))
end
auth_methods meth
end
def response(name=feature_name)
meth = :"#{name}_response"
overridable_meth = :"_#{meth}"
notice_flash_meth = :"#{name}_notice_flash"
redirect_meth = :"#{name}_redirect"
define_method(overridable_meth) do
set_notice_flash send(notice_flash_meth)
redirect send(redirect_meth)
end
define_method(meth) do
require_response(overridable_meth)
end
private overridable_meth, meth
auth_private_methods meth
end
def loaded_templates(v)
define_method(:loaded_templates) do
super().concat(v)
end
private :loaded_templates
end
def depends(*deps)
dependencies.concat(deps)
end
%w'after before'.each do |hook|
define_method(hook) do |name=feature_name|
meth = "#{hook}_#{name}"
class_eval("def #{meth}; super if defined?(super); _#{meth}; hook_action(:#{hook}, :#{name}); nil end", __FILE__, __LINE__)
class_eval("def _#{meth}; nil end", __FILE__, __LINE__)
private meth, :"_#{meth}"
auth_private_methods(meth)
end
end
def email(type, subject, opts = {})
subject_method = :"#{type}_email_subject"
body_method = :"#{type}_email_body"
create_method = :"create_#{type}_email"
send_method = :"send_#{type}_email"
translatable_method subject_method, subject
auth_methods create_method, send_method
body_template = "#{type.to_s.tr('_', '-')}-email"
if opts[:translatable]
auth_value_methods body_method
define_method(body_method){translate(body_method, render(body_template))}
else
auth_methods body_method
define_method(body_method){render(body_template)}
end
define_method(create_method) do
create_email(send(subject_method), send(body_method))
end
define_method(send_method) do
send_email(send(create_method))
end
end
def additional_form_tags(name=feature_name)
auth_value_method(:"#{name}_additional_form_tags", nil)
end
def session_key(meth, value)
define_method(meth){convert_session_key(value)}
auth_value_methods(meth)
end
def flash_key(meth, value)
define_method(meth){normalize_session_or_flash_key(value)}
auth_value_methods(meth)
end
def auth_value_method(meth, value)
define_method(meth){value}
auth_value_methods(meth)
end
def translatable_method(meth, value)
define_method(meth){translate(meth, value)}
auth_value_methods(meth)
end
def auth_cached_method(meth, iv=:"@#{meth}")
umeth = :"_#{meth}"
define_method(meth) do
if instance_variable_defined?(iv)
instance_variable_get(iv)
else
instance_variable_set(iv, send(umeth))
end
end
alias_method(meth, meth)
auth_private_methods(meth)
end
[:notice_flash, :error_flash, :button].each do |meth|
define_method(meth) do |v, name=feature_name|
translatable_method(:"#{name}_#{meth}", v)
end
end
end
class Configuration
attr_reader :auth
def initialize(auth, &block)
@auth = auth
# :nocov:
# Only for backwards compatibility
# RODAUTH3: Remove
apply(&block) if block
# :nocov:
end
def apply(&block)
load_feature(:base)
instance_exec(&block)
end
def enable(*features)
features.each do |feature|
next if @auth.features.include?(feature)
load_feature(feature)
@auth.features << feature
end
end
private
def load_feature(feature_name)
require "rodauth/features/#{feature_name}" unless FEATURES[feature_name]
feature = FEATURES[feature_name]
enable(*feature.dependencies)
extend feature.configuration
@auth.routes.concat(feature.routes)
@auth.send(:include, feature)
end
end
class Auth
@features = []
@routes = []
@route_hash = {}
@configuration = Configuration.new(self)
class << self
attr_accessor :roda_class
attr_reader :features
attr_reader :routes
attr_accessor :route_hash
attr_reader :configuration_name
attr_reader :configuration
end
def self.inherited(subclass)
super
superclass = self
subclass.instance_exec do
@roda_class = superclass.roda_class
@features = superclass.features.clone
@routes = superclass.routes.clone
@route_hash = superclass.route_hash.clone
@configuration = superclass.configuration.clone
@configuration.instance_variable_set(:@auth, self)
end
end
def self.configure(&block)
@configuration.apply(&block)
end
def self.freeze
@features.freeze
@routes.freeze
@route_hash.freeze
super
end
end
module InstanceMethods
def default_rodauth_name
nil
end
def rodauth(name=default_rodauth_name)
if name
(@_rodauths ||= {})[name] ||= self.class.rodauth(name).new(self)
else
@_rodauth ||= self.class.rodauth.new(self)
end
end
end
module ClassMethods
def rodauth(name=nil)
opts[:rodauths][name]
end
def precompile_rodauth_templates
instance = allocate
rodauth = instance.rodauth
view_opts = rodauth.send(:loaded_templates).map do |page|
rodauth.send(:_view_opts, page)
end
view_opts << rodauth.send(:button_opts, '', {})
view_opts.each do |opts|
instance.send(:retrieve_template, opts).send(:compiled_method, opts[:locals].keys.sort_by(&:to_s))
end
nil
end
def freeze
opts[:rodauths].each_value(&:freeze)
opts[:rodauths].freeze
super
end
end
module RequestMethods
def rodauth(name=scope.default_rodauth_name)
scope.rodauth(name).route!
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/ 0000775 0000000 0000000 00000000000 15157255142 0020260 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/lib/rodauth/features/ 0000775 0000000 0000000 00000000000 15157255142 0022076 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/lib/rodauth/features/account_expiration.rb 0000664 0000000 0000000 00000007460 15157255142 0026330 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:account_expiration, :AccountExpiration) do
error_flash "You cannot log into this account as it has expired"
redirect
after
auth_value_method :account_activity_expired_column, :expired_at
auth_value_method :account_activity_id_column, :id
auth_value_method :account_activity_last_activity_column, :last_activity_at
auth_value_method :account_activity_last_login_column, :last_login_at
auth_value_method :account_activity_table, :account_activity_times
auth_value_method :expire_account_after, 180*86400
auth_value_method :expire_account_on_last_activity?, false
auth_methods(
:account_expired?,
:account_expired_at,
:last_account_activity_at,
:last_account_login_at,
:set_expired,
:update_last_activity,
:update_last_login
)
def last_account_activity_at
get_activity_timestamp(session_value, account_activity_last_activity_column)
end
def last_account_login_at
get_activity_timestamp(session_value, account_activity_last_login_column)
end
def account_expired_at
get_activity_timestamp(account_id, account_activity_expired_column)
end
def update_last_login
update_activity(account_id, account_activity_last_login_column, account_activity_last_activity_column)
end
def update_last_activity
if session_value
update_activity(session_value, account_activity_last_activity_column)
end
end
def set_expired
update_activity(account_id, account_activity_expired_column)
after_account_expiration
end
def account_expired?
columns = [account_activity_last_activity_column, account_activity_last_login_column, account_activity_expired_column]
last_activity, last_login, expired = account_activity_ds(account_id).get(columns)
return true if expired
timestamp = convert_timestamp(expire_account_on_last_activity? ? last_activity : last_login)
return false unless timestamp
timestamp < Time.now - expire_account_after
end
def check_account_expiration
if account_expired?
set_expired unless account_expired_at
set_redirect_error_flash account_expiration_error_flash
redirect account_expiration_redirect
end
update_last_login
end
def update_session
check_account_expiration
super
end
private
def before_reset_password
check_account_expiration
super if defined?(super)
end
def before_reset_password_request
check_account_expiration
super if defined?(super)
end
def before_unlock_account
check_account_expiration
super if defined?(super)
end
def before_unlock_account_request
check_account_expiration
super if defined?(super)
end
def after_close_account
super if defined?(super)
account_activity_ds(account_id).delete
end
def account_activity_ds(account_id)
db[account_activity_table].
where(account_activity_id_column=>account_id)
end
def get_activity_timestamp(account_id, column)
convert_timestamp(account_activity_ds(account_id).get(column))
end
def update_activity(account_id, *columns)
ds = account_activity_ds(account_id)
hash = {}
columns.each do |c|
hash[c] = Sequel::CURRENT_TIMESTAMP
end
if ds.update(hash) == 0
hash[account_activity_id_column] = account_id
hash[account_activity_last_activity_column] ||= Sequel::CURRENT_TIMESTAMP
hash[account_activity_last_login_column] ||= Sequel::CURRENT_TIMESTAMP
# It is safe to ignore uniqueness violations here, as a concurrent insert would also use current timestamps.
ignore_uniqueness_violation{ds.insert(hash)}
end
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/active_sessions.rb 0000664 0000000 0000000 00000014506 15157255142 0025632 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:active_sessions, :ActiveSessions) do
depends :logout
error_flash 'This session has been logged out'
redirect
session_key :session_id_session_key, :active_session_id
auth_value_method :active_sessions_account_id_column, :account_id
auth_value_method :active_sessions_created_at_column, :created_at
auth_value_method :active_sessions_last_use_column, :last_use
auth_value_method :active_sessions_session_id_column, :session_id
auth_value_method :active_sessions_table, :account_active_session_keys
translatable_method :global_logout_label, 'Logout all Logged In Sessions?'
auth_value_method :global_logout_param, 'global_logout'
auth_value_method :inactive_session_error_status, 401
auth_value_method :session_inactivity_deadline, 86400
auth_value_method(:session_lifetime_deadline, 86400*30)
auth_value_methods :update_current_session?
auth_methods(
:active_sessions_insert_hash,
:active_sessions_key,
:active_sessions_update_hash,
:add_active_session,
:currently_active_session?,
:handle_duplicate_active_session_id,
:no_longer_active_session,
:remove_active_session,
:remove_all_active_sessions,
:remove_all_active_sessions_except_for,
:remove_all_active_sessions_except_current,
:remove_current_session,
:remove_inactive_sessions,
)
def currently_active_session?
return false unless session_id = session[session_id_session_key]
remove_inactive_sessions
ds = active_sessions_ds.
where(active_sessions_session_id_column => compute_hmacs(session_id))
if update_current_session?
ds.update(active_sessions_update_hash) == 1
else
ds.count == 1
end
end
def check_active_session
if logged_in? && !currently_active_session?
no_longer_active_session
end
end
def no_longer_active_session
clear_session
set_redirect_error_status inactive_session_error_status
set_error_reason :inactive_session
set_redirect_error_flash active_sessions_error_flash
redirect active_sessions_redirect
end
def add_active_session
key = generate_active_sessions_key
set_session_value(session_id_session_key, key)
if e = raises_uniqueness_violation?{active_sessions_ds.insert(active_sessions_insert_hash)}
handle_duplicate_active_session_id(e)
end
nil
end
def handle_duplicate_active_session_id(_e)
# Do nothing by default as session is already tracked. This will result in
# the current session and the existing session with the same id
# being tracked together, so that a logout of one will logout
# the other, and updating the last use on one will update the other,
# but this should be acceptable. However, this can be overridden if different
# behavior is desired.
end
def remove_current_session
if session_id = session[session_id_session_key]
remove_active_session(compute_hmacs(session_id))
end
end
def remove_active_session(session_id)
active_sessions_ds.where(active_sessions_session_id_column=>session_id).delete
end
def remove_all_active_sessions
active_sessions_ds.delete
end
def remove_all_active_sessions_except_for(session_id)
active_sessions_ds.exclude(active_sessions_session_id_column=>compute_hmacs(session_id)).delete
end
def remove_all_active_sessions_except_current
if session_id = session[session_id_session_key]
remove_all_active_sessions_except_for(session_id)
else
remove_all_active_sessions
end
end
def remove_inactive_sessions
if cond = inactive_session_cond
active_sessions_ds.where(cond).delete
end
end
def logout_additional_form_tags
super.to_s + render('global-logout-field')
end
def update_session
remove_current_session
super
add_active_session
end
def clear_tokens(reason)
super
remove_all_active_sessions_except_current
end
private
def after_refresh_token
super if defined?(super)
if prev_key = session[session_id_session_key]
key = generate_active_sessions_key
set_session_value(session_id_session_key, key)
active_sessions_ds.
where(active_sessions_session_id_column => compute_hmacs(prev_key)).
update(active_sessions_session_id_column => compute_hmac(key))
end
end
def after_close_account
super if defined?(super)
remove_all_active_sessions
end
def before_logout
if param_or_nil(global_logout_param)
remove_remember_key(session_value) if respond_to?(:remove_remember_key)
remove_all_active_sessions
else
remove_current_session
end
super
end
attr_reader :active_sessions_key
def generate_active_sessions_key
@active_sessions_key = random_key
end
def active_sessions_insert_hash
{active_sessions_account_id_column => session_value, active_sessions_session_id_column => compute_hmac(active_sessions_key)}
end
def active_sessions_update_hash
h = {active_sessions_last_use_column => Sequel::CURRENT_TIMESTAMP}
if hmac_secret_rotation?
h[active_sessions_session_id_column] = compute_hmac(session[session_id_session_key])
end
h
end
def session_inactivity_deadline_condition
if deadline = session_inactivity_deadline
Sequel[active_sessions_last_use_column] < Sequel.date_sub(Sequel::CURRENT_TIMESTAMP, seconds: deadline)
end
end
def session_lifetime_deadline_condition
if deadline = session_lifetime_deadline
Sequel[active_sessions_created_at_column] < Sequel.date_sub(Sequel::CURRENT_TIMESTAMP, seconds: deadline)
end
end
def inactive_session_cond
cond = session_inactivity_deadline_condition
cond2 = session_lifetime_deadline_condition
return false unless cond || cond2
Sequel.|(*[cond, cond2].compact)
end
def update_current_session?
!!session_inactivity_deadline
end
def active_sessions_ds
db[active_sessions_table].
where(active_sessions_account_id_column=>session_value || account_id)
end
def use_date_arithmetic?
true
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/argon2.rb 0000664 0000000 0000000 00000007052 15157255142 0023617 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'argon2'
# :nocov:
if !defined?(Argon2::VERSION) || Argon2::VERSION < '2'
raise LoadError, "argon2 version 1.x not supported as it does not support argon2id hashes"
end
# :nocov:
module Rodauth
Feature.define(:argon2, :Argon2) do
depends :login_password_requirements_base
auth_value_method :argon2_old_secret, nil
auth_value_method :argon2_secret, nil
auth_value_method :use_argon2?, true
def password_hash(password)
return super unless use_argon2?
if secret = argon2_secret
argon2_params = Hash[password_hash_cost]
argon2_params[:secret] = secret
else
argon2_params = password_hash_cost
end
::Argon2::Password.new(argon2_params).create(password)
end
private
if Argon2::VERSION != '2.1.0'
def argon2_salt_option
:salt_do_not_supply
end
# :nocov:
else
def argon2_salt_option
:salt_for_testing_purposes_only
end
# :nocov:
end
def password_hash_cost
return super unless use_argon2?
argon2_hash_cost
end
def password_hash_match?(hash, password)
return super unless argon2_hash_algorithm?(hash)
argon2_password_hash_match?(hash, password)
end
def password_hash_using_salt(password, salt)
return super unless argon2_hash_algorithm?(salt)
argon2_password_hash_using_salt_and_secret(password, salt, argon2_secret)
end
def argon2_password_hash_using_salt_and_secret(password, salt, secret)
argon2_params = Hash[extract_password_hash_cost(salt)]
argon2_params[argon2_salt_option] = salt.split('$').last.unpack("m")[0]
argon2_params[:secret] = secret
::Argon2::Password.new(argon2_params).create(password)
end
if Argon2::VERSION >= '2.1'
def extract_password_hash_cost(hash)
return super unless argon2_hash_algorithm?(hash)
/\A\$argon2id\$v=\d+\$m=(\d+),t=(\d+),p=(\d+)/ =~ hash
{ t_cost: $2.to_i, m_cost: Math.log2($1.to_i).to_i, p_cost: $3.to_i }
end
if ENV['RACK_ENV'] == 'test'
def argon2_hash_cost
{ t_cost: 1, m_cost: 5, p_cost: 1 }
end
# :nocov:
else
def argon2_hash_cost
{ t_cost: 2, m_cost: 16, p_cost: 1 }
end
end
else
def extract_password_hash_cost(hash)
return super unless argon2_hash_algorithm?(hash )
/\A\$argon2id\$v=\d+\$m=(\d+),t=(\d+)/ =~ hash
{ t_cost: $2.to_i, m_cost: Math.log2($1.to_i).to_i }
end
if ENV['RACK_ENV'] == 'test'
def argon2_hash_cost
{ t_cost: 1, m_cost: 5 }
end
else
def argon2_hash_cost
{ t_cost: 2, m_cost: 16 }
end
end
end
# :nocov:
def argon2_hash_algorithm?(hash)
hash.start_with?('$argon2id$')
end
def argon2_password_hash_match?(hash, password)
ret = ::Argon2::Password.verify_password(password, hash, argon2_secret)
if ret == false && argon2_old_secret != argon2_secret && (ret = ::Argon2::Password.verify_password(password, hash, argon2_old_secret))
@update_password_hash = true
end
ret
end
def database_function_password_match?(name, hash_id, password, salt)
return true if super
if use_argon2? && argon2_hash_algorithm?(salt) && argon2_old_secret != argon2_secret && (ret = db.get(Sequel.function(function_name(name), hash_id, argon2_password_hash_using_salt_and_secret(password, salt, argon2_old_secret))))
@update_password_hash = true
end
!!ret
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/audit_logging.rb 0000664 0000000 0000000 00000005431 15157255142 0025242 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:audit_logging, :AuditLogging) do
auth_value_method :audit_logging_account_id_column, :account_id
auth_value_method :audit_logging_message_column, :message
auth_value_method :audit_logging_metadata_column, :metadata
auth_value_method :audit_logging_table, :account_authentication_audit_logs
auth_value_method :audit_log_metadata_default, nil
auth_methods(
:add_audit_log,
:audit_log_insert_hash,
:audit_log_message,
:audit_log_message_default,
:audit_log_metadata,
:serialize_audit_log_metadata,
)
configuration_module_eval do
[:audit_log_message_for, :audit_log_metadata_for].each do |method|
define_method(method) do |action, value=nil, &block|
block ||= proc{value}
meth = :"#{method}_#{action}"
@auth.send(:define_method, meth, &block)
@auth.send(:private, meth)
end
end
end
def hook_action(hook_type, action)
super
# In after_logout, session is already cleared, so use before_logout in that case
if (hook_type == :after || action == :logout) && (id = account ? account_id : session_value)
add_audit_log(id, action)
end
end
def add_audit_log(account_id, action)
if hash = audit_log_insert_hash(account_id, action)
audit_log_ds.insert(hash)
end
end
def audit_log_insert_hash(account_id, action)
if message = audit_log_message(action)
{
audit_logging_account_id_column => account_id,
audit_logging_message_column => message,
audit_logging_metadata_column => serialize_audit_log_metadata(audit_log_metadata(action))
}
end
end
def serialize_audit_log_metadata(metadata)
metadata.to_json unless metadata.nil?
end
def audit_log_message_default(action)
action.to_s
end
def audit_log_message(action)
meth = :"audit_log_message_for_#{action}"
if respond_to?(meth, true)
send(meth)
else
audit_log_message_default(action)
end
end
def audit_log_metadata(action)
meth = :"audit_log_metadata_for_#{action}"
if respond_to?(meth, true)
send(meth)
else
audit_log_metadata_default
end
end
private
def audit_log_ds
ds = db[audit_logging_table]
# :nocov:
if db.database_type == :postgres
# :nocov:
# For PostgreSQL, use RETURNING NULL. This allows the feature
# to be used with INSERT but not SELECT permissions on the
# table, useful for audit logging where the database user
# the application is running as should not need to read the
# logs.
ds = ds.returning(nil)
end
ds
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/base.rb 0000664 0000000 0000000 00000065544 15157255142 0023353 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'rack/request'
require 'rack/utils'
module Rodauth
Feature.define(:base, :Base) do
after 'login'
after 'login_failure'
before 'login'
before 'login_attempt'
before 'rodauth'
error_flash "Please login to continue", 'require_login'
auth_value_method :account_id_column, :id
auth_value_method :account_open_status_value, 2
auth_value_method :account_password_hash_column, nil
auth_value_method :account_select, nil
auth_value_method :account_status_column, :status_id
auth_value_method :account_unverified_status_value, 1
auth_value_method :accounts_table, :accounts
auth_value_method :cache_templates, true
auth_value_method :check_csrf_block, nil
auth_value_method :check_csrf_opts, {}.freeze
auth_value_method :default_redirect, '/'
auth_value_method :convert_token_id_to_integer?, nil
flash_key :flash_error_key, :error
flash_key :flash_notice_key, :notice
auth_value_method :hmac_old_secret, nil
auth_value_method :hmac_secret, nil
translatable_method :input_field_label_suffix, ''
auth_value_method :input_field_error_class, 'error is-invalid'
auth_value_method :input_field_error_message_class, 'error_message invalid-feedback'
auth_value_method :invalid_field_error_status, 422
auth_value_method :invalid_key_error_status, 401
auth_value_method :invalid_password_error_status, 401
translatable_method :invalid_password_message, "invalid password"
auth_value_method :login_column, :email
auth_value_method :login_required_error_status, 401
auth_value_method :lockout_error_status, 403
auth_value_method :max_param_bytesize, 1024
auth_value_method :password_hash_id_column, :id
auth_value_method :password_hash_column, :password_hash
auth_value_method :password_hash_table, :account_password_hashes
auth_value_method :no_matching_login_error_status, 401
translatable_method :no_matching_login_message, "no matching login"
auth_value_method :login_param, 'login'
translatable_method :login_label, 'Login'
translatable_method :password_label, 'Password'
auth_value_method :password_param, 'password'
session_key :session_key, :account_id
session_key :authenticated_by_session_key, :authenticated_by
session_key :autologin_type_session_key, :autologin_type
auth_value_method :prefix, ''
auth_value_method :session_key_prefix, nil
auth_value_method :require_bcrypt?, true
auth_value_method :mark_input_fields_as_required?, true
auth_value_method :mark_input_fields_with_autocomplete?, true
auth_value_method :mark_input_fields_with_inputmode?, true
auth_value_method :skip_status_checks?, true
translatable_method :strftime_format, '%F %T'
auth_value_method :template_opts, {}.freeze
auth_value_method :title_instance_variable, nil
auth_value_method :token_separator, "_"
auth_value_method :unmatched_field_error_status, 422
auth_value_method :unopen_account_error_status, 403
translatable_method :unverified_account_message, "unverified account, please verify account before logging in"
auth_value_method :default_field_attributes, ''
auth_value_method :use_template_fixed_locals?, true
redirect(:require_login){"#{prefix}/login"}
auth_value_methods(
:base_url,
:check_csrf?,
:db,
:domain,
:login_input_type,
:login_uses_email?,
:modifications_require_password?,
:set_deadline_values?,
:use_date_arithmetic?,
:use_database_authentication_functions?,
:use_request_specific_csrf_tokens?
)
auth_methods(
:account_id,
:account_session_value,
:already_logged_in,
:authenticated?,
:autocomplete_for_field?,
:check_csrf,
:clear_session,
:clear_tokens,
:csrf_tag,
:function_name,
:hook_action,
:inputmode_for_field?,
:logged_in?,
:login_required,
:normalize_login,
:null_byte_parameter_value,
:open_account?,
:over_max_bytesize_param_value,
:password_match?,
:random_key,
:redirect,
:session_value,
:set_error_flash,
:set_notice_flash,
:set_notice_now_flash,
:set_redirect_error_flash,
:set_error_reason,
:set_title,
:translate,
:update_session
)
auth_private_methods(
:account_from_id,
:account_from_login,
:account_from_session,
:convert_token_id,
:field_attributes,
:field_error_attributes,
:formatted_field_error,
:around_rodauth
)
internal_request_method :account_exists?
internal_request_method :account_id_for_login
internal_request_method :internal_request_eval
configuration_module_eval do
def auth_class_eval(&block)
auth.class_eval(&block)
end
end
attr_reader :scope
attr_reader :account
attr_reader :current_route
def initialize(scope)
@scope = scope
end
def features
self.class.features
end
def request
scope.request
end
def response
scope.response
end
def session
scope.session
end
def flash
scope.flash
end
def route!
if meth = self.class.route_hash[request.remaining_path]
send(meth)
end
nil
end
def set_field_error(field, error)
(@field_errors ||= {})[field] = error
end
def field_error(field)
return nil unless @field_errors
@field_errors[field]
end
def add_field_error_class(field)
if field_error(field)
" #{input_field_error_class}"
end
end
def input_field_string(param, id, opts={})
type = opts.fetch(:type, "text")
unless type == "password"
value = opts.fetch(:value){scope.h param(param)}
end
field_class = opts.fetch(:class, "form-control")
if autocomplete_for_field?(param) && opts[:autocomplete]
autocomplete = "autocomplete=\"#{opts[:autocomplete]}\""
end
if inputmode_for_field?(param) && opts[:inputmode]
inputmode = "inputmode=\"#{opts[:inputmode]}\""
end
if mark_input_fields_as_required? && opts[:required] != false
required = "required=\"required\""
end
" #{formatted_field_error(param) unless opts[:skip_error_message]}"
end
def autocomplete_for_field?(_param)
mark_input_fields_with_autocomplete?
end
def inputmode_for_field?(_param)
mark_input_fields_with_inputmode?
end
def field_attributes(field)
_field_attributes(field) || default_field_attributes
end
def field_error_attributes(field)
if field_error(field)
_field_error_attributes(field)
end
end
def formatted_field_error(field)
if error = field_error(field)
_formatted_field_error(field, error)
end
end
def hook_action(_hook_type, _action)
# nothing by default
end
def translate(_key, default)
# do not attempt to translate by default
default
end
# Return urlsafe base64 HMAC for data, assumes hmac_secret is set.
def compute_hmac(data)
_process_raw_hmac(compute_raw_hmac(data))
end
# Return urlsafe base64 HMAC for data using hmac_old_secret, assumes hmac_old_secret is set.
def compute_old_hmac(data)
_process_raw_hmac(compute_raw_hmac_with_secret(data, hmac_old_secret))
end
# Return array of hmacs. Array has two strings if hmac_old_secret
# is set, or one string otherwise.
def compute_hmacs(data)
hmacs = [compute_hmac(data)]
if hmac_old_secret
hmacs << compute_old_hmac(data)
end
hmacs
end
def account_id
account[account_id_column]
end
alias account_session_value account_id
def session_value
session[session_key]
end
alias logged_in? session_value
def account_from_login(login)
@account = _account_from_login(login)
end
def open_account?
skip_status_checks? || account[account_status_column] == account_open_status_value
end
def db
Sequel::DATABASES.first or raise "Sequel database connection is missing"
end
def login_field_autocomplete_value
login_uses_email? ? "email" : "on"
end
def password_field_autocomplete_value
@password_field_autocomplete_value || 'current-password'
end
alias account_password_hash_column account_password_hash_column
# If the account_password_hash_column is set, the password hash is verified in
# ruby, it will not use a database function to do so, it will check the password
# hash using bcrypt.
def account_password_hash_column
nil
end
def check_already_logged_in
already_logged_in if logged_in?
end
def already_logged_in
nil
end
def login_input_type
login_uses_email? ? 'email' : 'text'
end
def login_uses_email?
login_column == :email
end
def clear_session
if use_scope_clear_session?
scope.clear_session
else
session.clear
end
end
def clear_tokens(reason)
end
def login_required
set_redirect_error_status(login_required_error_status)
set_error_reason :login_required
set_redirect_error_flash require_login_error_flash
redirect require_login_redirect
end
def set_title(title)
if title_instance_variable
scope.instance_variable_set(title_instance_variable, title)
end
end
def set_error_flash(message)
flash.now[flash_error_key] = message
end
def set_redirect_error_flash(message)
flash[flash_error_key] = message
end
def set_notice_flash(message)
flash[flash_notice_key] = message
end
def set_notice_now_flash(message)
flash.now[flash_notice_key] = message
end
def require_login
login_required unless logged_in?
end
def authenticated?
logged_in?
end
def require_authentication
require_login
end
def require_account
require_authentication
require_account_session
end
def account_initial_status_value
account_open_status_value
end
def account!
account || (session_value && account_from_session)
end
def account_from_session
@account = _account_from_session
end
def account_from_id(id, status_id=nil)
@account = _account_from_id(id, status_id)
end
def check_csrf
scope.check_csrf!(check_csrf_opts, &check_csrf_block)
end
def csrf_tag(path=request.path)
return unless scope.respond_to?(:csrf_tag)
if use_request_specific_csrf_tokens?
scope.csrf_tag(path)
else
# :nocov:
scope.csrf_tag
# :nocov:
end
end
def button_opts(value, opts)
opts = Hash[template_opts].merge!(opts)
_merge_fixed_locals_opts(opts, button_fixed_locals)
opts[:locals] = {:value=>value, :opts=>opts}
opts[:cache] = cache_templates
opts[:cache_key] = :rodauth_button
_template_opts(opts, 'button')
end
def button(value, opts={})
scope.render(button_opts(value, opts))
end
def view(page, title)
set_title(title)
_view(:view, page)
end
def render(page)
_view(:render, page)
end
def only_json?
scope.class.opts[:rodauth_json] == :only
end
def post_configure
require 'bcrypt' if require_bcrypt?
db.extension :date_arithmetic if use_date_arithmetic?
if method(:convert_token_id_to_integer?).owner == Rodauth::Base && (db rescue false) && db.table_exists?(accounts_table) && db.schema(accounts_table).find{|col, v| break v[:type] == :integer if col == account_id_column}
self.class.send(:define_method, :convert_token_id_to_integer?){true}
end
route_hash= {}
self.class.routes.each do |meth|
route_meth = "#{meth.to_s.sub(/\Ahandle_/, '')}_route"
if route = send(route_meth)
route_hash["/#{route}"] = meth
end
end
self.class.route_hash = route_hash.freeze
end
def password_match?(password)
if hash = get_password_hash
if account_password_hash_column || !use_database_authentication_functions?
password_hash_match?(hash, password)
else
database_function_password_match?(:rodauth_valid_password_hash, account_id, password, hash)
end
end
end
def update_session
clear_session
set_session_value(session_key, account_session_value)
end
def authenticated_by
session[authenticated_by_session_key]
end
def login_session(auth_type)
update_session
set_session_value(authenticated_by_session_key, [auth_type])
end
def autologin_type
session[autologin_type_session_key]
end
def autologin_session(autologin_type)
login_session('autologin')
set_session_value(autologin_type_session_key, autologin_type)
end
# Return a string for the parameter name. This will be an empty
# string if the parameter doesn't exist.
def param(key)
param_or_nil(key).to_s
end
# Return a string for the parameter name, or nil if there is no
# parameter with that name.
def param_or_nil(key)
value = raw_param(key)
unless value.nil?
value = value.to_s
value = over_max_bytesize_param_value(key, value) if max_param_bytesize && value.bytesize > max_param_bytesize
value = null_byte_parameter_value(key, value) if value && value.include?("\0")
end
value
end
# Return nil by default for values over maximum bytesize.
def over_max_bytesize_param_value(key, value)
nil
end
# The normalized value of the login parameter
def login_param_value
normalize_login(param(login_param))
end
def normalize_login(login)
login
end
# Return nil by default for values with null bytes
def null_byte_parameter_value(key, value)
nil
end
def raw_param(key)
request.params[key]
end
def base_url
url = String.new("#{request.scheme}://#{domain}")
url << ":#{request.port}" if request.port != Rack::Request::DEFAULT_PORTS[request.scheme]
url
end
def domain
request.host
end
def modifications_require_password?
has_password?
end
def possible_authentication_methods
has_password? ? ['password'] : []
end
def has_password?
return @has_password if defined?(@has_password)
return false unless account || session_value
@has_password = !!get_password_hash
end
private
def _around_rodauth
yield
end
def _process_raw_hmac(hmac)
s = [hmac].pack('m')
s.chomp!("=\n")
s.tr!('+/', '-_')
s
end
if Rack.release >= '3'
def set_response_header(key, value)
response.headers[key] = value
end
def convert_response_header_key(key)
key
end
# :nocov:
else
def set_response_header(key, value)
response.headers[convert_response_header_key(key)] = value
end
# Attempt backwards compatibility on Rack < 3 by changing
# known cases from lower case to mixed case.
mixed_case_headers = {}
(<<-END).split.each { |k| mixed_case_headers[k.downcase.freeze] = k.freeze }
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Access-Control-Max-Age
Allow
Authorization
Content-Type
Content-Length
WWW-Authenticate
END
mixed_case_headers.freeze
define_method(:convert_response_header_key) do |key|
mixed_case_headers.fetch(key, key)
end
end
# :nocov:
if RUBY_VERSION >= '2.1'
def button_fixed_locals
'(value:, opts:)'
end
# :nocov:
else
# Work on Ruby 2.0 when using Tilt 2.6+, as Ruby 2.0 does
# not support required keyword arguments.
def button_fixed_locals
'(value: nil, opts: nil)'
end
end
# :nocov:
def database_function_password_match?(name, hash_id, password, salt)
db.get(Sequel.function(function_name(name), hash_id, password_hash_using_salt(password, salt)))
end
def password_hash_match?(hash, password)
BCrypt::Password.new(hash) == password
end
def convert_token_key(key)
if key && hmac_secret
compute_hmac(key)
else
key
end
end
def split_token(token)
token.split(token_separator, 2)
end
def convert_token_id(id)
if convert_token_id_to_integer?
convert_token_id_to_integer(id)
else
id
end
end
def convert_token_id_to_integer(id)
if id = (Integer(id, 10) rescue nil)
if id > 9223372036854775807 || id < -9223372036854775808
# Only allow 64-bit signed integer range to avoid problems on PostgreSQL
id = nil
end
end
id
end
def redirect(path)
request.redirect(path)
end
def return_response(body=nil)
response.write(body) if body
request.halt
end
def route_path(route, opts={})
path = "#{prefix}/#{route}"
path += "?#{Rack::Utils.build_nested_query(opts)}" unless opts.empty?
path
end
def route_url(route, opts={})
"#{base_url}#{route_path(route, opts)}"
end
def transaction(opts={}, &block)
db.transaction(opts, &block)
end
def random_key
SecureRandom.urlsafe_base64(32)
end
def convert_session_key(key)
key = :"#{session_key_prefix}#{key}" if session_key_prefix
normalize_session_or_flash_key(key)
end
def normalize_session_or_flash_key(key)
scope.opts[:sessions_convert_symbols] ? key.to_s : key
end
def timing_safe_eql?(provided, actual)
provided = provided.to_s
Rack::Utils.secure_compare(provided.ljust(actual.length), actual) && provided.length == actual.length
end
def require_account_session
unless account_from_session
clear_session
login_required
end
end
def catch_error(&block)
catch(:rodauth_error, &block)
end
# Don't set an error status when redirecting in an error case, as a redirect status is needed.
def set_redirect_error_status(status)
end
def set_response_error_status(status)
response.status = status
end
def set_response_error_reason_status(reason, status)
set_error_reason(reason)
set_response_error_status(status)
end
def throw_rodauth_error
throw :rodauth_error
end
def throw_error(field, error)
set_field_error(field, error)
throw_rodauth_error
end
def throw_error_status(status, field, error)
set_response_error_status(status)
throw_error(field, error)
end
def set_error_reason(reason)
end
def throw_error_reason(reason, status, field, message)
set_error_reason(reason)
throw_error_status(status, field, message)
end
def use_date_arithmetic?
set_deadline_values?
end
def set_deadline_values?
db.database_type == :mysql
end
def use_database_authentication_functions?
case db.database_type
when :postgres, :mysql, :mssql
true
else
# :nocov:
false
# :nocov:
end
end
def use_request_specific_csrf_tokens?
scope.opts[:rodauth_route_csrf] && scope.use_request_specific_csrf_tokens?
end
def check_csrf?
scope.opts[:rodauth_route_csrf]
end
def function_name(name)
if db.database_type == :mssql
# :nocov:
"dbo.#{name}"
# :nocov:
else
name
end
end
def password_hash_using_salt(password, salt)
BCrypt::Engine.hash_secret(password, salt)
end
# Get the password hash for the user. When using database authentication functions,
# note that only the salt is returned.
def get_password_hash
if account_password_hash_column
account[account_password_hash_column] if account!
elsif use_database_authentication_functions?
db.get(Sequel.function(function_name(:rodauth_get_salt), account ? account_id : session_value))
else
# :nocov:
password_hash_ds.get(password_hash_column)
# :nocov:
end
end
def _account_from_login(login)
ds = account_table_ds.where(login_column=>login)
ds = ds.select(*account_select) if account_select
ds = ds.where(account_status_column=>[account_unverified_status_value, account_open_status_value]) unless skip_status_checks?
ds.first
end
def _account_from_session
ds = account_ds(session_value)
ds = ds.where(account_session_status_filter) unless skip_status_checks?
ds.first
end
def _account_from_id(id, status_id=nil)
ds = account_ds(id)
ds = ds.where(account_status_column=>status_id) if status_id && !skip_status_checks?
ds.first
end
def hmac_secret_rotation?
hmac_secret && hmac_old_secret && hmac_secret != hmac_old_secret
end
def compute_raw_hmac(data)
raise ConfigurationError, "hmac_secret not set" unless hmac_secret
compute_raw_hmac_with_secret(data, hmac_secret)
end
def compute_raw_hmac_with_secret(data, secret)
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, data)
end
def _field_attributes(field)
nil
end
def _field_error_attributes(field)
" aria-invalid=\"true\" aria-describedby=\"#{field}_error_message\" "
end
def _formatted_field_error(field, error)
""
end
def account_session_status_filter
{account_status_column=>account_open_status_value}
end
def template_path(page)
File.join(File.dirname(__FILE__), '../../../templates', "#{page}.str")
end
def account_ds(id=account_id)
raise ArgumentError, "invalid account id passed to account_ds" unless id
ds = account_table_ds.where(account_id_column=>id)
ds = ds.select(*account_select) if account_select
ds
end
def account_table_ds
db[accounts_table]
end
def password_hash_ds
db[password_hash_table].where(password_hash_id_column=>account ? account_id : session_value)
end
# This is needed for jdbc/sqlite, which returns timestamp columns as strings
def convert_timestamp(timestamp)
timestamp = db.to_application_timestamp(timestamp) if timestamp.is_a?(String)
timestamp
end
def loaded_templates
[]
end
# This is used to avoid race conditions when using the pattern of inserting when
# an update affects no rows. In such cases, if a row is inserted between the
# update and the insert, the insert will fail with a uniqueness error, but
# retrying will work. It is possible for it to fail again, but only if the row
# is deleted before the update and readded before the insert, which is very
# unlikely to happen. In such cases, raising an exception is acceptable.
def retry_on_uniqueness_violation(&block)
if raises_uniqueness_violation?(&block)
yield
end
end
# In cases where retrying on uniqueness violations cannot work, this will detect
# whether a uniqueness violation is raised by the block and return the exception if so.
# This method should be used if you don't care about the exception itself.
def raises_uniqueness_violation?(&block)
transaction(:savepoint=>:only, &block)
false
rescue unique_constraint_violation_class => e
e
end
# Work around jdbc/sqlite issue where it only raises ConstraintViolation and not
# UniqueConstraintViolation.
def unique_constraint_violation_class
if db.adapter_scheme == :jdbc && db.database_type == :sqlite
# :nocov:
Sequel::ConstraintViolation
# :nocov:
else
Sequel::UniqueConstraintViolation
end
end
# If you would like to operate/reraise the exception, this alias makes more sense.
alias raised_uniqueness_violation raises_uniqueness_violation?
# If you just want to ignore uniqueness violations, this alias makes more sense.
alias ignore_uniqueness_violation raises_uniqueness_violation?
# This is needed on MySQL, which doesn't support non constant defaults other than
# CURRENT_TIMESTAMP.
def set_deadline_value(hash, column, interval)
if set_deadline_values?
# :nocov:
hash[column] = Sequel.date_add(Sequel::CURRENT_TIMESTAMP, interval)
# :nocov:
end
end
def _filter_links(links)
links.select!{|_, link| link}
links.sort!
links
end
def internal_request?
false
end
def use_scope_clear_session?
scope.respond_to?(:clear_session)
end
def require_response(meth)
send(meth)
raise ConfigurationError, "#{meth.to_s.sub(/\A_/, '')} overridden without returning a response (should use redirect or request.halt)."
end
def set_session_value(key, value)
session[key] = value
end
def remove_session_value(key)
session.delete(key)
end
def update_hash_ds(hash, ds, values)
num = ds.update(values)
if num == 1
values.each do |k, v|
hash[k] = Sequel::CURRENT_TIMESTAMP == v ? Time.now : v
end
end
num
end
def update_account(values, ds=account_ds)
update_hash_ds(account, ds, values)
end
def _view_opts(page)
opts = template_opts.dup
_merge_fixed_locals_opts(opts, '(rodauth: self.rodauth)')
opts[:locals] = opts[:locals] ? opts[:locals].dup : {}
opts[:locals][:rodauth] = self
opts[:cache] = cache_templates
opts[:cache_key] = :"rodauth_#{page}"
_template_opts(opts, page)
end
def _merge_fixed_locals_opts(opts, fixed_locals)
if use_template_fixed_locals? && !opts[:locals]
fixed_locals_opts = {default_fixed_locals: fixed_locals}
fixed_locals_opts.merge!(opts[:template_opts]) if opts[:template_opts]
opts[:template_opts] = fixed_locals_opts
end
end
# Set the template path only if there isn't an overridden template in the application.
# Result should replace existing template opts.
def _template_opts(opts, page)
opts = scope.send(:find_template, scope.send(:parse_template_opts, page, opts))
unless File.file?(scope.send(:template_path, opts))
opts[:path] = template_path(page)
end
opts
end
def _view(meth, page)
unless scope.respond_to?(meth)
raise ConfigurationError, "attempted to render a built-in view/email template (#{page.inspect}), but rendering is disabled"
end
scope.send(meth, _view_opts(page))
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/change_login.rb 0000664 0000000 0000000 00000005403 15157255142 0025042 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:change_login, :ChangeLogin) do
depends :login_password_requirements_base
notice_flash 'Your login has been changed'
error_flash 'There was an error changing your login'
translatable_method :same_as_current_login_message, 'same as current login'
loaded_templates %w'change-login login-field login-confirm-field password-field'
view 'change-login', 'Change Login'
after
before
additional_form_tags
button 'Change Login'
redirect
response
auth_value_methods :change_login_requires_password?
auth_methods :change_login
internal_request_method
route do |r|
require_account
before_change_login_route
r.get do
change_login_view
end
r.post do
catch_error do
if change_login_requires_password? && !password_match?(param(password_param))
throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
end
login = login_param_value
unless login_meets_requirements?(login)
throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
end
if require_login_confirmation? && !login_confirmation_matches?(login, param(login_confirm_param))
throw_error_reason(:logins_do_not_match, unmatched_field_error_status, login_param, logins_do_not_match_message)
end
transaction do
before_change_login
unless change_login(login)
throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
end
after_change_login
end
change_login_response
end
set_error_flash change_login_error_flash
change_login_view
end
end
def change_login_requires_password?
modifications_require_password?
end
def change_login(login)
if account_ds.get(login_column).downcase == login.downcase
set_login_requirement_error_message(:same_as_current_login, same_as_current_login_message)
return false
end
update_login(login)
end
private
def update_login(login)
_update_login(login)
end
def _update_login(login)
updated = nil
raised = raises_uniqueness_violation?{updated = update_account({login_column=>login}, account_ds.exclude(login_column=>login)) == 1}
if raised
set_login_requirement_error_message(:already_an_account_with_this_login, already_an_account_with_this_login_message)
end
change_made = updated && !raised
clear_tokens(:change_login) if change_made
change_made
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/change_password.rb 0000664 0000000 0000000 00000004415 15157255142 0025576 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:change_password, :ChangePassword) do
depends :login_password_requirements_base
notice_flash 'Your password has been changed'
error_flash 'There was an error changing your password'
loaded_templates %w'change-password password-field password-confirm-field'
view 'change-password', 'Change Password'
after
before
additional_form_tags
button 'Change Password'
redirect
response
translatable_method :new_password_label, 'New Password'
auth_value_method :new_password_param, 'new-password'
auth_value_methods(
:change_password_requires_password?,
:invalid_previous_password_message
)
internal_request_method
route do |r|
require_account
before_change_password_route
r.get do
change_password_view
end
r.post do
catch_error do
if change_password_requires_password? && !password_match?(param(password_param))
throw_error_reason(:invalid_previous_password, invalid_password_error_status, password_param, invalid_previous_password_message)
end
password = param(new_password_param)
if require_password_confirmation? && password != param(password_confirm_param)
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, new_password_param, passwords_do_not_match_message)
end
if password_match?(password)
throw_error_reason(:same_as_existing_password, invalid_field_error_status, new_password_param, same_as_existing_password_message)
end
unless password_meets_requirements?(password)
throw_error_status(invalid_field_error_status, new_password_param, password_does_not_meet_requirements_message)
end
transaction do
before_change_password
set_password(password)
after_change_password
end
change_password_response
end
set_error_flash change_password_error_flash
change_password_view
end
end
def change_password_requires_password?
modifications_require_password?
end
def invalid_previous_password_message
invalid_password_message
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/change_password_notify.rb 0000664 0000000 0000000 00000000576 15157255142 0027172 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:change_password_notify, :ChangePasswordNotify) do
depends :change_password, :email_base
loaded_templates %w'password-changed-email'
email :password_changed, 'Password Changed', :translatable=>true
private
def after_change_password
super
send_password_changed_email
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/close_account.rb 0000664 0000000 0000000 00000003671 15157255142 0025253 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:close_account, :CloseAccount) do
notice_flash 'Your account has been closed'
error_flash 'There was an error closing your account'
loaded_templates %w'close-account password-field'
view 'close-account', 'Close Account'
additional_form_tags
button 'Close Account'
after
before
redirect
response
auth_value_method :account_closed_status_value, 3
auth_value_methods(
:close_account_requires_password?,
:delete_account_on_close?
)
auth_methods(
:close_account,
:delete_account
)
internal_request_method
route do |r|
require_account
before_close_account_route
r.get do
close_account_view
end
r.post do
catch_error do
if close_account_requires_password? && !password_match?(param(password_param))
throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
end
transaction do
before_close_account
close_account
after_close_account
clear_session
clear_tokens(:close_account)
if delete_account_on_close?
delete_account
end
end
close_account_response
end
set_error_flash close_account_error_flash
close_account_view
end
end
def close_account_requires_password?
modifications_require_password?
end
def close_account
unless skip_status_checks?
update_account(account_status_column=>account_closed_status_value)
end
unless account_password_hash_column
password_hash_ds.delete
end
end
def delete_account
account_ds.delete
end
def delete_account_on_close?
skip_status_checks?
end
def skip_status_checks?
false
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/confirm_password.rb 0000664 0000000 0000000 00000005643 15157255142 0026012 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:confirm_password, :ConfirmPassword) do
notice_flash "Your password has been confirmed"
error_flash "There was an error confirming your password"
error_flash "You need to confirm your password before continuing", 'password_authentication_required'
loaded_templates %w'confirm-password password-field'
view 'confirm-password', 'Confirm Password'
additional_form_tags
button 'Confirm Password'
before
after
response
redirect(:password_authentication_required){confirm_password_path}
session_key :confirm_password_redirect_session_key, :confirm_password_redirect
translatable_method :confirm_password_link_text, "Enter Password"
auth_value_method :password_authentication_required_error_status, 401
auth_value_methods :confirm_password_redirect
auth_methods :confirm_password
route do |r|
require_login
require_account_session
before_confirm_password_route
r.get do
confirm_password_view
end
r.post do
if password_match?(param(password_param))
transaction do
before_confirm_password
confirm_password
after_confirm_password
end
confirm_password_response
else
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
set_field_error(password_param, invalid_password_message)
set_error_flash confirm_password_error_flash
confirm_password_view
end
end
end
def require_password_authentication
require_login
if require_password_authentication? && has_password?
set_redirect_error_status(password_authentication_required_error_status)
set_error_reason :password_authentication_required
set_redirect_error_flash password_authentication_required_error_flash
set_session_value(confirm_password_redirect_session_key, request.fullpath)
redirect password_authentication_required_redirect
end
end
def confirm_password
authenticated_by.delete('autologin')
authenticated_by.delete('remember')
authenticated_by.delete('email_auth')
authenticated_by.delete('password')
authenticated_by.unshift("password")
remove_session_value(autologin_type_session_key)
nil
end
def confirm_password_redirect
remove_session_value(confirm_password_redirect_session_key) || default_redirect
end
private
def _two_factor_auth_links
links = (super if defined?(super)) || []
if authenticated_by.length == 1 && !authenticated_by.include?('password') && has_password?
links << [5, confirm_password_path, confirm_password_link_text]
end
links
end
def require_password_authentication?
return true if defined?(super) && super
!authenticated_by.include?('password')
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/create_account.rb 0000664 0000000 0000000 00000007343 15157255142 0025411 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:create_account, :CreateAccount) do
depends :login, :login_password_requirements_base
notice_flash 'Your account has been created'
error_flash "There was an error creating your account"
loaded_templates %w'create-account login-field login-confirm-field password-field password-confirm-field'
view 'create-account', 'Create Account'
after
before
button 'Create Account'
additional_form_tags
redirect
response
auth_value_method :create_account_autologin?, true
translatable_method :create_account_link_text, "Create a New Account"
auth_value_method :create_account_set_password?, true
auth_methods(
:save_account,
:set_new_account_password
)
auth_private_methods(
:new_account
)
internal_request_method
route do |r|
check_already_logged_in
before_create_account_route
@password_field_autocomplete_value = 'new-password'
r.get do
create_account_view
end
r.post do
login = login_param_value
password = param(password_param)
new_account(login)
catch_error do
if require_login_confirmation? && !login_confirmation_matches?(login, param(login_confirm_param))
throw_error_reason(:logins_do_not_match, unmatched_field_error_status, login_param, logins_do_not_match_message)
end
unless login_meets_requirements?(login)
throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
end
if create_account_set_password?
if require_password_confirmation? && password != param(password_confirm_param)
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, password_param, passwords_do_not_match_message)
end
unless password_meets_requirements?(password)
throw_error_reason(:password_does_not_meet_requirements, invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
end
if account_password_hash_column
set_new_account_password(password)
end
end
transaction do
before_create_account
unless save_account
throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
end
if create_account_set_password? && !account_password_hash_column
set_password(password)
end
after_create_account
if create_account_autologin?
autologin_session('create_account')
end
create_account_response
end
end
set_error_flash create_account_error_flash
create_account_view
end
end
def set_new_account_password(password)
account[account_password_hash_column] = password_hash(password)
end
def new_account(login)
@account = _new_account(login)
end
def save_account
id = nil
raised = raises_uniqueness_violation?{id = db[accounts_table].insert(account)}
if raised
set_login_requirement_error_message(:already_an_account_with_this_login, already_an_account_with_this_login_message)
end
if id
account[account_id_column] ||= id
end
id && !raised
end
private
def _login_form_footer_links
super << [10, create_account_path, create_account_link_text]
end
def _new_account(login)
acc = {login_column=>login}
unless skip_status_checks?
acc[account_status_column] = account_initial_status_value
end
acc
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/disallow_common_passwords.rb 0000664 0000000 0000000 00000002444 15157255142 0027722 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:disallow_common_passwords, :DisallowCommonPasswords) do
depends :login_password_requirements_base
auth_value_method :most_common_passwords_file, File.expand_path('../../../../dict/top-10_000-passwords.txt', __FILE__)
translatable_method :password_is_one_of_the_most_common_message, "is one of the most common passwords"
auth_value_method :most_common_passwords, nil
auth_methods :password_one_of_most_common?
def password_meets_requirements?(password)
super && password_not_one_of_the_most_common?(password)
end
def post_configure
super
return if most_common_passwords || !most_common_passwords_file
require 'set'
most_common = Set.new(File.read(most_common_passwords_file).split("\n").each(&:freeze)).freeze
self.class.send(:define_method, :most_common_passwords){most_common}
end
def password_one_of_most_common?(password)
most_common_passwords.include?(password)
end
private
def password_not_one_of_the_most_common?(password)
return true unless password_one_of_most_common?(password)
set_password_requirement_error_message(:password_is_one_of_the_most_common, password_is_one_of_the_most_common_message)
false
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/disallow_password_reuse.rb 0000664 0000000 0000000 00000006252 15157255142 0027373 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:disallow_password_reuse, :DisallowPasswordReuse) do
depends :login_password_requirements_base
translatable_method :password_same_as_previous_password_message, "same as previous password"
auth_value_method :previous_password_account_id_column, :account_id
auth_value_method :previous_password_hash_column, :password_hash
auth_value_method :previous_password_hash_table, :account_previous_password_hashes
auth_value_method :previous_password_id_column, :id
auth_value_method :previous_passwords_to_check, 6
auth_methods(
:add_previous_password_hash,
:password_doesnt_match_previous_password?
)
def set_password(password)
hash = super
add_previous_password_hash(hash)
hash
end
def add_previous_password_hash(hash)
ds = previous_password_ds
unless @dont_check_previous_password
keep_before = ds.reverse(previous_password_id_column).
limit(nil, previous_passwords_to_check).
get(previous_password_id_column)
if keep_before
ds.where(Sequel.expr(previous_password_id_column) <= keep_before).
delete
end
end
# This should never raise uniqueness violations, as it uses a serial primary key
ds.insert(previous_password_account_id_column=>account_id, previous_password_hash_column=>hash)
end
def password_meets_requirements?(password)
super &&
(@dont_check_previous_password || password_doesnt_match_previous_password?(password))
end
private
def password_doesnt_match_previous_password?(password)
match = if use_database_authentication_functions?
salts = previous_password_ds.
select_map([previous_password_id_column, Sequel.function(function_name(:rodauth_get_previous_salt), previous_password_id_column).as(:salt)])
return true if salts.empty?
salts.any? do |hash_id, salt|
database_function_password_match?(:rodauth_previous_password_hash_match, hash_id, password, salt)
end
else
# :nocov:
previous_password_ds.select_map(previous_password_hash_column).any? do |hash|
password_hash_match?(hash, password)
end
# :nocov:
end
return true unless match
set_password_requirement_error_message(:password_same_as_previous_password, password_same_as_previous_password_message)
false
end
def after_close_account
super if defined?(super)
previous_password_ds.delete
end
def before_create_account_route
super if defined?(super)
@dont_check_previous_password = true
end
def before_verify_account_route
super if defined?(super)
@dont_check_previous_password = true
end
def after_create_account
if account_password_hash_column && !(respond_to?(:verify_account_set_password?) && verify_account_set_password?)
add_previous_password_hash(password_hash(param(password_param)))
end
super if defined?(super)
end
def previous_password_ds
db[previous_password_hash_table].where(previous_password_account_id_column=>account_id)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/email_auth.rb 0000664 0000000 0000000 00000017077 15157255142 0024547 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:email_auth, :EmailAuth) do
depends :login, :email_base
notice_flash "An email has been sent to you with a link to login to your account", 'email_auth_email_sent'
error_flash "There was an error logging you in"
error_flash "There was an error requesting an email link to authenticate", 'email_auth_request'
error_flash "An email has recently been sent to you with a link to login", 'email_auth_email_recently_sent'
error_flash "There was an error logging you in: invalid email authentication key", 'no_matching_email_auth_key'
loaded_templates %w'email-auth email-auth-request-form email-auth-email'
view 'email-auth', 'Login'
additional_form_tags
additional_form_tags 'email_auth_request'
before 'email_auth_request'
after 'email_auth_request'
button 'Send Login Link Via Email', 'email_auth_request'
redirect(:email_auth_email_sent){default_post_email_redirect}
redirect(:email_auth_email_recently_sent){default_post_email_redirect}
response :email_auth_email_sent
email :email_auth, 'Login Link'
auth_value_method :email_auth_deadline_column, :deadline
auth_value_method :email_auth_deadline_interval, {:days=>1}.freeze
auth_value_method :email_auth_id_column, :id
auth_value_method :email_auth_key_column, :key
auth_value_method :email_auth_key_param, 'key'
auth_value_method :email_auth_email_last_sent_column, :email_last_sent
auth_value_method :email_auth_skip_resend_email_within, 300
auth_value_method :email_auth_table, :account_email_auth_keys
auth_value_method :force_email_auth?, false
session_key :email_auth_session_key, :email_auth_key
auth_methods(
:create_email_auth_key,
:email_auth_email_link,
:email_auth_key_insert_hash,
:email_auth_key_value,
:email_auth_request_form,
:get_email_auth_key,
:get_email_auth_email_last_sent,
:remove_email_auth_key,
:set_email_auth_email_last_sent
)
auth_private_methods :account_from_email_auth_key
internal_request_method
internal_request_method :email_auth_request
internal_request_method :valid_email_auth?
route(:email_auth_request) do |r|
check_already_logged_in
before_email_auth_request_route
r.post do
if account_from_login(login_param_value) && open_account?
_email_auth_request
end
set_redirect_error_status(no_matching_login_error_status)
set_error_reason :no_matching_login
set_redirect_error_flash email_auth_request_error_flash
redirect email_auth_email_sent_redirect
end
end
route do |r|
check_already_logged_in
before_email_auth_route
r.get do
if key = param_or_nil(email_auth_key_param)
set_session_value(email_auth_session_key, key)
redirect(r.path)
end
if (key = session[email_auth_session_key]) && account_from_email_auth_key(key)
email_auth_view
else
remove_session_value(email_auth_session_key)
set_redirect_error_flash no_matching_email_auth_key_error_flash
redirect require_login_redirect
end
end
r.post do
key = session[email_auth_session_key] || param(email_auth_key_param)
unless account_from_email_auth_key(key)
set_redirect_error_status(invalid_key_error_status)
set_error_reason :invalid_email_auth_key
set_redirect_error_flash email_auth_error_flash
redirect email_auth_email_sent_redirect
end
login('email_auth')
end
end
def create_email_auth_key
transaction do
if email_auth_key_value = get_email_auth_key(account_id)
set_email_auth_email_last_sent
@email_auth_key_value = email_auth_key_value
elsif e = raised_uniqueness_violation{email_auth_ds.insert(email_auth_key_insert_hash)}
# If inserting into the email auth table causes a violation, we can pull the
# existing email auth key from the table, or reraise.
raise e unless @email_auth_key_value = get_email_auth_key(account_id)
end
end
end
def set_email_auth_email_last_sent
email_auth_ds.update(email_auth_email_last_sent_column=>Sequel::CURRENT_TIMESTAMP) if email_auth_email_last_sent_column
end
def get_email_auth_email_last_sent
if column = email_auth_email_last_sent_column
if ts = email_auth_ds.get(column)
convert_timestamp(ts)
end
end
end
def remove_email_auth_key
email_auth_ds.delete
end
def account_from_email_auth_key(key)
@account = _account_from_email_auth_key(key)
end
def email_auth_email_link
token_link(email_auth_route, email_auth_key_param, email_auth_key_value)
end
def get_email_auth_key(id)
ds = email_auth_ds(id)
ds.where(Sequel::CURRENT_TIMESTAMP > email_auth_deadline_column).delete
ds.get(email_auth_key_column)
end
def email_auth_request_form
render('email-auth-request-form')
end
def after_login_entered_during_multi_phase_login
# If forcing email auth, just send the email link.
_email_auth_request if force_email_auth?
super
end
def use_multi_phase_login?
true
end
def possible_authentication_methods
methods = super
methods << 'email_auth' if !methods.include?('password') && allow_email_auth?
methods
end
def email_auth_email_recently_sent?
(email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
end
def clear_tokens(reason)
super
remove_email_auth_key
end
private
def _multi_phase_login_forms
forms = super
forms << [30, email_auth_request_form, :_email_auth_request] if valid_login_entered? && allow_email_auth?
forms
end
def _email_auth_request
if email_auth_email_recently_sent?
set_redirect_error_flash email_auth_email_recently_sent_error_flash
redirect email_auth_email_recently_sent_redirect
end
generate_email_auth_key_value
transaction do
before_email_auth_request
create_email_auth_key
send_email_auth_email
after_email_auth_request
end
email_auth_email_sent_response
end
attr_reader :email_auth_key_value
def allow_email_auth?
defined?(super) ? super : true
end
def after_login
# Remove the email auth key after any login, even if
# it is a password login. This is done to invalidate
# the email login when a user has a password and requests
# email authentication, but then remembers their password
# and doesn't need the link. At that point, the link
# that allows login access to the account becomes a
# security liability, and it is best to remove it.
remove_email_auth_key
super
end
def generate_email_auth_key_value
@email_auth_key_value = random_key
end
def use_date_arithmetic?
super || db.database_type == :mysql
end
def email_auth_key_insert_hash
hash = {email_auth_id_column=>account_id, email_auth_key_column=>email_auth_key_value}
set_deadline_value(hash, email_auth_deadline_column, email_auth_deadline_interval)
hash
end
def email_auth_ds(id=account_id)
db[email_auth_table].where(email_auth_id_column=>id)
end
def _account_from_email_auth_key(token)
account_from_key(token, account_open_status_value){|id| get_email_auth_key(id)}
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/email_base.rb 0000664 0000000 0000000 00000003345 15157255142 0024511 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:email_base, :EmailBase) do
translatable_method :email_subject_prefix, ''
auth_value_method :require_mail?, true
auth_value_method :allow_raw_email_token?, false
redirect :default_post_email
auth_value_methods(
:email_from
)
auth_methods(
:create_email,
:email_to,
:send_email
)
def post_configure
super
require 'mail' if require_mail?
end
def email_from
"webmaster@#{domain}"
end
def email_to
account[login_column]
end
private
def send_email(email)
email.deliver!
end
def create_email(subject, body)
create_email_to(email_to, subject, body)
end
def create_email_to(to, subject, body)
m = Mail.new
m.from = email_from
m.to = to
m.subject = "#{email_subject_prefix}#{subject}"
m.body = body
m
end
def token_link(route, param, key)
route_url(route, param => token_param_value(key))
end
def token_param_value(key)
"#{account_id}#{token_separator}#{convert_email_token_key(key)}"
end
def convert_email_token_key(key)
convert_token_key(key)
end
def account_from_key(token, status_id=nil)
id, key = split_token(token)
id = convert_token_id(id)
return unless id && key
return unless actual = yield(id)
unless (hmac_secret && timing_safe_eql?(key, convert_email_token_key(actual))) ||
(hmac_secret_rotation? && timing_safe_eql?(key, compute_old_hmac(actual))) ||
((!hmac_secret || allow_raw_email_token?) && timing_safe_eql?(key, actual))
return
end
_account_from_id(id, status_id)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/http_basic_auth.rb 0000664 0000000 0000000 00000003647 15157255142 0025576 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:http_basic_auth, :HttpBasicAuth) do
auth_value_method :http_basic_auth_realm, "protected"
auth_value_method :require_http_basic_auth?, false
def logged_in?
ret = super
if !ret && !defined?(@checked_http_basic_auth)
http_basic_auth
ret = super
end
ret
end
def require_login
if require_http_basic_auth?
require_http_basic_auth
end
super
end
def require_http_basic_auth
unless http_basic_auth
set_http_basic_auth_error_response
return_response
end
end
def http_basic_auth
return @checked_http_basic_auth if defined?(@checked_http_basic_auth)
@checked_http_basic_auth = nil
return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
username, password = token.unpack("m*").first.split(/:/, 2)
return unless username && password
catch_error do
unless account_from_login(username)
throw_basic_auth_error(login_param, no_matching_login_message)
end
before_login_attempt
unless open_account?
throw_basic_auth_error(login_param, no_matching_login_message)
end
unless password_match?(password)
after_login_failure
throw_basic_auth_error(password_param, invalid_password_message)
end
transaction do
before_login
login_session('password')
after_login
end
@checked_http_basic_auth = true
return true
end
nil
end
private
def set_http_basic_auth_error_response
response.status = 401
set_response_header("www-authenticate", "Basic realm=\"#{http_basic_auth_realm}\"")
end
def throw_basic_auth_error(*args)
set_http_basic_auth_error_response
throw_error(*args)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/internal_request.rb 0000664 0000000 0000000 00000027101 15157255142 0026010 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'stringio'
module Rodauth
INVALID_DOMAIN = "invalidurl @@.com"
class InternalRequestError < StandardError
attr_accessor :flash
attr_accessor :reason
attr_accessor :field_errors
def initialize(attrs)
return super if attrs.is_a?(String)
@flash = attrs[:flash]
@reason = attrs[:reason]
@field_errors = attrs[:field_errors] || {}
super(build_message)
end
private
def build_message
extras = []
extras << reason if reason
extras << field_errors unless field_errors.empty?
extras = (" (#{extras.join(", ")})" unless extras.empty?)
"#{flash}#{extras}"
end
end
module InternalRequestMethods
attr_accessor :session
attr_accessor :params
attr_reader :flash
attr_accessor :internal_request_block
def domain
d = super
if d.nil? || d == INVALID_DOMAIN
raise InternalRequestError, "must set domain in configuration, as it cannot be determined from internal request"
end
d
end
def raw_param(k)
@params[k]
end
def clear_session
@session.clear
end
def set_error_flash(message)
@flash = message
_handle_internal_request_error
end
alias set_redirect_error_flash set_error_flash
def set_notice_flash(message)
@flash = message
end
alias set_notice_now_flash set_notice_flash
def modifications_require_password?
false
end
alias require_login_confirmation? modifications_require_password?
alias require_password_confirmation? modifications_require_password?
alias change_login_requires_password? modifications_require_password?
alias change_password_requires_password? modifications_require_password?
alias close_account_requires_password? modifications_require_password?
alias two_factor_modifications_require_password? modifications_require_password?
def otp_setup_view
hash = {:otp_setup=>otp_user_key}
hash[:otp_setup_raw] = otp_key if hmac_secret
_return_from_internal_request(hash)
end
def add_recovery_codes_view
_return_from_internal_request(recovery_codes)
end
def webauthn_setup_view
cred = new_webauthn_credential
_return_from_internal_request({
webauthn_setup: cred.as_json,
webauthn_setup_challenge: cred.challenge,
webauthn_setup_challenge_hmac: compute_hmac(cred.challenge)
})
end
def webauthn_auth_view
cred = webauthn_credential_options_for_get
_return_from_internal_request({
webauthn_auth: cred.as_json,
webauthn_auth_challenge: cred.challenge,
webauthn_auth_challenge_hmac: compute_hmac(cred.challenge)
})
end
def handle_internal_request(meth)
catch(:halt) do
_around_rodauth do
before_rodauth
send(meth, request)
end
end
@internal_request_return_value
end
def only_json?
false
end
private
def internal_request?
true
end
def set_error_reason(reason)
@error_reason = reason
end
def after_login
super
_set_internal_request_return_value(account_id) unless @return_false_on_error
end
def after_remember
super
if params[remember_param] == remember_remember_param_value
_set_internal_request_return_value("#{account_id}_#{convert_token_key(remember_key_value)}")
end
end
def after_load_memory
super
_return_from_internal_request(session_value)
end
def before_change_password_route
super
params[new_password_param] ||= params[password_param]
end
def before_email_auth_request_route
super
_set_login_param_from_account
end
def before_login_route
super
_set_login_param_from_account
end
def before_unlock_account_request_route
super
_set_login_param_from_account
end
def before_reset_password_request_route
super
_set_login_param_from_account
end
def before_verify_account_resend_route
super
_set_login_param_from_account
end
def before_webauthn_login_route
super
_set_login_param_from_account
end
def account_from_key(token, status_id=nil)
return super unless session_value
return unless yield session_value
_account_from_id(session_value, status_id)
end
def _set_internal_request_return_value(value)
@internal_request_return_value = value
end
def _return_from_internal_request(value)
_set_internal_request_return_value(value)
throw(:halt)
end
def _handle_internal_request_error
if @return_false_on_error
_return_from_internal_request(false)
else
raise InternalRequestError.new(flash: @flash, reason: @error_reason, field_errors: @field_errors)
end
end
def _return_false_on_error!
@return_false_on_error = true
end
def _set_login_param_from_account
if session_value && !params[login_param] && (account = _account_from_id(session_value))
params[login_param] = account[login_column]
end
end
def _get_remember_cookie
params[remember_param]
end
def _handle_internal_request_eval(_)
v = instance_eval(&internal_request_block)
_set_internal_request_return_value(v) unless defined?(@internal_request_return_value)
end
def _handle_account_id_for_login(_)
raise InternalRequestError, "no login provided" unless param_or_nil(login_param)
raise InternalRequestError, "no account for login" unless account = account_from_login(login_param_value)
_return_from_internal_request(account[account_id_column])
end
def _handle_account_exists?(_)
raise InternalRequestError, "no login provided" unless param_or_nil(login_param)
_return_from_internal_request(!!account_from_login(login_param_value))
end
def _handle_lock_account(_)
raised_uniqueness_violation{account_lockouts_ds(session_value).insert(_setup_account_lockouts_hash(session_value, generate_unlock_account_key))}
end
def _handle_remember_setup(request)
params[remember_param] = remember_remember_param_value
_handle_remember(request)
end
def _handle_remember_disable(request)
params[remember_param] = remember_disable_param_value
_handle_remember(request)
end
def _handle_account_id_for_remember_key(request)
load_memory
raise InternalRequestError, "invalid remember key"
end
def _handle_otp_setup_params(request)
request.env['REQUEST_METHOD'] = 'GET'
_handle_otp_setup(request)
end
def _handle_webauthn_setup_params(request)
request.env['REQUEST_METHOD'] = 'GET'
_handle_webauthn_setup(request)
end
def _handle_webauthn_auth_params(request)
request.env['REQUEST_METHOD'] = 'GET'
_handle_webauthn_auth(request)
end
def _handle_webauthn_login_params(request)
_set_login_param_from_account
unless webauthn_login_options?
raise InternalRequestError, "no login provided" unless param_or_nil(login_param)
raise InternalRequestError, "no account for login"
end
webauthn_auth_view
end
def _predicate_internal_request(meth, request)
_return_false_on_error!
_set_internal_request_return_value(true)
send(meth, request)
end
def _handle_valid_login_and_password?(request)
_predicate_internal_request(:_handle_login, request)
end
def _handle_valid_email_auth?(request)
_predicate_internal_request(:_handle_email_auth, request)
end
def _handle_valid_otp_auth?(request)
_predicate_internal_request(:_handle_otp_auth, request)
end
def _handle_valid_recovery_auth?(request)
_predicate_internal_request(:_handle_recovery_auth, request)
end
def _handle_valid_sms_auth?(request)
_predicate_internal_request(:_handle_sms_auth, request)
end
end
module InternalRequestClassMethods
def internal_request(route, opts={}, &block)
opts = opts.dup
env = {
'REQUEST_METHOD'=>'POST',
'PATH_INFO'=>'/'.dup,
"SCRIPT_NAME" => "",
"HTTP_HOST" => INVALID_DOMAIN,
"SERVER_NAME" => INVALID_DOMAIN,
"SERVER_PORT" => 443,
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"rack.input"=>StringIO.new(''),
"rack.url_scheme"=>"https"
}
env.merge!(opts.delete(:env)) if opts[:env]
session = {}
session.merge!(opts.delete(:session)) if opts[:session]
params = {}
params.merge!(opts.delete(:params)) if opts[:params]
scope = roda_class.new(env)
rodauth = new(scope)
rodauth.session = session
rodauth.params = params
rodauth.internal_request_block = block
unless account_id = opts.delete(:account_id)
if (account_login = opts.delete(:account_login))
if (account = rodauth.send(:_account_from_login, account_login))
account_id = account[rodauth.account_id_column]
else
raise InternalRequestError, "no account for login: #{account_login.inspect}"
end
end
end
if account_id
session[rodauth.session_key] = account_id
unless authenticated_by = opts.delete(:authenticated_by)
authenticated_by = case route
when :otp_auth, :sms_request, :sms_auth, :recovery_auth, :webauthn_auth, :webauthn_auth_params, :valid_otp_auth?, :valid_sms_auth?, :valid_recovery_auth?
['internal1']
else
['internal1', 'internal2']
end
end
session[rodauth.authenticated_by_session_key] = authenticated_by
end
opts.keys.each do |k|
meth = :"#{k}_param"
params[rodauth.public_send(meth).to_s] = opts.delete(k) if rodauth.respond_to?(meth)
end
unless opts.empty?
warn "unhandled options passed to #{route}: #{opts.inspect}"
end
rodauth.handle_internal_request(:"_handle_#{route}")
end
end
Feature.define(:internal_request, :InternalRequest) do
configuration_module_eval do
def internal_request_configuration(&block)
@auth.instance_exec do
(@internal_request_configuration_blocks ||= []) << block
end
end
end
def post_configure
super
return if is_a?(InternalRequestMethods)
superklasses = []
superklass = self.class
until superklass == Rodauth::Auth
superklasses << superklass
superklass = superklass.superclass
end
klass = self.class
internal_class = Class.new(klass)
internal_class.instance_variable_set(:@configuration_name, klass.configuration_name)
configuration = internal_class.configuration
superklasses.reverse_each do |superklass|
if blocks = superklass.instance_variable_get(:@internal_request_configuration_blocks)
blocks.each do |block|
configuration.instance_exec(&block)
end
end
end
internal_class.send(:extend, InternalRequestClassMethods)
internal_class.send(:include, InternalRequestMethods)
internal_class.allocate.post_configure
([:base] + internal_class.features).each do |feature_name|
feature = FEATURES[feature_name]
if meths = feature.internal_request_methods
meths.each do |name|
klass.define_singleton_method(name){|opts={}, &block| internal_class.internal_request(name, opts, &block)}
end
end
end
klass.const_set(:InternalRequest, internal_class)
klass.private_constant :InternalRequest
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/json.rb 0000664 0000000 0000000 00000016650 15157255142 0023404 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:json, :Json) do
translatable_method :json_not_accepted_error_message, 'Unsupported Accept header. Must accept "application/json" or compatible content type'
translatable_method :json_non_post_error_message, 'non-POST method used in JSON API'
auth_value_method :json_accept_regexp, /(?:(?:\*|\bapplication)\/\*|\bapplication\/(?:vnd\.api\+)?json\b)/i
auth_value_method :json_check_accept?, true
auth_value_method :json_request_content_type_regexp, /\bapplication\/(?:vnd\.api\+)?json\b/i
auth_value_method :json_response_content_type, 'application/json'
auth_value_method :json_response_custom_error_status?, true
auth_value_method :json_response_error_status, 400
auth_value_method :json_response_error_key, "error"
auth_value_method :json_response_field_error_key, "field-error"
auth_value_method :json_response_success_key, "success"
translatable_method :non_json_request_error_message, 'Only JSON format requests are allowed'
auth_value_methods(
:only_json?,
:use_json?,
)
auth_methods(
:json_request?,
:json_response_error?
)
auth_private_methods :json_response_body
def set_field_error(field, message)
return super unless use_json?
json_response[json_response_field_error_key] = [field, message]
end
def set_error_flash(message)
return super unless use_json?
json_response[json_response_error_key] = message
end
def set_redirect_error_flash(message)
return super unless use_json?
json_response[json_response_error_key] = message
end
def set_notice_flash(message)
return super unless use_json?
json_response[json_response_success_key] = message if include_success_messages?
end
def set_notice_now_flash(message)
return super unless use_json?
json_response[json_response_success_key] = message if include_success_messages?
end
def json_request?
return @json_request if defined?(@json_request)
@json_request = request.content_type =~ json_request_content_type_regexp
end
def use_json?
json_request? || only_json?
end
def view(page, title)
return super unless use_json?
return_json_response
end
def json_response_error?
!!json_response[json_response_error_key]
end
private
def check_csrf?
return false if use_json?
super
end
def _set_otp_unlock_info
if use_json?
json_response[:num_successes] = otp_unlock_num_successes
json_response[:required_successes] = otp_unlock_auths_required
json_response[:next_attempt_after] = otp_unlock_next_auth_attempt_after.to_i
end
end
def after_otp_unlock_auth_success
super if defined?(super)
if otp_locked_out?
_set_otp_unlock_info
json_response[:deadline] = otp_unlock_deadline.to_i
end
end
def after_otp_unlock_auth_failure
super if defined?(super)
_set_otp_unlock_info
end
def after_otp_unlock_not_yet_available
super if defined?(super)
_set_otp_unlock_info
end
def before_two_factor_manage_route
super if defined?(super)
if use_json?
json_response[:setup_links] = two_factor_setup_links.sort.map{|_,link| link}
json_response[:remove_links] = two_factor_remove_links.sort.map{|_,link| link}
json_response[json_response_success_key] ||= "" if include_success_messages?
return_json_response
end
end
def before_two_factor_auth_route
super if defined?(super)
if use_json?
json_response[:auth_links] = two_factor_auth_links.sort.map{|_,link| link}
json_response[json_response_success_key] ||= "" if include_success_messages?
return_json_response
end
end
def before_view_recovery_codes
super if defined?(super)
if use_json?
json_response[:codes] = recovery_codes
json_response[json_response_success_key] ||= "" if include_success_messages?
end
end
def before_webauthn_setup_route
super if defined?(super)
if use_json? && !param_or_nil(webauthn_setup_param)
cred = new_webauthn_credential
json_response[webauthn_setup_param] = cred.as_json
json_response[webauthn_setup_challenge_param] = cred.challenge
json_response[webauthn_setup_challenge_hmac_param] = compute_hmac(cred.challenge)
end
end
def before_webauthn_auth_route
super if defined?(super)
if use_json? && !param_or_nil(webauthn_auth_param)
cred = webauthn_credential_options_for_get
json_response[webauthn_auth_param] = cred.as_json
json_response[webauthn_auth_challenge_param] = cred.challenge
json_response[webauthn_auth_challenge_hmac_param] = compute_hmac(cred.challenge)
end
end
def before_webauthn_login_route
super if defined?(super)
if use_json? && !param_or_nil(webauthn_auth_param) && webauthn_login_options?
cred = webauthn_credential_options_for_get
json_response[webauthn_auth_param] = cred.as_json
json_response[webauthn_auth_challenge_param] = cred.challenge
json_response[webauthn_auth_challenge_hmac_param] = compute_hmac(cred.challenge)
end
end
def before_webauthn_remove_route
super if defined?(super)
if use_json? && !param_or_nil(webauthn_remove_param)
json_response[webauthn_remove_param] = account_webauthn_usage
end
end
def before_otp_setup_route
super if defined?(super)
if use_json? && otp_keys_use_hmac? && !param_or_nil(otp_setup_raw_param)
_otp_tmp_key(otp_new_secret)
json_response[otp_setup_param] = otp_user_key
json_response[otp_setup_raw_param] = otp_key
end
end
def before_rodauth
if json_request?
if json_check_accept? && (accept = request.env['HTTP_ACCEPT']) && accept !~ json_accept_regexp
response.status = 406
json_response[json_response_error_key] = json_not_accepted_error_message
_return_json_response
end
unless request.post?
response.status = 405
set_response_header('allow', 'POST')
json_response[json_response_error_key] = json_non_post_error_message
return_json_response
end
elsif only_json?
response.status = json_response_error_status
return_response non_json_request_error_message
end
super
end
def redirect(_)
return super unless use_json?
return_json_response
end
def return_json_response
_return_json_response
end
def _return_json_response
response.status ||= json_response_error_status if json_response_error?
response.headers[convert_response_header_key('content-type')] ||= json_response_content_type
return_response _json_response_body(json_response)
end
def include_success_messages?
!json_response_success_key.nil?
end
def _json_response_body(hash)
request.send(:convert_to_json, hash)
end
def json_response
@json_response ||= {}
end
def set_redirect_error_status(status)
if use_json? && json_response_custom_error_status?
response.status = status
end
end
def set_response_error_status(status)
if use_json? && !json_response_custom_error_status?
status = json_response_error_status
end
super
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/jwt.rb 0000664 0000000 0000000 00000006650 15157255142 0023236 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'jwt'
require 'jwt/version'
module Rodauth
Feature.define(:jwt, :Jwt) do
depends :json
translatable_method :invalid_jwt_format_error_message, "invalid JWT format or claim in Authorization header"
auth_value_method :jwt_algorithm, "HS256"
auth_value_method :jwt_authorization_ignore, /\A(?:Basic|Digest) /
auth_value_method :jwt_authorization_remove, /\ABearer:?\s+/
auth_value_method :jwt_decode_opts, {}.freeze
auth_value_method :jwt_old_secret, nil
auth_value_method :jwt_session_key, nil
auth_value_method :jwt_symbolize_deeply?, false
auth_value_methods(
:jwt_secret,
:use_jwt?
)
auth_methods(
:jwt_session_hash,
:jwt_token,
:session_jwt,
:set_jwt_token
)
def_deprecated_alias :json_check_accept?, :jwt_check_accept?
def session
return @session if defined?(@session)
return super unless use_jwt?
s = {}
if jwt_token
unless session_data = jwt_payload
json_response[json_response_error_key] ||= invalid_jwt_format_error_message
_return_json_response
end
if jwt_session_key
session_data = session_data[jwt_session_key]
end
if session_data
if jwt_symbolize_deeply?
s = JSON.parse(JSON.generate(session_data), :symbolize_names=>true)
elsif scope.opts[:sessions_convert_symbols]
s = session_data
else
session_data.each{|k,v| s[k.to_sym] = v}
end
end
end
@session = s
end
def clear_session
super
set_jwt if use_jwt?
end
def jwt_secret
raise ConfigurationError, "jwt_secret not set"
end
def jwt_session_hash
jwt_session_key ? {jwt_session_key=>session} : session
end
def session_jwt
JWT.encode(jwt_session_hash, jwt_secret, jwt_algorithm)
end
def jwt_token
return @jwt_token if defined?(@jwt_token)
if (v = request.env['HTTP_AUTHORIZATION']) && v !~ jwt_authorization_ignore
@jwt_token = v.sub(jwt_authorization_remove, '')
end
end
def set_jwt_token(token)
set_response_header('authorization', token)
end
def use_jwt?
use_json?
end
def use_json?
jwt_token || super
end
def valid_jwt?
!!(jwt_token && jwt_payload)
end
private
def _jwt_decode_opts
jwt_decode_opts
end
if JWT.gem_version >= Gem::Version.new("2.4")
def _jwt_decode_secrets
secrets = [jwt_secret, jwt_old_secret]
secrets.compact!
secrets
end
# :nocov:
else
def _jwt_decode_secrets
jwt_secret
end
# :nocov:
end
def jwt_payload
return @jwt_payload if defined?(@jwt_payload)
@jwt_payload = JWT.decode(jwt_token, _jwt_decode_secrets, true, _jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
rescue JWT::DecodeError => e
rescue_jwt_payload(e)
end
def rescue_jwt_payload(_)
@jwt_payload = false
end
def set_session_value(key, value)
super
set_jwt if use_jwt?
value
end
def remove_session_value(key)
value = super
set_jwt if use_jwt?
value
end
def return_json_response
set_jwt
super
end
def set_jwt
set_jwt_token(session_jwt)
end
def use_scope_clear_session?
super && !use_jwt?
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/jwt_cors.rb 0000664 0000000 0000000 00000002743 15157255142 0024263 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:jwt_cors, :JwtCors) do
depends :jwt
auth_value_method :jwt_cors_allow_origin, false
auth_value_method :jwt_cors_allow_methods, 'POST'
auth_value_method :jwt_cors_allow_headers, 'Content-Type, Authorization, Accept'
auth_value_method :jwt_cors_expose_headers, 'Authorization'
auth_value_method :jwt_cors_max_age, 86400
auth_methods(:jwt_cors_allow?)
def jwt_cors_allow?
return false unless origin = request.env['HTTP_ORIGIN']
case allowed = jwt_cors_allow_origin
when String
timing_safe_eql?(origin, allowed)
when Array
allowed.any?{|s| timing_safe_eql?(origin, s)}
when Regexp
allowed =~ origin
when true
true
else
false
end
end
private
def before_rodauth
if jwt_cors_allow?
set_response_header('access-control-allow-origin', request.env['HTTP_ORIGIN'])
# Handle CORS preflight request
if request.request_method == 'OPTIONS'
set_response_header('access-control-allow-methods', jwt_cors_allow_methods)
set_response_header('access-control-allow-headers', jwt_cors_allow_headers)
set_response_header('access-control-max-age', jwt_cors_max_age.to_s)
response.status = 204
return_response
end
set_response_header('access-control-expose-headers', jwt_cors_expose_headers)
end
super
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/jwt_refresh.rb 0000664 0000000 0000000 00000020442 15157255142 0024747 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:jwt_refresh, :JwtRefresh) do
depends :jwt
after 'refresh_token'
before 'refresh_token'
auth_value_method :allow_refresh_with_expired_jwt_access_token?, false
session_key :jwt_refresh_token_data_session_key, :jwt_refresh_token_data
session_key :jwt_refresh_token_hmac_session_key, :jwt_refresh_token_hash
auth_value_method :jwt_access_token_key, 'access_token'
auth_value_method :jwt_access_token_not_before_period, 5
auth_value_method :jwt_access_token_period, 1800
translatable_method :jwt_refresh_invalid_token_message, 'invalid JWT refresh token'
auth_value_method :jwt_refresh_token_account_id_column, :account_id
auth_value_method :jwt_refresh_token_deadline_column, :deadline
auth_value_method :jwt_refresh_token_deadline_interval, {:days=>14}.freeze
auth_value_method :jwt_refresh_token_id_column, :id
auth_value_method :jwt_refresh_token_key, 'refresh_token'
auth_value_method :jwt_refresh_token_key_column, :key
auth_value_method :jwt_refresh_token_key_param, 'refresh_token'
auth_value_method :jwt_refresh_token_table, :account_jwt_refresh_keys
translatable_method :jwt_refresh_without_access_token_message, 'no JWT access token provided during refresh'
auth_value_method :jwt_refresh_without_access_token_status, 401
translatable_method :expired_jwt_access_token_message, "expired JWT access token"
auth_value_method :expired_jwt_access_token_status, 400
auth_private_methods(
:account_from_refresh_token
)
route do |r|
@jwt_refresh_route = true
before_jwt_refresh_route
r.post do
if !session_value
response.status ||= jwt_refresh_without_access_token_status
json_response[json_response_error_key] = jwt_refresh_without_access_token_message
elsif (refresh_token = param_or_nil(jwt_refresh_token_key_param)) && account_from_refresh_token(refresh_token)
transaction do
before_refresh_token
formatted_token = generate_refresh_token
remove_jwt_refresh_token_key(refresh_token)
set_jwt_refresh_token_hmac_session_key(formatted_token)
json_response[jwt_refresh_token_key] = formatted_token
json_response[jwt_access_token_key] = session_jwt
after_refresh_token
end
else
json_response[json_response_error_key] = jwt_refresh_invalid_token_message
end
_return_json_response
end
end
def update_session
super
# JWT login puts the access token in the header.
# We put the refresh token in the body.
# Note, do not put the access_token in the body here, as the access token content is not yet finalised.
token = json_response[jwt_refresh_token_key] = generate_refresh_token
set_jwt_refresh_token_hmac_session_key(token)
end
def set_jwt_token(token)
super
if json_response[json_response_error_key]
json_response.delete(jwt_access_token_key)
else
json_response[jwt_access_token_key] = token
end
end
def jwt_session_hash
h = super
t = Time.now.to_i
h[convert_session_key(:exp)] = t + jwt_access_token_period
h[convert_session_key(:iat)] = t
h[convert_session_key(:nbf)] = t - jwt_access_token_not_before_period
h
end
def account_from_refresh_token(token)
@account = _account_from_refresh_token(token)
end
def clear_tokens(reason)
super
jwt_refresh_token_account_ds(account_id).delete unless logged_in?
end
private
def rescue_jwt_payload(e)
if e.instance_of?(JWT::ExpiredSignature)
begin
# Some versions of jwt will raise JWT::ExpiredSignature even when the
# JWT is invalid for other reasons. Make sure the expiration is the
# only reason the JWT isn't valid before treating this as an expired token.
JWT.decode(jwt_token, jwt_secret, true, Hash[jwt_decode_opts].merge!(:verify_expiration=>false, :algorithm=>jwt_algorithm))[0]
rescue
else
json_response[json_response_error_key] = expired_jwt_access_token_message
response.status ||= expired_jwt_access_token_status
end
end
super
end
def _account_from_refresh_token(token)
id, token_id, key = _account_refresh_token_split(token)
unless key &&
(id.to_s == session_value.to_s) &&
(actual = get_active_refresh_token(id, token_id)) &&
(timing_safe_eql?(key, convert_token_key(actual)) || (hmac_secret_rotation? && timing_safe_eql?(key, compute_old_hmac(actual)))) &&
jwt_refresh_token_match?(key)
return
end
ds = account_ds(id)
ds = ds.where(account_session_status_filter) unless skip_status_checks?
ds.first
end
def _account_refresh_token_split(token)
id, token = split_token(token)
id = convert_token_id(id)
return unless id && token
token_id, key = split_token(token)
token_id = convert_token_id(token_id)
return unless token_id && key
[id, token_id, key]
end
def _jwt_decode_opts
if allow_refresh_with_expired_jwt_access_token? && (@jwt_refresh_route || request.path == jwt_refresh_path)
Hash[super].merge!(:verify_expiration=>false)
else
super
end
end
def jwt_refresh_token_match?(key)
# We don't need to match tokens if we are requiring a valid current access token
return true unless allow_refresh_with_expired_jwt_access_token?
# If allowing with expired jwt access token, check the expired session contains
# hmac matching submitted and active refresh token.
s = session[jwt_refresh_token_hmac_session_key].to_s
h = session[jwt_refresh_token_data_session_key].to_s + key
timing_safe_eql?(compute_hmac(h), s) || (hmac_secret_rotation? && timing_safe_eql?(compute_old_hmac(h), s))
end
def get_active_refresh_token(account_id, token_id)
jwt_refresh_token_account_ds(account_id).
where(Sequel::CURRENT_TIMESTAMP > jwt_refresh_token_deadline_column).
delete
jwt_refresh_token_account_token_ds(account_id, token_id).
get(jwt_refresh_token_key_column)
end
def jwt_refresh_token_account_ds(account_id)
jwt_refresh_token_ds.where(jwt_refresh_token_account_id_column => account_id)
end
def jwt_refresh_token_account_token_ds(account_id, token_id)
jwt_refresh_token_account_ds(account_id).
where(jwt_refresh_token_id_column=>token_id)
end
def jwt_refresh_token_ds
db[jwt_refresh_token_table]
end
def remove_jwt_refresh_token_key(token)
account_id, token_id, _ = _account_refresh_token_split(token)
jwt_refresh_token_account_token_ds(account_id, token_id).delete
end
def generate_refresh_token
hash = jwt_refresh_token_insert_hash
[account_id, jwt_refresh_token_ds.insert(hash), convert_token_key(hash[jwt_refresh_token_key_column])].join(token_separator)
end
def jwt_refresh_token_insert_hash
hash = {jwt_refresh_token_account_id_column => account_id, jwt_refresh_token_key_column => random_key}
set_deadline_value(hash, jwt_refresh_token_deadline_column, jwt_refresh_token_deadline_interval)
hash
end
def set_jwt_refresh_token_hmac_session_key(token)
if allow_refresh_with_expired_jwt_access_token?
key = _account_refresh_token_split(token).last
data = random_key
set_session_value(jwt_refresh_token_data_session_key, data)
set_session_value(jwt_refresh_token_hmac_session_key, compute_hmac(data + key))
end
end
def before_logout
if token = param_or_nil(jwt_refresh_token_key_param)
if token == 'all'
jwt_refresh_token_account_ds(session_value).delete
else
id, token_id, key = _account_refresh_token_split(token)
if id && token_id && key && (actual = get_active_refresh_token(session_value, token_id)) && timing_safe_eql?(key, convert_token_key(actual))
jwt_refresh_token_account_token_ds(id, token_id).delete
end
end
end
super if defined?(super)
end
def after_close_account
jwt_refresh_token_account_ds(account_id).delete
super if defined?(super)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/lockout.rb 0000664 0000000 0000000 00000025315 15157255142 0024111 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:lockout, :Lockout) do
depends :login, :email_base
loaded_templates %w'unlock-account-request unlock-account password-field unlock-account-email'
view 'unlock-account-request', 'Request Account Unlock', 'unlock_account_request'
view 'unlock-account', 'Unlock Account', 'unlock_account'
before 'unlock_account'
before 'unlock_account_request'
after 'unlock_account'
after 'unlock_account_request'
after 'account_lockout'
additional_form_tags 'unlock_account'
additional_form_tags 'unlock_account_request'
button 'Unlock Account', 'unlock_account'
button 'Request Account Unlock', 'unlock_account_request'
error_flash "There was an error unlocking your account", 'unlock_account'
error_flash "This account is currently locked out and cannot be logged in to", "login_lockout"
error_flash "An email has recently been sent to you with a link to unlock the account", 'unlock_account_email_recently_sent'
error_flash "There was an error unlocking your account: invalid or expired unlock account key", 'no_matching_unlock_account_key'
notice_flash "Your account has been unlocked", 'unlock_account'
notice_flash "An email has been sent to you with a link to unlock your account", 'unlock_account_request'
redirect :unlock_account
response :unlock_account
response :unlock_account_request
redirect(:unlock_account_request){default_post_email_redirect}
redirect(:unlock_account_email_recently_sent){default_post_email_redirect}
email :unlock_account, 'Unlock Account'
auth_value_method :unlock_account_autologin?, true
auth_value_method :max_invalid_logins, 100
auth_value_method :account_login_failures_table, :account_login_failures
auth_value_method :account_login_failures_id_column, :id
auth_value_method :account_login_failures_number_column, :number
auth_value_method :account_lockouts_table, :account_lockouts
auth_value_method :account_lockouts_id_column, :id
auth_value_method :account_lockouts_key_column, :key
auth_value_method :account_lockouts_email_last_sent_column, :email_last_sent
auth_value_method :account_lockouts_deadline_column, :deadline
auth_value_method :account_lockouts_deadline_interval, {:days=>1}.freeze
translatable_method :unlock_account_explanatory_text, 'This account is currently locked out. You can unlock the account:
'
translatable_method :unlock_account_request_explanatory_text, 'This account is currently locked out. You can request that the account be unlocked:
'
auth_value_method :unlock_account_key_param, 'key'
auth_value_method :unlock_account_requires_password?, false
auth_value_method :unlock_account_skip_resend_email_within, 300
session_key :unlock_account_session_key, :unlock_account_key
auth_methods(
:clear_invalid_login_attempts,
:generate_unlock_account_key,
:get_unlock_account_key,
:get_unlock_account_email_last_sent,
:invalid_login_attempted,
:locked_out?,
:set_unlock_account_email_last_sent,
:unlock_account_email_link,
:unlock_account,
:unlock_account_key
)
auth_private_methods :account_from_unlock_key
internal_request_method(:lock_account)
internal_request_method(:unlock_account_request)
internal_request_method(:unlock_account)
route(:unlock_account_request) do |r|
check_already_logged_in
before_unlock_account_request_route
r.post do
if account_from_login(login_param_value) && get_unlock_account_key
if unlock_account_email_recently_sent?
set_redirect_error_flash unlock_account_email_recently_sent_error_flash
redirect unlock_account_email_recently_sent_redirect
end
@unlock_account_key_value = get_unlock_account_key
transaction do
before_unlock_account_request
set_unlock_account_email_last_sent
send_unlock_account_email
after_unlock_account_request
end
unlock_account_request_response
else
set_redirect_error_status(no_matching_login_error_status)
set_error_reason :no_matching_login
set_redirect_error_flash no_matching_login_message.to_s.capitalize
redirect unlock_account_request_redirect
end
end
end
route(:unlock_account) do |r|
check_already_logged_in
before_unlock_account_route
r.get do
if key = param_or_nil(unlock_account_key_param)
set_session_value(unlock_account_session_key, key)
redirect(r.path)
end
if (key = session[unlock_account_session_key]) && account_from_unlock_key(key)
unlock_account_view
else
remove_session_value(unlock_account_session_key)
set_redirect_error_flash no_matching_unlock_account_key_error_flash
redirect require_login_redirect
end
end
r.post do
key = session[unlock_account_session_key] || param(unlock_account_key_param)
unless account_from_unlock_key(key)
set_redirect_error_status invalid_key_error_status
set_error_reason :invalid_unlock_account_key
set_redirect_error_flash no_matching_unlock_account_key_error_flash
redirect unlock_account_request_redirect
end
if !unlock_account_requires_password? || password_match?(param(password_param))
transaction do
before_unlock_account
unlock_account
clear_tokens(:unlock_account)
after_unlock_account
if unlock_account_autologin?
autologin_session('unlock_account')
end
end
remove_session_value(unlock_account_session_key)
unlock_account_response
else
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
set_field_error(password_param, invalid_password_message)
set_error_flash unlock_account_error_flash
unlock_account_view
end
end
end
def locked_out?
if t = convert_timestamp(account_lockouts_ds.get(account_lockouts_deadline_column))
if Time.now < t
true
else
unlock_account
false
end
else
false
end
end
def unlock_account
transaction do
remove_lockout_metadata
end
end
def clear_invalid_login_attempts
unlock_account
end
def _setup_account_lockouts_hash(account_id, key)
hash = {account_lockouts_id_column=>account_id, account_lockouts_key_column=>key}
set_deadline_value(hash, account_lockouts_deadline_column, account_lockouts_deadline_interval)
hash
end
def invalid_login_attempted
ds = account_login_failures_ds.
where(account_login_failures_id_column=>account_id)
number = if db.database_type == :postgres
ds.returning(account_login_failures_number_column).
with_sql(:update_sql, account_login_failures_number_column=>Sequel.expr(account_login_failures_number_column)+1).
single_value
else
# :nocov:
if ds.update(account_login_failures_number_column=>Sequel.expr(account_login_failures_number_column)+1) > 0
ds.get(account_login_failures_number_column)
end
# :nocov:
end
unless number
# Ignoring the violation is safe here. It may allow slightly more than max_invalid_logins invalid logins before
# lockout, but allowing a few extra is OK if the race is lost.
ignore_uniqueness_violation{account_login_failures_ds.insert(account_login_failures_id_column=>account_id)}
number = 1
end
if number >= max_invalid_logins
@unlock_account_key_value = generate_unlock_account_key
hash = _setup_account_lockouts_hash(account_id, unlock_account_key_value)
if e = raised_uniqueness_violation{account_lockouts_ds.insert(hash)}
# If inserting into the lockout table raises a violation, we should just be able to pull the already inserted
# key out of it. If that doesn't return a valid key, we should reraise the error.
raise e unless @unlock_account_key_value = account_lockouts_ds.get(account_lockouts_key_column)
after_account_lockout
show_lockout_page
else
after_account_lockout
e
end
end
end
def get_unlock_account_key
account_lockouts_ds.get(account_lockouts_key_column)
end
def account_from_unlock_key(key)
@account = _account_from_unlock_key(key)
end
def unlock_account_email_link
token_link(unlock_account_route, unlock_account_key_param, unlock_account_key_value)
end
def get_unlock_account_email_last_sent
if column = account_lockouts_email_last_sent_column
if ts = account_lockouts_ds.get(column)
convert_timestamp(ts)
end
end
end
def set_unlock_account_email_last_sent
account_lockouts_ds.update(account_lockouts_email_last_sent_column=>Sequel::CURRENT_TIMESTAMP) if account_lockouts_email_last_sent_column
end
def unlock_account_email_recently_sent?
(email_last_sent = get_unlock_account_email_last_sent) && (Time.now - email_last_sent < unlock_account_skip_resend_email_within)
end
def clear_tokens(reason)
super
account_lockouts_ds.update(account_lockouts_key_column => generate_unlock_account_key)
end
private
attr_reader :unlock_account_key_value
def before_login_attempt
if locked_out?
show_lockout_page
end
super
end
def after_login
clear_invalid_login_attempts
super
end
def after_login_failure
invalid_login_attempted
super
end
def after_close_account
remove_lockout_metadata
super if defined?(super)
end
def generate_unlock_account_key
random_key
end
def remove_lockout_metadata
account_login_failures_ds.delete
account_lockouts_ds.delete
end
def show_lockout_page
set_response_error_reason_status(:account_locked_out, lockout_error_status)
set_error_flash login_lockout_error_flash
return_response unlock_account_request_view
end
def use_date_arithmetic?
super || db.database_type == :mysql
end
def account_login_failures_ds
db[account_login_failures_table].where(account_login_failures_id_column=>account_id)
end
def account_lockouts_ds(id=account_id)
db[account_lockouts_table].where(account_lockouts_id_column=>id)
end
def _account_from_unlock_key(token)
account_from_key(token){|id| account_lockouts_ds(id).get(account_lockouts_key_column)}
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/login.rb 0000664 0000000 0000000 00000011430 15157255142 0023532 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:login, :Login) do
notice_flash "You have been logged in"
notice_flash "Login recognized, please enter your password", "need_password"
error_flash "There was an error logging in"
loaded_templates %w'login login-form login-form-footer multi-phase-login login-field password-field login-display'
view 'login', 'Login'
view 'multi-phase-login', 'Login', 'multi_phase_login'
additional_form_tags
button 'Login'
redirect
auth_value_method :login_error_status, 401
translatable_method :login_form_footer_links_heading, 'Other Options
'
auth_value_method :login_return_to_requested_location?, false
auth_value_method :login_return_to_requested_location_max_path_size, 2048
auth_value_method :use_multi_phase_login?, false
session_key :login_redirect_session_key, :login_redirect
auth_cached_method :multi_phase_login_forms
auth_cached_method :login_form_footer
auth_value_methods :login_return_to_requested_location_path
auth_private_methods(
:login_form_footer_links,
:login_response
)
internal_request_method
internal_request_method :valid_login_and_password?
route do |r|
check_already_logged_in
before_login_route
r.get do
login_view
end
r.post do
skip_error_flash = false
view = :login_view
catch_error do
unless account_from_login(login_param_value)
throw_error_reason(:no_matching_login, no_matching_login_error_status, login_param, no_matching_login_message)
end
before_login_attempt
unless open_account?
throw_error_reason(:unverified_account, unopen_account_error_status, login_param, unverified_account_message)
end
if use_multi_phase_login?
@valid_login_entered = true
view = :multi_phase_login_view
unless param_or_nil(password_param)
after_login_entered_during_multi_phase_login
skip_error_flash = true
next
end
end
unless password_match?(param(password_param))
after_login_failure
throw_error_reason(:invalid_password, login_error_status, password_param, invalid_password_message)
end
login('password')
end
set_error_flash login_error_flash unless skip_error_flash
send(view)
end
end
attr_reader :login_form_header
attr_reader :saved_login_redirect
private :saved_login_redirect
def login(auth_type)
@saved_login_redirect = remove_session_value(login_redirect_session_key)
transaction do
before_login
login_session(auth_type)
yield if block_given?
after_login
end
require_response(:_login_response)
end
def login_required
if login_return_to_requested_location? && (path = login_return_to_requested_location_path) && path.bytesize <= login_return_to_requested_location_max_path_size
set_session_value(login_redirect_session_key, path)
end
super
end
def login_return_to_requested_location_path
request.fullpath if request.get?
end
def after_login_entered_during_multi_phase_login
set_notice_now_flash need_password_notice_flash
if multi_phase_login_forms.length == 1 && (meth = multi_phase_login_forms[0][2])
send(meth)
end
end
def skip_login_field_on_login?
return false unless use_multi_phase_login?
valid_login_entered?
end
def skip_password_field_on_login?
return false unless use_multi_phase_login?
!valid_login_entered?
end
def valid_login_entered?
@valid_login_entered
end
def login_hidden_field
""
end
def login_form_footer_links
@login_form_footer_links ||= _filter_links(_login_form_footer_links)
end
def render_multi_phase_login_forms
multi_phase_login_forms.sort.map{|_, form, _| form}.join("\n")
end
def require_login_redirect
login_path
end
private
def _login_response
set_notice_flash login_notice_flash
redirect(saved_login_redirect || login_redirect)
end
def _login_form_footer_links
[]
end
def _multi_phase_login_forms
forms = []
forms << [10, render("login-form"), nil] if has_password?
forms
end
def _login_form_footer
return '' if _login_form_footer_links.empty?
render('login-form-footer')
end
def _login(auth_type)
warn("Deprecated #_login method called, use #login instead.")
login(auth_type)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/login_password_requirements_base.rb 0000664 0000000 0000000 00000015452 15157255142 0031261 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:login_password_requirements_base, :LoginPasswordRequirementsBase) do
translatable_method :already_an_account_with_this_login_message, 'already an account with this login'
auth_value_method :login_confirm_param, 'login-confirm'
auth_value_method :login_email_regexp, /\A[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+\z/
auth_value_method :login_minimum_length, 3
auth_value_method :login_maximum_length, 255
auth_value_method :login_maximum_bytes, 255
translatable_method :login_not_valid_email_message, 'not a valid email address'
translatable_method :logins_do_not_match_message, 'logins do not match'
auth_value_method :password_confirm_param, 'password-confirm'
auth_value_method :password_minimum_length, 6
auth_value_method :password_maximum_bytes, nil
auth_value_method :password_maximum_length, nil
translatable_method :passwords_do_not_match_message, 'passwords do not match'
auth_value_method :require_email_address_logins?, true
auth_value_method :require_login_confirmation?, true
auth_value_method :require_password_confirmation?, true
translatable_method :same_as_existing_password_message, "invalid password, same as current password"
translatable_method :contains_null_byte_message, 'contains null byte'
auth_value_methods(
:login_confirm_label,
:login_does_not_meet_requirements_message,
:login_too_long_message,
:login_too_many_bytes_message,
:login_too_short_message,
:password_confirm_label,
:password_does_not_meet_requirements_message,
:password_hash_cost,
:password_too_long_message,
:password_too_many_bytes_message,
:password_too_short_message
)
auth_methods(
:login_confirmation_matches?,
:login_meets_requirements?,
:login_valid_email?,
:password_hash,
:password_meets_requirements?,
:set_password
)
def login_confirm_label
"Confirm #{login_label}"
end
def password_confirm_label
"Confirm #{password_label}"
end
def login_meets_requirements?(login)
login_meets_length_requirements?(login) && \
login_meets_email_requirements?(login)
end
def password_meets_requirements?(password)
password_meets_length_requirements?(password) && \
password_does_not_contain_null_byte?(password)
end
def set_password(password)
hash = password_hash(password)
if account_password_hash_column
update_account(account_password_hash_column=>hash)
elsif password_hash_ds.update(password_hash_column=>hash) == 0
# This shouldn't raise a uniqueness error, as the update should only fail for a new user,
# and an existing user should always have a valid password hash row. If this does
# fail, retrying it will cause problems, it will override a concurrently running update
# with potentially a different password.
db[password_hash_table].insert(password_hash_id_column=>account_id, password_hash_column=>hash)
end
hash
end
def password_hash(password)
BCrypt::Password.create(password, :cost=>password_hash_cost)
end
private
attr_reader :login_requirement_message
attr_reader :password_requirement_message
def password_does_not_meet_requirements_message
"invalid password, does not meet requirements#{" (#{password_requirement_message})" if password_requirement_message}"
end
def password_too_long_message
"maximum #{password_maximum_length} characters"
end
def password_too_many_bytes_message
"maximum #{password_maximum_bytes} bytes"
end
def password_too_short_message
"minimum #{password_minimum_length} characters"
end
def set_password_requirement_error_message(reason, message)
set_error_reason(reason)
@password_requirement_message = message
end
def login_does_not_meet_requirements_message
"invalid login#{", #{login_requirement_message}" if login_requirement_message}"
end
def login_too_long_message
"maximum #{login_maximum_length} characters"
end
def login_too_many_bytes_message
"maximum #{login_maximum_bytes} bytes"
end
def login_too_short_message
"minimum #{login_minimum_length} characters"
end
def set_login_requirement_error_message(reason, message)
set_error_reason(reason)
@login_requirement_message = message
end
if RUBY_VERSION >= '2.4'
def login_confirmation_matches?(login, login_confirmation)
login.casecmp?(login_confirmation)
end
# :nocov:
else
def login_confirmation_matches?(login, login_confirmation)
login.casecmp(login_confirmation) == 0
end
# :nocov:
end
def login_meets_length_requirements?(login)
if login_minimum_length > login.length
set_login_requirement_error_message(:login_too_short, login_too_short_message)
false
elsif login_maximum_length < login.length
set_login_requirement_error_message(:login_too_long, login_too_long_message)
false
elsif login_maximum_bytes < login.bytesize
set_login_requirement_error_message(:login_too_many_bytes, login_too_many_bytes_message)
false
else
true
end
end
def login_meets_email_requirements?(login)
return true unless require_email_address_logins?
return true if login_valid_email?(login)
set_login_requirement_error_message(:login_not_valid_email, login_not_valid_email_message)
return false
end
def login_valid_email?(login)
login =~ login_email_regexp
end
def password_meets_length_requirements?(password)
if password_minimum_length > password.length
set_password_requirement_error_message(:password_too_short, password_too_short_message)
false
elsif password_maximum_length && password_maximum_length < password.length
set_password_requirement_error_message(:password_too_long, password_too_long_message)
false
elsif password_maximum_bytes && password_maximum_bytes < password.bytesize
set_password_requirement_error_message(:password_too_many_bytes, password_too_many_bytes_message)
false
else
true
end
end
def password_does_not_contain_null_byte?(password)
return true unless password.include?("\0")
set_password_requirement_error_message(:password_contains_null_byte, contains_null_byte_message)
false
end
if ENV['RACK_ENV'] == 'test'
def password_hash_cost
BCrypt::Engine::MIN_COST
end
else
# :nocov:
def password_hash_cost
BCrypt::Engine::DEFAULT_COST
end
# :nocov:
end
def extract_password_hash_cost(hash)
hash[4, 2].to_i
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/logout.rb 0000664 0000000 0000000 00000001170 15157255142 0023733 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:logout, :Logout) do
notice_flash "You have been logged out"
loaded_templates %w'logout'
view 'logout', 'Logout'
additional_form_tags
before
after
button 'Logout'
redirect{require_login_redirect}
response
auth_methods :logout
route do |r|
before_logout_route
r.get do
logout_view
end
r.post do
transaction do
before_logout
logout
after_logout
end
logout_response
end
end
def logout
clear_session
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/otp.rb 0000664 0000000 0000000 00000030701 15157255142 0023226 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'rotp'
require 'rqrcode'
module Rodauth
Feature.define(:otp, :Otp) do
depends :two_factor_base
additional_form_tags 'otp_disable'
additional_form_tags 'otp_auth'
additional_form_tags 'otp_setup'
after 'otp_authentication_failure'
after 'otp_disable'
after 'otp_setup'
before 'otp_authentication'
before 'otp_setup'
before 'otp_disable'
button 'Authenticate Using TOTP', 'otp_auth'
button 'Disable TOTP Authentication', 'otp_disable'
button 'Setup TOTP Authentication', 'otp_setup'
error_flash "Error disabling TOTP authentication", 'otp_disable'
error_flash "Error logging in via TOTP authentication", 'otp_auth'
error_flash "Error setting up TOTP authentication", 'otp_setup'
error_flash "You have already setup TOTP authentication", 'otp_already_setup'
error_flash "TOTP authentication code use locked out due to numerous failures", 'otp_lockout'
notice_flash "TOTP authentication has been disabled", 'otp_disable'
notice_flash "TOTP authentication is now setup", 'otp_setup'
redirect :otp_disable
redirect :otp_already_setup
redirect :otp_setup
response :otp_disable
response :otp_setup
redirect(:otp_lockout){two_factor_auth_required_redirect}
loaded_templates %w'otp-disable otp-auth otp-setup otp-auth-code-field password-field'
view 'otp-disable', 'Disable TOTP Authentication', 'otp_disable'
view 'otp-auth', 'Enter Authentication Code', 'otp_auth'
view 'otp-setup', 'Setup TOTP Authentication', 'otp_setup'
translatable_method :otp_auth_link_text, "Authenticate Using TOTP"
translatable_method :otp_setup_link_text, "Setup TOTP Authentication"
translatable_method :otp_disable_link_text, "Disable TOTP Authentication"
auth_value_method :otp_auth_failures_limit, 5
translatable_method :otp_auth_label, 'Authentication Code'
auth_value_method :otp_auth_param, 'otp'
auth_value_method :otp_class, ROTP::TOTP
auth_value_method :otp_digits, nil
auth_value_method :otp_drift, 30
auth_value_method :otp_interval, nil
translatable_method :otp_invalid_auth_code_message, "Invalid authentication code"
translatable_method :otp_invalid_secret_message, "invalid secret"
auth_value_method :otp_keys_column, :key
auth_value_method :otp_keys_id_column, :id
auth_value_method :otp_keys_failures_column, :num_failures
auth_value_method :otp_keys_table, :account_otp_keys
auth_value_method :otp_keys_last_use_column, :last_use
translatable_method :otp_provisioning_uri_label, 'Provisioning URL'
translatable_method :otp_secret_label, 'Secret'
auth_value_method :otp_setup_param, 'otp_secret'
auth_value_method :otp_setup_raw_param, 'otp_raw_secret'
translatable_method :otp_auth_form_footer, ''
auth_cached_method :otp_key
auth_cached_method :otp
private :otp
auth_value_methods(
:otp_issuer,
:otp_keys_use_hmac?
)
auth_methods(
:otp_available?,
:otp_exists?,
:otp_last_use,
:otp_locked_out?,
:otp_new_secret,
:otp_provisioning_name,
:otp_provisioning_uri,
:otp_qr_code,
:otp_record_authentication_failure,
:otp_remove,
:otp_remove_auth_failures,
:otp_update_last_use,
:otp_valid_code?,
:otp_valid_key?
)
auth_private_methods(
:otp_add_key,
:otp_tmp_key,
:otp_valid_code_for_old_secret
)
internal_request_method :otp_setup_params
internal_request_method :otp_setup
internal_request_method :otp_auth
internal_request_method :valid_otp_auth?
internal_request_method :otp_disable
route(:otp_auth) do |r|
require_login
require_account_session
require_two_factor_not_authenticated('totp')
require_otp_setup
if otp_locked_out?
set_response_error_reason_status(:otp_locked_out, lockout_error_status)
set_redirect_error_flash otp_lockout_error_flash
redirect otp_lockout_redirect
end
before_otp_auth_route
r.get do
otp_auth_view
end
r.post do
if otp_valid_code?(param(otp_auth_param)) && otp_update_last_use
before_otp_authentication
two_factor_authenticate('totp')
end
otp_record_authentication_failure
after_otp_authentication_failure
set_response_error_reason_status(:invalid_otp_auth_code, invalid_key_error_status)
set_field_error(otp_auth_param, otp_invalid_auth_code_message)
set_error_flash otp_auth_error_flash
otp_auth_view
end
end
route(:otp_setup) do |r|
require_account
if otp_exists?
set_redirect_error_flash otp_already_setup_error_flash
redirect otp_already_setup_redirect
end
before_otp_setup_route
r.get do
otp_tmp_key(otp_new_secret)
otp_setup_view
end
r.post do
secret = param(otp_setup_param)
catch_error do
unless otp_valid_key?(secret)
otp_tmp_key(otp_new_secret)
throw_error_reason(:invalid_otp_secret, invalid_field_error_status, otp_setup_param, otp_invalid_secret_message)
end
if otp_keys_use_hmac?
otp_tmp_key(param(otp_setup_raw_param))
else
otp_tmp_key(secret)
end
unless two_factor_password_match?(param(password_param))
throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
end
unless otp_valid_code?(param(otp_auth_param))
throw_error_reason(:invalid_otp_auth_code, invalid_key_error_status, otp_auth_param, otp_invalid_auth_code_message)
end
transaction do
before_otp_setup
otp_add_key
unless two_factor_authenticated?
two_factor_update_session('totp')
end
after_otp_setup
end
otp_setup_response
end
set_error_flash otp_setup_error_flash
otp_setup_view
end
end
route(:otp_disable) do |r|
require_account
require_otp_setup
before_otp_disable_route
r.get do
otp_disable_view
end
r.post do
if two_factor_password_match?(param(password_param))
transaction do
before_otp_disable
otp_remove
if two_factor_login_type_match?('totp')
two_factor_remove_session('totp')
end
after_otp_disable
end
otp_disable_response
end
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
set_field_error(password_param, invalid_password_message)
set_error_flash otp_disable_error_flash
otp_disable_view
end
end
def two_factor_remove
super
otp_remove
end
def two_factor_remove_auth_failures
super
otp_remove_auth_failures
end
def require_otp_setup
unless otp_exists?
set_redirect_error_status(two_factor_not_setup_error_status)
set_error_reason :two_factor_not_setup
set_redirect_error_flash two_factor_not_setup_error_flash
redirect two_factor_need_setup_redirect
end
end
def otp_available?
otp_exists? && !otp_locked_out?
end
def otp_exists?
!otp_key.nil?
end
def otp_valid_code?(ot_pass)
if _otp_valid_code?(ot_pass, otp)
true
elsif hmac_secret_rotation? && _otp_valid_code?(ot_pass, _otp_for_key(otp_hmac_old_secret(otp_key)))
_otp_valid_code_for_old_secret
true
else
false
end
end
def _otp_valid_code?(ot_pass, otp)
return false unless otp_exists?
ot_pass = ot_pass.gsub(/\s+/, '')
if drift = otp_drift
if otp.respond_to?(:verify_with_drift)
# :nocov:
otp.verify_with_drift(ot_pass, drift)
# :nocov:
else
otp.verify(ot_pass, :drift_behind=>drift, :drift_ahead=>drift)
end
else
otp.verify(ot_pass)
end
end
def otp_remove
otp_key_ds.delete
@otp_key = nil
end
def otp_add_key
_otp_add_key(otp_key)
super if defined?(super)
end
def otp_update_last_use
otp_key_ds.
where(Sequel.date_add(otp_keys_last_use_column, :seconds=>_otp_interval) < Sequel::CURRENT_TIMESTAMP).
update(otp_keys_last_use_column=>Sequel::CURRENT_TIMESTAMP) == 1
end
def otp_last_use
convert_timestamp(otp_key_ds.get(otp_keys_last_use_column))
end
def otp_record_authentication_failure
otp_key_ds.update(otp_keys_failures_column=>Sequel.identifier(otp_keys_failures_column) + 1)
end
def otp_remove_auth_failures
otp_key_ds.update(otp_keys_failures_column=>0)
end
def otp_locked_out?
otp_key_ds.get(otp_keys_failures_column) >= otp_auth_failures_limit
end
def otp_provisioning_uri
otp.provisioning_uri(otp_provisioning_name)
end
def otp_issuer
domain
end
def otp_provisioning_name
account[login_column]
end
def otp_qr_code
svg = RQRCode::QRCode.new(otp_provisioning_uri).as_svg(:module_size=>8, :viewbox=>true, :use_path=>true, :fill=>"fff")
svg.sub(/\A<\?xml version="1\.0" standalone="yes"\?>/, '')
end
def otp_user_key
@otp_user_key ||= if otp_keys_use_hmac?
otp_hmac_secret(otp_key)
else
otp_key
end
end
def otp_keys_use_hmac?
!!hmac_secret
end
def possible_authentication_methods
methods = super
methods << 'totp' if otp_exists? && !@otp_tmp_key
methods
end
private
def _two_factor_auth_links
links = super
links << [20, otp_auth_path, otp_auth_link_text] if show_otp_auth_link?
links
end
def _two_factor_setup_links
links = super
links << [20, otp_setup_path, otp_setup_link_text] unless otp_exists?
links
end
def _two_factor_remove_links
links = super
links << [20, otp_disable_path, otp_disable_link_text] if otp_exists?
links
end
def _two_factor_remove_all_from_session
two_factor_remove_session('totp')
super
end
def clear_cached_otp
remove_instance_variable(:@otp) if defined?(@otp)
end
def otp_tmp_key(secret)
_otp_tmp_key(secret)
clear_cached_otp
end
def otp_hmac_secret(key)
base32_encode(compute_raw_hmac(ROTP::Base32.decode(key)), key.bytesize)
end
def otp_hmac_old_secret(key)
base32_encode(compute_raw_hmac_with_secret(ROTP::Base32.decode(key), hmac_old_secret), key.bytesize)
end
def otp_valid_key?(secret)
return false unless secret =~ /\A([a-z2-7]{16}|[a-z2-7]{32})\z/
if otp_keys_use_hmac?
# Purposely do not allow creating new OTPs with old secrets,
# since OTP rotation is difficult. The user will get shown
# the same page with an updated secret, which they can submit
# to setup OTP.
timing_safe_eql?(otp_hmac_secret(param(otp_setup_raw_param)), secret)
else
true
end
end
if ROTP::Base32.respond_to?(:random_base32)
def otp_new_secret
ROTP::Base32.random_base32.downcase
end
else
# :nocov:
def otp_new_secret
ROTP::Base32.random.downcase
end
# :nocov:
end
def base32_encode(data, length)
chars = 'abcdefghijklmnopqrstuvwxyz234567'
length.times.map{|i|chars[data[i].ord % 32]}.join
end
def _otp_tmp_key(secret)
@otp_tmp_key = true
@otp_user_key = nil
@otp_key = secret
end
def _otp_interval
otp_interval || 30
end
# Called for valid OTP codes for old secrets
def _otp_valid_code_for_old_secret
end
def _otp_add_key(secret)
# Uniqueness errors can't be handled here, as we can't be sure the secret provided
# is the same as the current secret.
otp_key_ds.insert(otp_keys_id_column=>session_value, otp_keys_column=>secret)
end
def _otp_key
@otp_user_key = nil
otp_key_ds.get(otp_keys_column)
end
def _otp_for_key(key)
otp_class.new(key, :issuer=>otp_issuer, :digits=>otp_digits, :interval=>otp_interval)
end
def _otp
_otp_for_key(otp_user_key)
end
def otp_key_ds
db[otp_keys_table].where(otp_keys_id_column=>session_value)
end
def show_otp_auth_link?
otp_available?
end
def use_date_arithmetic?
true
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/otp_lockout_email.rb 0000664 0000000 0000000 00000002160 15157255142 0026133 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:otp_lockout_email, :OtpLockoutEmail) do
depends :otp_unlock, :email_base
loaded_templates %w'otp-locked-out-email otp-unlocked-email otp-unlock-failed-email'
email :otp_locked_out, 'TOTP Authentication Locked Out', :translatable=>true
email :otp_unlocked, 'TOTP Authentication Unlocked', :translatable=>true
email :otp_unlock_failed, 'TOTP Authentication Unlocking Failed', :translatable=>true
auth_value_method :send_otp_locked_out_email?, true
auth_value_method :send_otp_unlocked_email?, true
auth_value_method :send_otp_unlock_failed_email?, true
private
def after_otp_authentication_failure
super
if otp_locked_out? && send_otp_locked_out_email?
send_otp_locked_out_email
end
end
def after_otp_unlock_auth_success
super
if !otp_locked_out? && send_otp_unlocked_email?
send_otp_unlocked_email
end
end
def after_otp_unlock_auth_failure
super
if send_otp_unlock_failed_email?
send_otp_unlock_failed_email
end
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/otp_modify_email.rb 0000664 0000000 0000000 00000001001 15157255142 0025733 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:otp_modify_email, :OtpModifyEmail) do
depends :otp, :email_base
loaded_templates %w'otp-setup-email otp-disabled-email'
email :otp_setup, 'TOTP Authentication Setup', :translatable=>true
email :otp_disabled, 'TOTP Authentication Disabled', :translatable=>true
private
def after_otp_setup
super
send_otp_setup_email
end
def after_otp_disable
super
send_otp_disabled_email
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/otp_unlock.rb 0000664 0000000 0000000 00000022442 15157255142 0024604 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:otp_unlock, :OtpUnlock) do
depends :otp
before 'otp_unlock_attempt'
after 'otp_unlock_auth_success'
after 'otp_unlock_auth_failure'
after 'otp_unlock_not_yet_available'
error_flash "TOTP authentication is not currently locked out", 'otp_unlock_not_locked_out'
error_flash "TOTP invalid authentication", 'otp_unlock_auth_failure'
error_flash "Deadline past for unlocking TOTP authentication", 'otp_unlock_auth_deadline_passed'
error_flash "TOTP unlock attempt not yet available", 'otp_unlock_auth_not_yet_available'
notice_flash "TOTP authentication unlocked", 'otp_unlocked'
notice_flash "TOTP successful authentication, more successful authentication needed to unlock", 'otp_unlock_auth_success'
redirect :otp_unlock_not_locked_out
redirect :otp_unlocked
additional_form_tags
button 'Authenticate Using TOTP to Unlock', 'otp_unlock'
auth_value_method :otp_unlock_auth_deadline_passed_error_status, 403
auth_value_method :otp_unlock_auth_failure_cooldown_seconds, 900
auth_value_method :otp_unlock_auth_failure_error_status, 403
auth_value_method :otp_unlock_auth_not_yet_available_error_status, 403
auth_value_method :otp_unlock_auths_required, 3
auth_value_method :otp_unlock_deadline_seconds, 900
auth_value_method :otp_unlock_id_column, :id
auth_value_method :otp_unlock_next_auth_attempt_after_column, :next_auth_attempt_after
auth_value_method :otp_unlock_not_locked_out_error_status, 403
auth_value_method :otp_unlock_num_successes_column, :num_successes
auth_value_method :otp_unlock_table, :account_otp_unlocks
translatable_method :otp_unlock_consecutive_successes_label, 'Consecutive successful authentications'
translatable_method :otp_unlock_form_footer, ''
translatable_method :otp_unlock_next_auth_attempt_label, 'Can attempt next authentication after'
translatable_method :otp_unlock_next_auth_attempt_refresh_label, 'Page will automatically refresh when authentication is possible.'
translatable_method :otp_unlock_next_auth_deadline_label, 'Deadline for next authentication'
translatable_method :otp_unlock_required_consecutive_successes_label, 'Required consecutive successful authentications to unlock'
loaded_templates %w'otp-unlock otp-unlock-not-available'
view 'otp-unlock', 'Unlock TOTP Authentication', 'otp_unlock'
view 'otp-unlock-not-available', 'Must Wait to Unlock TOTP Authentication', 'otp_unlock_not_available'
auth_methods(
:otp_unlock_auth_failure,
:otp_unlock_auth_success,
:otp_unlock_available?,
:otp_unlock_deadline_passed?,
:otp_unlock_not_available_set_refresh_header,
:otp_unlock_refresh_tag,
)
route(:otp_unlock) do |r|
require_login
require_account_session
require_otp_setup
unless otp_locked_out?
set_response_error_reason_status(:otp_not_locked_out, otp_unlock_not_locked_out_error_status)
set_redirect_error_flash otp_unlock_not_locked_out_error_flash
redirect otp_unlock_not_locked_out_redirect
end
before_otp_unlock_route
r.get do
if otp_unlock_available?
otp_unlock_view
else
otp_unlock_not_available_set_refresh_header
otp_unlock_not_available_view
end
end
r.post do
db.transaction do
if otp_unlock_deadline_passed?
set_response_error_reason_status(:otp_unlock_deadline_passed, otp_unlock_auth_deadline_passed_error_status)
set_redirect_error_flash otp_unlock_auth_deadline_passed_error_flash
elsif !otp_unlock_available?
after_otp_unlock_not_yet_available
set_response_error_reason_status(:otp_unlock_not_yet_available, otp_unlock_auth_not_yet_available_error_status)
set_redirect_error_flash otp_unlock_auth_not_yet_available_error_flash
else
before_otp_unlock_attempt
if otp_valid_code?(param(otp_auth_param))
otp_unlock_auth_success
after_otp_unlock_auth_success
unless otp_locked_out?
set_notice_flash otp_unlocked_notice_flash
redirect otp_unlocked_redirect
end
set_notice_flash otp_unlock_auth_success_notice_flash
else
otp_unlock_auth_failure
after_otp_unlock_auth_failure
set_response_error_reason_status(:otp_unlock_auth_failure, otp_unlock_auth_failure_error_status)
set_redirect_error_flash otp_unlock_auth_failure_error_flash
end
end
end
redirect request.path
end
end
def otp_unlock_available?
if otp_unlock_data
next_auth_attempt_after = otp_unlock_next_auth_attempt_after
current_timestamp = Time.now
if (next_auth_attempt_after < current_timestamp - otp_unlock_deadline_seconds)
# Unlock process not fully completed within deadline, reset process
otp_unlock_reset
true
else
if next_auth_attempt_after > current_timestamp
# If next auth attempt after timestamp is in the future, that means the next
# unlock attempt cannot happen until then.
false
else
if otp_unlock_num_successes == 0
# 0 value indicates previous attempt was a failure. Since failure cooldown
# period has passed, reset process so user gets full deadline period
otp_unlock_reset
end
true
end
end
else
# No row means no unlock attempts yet (or previous attempt was more than the
# deadline account, so unlocking is available
true
end
end
def otp_unlock_auth_failure
h = {
otp_unlock_num_successes_column=>0,
otp_unlock_next_auth_attempt_after_column=>Sequel.date_add(Sequel::CURRENT_TIMESTAMP, :seconds=>otp_unlock_auth_failure_cooldown_seconds)
}
if otp_unlock_ds.update(h) == 0
h[otp_unlock_id_column] = session_value
# If row already exists when inserting, no need to do anything
raises_uniqueness_violation?{otp_unlock_ds.insert(h)}
end
end
def otp_unlock_auth_success
deadline = Sequel.date_add(Sequel::CURRENT_TIMESTAMP, :seconds=>otp_unlock_success_cooldown_seconds)
# Add WHERE to avoid possible race condition when multiple unlock auth requests
# are sent at the same time (only the first should increment num successes).
if otp_unlock_ds.
where(Sequel[otp_unlock_next_auth_attempt_after_column] < Sequel::CURRENT_TIMESTAMP).
update(
otp_unlock_num_successes_column=>Sequel[otp_unlock_num_successes_column]+1,
otp_unlock_next_auth_attempt_after_column=>deadline
) == 0
# Ignore uniqueness errors when inserting after a failed update,
# which could be caused due to the race condition mentioned above.
raises_uniqueness_violation? do
otp_unlock_ds.insert(
otp_unlock_id_column=>session_value,
otp_unlock_next_auth_attempt_after_column=>deadline
)
end
end
@otp_unlock_data = nil
# :nocov:
if otp_unlock_data
# :nocov:
if otp_unlock_num_successes >= otp_unlock_auths_required
# At least the requisite number of consecutive successful unlock
# authentications. Unlock OTP authentication.
otp_key_ds.update(otp_keys_failures_column => 0)
# Remove OTP unlock metadata when unlocking OTP authentication
otp_unlock_reset
# else
# # Still need additional consecutive successful unlock attempts.
end
# else
# # if row isn't available, probably the process was reset during this,
# # and it's safe to do nothing in that case.
end
end
def otp_unlock_deadline_passed?
otp_unlock_data ? (otp_unlock_next_auth_attempt_after < Time.now - otp_unlock_deadline_seconds) : false
end
def otp_unlock_refresh_tag
# RODAUTH3: Remove
""
end
def otp_lockout_redirect
otp_unlock_path
end
def otp_unlock_next_auth_attempt_after
if otp_unlock_data
convert_timestamp(otp_unlock_data[otp_unlock_next_auth_attempt_after_column])
else
Time.now
end
end
def otp_unlock_deadline
otp_unlock_next_auth_attempt_after + otp_unlock_deadline_seconds
end
def otp_unlock_num_successes
otp_unlock_data ? otp_unlock_data[otp_unlock_num_successes_column] : 0
end
def otp_unlock_not_available_set_refresh_header
response.headers["refresh"] = ((otp_unlock_next_auth_attempt_after - Time.now).to_i + 1).to_s
end
private
def show_otp_auth_link?
super || (otp_exists? && otp_locked_out?)
end
def otp_unlock_data
@otp_unlock_data ||= otp_unlock_ds.first
end
def otp_unlock_success_cooldown_seconds
(_otp_interval+(otp_drift||0))*2
end
def otp_unlock_reset
otp_unlock_ds.delete
@otp_unlock_data = nil
end
def otp_unlock_ds
db[otp_unlock_table].where(otp_unlock_id_column=>session_value)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/password_complexity.rb 0000664 0000000 0000000 00000006741 15157255142 0026552 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:password_complexity, :PasswordComplexity) do
depends :login_password_requirements_base
auth_value_method :password_dictionary_file, nil
auth_value_method :password_dictionary, nil
auth_value_method :password_character_groups, [/[a-z]/, /[A-Z]/, /\d/, /[^a-zA-Z\d]/]
auth_value_method :password_min_groups, 3
auth_value_method :password_max_length_for_groups_check, 11
auth_value_method :password_max_repeating_characters, 3
auth_value_method :password_invalid_pattern, Regexp.union([/qwerty/i, /azerty/i, /asdf/i, /zxcv/i] + (1..8).map{|i| /#{i}#{i+1}#{(i+2)%10}/})
translatable_method :password_not_enough_character_groups_message, "does not include uppercase letters, lowercase letters, and numbers"
translatable_method :password_invalid_pattern_message, "includes common character sequence"
translatable_method :password_in_dictionary_message, "is a word in a dictionary"
translatable_method :password_too_many_repeating_characters_message, "contains too many of the same character in a row"
def password_meets_requirements?(password)
super && \
password_has_enough_character_groups?(password) && \
password_has_no_invalid_pattern?(password) && \
password_not_too_many_repeating_characters?(password) && \
password_not_in_dictionary?(password)
end
def post_configure
super
return if method(:password_dictionary).owner != Rodauth::PasswordComplexity
case password_dictionary_file
when false
# nothing
when nil
default_dictionary_file = '/usr/share/dict/words'
# :nocov:
if File.file?(default_dictionary_file)
# :nocov:
words = File.read(default_dictionary_file)
end
else
words = File.read(password_dictionary_file)
end
return unless words
require 'set'
dict = Set.new(words.downcase.split)
self.class.send(:define_method, :password_dictionary){dict}
end
private
def password_has_enough_character_groups?(password)
return true if password.length > password_max_length_for_groups_check
return true if password_character_groups.select{|re| password =~ re}.length >= password_min_groups
set_password_requirement_error_message(:not_enough_character_groups_in_password, password_not_enough_character_groups_message)
false
end
def password_has_no_invalid_pattern?(password)
return true unless password_invalid_pattern
return true if password !~ password_invalid_pattern
set_password_requirement_error_message(:invalid_password_pattern, password_invalid_pattern_message)
false
end
def password_not_too_many_repeating_characters?(password)
return true if password_max_repeating_characters < 2
return true if password !~ /(.)(\1){#{password_max_repeating_characters-1}}/
set_password_requirement_error_message(:too_many_repeating_characters_in_password, password_too_many_repeating_characters_message)
false
end
def password_not_in_dictionary?(password)
return true unless dict = password_dictionary
return true unless password =~ /\A(?:\d*)([A-Za-z!@$+|][A-Za-z!@$+|0134578]+[A-Za-z!@$+|])(?:\d*)\z/
word = $1.downcase.tr('!@$+|0134578', 'iastloleastb')
return true if !dict.include?(word)
set_password_requirement_error_message(:password_in_dictionary, password_in_dictionary_message)
false
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/password_expiration.rb 0000664 0000000 0000000 00000006714 15157255142 0026537 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:password_expiration, :PasswordExpiration) do
depends :login, :change_password
error_flash "Your password has expired and needs to be changed"
error_flash "Your password cannot be changed yet", 'password_not_changeable_yet'
redirect :password_not_changeable_yet
redirect(:password_change_needed){change_password_path}
auth_value_method :allow_password_change_after, -86400
auth_value_method :require_password_change_after, 90*86400
auth_value_method :password_expiration_table, :account_password_change_times
auth_value_method :password_expiration_id_column, :id
auth_value_method :password_expiration_changed_at_column, :changed_at
session_key :password_changed_at_session_key, :password_changed_at
auth_value_method :password_expiration_default, false
auth_methods(
:password_expired?,
:update_password_changed_at
)
def get_password_changed_at
convert_timestamp(password_expiration_ds.get(password_expiration_changed_at_column))
end
def check_password_change_allowed
if password_changed_at = get_password_changed_at
if password_changed_at > Time.now - allow_password_change_after
set_redirect_error_flash password_not_changeable_yet_error_flash
redirect password_not_changeable_yet_redirect
end
end
end
def set_password(password)
update_password_changed_at
set_session_value(password_changed_at_session_key, Time.now.to_i)
super
end
def account_from_reset_password_key(key)
if a = super
check_password_change_allowed
end
a
end
def update_password_changed_at
ds = password_expiration_ds
if ds.update(password_expiration_changed_at_column=>Sequel::CURRENT_TIMESTAMP) == 0
# Ignoring the violation is safe here, since a concurrent insert would also set it to the
# current timestamp.
ignore_uniqueness_violation{ds.insert(password_expiration_id_column=>account_id)}
end
end
def require_current_password
if authenticated? && password_expired? && password_change_needed_redirect != request.path_info
set_redirect_error_flash password_expiration_error_flash
redirect password_change_needed_redirect
end
end
def password_expired?
if password_changed_at = session[password_changed_at_session_key]
return password_changed_at + require_password_change_after < Time.now.to_i
end
account_from_session
if password_changed_at = get_password_changed_at
set_session_value(password_changed_at_session_key, password_changed_at.to_i)
password_changed_at + require_password_change_after < Time.now
else
set_session_value(password_changed_at_session_key, password_expiration_default ? 0 : 2147483647)
password_expiration_default
end
end
private
def after_close_account
super if defined?(super)
password_expiration_ds.delete
end
def before_change_password_route
check_password_change_allowed
super
end
def after_create_account
if account_password_hash_column
update_password_changed_at
end
super if defined?(super)
end
def after_login
require_current_password
super
end
def password_expiration_ds
db[password_expiration_table].where(password_expiration_id_column=>account_id)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/password_grace_period.rb 0000664 0000000 0000000 00000002530 15157255142 0026770 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:password_grace_period, :PasswordGracePeriod) do
auth_value_method :password_grace_period, 300
session_key :last_password_entry_session_key, :last_password_entry
auth_methods :password_recently_entered?
def modifications_require_password?
return false unless super
!password_recently_entered?
end
def password_match?(_)
if v = super
@last_password_entry = set_last_password_entry
end
v
end
def password_recently_entered?
return false unless last_password_entry = session[last_password_entry_session_key]
last_password_entry + password_grace_period > Time.now.to_i
end
def update_session
super
set_session_value(last_password_entry_session_key, @last_password_entry) if defined?(@last_password_entry)
end
private
def after_create_account
super if defined?(super)
@last_password_entry = Time.now.to_i
end
def after_reset_password
super if defined?(super)
@last_password_entry = Time.now.to_i
end
def set_last_password_entry
set_session_value(last_password_entry_session_key, Time.now.to_i)
end
def require_password_authentication?
return true if defined?(super) && super
!password_recently_entered?
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/password_pepper.rb 0000664 0000000 0000000 00000002300 15157255142 0025633 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:password_pepper, :PasswordPepper) do
depends :login_password_requirements_base
auth_value_method :password_pepper, nil
auth_value_method :previous_password_peppers, [""]
auth_value_method :password_pepper_update?, true
def password_match?(password)
if (result = super) && @previous_pepper_matched && password_pepper_update?
set_password(password)
end
result
end
def password_hash(password)
super(password + password_pepper.to_s)
end
private
def password_hash_match?(hash, password)
return super if password_pepper.nil?
return true if super(hash, password + password_pepper)
@previous_pepper_matched = previous_password_peppers.any? do |pepper|
super(hash, password + pepper)
end
end
def database_function_password_match?(name, hash_id, password, salt)
return super if password_pepper.nil?
return true if super(name, hash_id, password + password_pepper, salt)
@previous_pepper_matched = previous_password_peppers.any? do |pepper|
super(name, hash_id, password + pepper, salt)
end
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/path_class_methods.rb 0000664 0000000 0000000 00000001270 15157255142 0026267 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:path_class_methods, :PathClassMethods) do
def post_configure
super
klass = self.class
klass.features.each do |feature_name|
feature = FEATURES[feature_name]
feature.routes.each do |handle_meth|
route = handle_meth.to_s.sub(/\Ahandle_/, '')
path_meth = :"#{route}_path"
url_meth = :"#{route}_url"
instance = klass.allocate.freeze
klass.define_singleton_method(path_meth){|opts={}| instance.send(path_meth, opts)}
klass.define_singleton_method(url_meth){|opts={}| instance.send(url_meth, opts)}
end
end
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/recovery_codes.rb 0000664 0000000 0000000 00000017407 15157255142 0025447 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:recovery_codes, :RecoveryCodes) do
depends :two_factor_base
additional_form_tags 'recovery_auth'
additional_form_tags 'recovery_codes'
before 'add_recovery_codes'
before 'view_recovery_codes'
before 'recovery_auth'
after 'add_recovery_codes'
button 'Add Authentication Recovery Codes', 'add_recovery_codes'
button 'Authenticate via Recovery Code', 'recovery_auth'
button 'View Authentication Recovery Codes', 'view_recovery_codes'
error_flash "Error authenticating via recovery code", 'invalid_recovery_code'
error_flash "Unable to add recovery codes", 'add_recovery_codes'
error_flash "Unable to view recovery codes", 'view_recovery_codes'
notice_flash "Additional authentication recovery codes have been added", 'recovery_codes_added'
redirect(:recovery_auth){recovery_auth_path}
redirect(:add_recovery_codes){recovery_codes_path}
loaded_templates %w'add-recovery-codes recovery-auth recovery-codes password-field'
view 'add-recovery-codes', 'Authentication Recovery Codes', 'add_recovery_codes'
view 'recovery-auth', 'Enter Authentication Recovery Code', 'recovery_auth'
view 'recovery-codes', 'View Authentication Recovery Codes', 'recovery_codes'
auth_value_method :add_recovery_codes_param, 'add'
translatable_method :add_recovery_codes_heading, 'Add Additional Recovery Codes
'
auth_value_method :auto_add_recovery_codes?, false
auth_value_method :auto_remove_recovery_codes?, false
translatable_method :invalid_recovery_code_message, "Invalid recovery code"
auth_value_method :recovery_codes_limit, 16
auth_value_method :recovery_codes_column, :code
auth_value_method :recovery_codes_id_column, :id
translatable_method :recovery_codes_label, 'Recovery Code'
auth_value_method :recovery_codes_param, 'recovery-code'
auth_value_method :recovery_codes_table, :account_recovery_codes
translatable_method :recovery_auth_link_text, "Authenticate Using Recovery Code"
translatable_method :recovery_codes_link_text, "View Authentication Recovery Codes"
auth_cached_method :recovery_codes
auth_value_methods(
:recovery_codes_primary?
)
auth_methods(
:add_recovery_code,
:can_add_recovery_codes?,
:new_recovery_code,
:recovery_code_match?,
:recovery_codes_available?,
)
internal_request_method :recovery_codes
internal_request_method :recovery_auth
internal_request_method :valid_recovery_auth?
route(:recovery_auth) do |r|
require_login
require_account_session
require_two_factor_setup
require_two_factor_not_authenticated('recovery_code')
before_recovery_auth_route
r.get do
recovery_auth_view
end
r.post do
if recovery_code_match?(param(recovery_codes_param))
before_recovery_auth
two_factor_authenticate('recovery_code')
end
set_response_error_reason_status(:invalid_recovery_code, invalid_key_error_status)
set_field_error(recovery_codes_param, invalid_recovery_code_message)
set_error_flash invalid_recovery_code_error_flash
recovery_auth_view
end
end
route(:recovery_codes) do |r|
require_account
unless recovery_codes_primary?
require_two_factor_setup
require_two_factor_authenticated
end
before_recovery_codes_route
r.get do
recovery_codes_view
end
r.post do
if two_factor_password_match?(param(password_param))
if can_add_recovery_codes?
if param_or_nil(add_recovery_codes_param)
transaction do
before_add_recovery_codes
add_recovery_codes(recovery_codes_limit - recovery_codes.length)
after_add_recovery_codes
end
set_notice_now_flash recovery_codes_added_notice_flash
end
self.recovery_codes_button = add_recovery_codes_button
end
before_view_recovery_codes
add_recovery_codes_view
else
if param_or_nil(add_recovery_codes_param)
set_error_flash add_recovery_codes_error_flash
else
set_error_flash view_recovery_codes_error_flash
end
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
set_field_error(password_param, invalid_password_message)
recovery_codes_view
end
end
end
attr_accessor :recovery_codes_button
def two_factor_remove
super
recovery_codes_remove
end
def otp_add_key
super if defined?(super)
auto_add_missing_recovery_codes
end
def sms_confirm
super if defined?(super)
auto_add_missing_recovery_codes
end
def add_webauthn_credential(_)
super if defined?(super)
auto_add_missing_recovery_codes
end
def recovery_codes_remove
recovery_codes_ds.delete
end
def recovery_code_match?(code)
recovery_codes.each do |s|
if timing_safe_eql?(code, s)
recovery_codes_ds.where(recovery_codes_column=>code).delete
if recovery_codes_primary?
add_recovery_code
end
return true
end
end
false
end
def can_add_recovery_codes?
recovery_codes.length < recovery_codes_limit
end
def add_recovery_codes(number)
return if number <= 0
transaction do
number.times do
add_recovery_code
end
end
remove_instance_variable(:@recovery_codes)
end
def add_recovery_code
# This should never raise uniqueness violations unless the recovery code is the same, and the odds of that
# are 1/256**32 assuming a good random number generator. Still, attempt to handle that case by retrying
# on such a uniqueness violation.
retry_on_uniqueness_violation do
recovery_codes_ds.insert(recovery_codes_id_column=>session_value, recovery_codes_column=>new_recovery_code)
end
end
def recovery_codes_available?
!recovery_codes_ds.empty?
end
def possible_authentication_methods
methods = super
methods << 'recovery_code' unless recovery_codes_ds.empty?
methods
end
private
def _two_factor_auth_links
links = super
links << [40, recovery_auth_path, recovery_auth_link_text] if recovery_codes_available?
links
end
def _two_factor_setup_links
links = super
links << [40, recovery_codes_path, recovery_codes_link_text] if (recovery_codes_primary? || uses_two_factor_authentication?)
links
end
def _two_factor_remove_all_from_session
two_factor_remove_session('recovery_code')
super
end
def after_otp_disable
super if defined?(super)
auto_remove_recovery_codes
end
def after_sms_disable
super if defined?(super)
auto_remove_recovery_codes
end
def after_webauthn_remove
super if defined?(super)
auto_remove_recovery_codes
end
def new_recovery_code
random_key
end
def recovery_codes_primary?
(features & [:otp, :sms_codes, :webauthn]).empty?
end
def auto_add_missing_recovery_codes
if auto_add_recovery_codes?
add_recovery_codes(recovery_codes_limit - recovery_codes.length)
end
end
def auto_remove_recovery_codes
if auto_remove_recovery_codes? && (%w'totp webauthn sms_code' & possible_authentication_methods).empty?
recovery_codes_remove
end
end
def _recovery_codes
recovery_codes_ds.select_map(recovery_codes_column)
end
def recovery_codes_ds
db[recovery_codes_table].where(recovery_codes_id_column=>session_value)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/remember.rb 0000664 0000000 0000000 00000020234 15157255142 0024222 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:remember, :Remember) do
notice_flash "Your remember setting has been updated"
error_flash "There was an error updating your remember setting"
loaded_templates %w'remember'
view 'remember', 'Change Remember Setting'
additional_form_tags
button 'Change Remember Setting'
before
before 'load_memory'
after
after 'load_memory'
redirect
response
auth_value_method :raw_remember_token_deadline, nil
auth_value_method :remember_cookie_options, {}.freeze
auth_value_method :extend_remember_deadline?, false
auth_value_method :extend_remember_deadline_period, 3600
auth_value_method :remember_period, {:days=>14}.freeze
auth_value_method :remember_deadline_interval, {:days=>14}.freeze
auth_value_method :remember_id_column, :id
auth_value_method :remember_key_column, :key
auth_value_method :remember_deadline_column, :deadline
auth_value_method :remember_table, :account_remember_keys
auth_value_method :remember_cookie_key, '_remember'
auth_value_method :remember_param, 'remember'
auth_value_method :remember_remember_param_value, 'remember'
auth_value_method :remember_forget_param_value, 'forget'
auth_value_method :remember_disable_param_value, 'disable'
session_key :remember_deadline_extended_session_key, :remember_deadline_extended_at
translatable_method :remember_remember_label, 'Remember Me'
translatable_method :remember_forget_label, 'Forget Me'
translatable_method :remember_disable_label, 'Disable Remember Me'
auth_methods(
:add_remember_key,
:disable_remember_login,
:forget_login,
:generate_remember_key_value,
:get_remember_key,
:load_memory,
:remembered_session_id,
:logged_in_via_remember_key?,
:remember_key_value,
:remember_login,
:remove_remember_key
)
internal_request_method :remember_setup
internal_request_method :remember_disable
internal_request_method :account_id_for_remember_key
route do |r|
require_account
before_remember_route
r.get do
remember_view
end
r.post do
remember = param(remember_param)
if [remember_remember_param_value, remember_forget_param_value, remember_disable_param_value].include?(remember)
transaction do
before_remember
# :nocov:
case remember
# :nocov:
when remember_remember_param_value
remember_login
when remember_forget_param_value
forget_login
when remember_disable_param_value
disable_remember_login
end
after_remember
end
remember_response
else
set_response_error_reason_status(:invalid_remember_param, invalid_field_error_status)
set_error_flash remember_error_flash
remember_view
end
end
end
def remembered_session_id
return unless cookie = _get_remember_cookie
id, key = cookie.split('_', 2)
return unless id && key
actual, deadline = active_remember_key_ds(id).get([remember_key_column, remember_deadline_column])
return unless actual
if hmac_secret && !(valid = timing_safe_eql?(key, compute_hmac(actual)))
if hmac_secret_rotation? && (valid = timing_safe_eql?(key, compute_old_hmac(actual)))
_set_remember_cookie(id, actual, deadline)
elsif !(raw_remember_token_deadline && raw_remember_token_deadline > convert_timestamp(deadline))
return
end
end
unless valid || timing_safe_eql?(key, actual)
return
end
id
end
def load_memory
if logged_in?
if extend_remember_deadline_while_logged_in?
if account_from_session
extend_remember_deadline
else
forget_login
clear_session
end
end
elsif account_from_remember_cookie
before_load_memory
login_session('remember')
extend_remember_deadline if extend_remember_deadline?
after_load_memory
end
end
def remember_login
get_remember_key
set_remember_cookie
set_session_value(remember_deadline_extended_session_key, Time.now.to_i) if extend_remember_deadline?
end
def forget_login
opts = Hash[remember_cookie_options]
opts[:path] = "/" unless opts.key?(:path)
::Rack::Utils.delete_cookie_header!(response.headers, remember_cookie_key, opts)
end
def get_remember_key
unless @remember_key_value = active_remember_key_ds.get(remember_key_column)
generate_remember_key_value
transaction do
remove_remember_key
add_remember_key
end
end
nil
end
def disable_remember_login
remove_remember_key
end
def add_remember_key
hash = {remember_id_column=>account_id, remember_key_column=>remember_key_value}
set_deadline_value(hash, remember_deadline_column, remember_deadline_interval)
if e = raised_uniqueness_violation{remember_key_ds.insert(hash)}
# If inserting into the remember key table causes a violation, we can pull the
# existing row from the table. If there is no invalid row, we can then reraise.
raise e unless @remember_key_value = active_remember_key_ds.get(remember_key_column)
end
end
def remove_remember_key(id=account_id)
remember_key_ds(id).delete
end
def logged_in_via_remember_key?
authenticated_by.include?('remember')
end
def clear_tokens(reason)
super
remove_remember_key
remember_login if logged_in? && logged_in_via_remember_key?
end
private
def _set_remember_cookie(account_id, remember_key_value, deadline)
opts = Hash[remember_cookie_options]
opts[:value] = "#{account_id}_#{convert_token_key(remember_key_value)}"
opts[:expires] = convert_timestamp(deadline)
opts[:path] = "/" unless opts.key?(:path)
opts[:httponly] = true unless opts.key?(:httponly) || opts.key?(:http_only)
opts[:secure] = true unless opts.key?(:secure) || !request.ssl?
::Rack::Utils.set_cookie_header!(response.headers, remember_cookie_key, opts)
end
def set_remember_cookie
_set_remember_cookie(account_id, remember_key_value, active_remember_key_ds.get(remember_deadline_column))
end
def extend_remember_deadline_while_logged_in?
return false unless extend_remember_deadline?
if extended_at = session[remember_deadline_extended_session_key]
extended_at + extend_remember_deadline_period < Time.now.to_i
elsif logged_in_via_remember_key?
# Handle existing sessions before the change to extend remember deadline
# while logged in.
true
end
end
def extend_remember_deadline
active_remember_key_ds.update(remember_deadline_column=>Sequel.date_add(Sequel::CURRENT_TIMESTAMP, remember_period))
remember_login
end
def account_from_remember_cookie
unless id = remembered_session_id
# Only set expired cookie if there is already a cookie set.
forget_login if _get_remember_cookie
return
end
set_session_value(session_key, id)
account_from_session
remove_session_value(session_key)
unless account
remove_remember_key(id)
forget_login
return
end
account
end
def _get_remember_cookie
request.cookies[remember_cookie_key]
end
def after_logout
forget_login
super if defined?(super)
end
def after_close_account
remove_remember_key
super if defined?(super)
end
attr_reader :remember_key_value
def generate_remember_key_value
@remember_key_value = random_key
end
def use_date_arithmetic?
super || extend_remember_deadline? || db.database_type == :mysql
end
def remember_key_ds(id=account_id)
db[remember_table].where(remember_id_column=>id)
end
def active_remember_key_ds(id=account_id)
remember_key_ds(id).where(Sequel.expr(remember_deadline_column) > Sequel::CURRENT_TIMESTAMP)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/reset_password.rb 0000664 0000000 0000000 00000022511 15157255142 0025470 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:reset_password, :ResetPassword) do
depends :login, :email_base, :login_password_requirements_base
notice_flash "Your password has been reset"
notice_flash "An email has been sent to you with a link to reset the password for your account", 'reset_password_email_sent'
error_flash "There was an error resetting your password"
error_flash "There was an error requesting a password reset", 'reset_password_request'
error_flash "An email has recently been sent to you with a link to reset your password", 'reset_password_email_recently_sent'
error_flash "There was an error resetting your password: invalid or expired password reset key", 'no_matching_reset_password_key'
loaded_templates %w'reset-password-request reset-password password-field password-confirm-field reset-password-email'
view 'reset-password', 'Reset Password'
view 'reset-password-request', 'Request Password Reset', 'reset_password_request'
additional_form_tags
additional_form_tags 'reset_password_request'
before
before 'reset_password_request'
after
after 'reset_password_request'
button 'Reset Password'
button 'Request Password Reset', 'reset_password_request'
redirect
redirect(:reset_password_email_sent){default_post_email_redirect}
redirect(:reset_password_email_recently_sent){default_post_email_redirect}
response
response :reset_password_email_sent
email :reset_password, 'Reset Password'
auth_value_method :reset_password_deadline_column, :deadline
auth_value_method :reset_password_deadline_interval, {:days=>1}.freeze
auth_value_method :reset_password_key_param, 'key'
auth_value_method :reset_password_autologin?, false
auth_value_method :reset_password_table, :account_password_reset_keys
auth_value_method :reset_password_id_column, :id
auth_value_method :reset_password_key_column, :key
auth_value_method :reset_password_email_last_sent_column, :email_last_sent
translatable_method :reset_password_explanatory_text, "If you have forgotten your password, you can request a password reset:
"
auth_value_method :reset_password_skip_resend_email_within, 300
translatable_method :reset_password_request_link_text, "Forgot Password?"
session_key :reset_password_session_key, :reset_password_key
auth_methods(
:create_reset_password_key,
:get_reset_password_key,
:get_reset_password_email_last_sent,
:login_failed_reset_password_request_form,
:remove_reset_password_key,
:reset_password_email_link,
:reset_password_key_insert_hash,
:reset_password_key_value,
:reset_password_request_for_unverified_account,
:set_reset_password_email_last_sent
)
auth_private_methods(
:account_from_reset_password_key
)
internal_request_method(:reset_password_request)
internal_request_method
route(:reset_password_request) do |r|
check_already_logged_in
before_reset_password_request_route
r.get do
reset_password_request_view
end
r.post do
catch_error do
unless account_from_login(login_param_value)
throw_error_reason(:no_matching_login, no_matching_login_error_status, login_param, no_matching_login_message)
end
reset_password_request_for_unverified_account unless open_account?
if reset_password_email_recently_sent?
set_redirect_error_flash reset_password_email_recently_sent_error_flash
redirect reset_password_email_recently_sent_redirect
end
generate_reset_password_key_value
transaction do
before_reset_password_request
create_reset_password_key
send_reset_password_email
after_reset_password_request
end
reset_password_email_sent_response
end
set_error_flash reset_password_request_error_flash
reset_password_request_view
end
end
route do |r|
check_already_logged_in
before_reset_password_route
@password_field_autocomplete_value = 'new-password'
r.get do
if key = param_or_nil(reset_password_key_param)
set_session_value(reset_password_session_key, key)
redirect(r.path)
end
if (key = session[reset_password_session_key]) && account_from_reset_password_key(key)
reset_password_view
else
remove_session_value(reset_password_session_key)
set_redirect_error_flash no_matching_reset_password_key_error_flash
redirect require_login_redirect
end
end
r.post do
key = session[reset_password_session_key] || param(reset_password_key_param)
unless account_from_reset_password_key(key)
set_redirect_error_status(invalid_key_error_status)
set_error_reason :invalid_reset_password_key
set_redirect_error_flash reset_password_error_flash
redirect reset_password_email_sent_redirect
end
password = param(password_param)
catch_error do
unless password_meets_requirements?(password)
throw_error_status(invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
end
if password_match?(password)
throw_error_reason(:same_as_existing_password, invalid_field_error_status, password_param, same_as_existing_password_message)
end
if require_password_confirmation? && password != param(password_confirm_param)
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, password_param, passwords_do_not_match_message)
end
transaction do
before_reset_password
set_password(password)
clear_tokens(:reset_password)
after_reset_password
end
if reset_password_autologin?
autologin_session('reset_password')
end
remove_session_value(reset_password_session_key)
reset_password_response
end
set_error_flash reset_password_error_flash
reset_password_view
end
end
def create_reset_password_key
transaction do
if reset_password_key_value = get_password_reset_key(account_id)
set_reset_password_email_last_sent
@reset_password_key_value = reset_password_key_value
elsif e = raised_uniqueness_violation{password_reset_ds.insert(reset_password_key_insert_hash)}
# If inserting into the reset password table causes a violation, we can pull the
# existing reset password key from the table, or reraise.
raise e unless @reset_password_key_value = get_password_reset_key(account_id)
end
end
end
def reset_password_request_for_unverified_account
throw_error_reason(:unverified_account, unopen_account_error_status, login_param, unverified_account_message)
end
def remove_reset_password_key
password_reset_ds.delete
end
def account_from_reset_password_key(key)
@account = _account_from_reset_password_key(key)
end
def reset_password_email_link
token_link(reset_password_route, reset_password_key_param, reset_password_key_value)
end
def get_password_reset_key(id)
ds = password_reset_ds(id)
ds.where(Sequel::CURRENT_TIMESTAMP > reset_password_deadline_column).delete
ds.get(reset_password_key_column)
end
def set_reset_password_email_last_sent
password_reset_ds.update(reset_password_email_last_sent_column=>Sequel::CURRENT_TIMESTAMP) if reset_password_email_last_sent_column
end
def get_reset_password_email_last_sent
if column = reset_password_email_last_sent_column
if ts = password_reset_ds.get(column)
convert_timestamp(ts)
end
end
end
def reset_password_email_recently_sent?
(email_last_sent = get_reset_password_email_last_sent) && (Time.now - email_last_sent < reset_password_skip_resend_email_within)
end
def clear_tokens(reason)
super
remove_reset_password_key
end
private
def _login_form_footer_links
super << [20, reset_password_request_path, reset_password_request_link_text]
end
attr_reader :reset_password_key_value
def after_login_failure
unless only_json? || internal_request?
@login_form_header = login_failed_reset_password_request_form
end
super
end
def generate_reset_password_key_value
@reset_password_key_value = random_key
end
def login_failed_reset_password_request_form
render("reset-password-request")
end
def use_date_arithmetic?
super || db.database_type == :mysql
end
def reset_password_key_insert_hash
hash = {reset_password_id_column=>account_id, reset_password_key_column=>reset_password_key_value}
set_deadline_value(hash, reset_password_deadline_column, reset_password_deadline_interval)
hash
end
def password_reset_ds(id=account_id)
db[reset_password_table].where(reset_password_id_column=>id)
end
def _account_from_reset_password_key(token)
account_from_key(token, reset_password_account_status_value){|id| get_password_reset_key(id)}
end
def reset_password_account_status_value
account_open_status_value
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/reset_password_notify.rb 0000664 0000000 0000000 00000000603 15157255142 0027056 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:reset_password_notify, :ResetPasswordNotify) do
depends :reset_password
loaded_templates %w'reset-password-notify-email'
email :reset_password_notify, 'Password Reset Completed', :translatable=>true
private
def after_reset_password
super
send_reset_password_notify_email
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/reset_password_verifies_account.rb 0000664 0000000 0000000 00000001014 15157255142 0031073 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:reset_password_verifies_account, :ResetPasswordVerifiesAccount) do
depends :reset_password, :verify_account
def reset_password_request_for_unverified_account
nil
end
private
def after_reset_password
super
unless open_account?
verify_account
remove_verify_account_key
end
end
def reset_password_account_status_value
Array(super) << account_unverified_status_value
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/session_expiration.rb 0000664 0000000 0000000 00000003156 15157255142 0026355 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:session_expiration, :SessionExpiration) do
error_flash "This session has expired, please login again"
redirect{require_login_redirect}
auth_value_method :max_session_lifetime, 86400
session_key :session_created_session_key, :session_created_at
auth_value_method :session_expiration_error_status, 401
auth_value_method :session_expiration_default, true
auth_value_method :session_inactivity_timeout, 1800
session_key :session_last_activity_session_key, :last_session_activity_at
def check_session_expiration
return unless logged_in?
unless session.has_key?(session_last_activity_session_key) && session.has_key?(session_created_session_key)
if session_expiration_default
expire_session
end
return
end
time = Time.now.to_i
if session[session_last_activity_session_key] + session_inactivity_timeout < time
expire_session
end
set_session_value(session_last_activity_session_key, time)
if session[session_created_session_key] + max_session_lifetime < time
expire_session
end
end
def expire_session
clear_session
set_redirect_error_status session_expiration_error_status
set_error_reason :session_expired
set_redirect_error_flash session_expiration_error_flash
redirect session_expiration_redirect
end
def update_session
super
t = Time.now.to_i
set_session_value(session_last_activity_session_key, t)
set_session_value(session_created_session_key, t)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/single_session.rb 0000664 0000000 0000000 00000006472 15157255142 0025460 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:single_session, :SingleSession) do
error_flash 'This session has been logged out as another session has become active'
redirect
auth_value_method :allow_raw_single_session_key?, false
auth_value_method :inactive_session_error_status, 401
auth_value_method :single_session_id_column, :id
auth_value_method :single_session_key_column, :key
session_key :single_session_session_key, :single_session_key
auth_value_method :single_session_table, :account_session_keys
auth_methods(
:currently_active_session?,
:no_longer_active_session,
:reset_single_session_key,
:update_single_session_key
)
def reset_single_session_key
if logged_in?
single_session_ds.update(single_session_key_column=>random_key)
end
end
def currently_active_session?
single_session_key = session[single_session_session_key]
current_key = single_session_ds.get(single_session_key_column)
if single_session_key.nil?
unless current_key
# No row exists for this user, indicating the feature has never
# been used, so it is OK to treat the current session as a new
# session.
update_single_session_key
end
true
elsif current_key
if hmac_secret && !(valid = timing_safe_eql?(single_session_key, hmac = compute_hmac(current_key)))
if hmac_secret_rotation? && (valid = timing_safe_eql?(single_session_key, compute_old_hmac(current_key)))
session[single_session_session_key] = hmac
elsif !allow_raw_single_session_key?
return false
end
end
valid || timing_safe_eql?(single_session_key, current_key)
end
end
def check_single_session
if logged_in? && !currently_active_session?
no_longer_active_session
end
end
def no_longer_active_session
clear_session
set_redirect_error_status inactive_session_error_status
set_error_reason :inactive_session
set_redirect_error_flash single_session_error_flash
redirect single_session_redirect
end
def update_single_session_key
key = random_key
set_single_session_key(key)
if single_session_ds.update(single_session_key_column=>key) == 0
# Don't handle uniqueness violations here. While we could get the stored key from the
# database, it could lead to two sessions sharing the same key, which this feature is
# designed to prevent.
single_session_ds.insert(single_session_id_column=>session_value, single_session_key_column=>key)
end
end
def update_session
super
update_single_session_key
end
def clear_tokens(reason)
super
single_session_ds(account_id).delete unless logged_in?
end
private
def after_close_account
super if defined?(super)
single_session_ds.delete
end
def before_logout
reset_single_session_key
super if defined?(super)
end
def set_single_session_key(data)
data = compute_hmac(data) if hmac_secret
set_session_value(single_session_session_key, data)
end
def single_session_ds(id=session_value)
db[single_session_table].
where(single_session_id_column=>id)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/sms_codes.rb 0000664 0000000 0000000 00000035770 15157255142 0024416 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:sms_codes, :SmsCodes) do
depends :two_factor_base
additional_form_tags 'sms_auth'
additional_form_tags 'sms_confirm'
additional_form_tags 'sms_disable'
additional_form_tags 'sms_request'
additional_form_tags 'sms_setup'
before 'sms_auth'
before 'sms_confirm'
before 'sms_disable'
before 'sms_request'
before 'sms_setup'
after 'sms_confirm'
after 'sms_disable'
after 'sms_failure'
after 'sms_request'
after 'sms_setup'
button 'Authenticate via SMS Code', 'sms_auth'
button 'Confirm SMS Backup Number', 'sms_confirm'
button 'Disable Backup SMS Authentication', 'sms_disable'
button 'Send SMS Code', 'sms_request'
button 'Setup SMS Backup Number', 'sms_setup'
error_flash "Error authenticating via SMS code", 'sms_invalid_code'
error_flash "Error disabling SMS authentication", 'sms_disable'
error_flash "Error setting up SMS authentication", 'sms_setup'
error_flash "Invalid or out of date SMS confirmation code used, must setup SMS authentication again", 'sms_invalid_confirmation_code'
error_flash "No current SMS code for this account", 'no_current_sms_code'
error_flash "SMS authentication has been locked out", 'sms_lockout'
error_flash "SMS authentication has already been setup", 'sms_already_setup'
error_flash "SMS authentication has not been setup yet", 'sms_not_setup'
error_flash "SMS authentication needs confirmation", 'sms_needs_confirmation'
notice_flash "SMS authentication code has been sent", 'sms_request'
notice_flash "SMS authentication has been disabled", 'sms_disable'
notice_flash "SMS authentication has been setup", 'sms_confirm'
translatable_method :sms_auth_link_text, "Authenticate Using SMS Code"
translatable_method :sms_setup_link_text, "Setup Backup SMS Authentication"
translatable_method :sms_disable_link_text, "Disable SMS Authentication"
redirect :sms_already_setup
redirect :sms_confirm
redirect :sms_disable
redirect(:sms_auth){sms_auth_path}
redirect(:sms_needs_confirmation){sms_confirm_path}
redirect(:sms_needs_setup){sms_setup_path}
redirect(:sms_request){sms_request_path}
redirect(:sms_lockout){two_factor_auth_required_redirect}
response :sms_confirm
response :sms_disable
response :sms_needs_confirmation
loaded_templates %w'sms-auth sms-confirm sms-disable sms-request sms-setup sms-code-field password-field'
view 'sms-auth', 'Authenticate via SMS Code', 'sms_auth'
view 'sms-confirm', 'Confirm SMS Backup Number', 'sms_confirm'
view 'sms-disable', 'Disable Backup SMS Authentication', 'sms_disable'
view 'sms-request', 'Send SMS Code', 'sms_request'
view 'sms-setup', 'Setup SMS Backup Number', 'sms_setup'
auth_value_method :sms_already_setup_error_status, 403
auth_value_method :sms_needs_confirmation_error_status, 403
auth_value_method :sms_auth_code_length, 6
auth_value_method :sms_code_allowed_seconds, 300
auth_value_method :sms_code_column, :code
translatable_method :sms_code_label, 'SMS Code'
auth_value_method :sms_code_param, 'sms-code'
auth_value_method :sms_codes_table, :account_sms_codes
auth_value_method :sms_confirm_code_length, 12
auth_value_method :sms_confirm_deadline, 86400
auth_value_method :sms_failure_limit, 5
auth_value_method :sms_failures_column, :num_failures
auth_value_method :sms_id_column, :id
translatable_method :sms_invalid_code_message, "invalid SMS code"
translatable_method :sms_invalid_phone_message, "invalid SMS phone number"
auth_value_method :sms_issued_at_column, :code_issued_at
auth_value_method :sms_phone_column, :phone_number
translatable_method :sms_phone_label, 'Phone Number'
auth_value_method :sms_phone_input_type, 'tel'
auth_value_method :sms_phone_min_length, 7
auth_value_method :sms_phone_param, 'sms-phone'
auth_cached_method :sms
auth_value_methods(
:sms_codes_primary?,
:sms_needs_confirmation_notice_flash,
:sms_request_response
)
auth_methods(
:sms_auth_message,
:sms_available?,
:sms_code_issued_at,
:sms_code_match?,
:sms_confirm_message,
:sms_confirmation_match?,
:sms_current_auth?,
:sms_disable,
:sms_failures,
:sms_locked_out?,
:sms_needs_confirmation?,
:sms_new_auth_code,
:sms_new_confirm_code,
:sms_normalize_phone,
:sms_record_failure,
:sms_remove_expired_confirm_code,
:sms_remove_failures,
:sms_send,
:sms_set_code,
:sms_setup,
:sms_setup?,
:sms_valid_phone?
)
internal_request_method :sms_setup
internal_request_method :sms_confirm
internal_request_method :sms_request
internal_request_method :sms_auth
internal_request_method :valid_sms_auth?
internal_request_method :sms_disable
route(:sms_request) do |r|
require_login
require_account_session
require_two_factor_not_authenticated('sms_code')
require_sms_available
before_sms_request_route
r.get do
sms_request_view
end
r.post do
transaction do
before_sms_request
sms_send_auth_code
after_sms_request
end
require_response(:_sms_request_response)
end
end
route(:sms_auth) do |r|
require_login
require_account_session
require_two_factor_not_authenticated('sms_code')
require_sms_available
unless sms_current_auth?
if sms_code
sms_set_code(nil)
end
set_response_error_reason_status(:no_current_sms_code, invalid_key_error_status)
set_redirect_error_flash no_current_sms_code_error_flash
redirect sms_request_redirect
end
before_sms_auth_route
r.get do
sms_auth_view
end
r.post do
transaction do
if sms_code_match?(param(sms_code_param))
before_sms_auth
sms_remove_failures
two_factor_authenticate('sms_code')
else
sms_record_failure
after_sms_failure
end
end
set_response_error_reason_status(:invalid_sms_code, invalid_key_error_status)
set_field_error(sms_code_param, sms_invalid_code_message)
set_error_flash sms_invalid_code_error_flash
sms_auth_view
end
end
route(:sms_setup) do |r|
require_account
unless sms_codes_primary?
require_two_factor_setup
require_two_factor_authenticated
end
sms_remove_expired_confirm_code
require_sms_not_setup
if sms_needs_confirmation?
set_redirect_error_status(sms_needs_confirmation_error_status)
set_error_reason :sms_needs_confirmation
set_redirect_error_flash sms_needs_confirmation_error_flash
redirect sms_needs_confirmation_redirect
end
before_sms_setup_route
r.get do
sms_setup_view
end
r.post do
catch_error do
unless two_factor_password_match?(param(password_param))
throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
end
phone = sms_normalize_phone(param(sms_phone_param))
unless sms_valid_phone?(phone)
throw_error_reason(:invalid_phone_number, invalid_field_error_status, sms_phone_param, sms_invalid_phone_message)
end
transaction do
before_sms_setup
sms_setup(phone)
sms_send_confirm_code
after_sms_setup
end
sms_needs_confirmation_response
end
set_error_flash sms_setup_error_flash
sms_setup_view
end
end
route(:sms_confirm) do |r|
require_account
unless sms_codes_primary?
require_two_factor_setup
require_two_factor_authenticated
end
sms_remove_expired_confirm_code
require_sms_not_setup
before_sms_confirm_route
r.get do
sms_confirm_view
end
r.post do
if sms_confirmation_match?(param(sms_code_param))
transaction do
before_sms_confirm
sms_confirm
after_sms_confirm
unless two_factor_authenticated?
two_factor_update_session('sms_code')
end
end
sms_confirm_response
end
sms_confirm_failure
set_redirect_error_status(invalid_key_error_status)
set_error_reason :invalid_sms_confirmation_code
set_redirect_error_flash sms_invalid_confirmation_code_error_flash
redirect sms_needs_setup_redirect
end
end
route(:sms_disable) do |r|
require_account
require_sms_setup
before_sms_disable_route
r.get do
sms_disable_view
end
r.post do
if two_factor_password_match?(param(password_param))
transaction do
before_sms_disable
sms_disable
if two_factor_login_type_match?('sms_code')
two_factor_remove_session('sms_code')
end
after_sms_disable
end
sms_disable_response
end
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
set_field_error(password_param, invalid_password_message)
set_error_flash sms_disable_error_flash
sms_disable_view
end
end
def two_factor_remove
super
sms_disable
end
def two_factor_remove_auth_failures
super
sms_remove_failures
end
def require_sms_setup
unless sms_setup?
set_redirect_error_status(two_factor_not_setup_error_status)
set_error_reason :sms_not_setup
set_redirect_error_flash sms_not_setup_error_flash
redirect sms_needs_setup_redirect
end
end
def require_sms_not_setup
if sms_setup?
set_redirect_error_status(sms_already_setup_error_status)
set_error_reason :sms_already_setup
set_redirect_error_flash sms_already_setup_error_flash
redirect sms_already_setup_redirect
end
end
def require_sms_available
require_sms_setup
if sms_locked_out?
set_redirect_error_status(lockout_error_status)
set_error_reason :sms_locked_out
set_redirect_error_flash sms_lockout_error_flash
redirect sms_lockout_redirect
end
end
def sms_code_match?(code)
return false unless sms_current_auth?
timing_safe_eql?(code, sms_code)
end
def sms_confirmation_match?(code)
sms_needs_confirmation? && sms_code_match?(code)
end
def sms_disable
sms_ds.delete
@sms = nil
end
def sms_confirm_failure
sms_ds.delete
end
def sms_setup(phone_number)
# Cannot handle uniqueness violation here, as the phone number given may not match the
# one in the table.
sms_ds.insert(sms_id_column=>session_value, sms_phone_column=>phone_number, sms_failures_column => nil)
remove_instance_variable(:@sms) if instance_variable_defined?(:@sms)
end
def sms_remove_failures
return if sms_needs_confirmation?
update_hash_ds(sms, sms_ds.exclude(sms_failures_column => nil), sms_failures_column => 0, sms_code_column => nil)
end
def sms_confirm
update_hash_ds(sms, sms_ds.where(sms_failures_column => nil), sms_failures_column => 0, sms_code_column => nil)
super if defined?(super)
end
def sms_send_auth_code
code = sms_new_auth_code
sms_set_code(code)
sms_send(sms_phone, sms_auth_message(code))
end
def sms_send_confirm_code
code = sms_new_confirm_code
sms_set_code(code)
sms_send(sms_phone, sms_confirm_message(code))
end
def sms_valid_phone?(phone)
phone.length >= sms_phone_min_length
end
def sms_auth_message(code)
"SMS authentication code for #{domain} is #{code}"
end
def sms_confirm_message(code)
"SMS confirmation code for #{domain} is #{code}"
end
def sms_needs_confirmation_notice_flash
sms_needs_confirmation_error_flash
end
def sms_set_code(code)
update_sms(sms_code_column=>code, sms_issued_at_column=>Sequel::CURRENT_TIMESTAMP)
end
def sms_remove_expired_confirm_code
db[sms_codes_table].
where(sms_id_column=>session_value, sms_failures_column => nil).
where(Sequel[sms_issued_at_column] < Sequel.date_sub(Sequel::CURRENT_TIMESTAMP, seconds: sms_confirm_deadline)).
delete
end
def sms_record_failure
update_sms(sms_failures_column=>Sequel.expr(sms_failures_column)+1)
sms[sms_failures_column] = sms_ds.get(sms_failures_column)
end
def sms_phone
sms[sms_phone_column]
end
def sms_code
sms[sms_code_column]
end
def sms_code_issued_at
convert_timestamp(sms[sms_issued_at_column])
end
def sms_failures
sms[sms_failures_column]
end
def sms_setup?
return false unless sms
!sms_needs_confirmation?
end
def sms_needs_confirmation?
sms && sms_failures.nil?
end
def sms_available?
sms_setup? && !sms_locked_out?
end
def sms_locked_out?
sms_failures >= sms_failure_limit
end
def sms_current_auth?
sms_code && sms_code_issued_at + sms_code_allowed_seconds > Time.now
end
def possible_authentication_methods
methods = super
methods << 'sms_code' if sms_setup?
methods
end
private
def _sms_request_response
set_notice_flash sms_request_notice_flash
redirect sms_auth_redirect
end
def _two_factor_auth_links
links = super
links << [30, sms_request_path, sms_auth_link_text] if sms_available?
links
end
def _two_factor_setup_links
links = super
links << [30, sms_setup_path, sms_setup_link_text] if !sms_setup? && (sms_codes_primary? || uses_two_factor_authentication?)
links
end
def _two_factor_remove_links
links = super
links << [30, sms_disable_path, sms_disable_link_text] if sms_setup?
links
end
def _two_factor_remove_all_from_session
two_factor_remove_session('sms_code')
super
end
def sms_codes_primary?
(features & [:otp, :webauthn]).empty?
end
def sms_normalize_phone(phone)
phone.to_s.gsub(/\D+/, '')
end
def sms_new_auth_code
SecureRandom.random_number(10**sms_auth_code_length).to_s.rjust(sms_auth_code_length, "0")
end
def sms_new_confirm_code
SecureRandom.random_number(10**sms_confirm_code_length).to_s.rjust(sms_confirm_code_length, "0")
end
def sms_send(phone, message)
raise ConfigurationError, "sms_send needs to be defined in the Rodauth configuration for SMS sending to work"
end
def update_sms(values)
update_hash_ds(sms, sms_ds, values)
end
def _sms
sms_ds.first
end
def sms_ds
db[sms_codes_table].where(sms_id_column=>session_value)
end
def use_date_arithmetic?
true
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/two_factor_base.rb 0000664 0000000 0000000 00000020460 15157255142 0025566 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:two_factor_base, :TwoFactorBase) do
loaded_templates %w'two-factor-manage two-factor-auth two-factor-disable'
view 'two-factor-manage', 'Manage Multifactor Authentication', 'two_factor_manage'
view 'two-factor-auth', 'Authenticate Using Additional Factor', 'two_factor_auth'
view 'two-factor-disable', 'Remove All Multifactor Authentication Methods', 'two_factor_disable'
before :two_factor_disable
after :two_factor_authentication
after :two_factor_disable
additional_form_tags :two_factor_disable
button "Remove All Multifactor Authentication Methods", :two_factor_disable
redirect(:two_factor_auth)
redirect(:two_factor_already_authenticated)
redirect(:two_factor_disable)
redirect(:two_factor_need_setup){two_factor_manage_path}
redirect(:two_factor_auth_required){two_factor_auth_path}
response :two_factor_disable
notice_flash "You have been multifactor authenticated", "two_factor_auth"
notice_flash "All multifactor authentication methods have been disabled", "two_factor_disable"
error_flash "This account has not been setup for multifactor authentication", 'two_factor_not_setup'
error_flash "You have already been multifactor authenticated", 'two_factor_already_authenticated'
error_flash "You need to authenticate via an additional factor before continuing", 'two_factor_need_authentication'
error_flash "Unable to remove all multifactor authentication methods", "two_factor_disable"
auth_value_method :two_factor_already_authenticated_error_status, 403
auth_value_method :two_factor_need_authentication_error_status, 401
auth_value_method :two_factor_not_setup_error_status, 403
session_key :two_factor_setup_session_key, :two_factor_auth_setup
session_key :two_factor_auth_redirect_session_key, :two_factor_auth_redirect
translatable_method :two_factor_setup_heading, "Setup Multifactor Authentication
"
translatable_method :two_factor_remove_heading, "Remove Multifactor Authentication
"
translatable_method :two_factor_disable_link_text, "Remove All Multifactor Authentication Methods"
auth_value_method :two_factor_auth_return_to_requested_location?, false
auth_value_methods :two_factor_modifications_require_password?
auth_methods(
:two_factor_authenticated?,
:two_factor_remove,
:two_factor_remove_auth_failures,
:two_factor_remove_session,
:two_factor_update_session
)
auth_private_methods(
:two_factor_auth_links,
:two_factor_auth_response,
:two_factor_setup_links,
:two_factor_remove_links
)
internal_request_method :two_factor_disable
route(:two_factor_manage, 'multifactor-manage') do |r|
require_account
before_two_factor_manage_route
r.get do
all_links = two_factor_setup_links + two_factor_remove_links
if all_links.length == 1
redirect all_links[0][1]
end
two_factor_manage_view
end
end
route(:two_factor_auth, 'multifactor-auth') do |r|
require_login
require_account_session
require_two_factor_setup
require_two_factor_not_authenticated
before_two_factor_auth_route
r.get do
if two_factor_auth_links.length == 1
redirect two_factor_auth_links[0][1]
end
two_factor_auth_view
end
end
route(:two_factor_disable, 'multifactor-disable') do |r|
require_account
require_two_factor_setup
before_two_factor_disable_route
r.get do
two_factor_disable_view
end
r.post do
if two_factor_password_match?(param(password_param))
transaction do
before_two_factor_disable
two_factor_remove
_two_factor_remove_all_from_session
after_two_factor_disable
end
two_factor_disable_response
end
set_response_error_reason_status(:invalid_password, invalid_password_error_status)
set_field_error(password_param, invalid_password_message)
set_error_flash two_factor_disable_error_flash
two_factor_disable_view
end
end
def two_factor_modifications_require_password?
modifications_require_password?
end
def authenticated?
super && !two_factor_partially_authenticated?
end
def require_authentication
super
require_two_factor_authenticated if two_factor_partially_authenticated?
end
def require_two_factor_setup
# Avoid database query if already authenticated via 2nd factor
return if two_factor_authenticated?
return if uses_two_factor_authentication?
set_redirect_error_status(two_factor_not_setup_error_status)
set_error_reason :two_factor_not_setup
set_redirect_error_flash two_factor_not_setup_error_flash
redirect two_factor_need_setup_redirect
end
def require_two_factor_not_authenticated(auth_type = nil)
if two_factor_authenticated? || (auth_type && two_factor_login_type_match?(auth_type))
set_redirect_error_status(two_factor_already_authenticated_error_status)
set_error_reason :two_factor_already_authenticated
set_redirect_error_flash two_factor_already_authenticated_error_flash
redirect two_factor_already_authenticated_redirect
end
end
def require_two_factor_authenticated
unless two_factor_authenticated?
if two_factor_auth_return_to_requested_location?
set_session_value(two_factor_auth_redirect_session_key, request.fullpath)
end
set_redirect_error_status(two_factor_need_authentication_error_status)
set_error_reason :two_factor_need_authentication
set_redirect_error_flash two_factor_need_authentication_error_flash
redirect two_factor_auth_required_redirect
end
end
def two_factor_remove_auth_failures
nil
end
def two_factor_password_match?(password)
if two_factor_modifications_require_password?
password_match?(password)
else
true
end
end
def two_factor_partially_authenticated?
logged_in? && !two_factor_authenticated? && uses_two_factor_authentication?
end
def two_factor_authenticated?
authenticated_by && authenticated_by.length >= 2
end
def two_factor_authentication_setup?
possible_authentication_methods.length >= 2
end
def uses_two_factor_authentication?
return false unless logged_in?
set_session_value(two_factor_setup_session_key, two_factor_authentication_setup?) unless session.has_key?(two_factor_setup_session_key)
session[two_factor_setup_session_key]
end
def two_factor_login_type_match?(type)
authenticated_by && authenticated_by.include?(type)
end
def two_factor_remove
nil
end
def two_factor_auth_links
@two_factor_auth_links ||= _filter_links(_two_factor_auth_links)
end
def two_factor_setup_links
@two_factor_setup_links ||= _filter_links(_two_factor_setup_links)
end
def two_factor_remove_links
@two_factor_remove_links ||= _filter_links(_two_factor_remove_links)
end
private
def _two_factor_auth_links
(super if defined?(super)) || []
end
def _two_factor_setup_links
[]
end
def _two_factor_remove_links
[]
end
def _two_factor_remove_all_from_session
nil
end
def after_close_account
super if defined?(super)
two_factor_remove
end
def two_factor_authenticate(type)
two_factor_update_session(type)
two_factor_remove_auth_failures
after_two_factor_authentication
require_response(:_two_factor_auth_response)
end
def _two_factor_auth_response
saved_two_factor_auth_redirect = remove_session_value(two_factor_auth_redirect_session_key)
set_notice_flash two_factor_auth_notice_flash
redirect(saved_two_factor_auth_redirect || two_factor_auth_redirect)
end
def two_factor_remove_session(type)
authenticated_by.delete(type)
remove_session_value(two_factor_setup_session_key)
if authenticated_by.empty?
clear_session
end
end
def two_factor_update_session(auth_type)
authenticated_by << auth_type
set_session_value(two_factor_setup_session_key, true)
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/update_password_hash.rb 0000664 0000000 0000000 00000001174 15157255142 0026635 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:update_password_hash, :UpdatePasswordHash) do
depends :login_password_requirements_base
def password_match?(password)
if (result = super) && update_password_hash?
@update_password_hash = false
set_password(password)
end
result
end
private
def update_password_hash?
password_hash_cost != @current_password_hash_cost || @update_password_hash
end
def get_password_hash
if hash = super
@current_password_hash_cost = extract_password_hash_cost(hash)
end
hash
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/verify_account.rb 0000664 0000000 0000000 00000024510 15157255142 0025445 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:verify_account, :VerifyAccount) do
depends :login, :create_account, :email_base
error_flash "Unable to verify account"
error_flash "Unable to resend verify account email", 'verify_account_resend'
error_flash "An email has recently been sent to you with a link to verify your account", 'verify_account_email_recently_sent'
error_flash "There was an error verifying your account: invalid verify account key", 'no_matching_verify_account_key'
error_flash "The account you tried to create is currently awaiting verification", 'attempt_to_create_unverified_account'
error_flash "The account you tried to login with is currently awaiting verification", 'attempt_to_login_to_unverified_account'
notice_flash "Your account has been verified"
notice_flash "An email has been sent to you with a link to verify your account", 'verify_account_email_sent'
loaded_templates %w'verify-account verify-account-resend verify-account-email'
view 'verify-account', 'Verify Account'
view 'verify-account-resend', 'Resend Verification Email', 'resend_verify_account'
additional_form_tags
additional_form_tags 'verify_account_resend'
after
after 'verify_account_email_resend'
before
before 'verify_account_email_resend'
button 'Verify Account'
button 'Send Verification Email Again', 'verify_account_resend'
redirect
response
response :verify_account_email_sent
redirect(:verify_account_email_sent){default_post_email_redirect}
redirect(:verify_account_email_recently_sent){default_post_email_redirect}
email :verify_account, 'Verify Account'
auth_value_method :verify_account_key_param, 'key'
auth_value_method :verify_account_autologin?, true
auth_value_method :verify_account_table, :account_verification_keys
auth_value_method :verify_account_id_column, :id
auth_value_method :verify_account_email_last_sent_column, :email_last_sent
auth_value_method :verify_account_skip_resend_email_within, 300
auth_value_method :verify_account_key_column, :key
translatable_method :verify_account_resend_explanatory_text, "If you no longer have the email to verify the account, you can request that it be resent to you:
"
translatable_method :verify_account_resend_link_text, "Resend Verify Account Information"
session_key :verify_account_session_key, :verify_account_key
auth_value_method :verify_account_set_password?, true
auth_methods(
:allow_resending_verify_account_email?,
:create_verify_account_key,
:get_verify_account_key,
:get_verify_account_email_last_sent,
:remove_verify_account_key,
:set_verify_account_email_last_sent,
:verify_account,
:verify_account_email_link,
:verify_account_key_insert_hash,
:verify_account_key_value
)
auth_private_methods(
:account_from_verify_account_key
)
internal_request_method(:verify_account_resend)
internal_request_method
route(:verify_account_resend) do |r|
verify_account_check_already_logged_in
before_verify_account_resend_route
r.get do
resend_verify_account_view
end
r.post do
if account_from_login(login_param_value) && allow_resending_verify_account_email?
if verify_account_email_recently_sent?
set_redirect_error_flash verify_account_email_recently_sent_error_flash
redirect verify_account_email_recently_sent_redirect
end
before_verify_account_email_resend
if verify_account_email_resend
after_verify_account_email_resend
verify_account_email_sent_response
end
end
set_redirect_error_status(no_matching_login_error_status)
set_error_reason :no_matching_login
set_redirect_error_flash verify_account_resend_error_flash
redirect verify_account_email_sent_redirect
end
end
route do |r|
verify_account_check_already_logged_in
before_verify_account_route
@password_field_autocomplete_value = 'new-password'
r.get do
if key = param_or_nil(verify_account_key_param)
set_session_value(verify_account_session_key, key)
redirect(r.path)
end
if (key = session[verify_account_session_key]) && account_from_verify_account_key(key)
verify_account_view
else
remove_session_value(verify_account_session_key)
set_redirect_error_flash no_matching_verify_account_key_error_flash
redirect require_login_redirect
end
end
r.post do
key = session[verify_account_session_key] || param(verify_account_key_param)
unless account_from_verify_account_key(key)
set_redirect_error_status(invalid_key_error_status)
set_error_reason :invalid_verify_account_key
set_redirect_error_flash verify_account_error_flash
redirect verify_account_redirect
end
catch_error do
if verify_account_set_password?
password = param(password_param)
if require_password_confirmation? && password != param(password_confirm_param)
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, password_param, passwords_do_not_match_message)
end
unless password_meets_requirements?(password)
throw_error_status(invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
end
end
transaction do
before_verify_account
verify_account
if verify_account_set_password?
set_password(password)
end
clear_tokens(:verify_account)
after_verify_account
end
if verify_account_autologin?
autologin_session('verify_account')
end
remove_session_value(verify_account_session_key)
verify_account_response
end
set_error_flash verify_account_error_flash
verify_account_view
end
end
def require_login_confirmation?
false
end
def allow_resending_verify_account_email?
account[account_status_column] == account_unverified_status_value
end
def remove_verify_account_key
verify_account_ds.delete
end
def verify_account
update_account(account_status_column=>account_open_status_value) == 1
end
def verify_account_email_resend
if @verify_account_key_value = get_verify_account_key(account_id)
set_verify_account_email_last_sent
send_verify_account_email
true
end
end
def create_account_notice_flash
verify_account_email_sent_notice_flash
end
def new_account(login)
if account_from_login(login) && allow_resending_verify_account_email?
set_response_error_reason_status(:already_an_unverified_account_with_this_login, unopen_account_error_status)
set_error_flash attempt_to_create_unverified_account_error_flash
return_response resend_verify_account_view
end
super
end
def account_from_verify_account_key(key)
@account = _account_from_verify_account_key(key)
end
def account_initial_status_value
account_unverified_status_value
end
def verify_account_email_link
token_link(verify_account_route, verify_account_key_param, verify_account_key_value)
end
def get_verify_account_key(id)
verify_account_ds(id).get(verify_account_key_column)
end
def skip_status_checks?
false
end
def create_account_autologin?
false
end
def create_account_set_password?
return false if verify_account_set_password?
super
end
def set_verify_account_email_last_sent
verify_account_ds.update(verify_account_email_last_sent_column=>Sequel::CURRENT_TIMESTAMP) if verify_account_email_last_sent_column
end
def get_verify_account_email_last_sent
if column = verify_account_email_last_sent_column
if ts = verify_account_ds.get(column)
convert_timestamp(ts)
end
end
end
def setup_account_verification
generate_verify_account_key_value
create_verify_account_key
send_verify_account_email
end
def verify_account_email_recently_sent?
account && (email_last_sent = get_verify_account_email_last_sent) && (Time.now - email_last_sent < verify_account_skip_resend_email_within)
end
def clear_tokens(reason)
super
remove_verify_account_key
end
private
def _login_form_footer_links
links = super
if !param_or_nil(login_param) || ((account || account_from_login(login_param_value)) && allow_resending_verify_account_email?)
links << [30, verify_account_resend_path, verify_account_resend_link_text]
end
links
end
attr_reader :verify_account_key_value
def before_login_attempt
unless open_account?
set_response_error_reason_status(:unverified_account, unopen_account_error_status)
set_error_flash attempt_to_login_to_unverified_account_error_flash
return_response resend_verify_account_view
end
super
end
def after_create_account
setup_account_verification
super
end
def verify_account_check_already_logged_in
check_already_logged_in
end
def generate_verify_account_key_value
@verify_account_key_value = random_key
end
def create_verify_account_key
ds = verify_account_ds
transaction do
if ds.empty?
if e = raised_uniqueness_violation{ds.insert(verify_account_key_insert_hash)}
# If inserting into the verify account table causes a violation, we can pull the
# key from the verify account table, or reraise.
raise e unless @verify_account_key_value = get_verify_account_key(account_id)
end
end
end
end
def verify_account_key_insert_hash
{verify_account_id_column=>account_id, verify_account_key_column=>verify_account_key_value}
end
def verify_account_ds(id=account_id)
db[verify_account_table].where(verify_account_id_column=>id)
end
def _account_from_verify_account_key(token)
account_from_key(token, account_unverified_status_value){|id| get_verify_account_key(id)}
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/verify_account_grace_period.rb 0000664 0000000 0000000 00000005503 15157255142 0030151 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:verify_account_grace_period, :VerifyAccountGracePeriod) do
depends :verify_account
error_flash "Please verify this account before changing the login", "unverified_change_login"
redirect :unverified_change_login
auth_value_method :verification_requested_at_column, :requested_at
session_key :unverified_account_session_key, :unverified_account
auth_value_method :verify_account_grace_period, 86400
auth_methods(
:account_in_unverified_grace_period?
)
def verified_account?
logged_in? && !session[unverified_account_session_key]
end
def create_account_autologin?
true
end
def open_account?
super || (account_in_unverified_grace_period? && has_password?)
end
def verify_account_set_password?
false
end
def logged_in?
super && !unverified_grace_period_expired?
end
def require_login
if unverified_grace_period_expired?
clear_session
end
super
end
def update_session
super
if account_in_unverified_grace_period?
set_session_value(unverified_account_session_key, Time.now.to_i + verify_account_grace_period)
end
end
private
def after_close_account
super if defined?(super)
verify_account_ds.delete
end
def before_change_login_route
unless verified_account?
set_redirect_error_flash unverified_change_login_error_flash
redirect unverified_change_login_redirect
end
super if defined?(super)
end
def allow_email_auth?
(defined?(super) ? super : true) && !account_in_unverified_grace_period?
end
def verify_account_check_already_logged_in
nil
end
def account_session_status_filter
s = super
if verify_account_grace_period
grace_period_ds = db[verify_account_table].
select(verify_account_id_column).
where((Sequel.date_add(verification_requested_at_column, :seconds=>verify_account_grace_period) > Sequel::CURRENT_TIMESTAMP))
s = Sequel.|(s, Sequel.expr(account_status_column=>account_unverified_status_value) & {account_id_column => grace_period_ds})
end
s
end
def account_in_unverified_grace_period?
return false unless account!
account[account_status_column] == account_unverified_status_value &&
verify_account_grace_period &&
!verify_account_ds.where(Sequel.date_add(verification_requested_at_column, :seconds=>verify_account_grace_period) > Sequel::CURRENT_TIMESTAMP).empty?
end
def unverified_grace_period_expired?
return false unless expires_at = session[unverified_account_session_key]
expires_at.is_a?(Integer) && Time.now.to_i > expires_at
end
def use_date_arithmetic?
true
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/verify_login_change.rb 0000664 0000000 0000000 00000016744 15157255142 0026440 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:verify_login_change, :VerifyLoginChange) do
depends :change_login, :email_base
error_flash "Unable to verify login change"
error_flash "Unable to change login as there is already an account with the new login", 'verify_login_change_duplicate_account'
error_flash "There was an error verifying your login change: invalid verify login change key", 'no_matching_verify_login_change_key'
notice_flash "Your login change has been verified"
notice_flash "An email has been sent to you with a link to verify your login change", 'change_login_needs_verification'
loaded_templates %w'verify-login-change verify-login-change-email'
view 'verify-login-change', 'Verify Login Change'
additional_form_tags
after
after 'verify_login_change_email'
before
before 'verify_login_change_email'
button 'Verify Login Change'
redirect
response
redirect(:verify_login_change_duplicate_account){require_login_redirect}
auth_value_method :verify_login_change_autologin?, false
auth_value_method :verify_login_change_deadline_column, :deadline
auth_value_method :verify_login_change_deadline_interval, {:days=>1}.freeze
translatable_method :verify_login_change_email_subject, 'Verify Login Change'
auth_value_method :verify_login_change_id_column, :id
auth_value_method :verify_login_change_key_column, :key
auth_value_method :verify_login_change_key_param, 'key'
auth_value_method :verify_login_change_login_column, :login
session_key :verify_login_change_session_key, :verify_login_change_key
auth_value_method :verify_login_change_table, :account_login_change_keys
auth_methods(
:create_verify_login_change_email,
:create_verify_login_change_key,
:get_verify_login_change_login_and_key,
:remove_verify_login_change_key,
:send_verify_login_change_email,
:verify_login_change,
:verify_login_change_email_body,
:verify_login_change_email_link,
:verify_login_change_key_insert_hash,
:verify_login_change_key_value,
:verify_login_change_new_login,
:verify_login_change_old_login
)
auth_private_methods(
:account_from_verify_login_change_key
)
internal_request_method
route do |r|
before_verify_login_change_route
r.get do
if key = param_or_nil(verify_login_change_key_param)
set_session_value(verify_login_change_session_key, key)
redirect(r.path)
end
if (key = session[verify_login_change_session_key]) && account_from_verify_login_change_key(key)
verify_login_change_view
else
remove_session_value(verify_login_change_session_key)
set_redirect_error_flash no_matching_verify_login_change_key_error_flash
redirect require_login_redirect
end
end
r.post do
key = session[verify_login_change_session_key] || param(verify_login_change_key_param)
unless account_from_verify_login_change_key(key)
set_redirect_error_status(invalid_key_error_status)
set_error_reason :invalid_verify_login_change_key
set_redirect_error_flash verify_login_change_error_flash
redirect verify_login_change_redirect
end
transaction do
before_verify_login_change
unless verify_login_change
set_redirect_error_status(invalid_key_error_status)
set_error_reason :already_an_account_with_this_login
set_redirect_error_flash verify_login_change_duplicate_account_error_flash
redirect verify_login_change_duplicate_account_redirect
end
remove_verify_login_change_key
after_verify_login_change
end
if verify_login_change_autologin?
autologin_session('verify_login_change')
end
remove_session_value(verify_login_change_session_key)
verify_login_change_response
end
end
def require_login_confirmation?
false
end
def remove_verify_login_change_key
verify_login_change_ds.delete
end
def verify_login_change
unless res = _update_login(verify_login_change_new_login)
remove_verify_login_change_key
end
res
end
def account_from_verify_login_change_key(key)
@account = _account_from_verify_login_change_key(key)
end
def send_verify_login_change_email(login)
send_email(create_verify_login_change_email(login))
end
def verify_login_change_email_link
token_link(verify_login_change_route, verify_login_change_key_param, verify_login_change_key_value)
end
def get_verify_login_change_login_and_key(id)
verify_login_change_ds(id).get([verify_login_change_login_column, verify_login_change_key_column])
end
def change_login_notice_flash
change_login_needs_verification_notice_flash
end
def verify_login_change_old_login
account_ds.get(login_column)
end
attr_reader :verify_login_change_key_value
attr_reader :verify_login_change_new_login
def clear_tokens(reason)
super
remove_verify_login_change_key
end
private
def update_login(login)
if _account_from_login(login)
set_login_requirement_error_message(:already_an_account_with_this_login, already_an_account_with_this_login_message)
return false
end
transaction do
before_verify_login_change_email
generate_verify_login_change_key_value
@verify_login_change_new_login = login
create_verify_login_change_key(login)
send_verify_login_change_email(login)
after_verify_login_change_email
end
true
end
def generate_verify_login_change_key_value
@verify_login_change_key_value = random_key
end
def create_verify_login_change_key(login)
ds = verify_login_change_ds
transaction do
ds.where((Sequel::CURRENT_TIMESTAMP > verify_login_change_deadline_column) | ~Sequel.expr(verify_login_change_login_column=>login)).delete
if e = raised_uniqueness_violation{ds.insert(verify_login_change_key_insert_hash(login))}
old_login, key = get_verify_login_change_login_and_key(account_id)
# If inserting into the verify login change table causes a violation, we can pull the
# key from the verify login change table if the logins match, or reraise.
@verify_login_change_key_value = if old_login.downcase == login.downcase
key
end
raise e unless @verify_login_change_key_value
end
end
end
def verify_login_change_key_insert_hash(login)
hash = {verify_login_change_id_column=>account_id, verify_login_change_key_column=>verify_login_change_key_value, verify_login_change_login_column=>login}
set_deadline_value(hash, verify_login_change_deadline_column, verify_login_change_deadline_interval)
hash
end
def create_verify_login_change_email(login)
create_email_to(login, verify_login_change_email_subject, verify_login_change_email_body)
end
def verify_login_change_email_body
render('verify-login-change-email')
end
def verify_login_change_ds(id=account_id)
db[verify_login_change_table].where(verify_login_change_id_column=>id)
end
def _account_from_verify_login_change_key(token)
account_from_key(token) do |id|
@verify_login_change_new_login, key = get_verify_login_change_login_and_key(id)
key
end
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/webauthn.rb 0000664 0000000 0000000 00000050052 15157255142 0024242 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'webauthn'
module Rodauth
Feature.define(:webauthn, :Webauthn) do
depends :two_factor_base
loaded_templates %w'webauthn-setup webauthn-auth webauthn-remove'
view 'webauthn-setup', 'Setup WebAuthn Authentication', 'webauthn_setup'
view 'webauthn-auth', 'Authenticate Using WebAuthn', 'webauthn_auth'
view 'webauthn-remove', 'Remove WebAuthn Authenticator', 'webauthn_remove'
additional_form_tags 'webauthn_setup'
additional_form_tags 'webauthn_auth'
additional_form_tags 'webauthn_remove'
before :webauthn_setup
before :webauthn_auth
before :webauthn_remove
after :webauthn_setup
after :webauthn_auth_failure
after :webauthn_remove
button 'Setup WebAuthn Authentication', 'webauthn_setup'
button 'Authenticate Using WebAuthn', 'webauthn_auth'
button 'Remove WebAuthn Authenticator', 'webauthn_remove'
redirect :webauthn_setup
redirect :webauthn_remove
response :webauthn_setup
response :webauthn_remove
notice_flash "WebAuthn authentication is now setup", 'webauthn_setup'
notice_flash "WebAuthn authenticator has been removed", 'webauthn_remove'
error_flash "Error setting up WebAuthn authentication", 'webauthn_setup'
error_flash "Error authenticating using WebAuthn", 'webauthn_auth'
error_flash 'This account has not been setup for WebAuthn authentication', 'webauthn_not_setup'
error_flash "Error removing WebAuthn authenticator", 'webauthn_remove'
session_key :authenticated_webauthn_id_session_key, :webauthn_id
translatable_method :webauthn_auth_link_text, "Authenticate Using WebAuthn"
translatable_method :webauthn_setup_link_text, "Setup WebAuthn Authentication"
translatable_method :webauthn_remove_link_text, "Remove WebAuthn Authenticator"
auth_value_method :webauthn_setup_param, 'webauthn_setup'
auth_value_method :webauthn_auth_param, 'webauthn_auth'
auth_value_method :webauthn_remove_param, 'webauthn_remove'
auth_value_method :webauthn_setup_challenge_param, 'webauthn_setup_challenge'
auth_value_method :webauthn_setup_challenge_hmac_param, 'webauthn_setup_challenge_hmac'
auth_value_method :webauthn_auth_challenge_param, 'webauthn_auth_challenge'
auth_value_method :webauthn_auth_challenge_hmac_param, 'webauthn_auth_challenge_hmac'
auth_value_method :webauthn_keys_account_id_column, :account_id
auth_value_method :webauthn_keys_webauthn_id_column, :webauthn_id
auth_value_method :webauthn_keys_public_key_column, :public_key
auth_value_method :webauthn_keys_sign_count_column, :sign_count
auth_value_method :webauthn_keys_last_use_column, :last_use
auth_value_method :webauthn_keys_table, :account_webauthn_keys
auth_value_method :webauthn_user_ids_account_id_column, :id
auth_value_method :webauthn_user_ids_webauthn_id_column, :webauthn_id
auth_value_method :webauthn_user_ids_table, :account_webauthn_user_ids
auth_value_method :webauthn_setup_js, File.binread(File.expand_path('../../../../javascript/webauthn_setup.js', __FILE__)).freeze
auth_value_method :webauthn_auth_js, File.binread(File.expand_path('../../../../javascript/webauthn_auth.js', __FILE__)).freeze
auth_value_method :webauthn_js_host, ''
auth_value_method :webauthn_setup_timeout, 120000
auth_value_method :webauthn_auth_timeout, 60000
auth_value_method :webauthn_user_verification, 'discouraged'
auth_value_method :webauthn_attestation, 'none'
auth_value_method :webauthn_not_setup_error_status, 403
translatable_method :webauthn_invalid_setup_param_message, "invalid webauthn setup param"
translatable_method :webauthn_duplicate_webauthn_id_message, "attempt to insert duplicate webauthn id"
translatable_method :webauthn_invalid_auth_param_message, "invalid webauthn authentication param"
translatable_method :webauthn_invalid_sign_count_message, "webauthn credential has invalid sign count"
translatable_method :webauthn_invalid_remove_param_message, "must select valid webauthn authenticator to remove"
auth_value_methods(
:webauthn_authenticator_selection,
:webauthn_extensions,
:webauthn_origin,
:webauthn_rp_id,
:webauthn_rp_name,
)
auth_methods(
:account_webauthn_ids,
:account_webauthn_usage,
:account_webauthn_user_id,
:add_webauthn_credential,
:authenticated_webauthn_id,
:handle_webauthn_sign_count_verification_error,
:new_webauthn_credential,
:remove_webauthn_key,
:remove_all_webauthn_keys_and_user_ids,
:valid_new_webauthn_credential?,
:valid_webauthn_credential_auth?,
:webauthn_auth_js_path,
:webauthn_credential_options_for_get,
:webauthn_key_insert_hash,
:webauthn_remove_authenticated_session,
:webauthn_setup_js_path,
:webauthn_update_session,
:webauthn_user_name,
)
def_deprecated_alias :webauthn_credential_options_for_get, :webauth_credential_options_for_get
internal_request_method :webauthn_setup_params
internal_request_method :webauthn_setup
internal_request_method :webauthn_auth_params
internal_request_method :webauthn_auth
internal_request_method :webauthn_remove
route(:webauthn_auth_js) do |r|
before_webauthn_auth_js_route
r.get do
set_response_header('content-type', 'text/javascript')
webauthn_auth_js
end
end
route(:webauthn_auth) do |r|
require_login
require_account_session
require_two_factor_not_authenticated('webauthn')
require_webauthn_setup
before_webauthn_auth_route
r.get do
webauthn_auth_view
end
r.post do
catch_error do
webauthn_credential = webauthn_auth_credential_from_form_submission
transaction do
before_webauthn_auth
webauthn_update_session(webauthn_credential.id)
two_factor_authenticate('webauthn')
end
end
after_webauthn_auth_failure
set_error_flash webauthn_auth_error_flash
webauthn_auth_view
end
end
route(:webauthn_setup_js) do |r|
before_webauthn_setup_js_route
r.get do
set_response_header('content-type', 'text/javascript')
webauthn_setup_js
end
end
route(:webauthn_setup) do |r|
require_authentication unless two_factor_login_type_match?('webauthn')
require_account_session
before_webauthn_setup_route
r.get do
webauthn_setup_view
end
r.post do
catch_error do
webauthn_credential = webauthn_setup_credential_from_form_submission
throw_error = false
transaction do
before_webauthn_setup
if raises_uniqueness_violation?{add_webauthn_credential(webauthn_credential)}
throw_error = true
raise Sequel::Rollback
end
unless two_factor_authenticated?
webauthn_update_session(webauthn_credential.id)
two_factor_update_session('webauthn')
end
after_webauthn_setup
end
if throw_error
throw_error_reason(:duplicate_webauthn_id, invalid_field_error_status, webauthn_setup_param, webauthn_duplicate_webauthn_id_message)
end
webauthn_setup_response
end
set_error_flash webauthn_setup_error_flash
webauthn_setup_view
end
end
route(:webauthn_remove) do |r|
require_authentication unless two_factor_login_type_match?('webauthn')
require_account_session
require_webauthn_setup
before_webauthn_remove_route
r.get do
webauthn_remove_view
end
r.post do
catch_error do
unless webauthn_id = param_or_nil(webauthn_remove_param)
throw_error_reason(:invalid_webauthn_remove_param, invalid_field_error_status, webauthn_remove_param, webauthn_invalid_remove_param_message)
end
unless two_factor_password_match?(param(password_param))
throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
end
transaction do
before_webauthn_remove
unless remove_webauthn_key(webauthn_id)
throw_error_reason(:invalid_webauthn_remove_param, invalid_field_error_status, webauthn_remove_param, webauthn_invalid_remove_param_message)
end
if authenticated_webauthn_id == webauthn_id && two_factor_login_type_match?('webauthn')
webauthn_remove_authenticated_session
two_factor_remove_session('webauthn')
end
after_webauthn_remove
end
webauthn_remove_response
end
set_error_flash webauthn_remove_error_flash
webauthn_remove_view
end
end
def webauthn_auth_form_path
webauthn_auth_path
end
def authenticated_webauthn_id
session[authenticated_webauthn_id_session_key]
end
def webauthn_remove_authenticated_session
remove_session_value(authenticated_webauthn_id_session_key)
end
def webauthn_update_session(webauthn_id)
set_session_value(authenticated_webauthn_id_session_key, webauthn_id)
end
def webauthn_authenticator_selection
{'requireResidentKey' => false, 'userVerification' => webauthn_user_verification}
end
def webauthn_extensions
{}
end
def account_webauthn_ids
webauthn_keys_ds.select_map(webauthn_keys_webauthn_id_column)
end
def account_webauthn_usage
webauthn_keys_ds.select_hash(webauthn_keys_webauthn_id_column, webauthn_keys_last_use_column)
end
def account_webauthn_user_id
unless webauthn_id = webauthn_user_ids_ds.get(webauthn_user_ids_webauthn_id_column)
webauthn_id = WebAuthn.generate_user_id
if e = raised_uniqueness_violation do
webauthn_user_ids_ds.insert(
webauthn_user_ids_account_id_column => webauthn_account_id,
webauthn_user_ids_webauthn_id_column => webauthn_id
)
end
# If two requests to create a webauthn user id are sent at the same time and an insert
# is attempted for both, one will fail with a unique constraint violation. In that case
# it is safe for the second one to use the webauthn user id inserted by the other request.
# If there is still no webauthn user id at this point, then we'll just reraise the
# exception.
# :nocov:
raise e unless webauthn_id = webauthn_user_ids_ds.get(webauthn_user_ids_webauthn_id_column)
# :nocov:
end
end
webauthn_id
end
def new_webauthn_credential
WebAuthn::Credential.options_for_create(
:timeout => webauthn_setup_timeout,
:user => {:id=>account_webauthn_user_id, :name=>webauthn_user_name},
:authenticator_selection => webauthn_authenticator_selection,
:attestation => webauthn_attestation,
:extensions => webauthn_extensions,
:exclude => account_webauthn_ids,
**webauthn_create_relying_party_opts
)
end
def valid_new_webauthn_credential?(webauthn_credential)
_override_webauthn_credential_response_verify(webauthn_credential)
(challenge = param_or_nil(webauthn_setup_challenge_param)) &&
(hmac = param_or_nil(webauthn_setup_challenge_hmac_param)) &&
(timing_safe_eql?(compute_hmac(challenge), hmac) || (hmac_secret_rotation? && timing_safe_eql?(compute_old_hmac(challenge), hmac))) &&
webauthn_credential.verify(challenge)
end
def webauthn_credential_options_for_get
WebAuthn::Credential.options_for_get(
:allow => webauthn_allow,
:timeout => webauthn_auth_timeout,
:user_verification => webauthn_user_verification,
:extensions => webauthn_extensions,
**webauthn_get_relying_party_opts
)
end
def webauthn_user_name
account![login_column]
end
def webauthn_origin
base_url
end
def webauthn_allow
account_webauthn_ids
end
def webauthn_rp_id
webauthn_origin.sub(/\Ahttps?:\/\//, '').sub(/:\d+\z/, '')
end
def webauthn_rp_name
webauthn_rp_id
end
def handle_webauthn_sign_count_verification_error
throw_error_reason(:invalid_webauthn_sign_count, invalid_field_error_status, webauthn_auth_param, webauthn_invalid_sign_count_message)
end
def add_webauthn_credential(webauthn_credential)
webauthn_keys_ds.insert(webauthn_key_insert_hash(webauthn_credential))
super if defined?(super)
nil
end
def valid_webauthn_credential_auth?(webauthn_credential)
ds = webauthn_keys_ds.where(webauthn_keys_webauthn_id_column => webauthn_credential.id)
pub_key, sign_count = ds.get([webauthn_keys_public_key_column, webauthn_keys_sign_count_column])
_override_webauthn_credential_response_verify(webauthn_credential)
(challenge = param_or_nil(webauthn_auth_challenge_param)) &&
(hmac = param_or_nil(webauthn_auth_challenge_hmac_param)) &&
(timing_safe_eql?(compute_hmac(challenge), hmac) || (hmac_secret_rotation? && timing_safe_eql?(compute_old_hmac(challenge), hmac))) &&
webauthn_credential.verify(challenge, public_key: pub_key, sign_count: sign_count) &&
ds.update(
webauthn_keys_sign_count_column => Integer(webauthn_credential.sign_count),
webauthn_keys_last_use_column => Sequel::CURRENT_TIMESTAMP
) == 1
end
def remove_webauthn_key(webauthn_id)
webauthn_keys_ds.where(webauthn_keys_webauthn_id_column=>webauthn_id).delete == 1
end
def remove_all_webauthn_keys_and_user_ids
webauthn_user_ids_ds.delete
webauthn_keys_ds.delete
end
def webauthn_setup?
!webauthn_keys_ds.empty?
end
def require_webauthn_setup
unless webauthn_setup?
set_redirect_error_status(webauthn_not_setup_error_status)
set_error_reason :webauthn_not_setup
set_redirect_error_flash webauthn_not_setup_error_flash
redirect two_factor_need_setup_redirect
end
end
def two_factor_remove
super
remove_all_webauthn_keys_and_user_ids
end
def possible_authentication_methods
methods = super
methods << 'webauthn' if webauthn_setup?
methods
end
private
if WebAuthn::VERSION >= '3'
if WebAuthn::RelyingParty.instance_method(:initialize).parameters.include?([:key, :allowed_origins])
def webauthn_relying_party
# No need to memoize, only called once per request
WebAuthn::RelyingParty.new(
allowed_origins: [webauthn_origin],
id: webauthn_rp_id,
name: webauthn_rp_name,
)
end
# :nocov:
else
def webauthn_relying_party
WebAuthn::RelyingParty.new(
origin: webauthn_origin,
id: webauthn_rp_id,
name: webauthn_rp_name,
)
end
# :nocov:
end
def webauthn_create_relying_party_opts
{ :relying_party => webauthn_relying_party }
end
alias webauthn_get_relying_party_opts webauthn_create_relying_party_opts
def webauthn_form_submission_call(meth, arg)
WebAuthn::Credential.public_send(meth, arg, :relying_party => webauthn_relying_party)
end
def _override_webauthn_credential_response_verify(webauthn_credential)
# no need to override
end
# :nocov:
else
def webauthn_create_relying_party_opts
{:rp => {:name=>webauthn_rp_name, :id=>webauthn_rp_id}}
end
def webauthn_get_relying_party_opts
{ :rp_id => webauthn_rp_id }
end
def webauthn_form_submission_call(meth, arg)
WebAuthn::Credential.public_send(meth, arg)
end
def _override_webauthn_credential_response_verify(webauthn_credential)
# Hack around inability to override expected_origin and rp_id
origin = webauthn_origin
rp_id = webauthn_rp_id
webauthn_credential.response.define_singleton_method(:verify) do |expected_challenge, expected_origin = nil, **kw|
kw[:rp_id] = rp_id
super(expected_challenge, expected_origin || origin, **kw)
end
end
# :nocov:
end
def _two_factor_auth_links
links = super
links << [10, webauthn_auth_path, webauthn_auth_link_text] if webauthn_setup? && !two_factor_login_type_match?('webauthn')
links
end
def _two_factor_setup_links
super << [10, webauthn_setup_path, webauthn_setup_link_text]
end
def _two_factor_remove_links
links = super
links << [10, webauthn_remove_path, webauthn_remove_link_text] if webauthn_setup?
links
end
def _two_factor_remove_all_from_session
two_factor_remove_session('webauthn')
remove_session_value(authenticated_webauthn_id_session_key)
super
end
def webauthn_key_insert_hash(webauthn_credential)
{
webauthn_keys_account_id_column => webauthn_account_id,
webauthn_keys_webauthn_id_column => webauthn_credential.id,
webauthn_keys_public_key_column => webauthn_credential.public_key,
webauthn_keys_sign_count_column => Integer(webauthn_credential.sign_count)
}
end
def webauthn_account_id
session_value
end
def webauthn_user_ids_ds
db[webauthn_user_ids_table].where(webauthn_user_ids_account_id_column => webauthn_account_id)
end
def webauthn_keys_ds
db[webauthn_keys_table].where(webauthn_keys_account_id_column => webauthn_account_id)
end
def webauthn_auth_credential_from_form_submission
begin
webauthn_credential = webauthn_form_submission_call(:from_get, webauthn_auth_data)
unless valid_webauthn_credential_auth?(webauthn_credential)
throw_error_reason(:invalid_webauthn_auth_param, invalid_key_error_status, webauthn_auth_param, webauthn_invalid_auth_param_message)
end
rescue WebAuthn::SignCountVerificationError
handle_webauthn_sign_count_verification_error
rescue WebAuthn::Error, RuntimeError, NoMethodError
throw_error_reason(:invalid_webauthn_auth_param, invalid_field_error_status, webauthn_auth_param, webauthn_invalid_auth_param_message)
end
webauthn_credential
end
def webauthn_auth_data
case auth_data = raw_param(webauthn_auth_param)
when String
begin
JSON.parse(auth_data)
rescue
throw_error_reason(:invalid_webauthn_auth_param, invalid_field_error_status, webauthn_auth_param, webauthn_invalid_auth_param_message)
end
when Hash
auth_data
else
throw_error_reason(:invalid_webauthn_auth_param, invalid_field_error_status, webauthn_auth_param, webauthn_invalid_auth_param_message)
end
end
def webauthn_setup_credential_from_form_submission
unless two_factor_password_match?(param(password_param))
throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
end
begin
webauthn_credential = webauthn_form_submission_call(:from_create, webauthn_setup_data)
unless valid_new_webauthn_credential?(webauthn_credential)
throw_error_reason(:invalid_webauthn_setup_param, invalid_field_error_status, webauthn_setup_param, webauthn_invalid_setup_param_message)
end
rescue WebAuthn::Error, RuntimeError, NoMethodError
throw_error_reason(:invalid_webauthn_setup_param, invalid_field_error_status, webauthn_setup_param, webauthn_invalid_setup_param_message)
end
webauthn_credential
end
def webauthn_setup_data
case setup_data = raw_param(webauthn_setup_param)
when String
begin
JSON.parse(setup_data)
rescue
throw_error_reason(:invalid_webauthn_setup_param, invalid_field_error_status, webauthn_setup_param, webauthn_invalid_setup_param_message)
end
when Hash
setup_data
else
throw_error_reason(:invalid_webauthn_setup_param, invalid_field_error_status, webauthn_setup_param, webauthn_invalid_setup_param_message)
end
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/webauthn_autofill.rb 0000664 0000000 0000000 00000003432 15157255142 0026141 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:webauthn_autofill, :WebauthnAutofill) do
depends :webauthn_login
auth_value_method :webauthn_autofill?, true
auth_value_method :webauthn_autofill_js, File.binread(File.expand_path('../../../../javascript/webauthn_autofill.js', __FILE__)).freeze
translatable_method :webauthn_invalid_webauthn_id_message, "no webauthn key with given id found"
route(:webauthn_autofill_js) do |r|
before_webauthn_autofill_js_route
r.get do
set_response_header('content-type', 'text/javascript')
webauthn_autofill_js
end
end
def webauthn_allow
return [] unless logged_in? || account
super
end
def webauthn_user_verification
'preferred'
end
def webauthn_authenticator_selection
super.merge({ 'residentKey' => 'required', 'requireResidentKey' => true })
end
def login_field_autocomplete_value
request.path_info == login_path ? "#{super} webauthn" : super
end
private
def _login_form_footer
footer = super
footer += render("webauthn-autofill") if webauthn_autofill? && !valid_login_entered?
footer
end
def account_from_webauthn_login
return super if param_or_nil(login_param)
credential_id = webauthn_auth_data["id"]
account_id = db[webauthn_keys_table]
.where(webauthn_keys_webauthn_id_column => credential_id)
.get(webauthn_keys_account_id_column)
unless account_id
throw_error_reason(:invalid_webauthn_id, invalid_field_error_status, webauthn_auth_param, webauthn_invalid_webauthn_id_message)
end
account_from_id(account_id)
end
def webauthn_login_options?
return true unless param_or_nil(login_param)
super
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/webauthn_login.rb 0000664 0000000 0000000 00000004662 15157255142 0025440 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:webauthn_login, :WebauthnLogin) do
depends :login, :webauthn
before
redirect(:webauthn_login_failure){require_login_redirect}
error_flash "There was an error authenticating via WebAuthn"
auth_value_method :webauthn_login_user_verification_additional_factor?, false
internal_request_method :webauthn_login_params
internal_request_method :webauthn_login
route(:webauthn_login) do |r|
check_already_logged_in
before_webauthn_login_route
r.post do
catch_error do
unless account_from_webauthn_login && open_account?
throw_error_reason(:no_matching_login, no_matching_login_error_status, login_param, no_matching_login_message)
end
webauthn_credential = webauthn_auth_credential_from_form_submission
before_webauthn_login
login('webauthn') do
webauthn_update_session(webauthn_credential.id)
if webauthn_login_verification_factor?(webauthn_credential)
two_factor_update_session('webauthn-verification')
end
end
end
set_redirect_error_flash webauthn_login_error_flash
redirect webauthn_login_failure_redirect
end
end
def webauthn_auth_additional_form_tags
if @webauthn_login
super.to_s + login_hidden_field
else
super
end
end
def webauthn_auth_form_path
if @webauthn_login
webauthn_login_path
else
super
end
end
def webauthn_user_verification
return 'preferred' if webauthn_login_user_verification_additional_factor?
super
end
def use_multi_phase_login?
true
end
private
def webauthn_login_verification_factor?(webauthn_credential)
webauthn_login_user_verification_additional_factor? &&
webauthn_credential.response.authenticator_data.user_verified? &&
uses_two_factor_authentication?
end
def account_from_webauthn_login
account_from_login(login_param_value)
end
def webauthn_login_options?
!!account_from_webauthn_login
end
def _multi_phase_login_forms
forms = super
if valid_login_entered? && webauthn_setup?
@webauthn_login = true
forms << [20, render('webauthn-auth'), nil]
end
forms
end
def webauthn_account_id
super || account_id
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/webauthn_modify_email.rb 0000664 0000000 0000000 00000001215 15157255142 0026755 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:webauthn_modify_email, :WebauthnModifyEmail) do
depends :webauthn, :email_base
loaded_templates %w'webauthn-authenticator-added-email webauthn-authenticator-removed-email'
email :webauthn_authenticator_added, 'WebAuthn Authenticator Added', :translatable=>true
email :webauthn_authenticator_removed, 'WebAuthn Authenticator Removed', :translatable=>true
private
def after_webauthn_setup
super
send_webauthn_authenticator_added_email
end
def after_webauthn_remove
super
send_webauthn_authenticator_removed_email
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/features/webauthn_verify_account.rb 0000664 0000000 0000000 00000002347 15157255142 0027346 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
Feature.define(:webauthn_verify_account, :WebauthnVerifyAccount) do
depends :verify_account, :webauthn
def verify_account_view
webauthn_setup_view
end
def create_account_set_password?
false
end
def verify_account_set_password?
false
end
def autologin_session(autologin_type)
super
if autologin_type == 'verify_account'
set_session_value(authenticated_by_session_key, ['webauthn'])
remove_session_value(autologin_type_session_key)
webauthn_update_session(@webauthn_credential.id)
end
end
private
def before_verify_account
super
if features.include?(:json) && use_json? && !param_or_nil(webauthn_setup_param)
cred = new_webauthn_credential
json_response[webauthn_setup_param] = cred.as_json
json_response[webauthn_setup_challenge_param] = cred.challenge
json_response[webauthn_setup_challenge_hmac_param] = compute_hmac(cred.challenge)
end
@webauthn_credential = webauthn_setup_credential_from_form_submission
add_webauthn_credential(@webauthn_credential)
end
def webauthn_account_id
super || account_id
end
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/migrations.rb 0000664 0000000 0000000 00000010761 15157255142 0022766 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
def self.create_database_authentication_functions(db, opts={})
table_name = opts[:table_name] || :account_password_hashes
get_salt_name = opts[:get_salt_name] || :rodauth_get_salt
valid_hash_name = opts[:valid_hash_name] || :rodauth_valid_password_hash
argon2 = opts[:argon2]
case db.database_type
when :postgres
search_path = opts[:search_path] || 'public, pg_temp'
primary_key_type =
case db.schema(table_name).find { |row| row.first == :id }[1][:db_type]
when 'uuid' then :uuid
else :int8
end
table_name = db.literal(table_name) unless table_name.is_a?(String)
argon_sql = <:account_previous_password_hashes, :get_salt_name=>:rodauth_get_previous_salt, :valid_hash_name=>:rodauth_previous_password_hash_match}.merge(opts))
end
def self.drop_database_previous_password_check_functions(db, opts={})
drop_database_authentication_functions(db, {:table_name=>:account_previous_password_hashes, :get_salt_name=>:rodauth_get_previous_salt, :valid_hash_name=>:rodauth_previous_password_hash_match}.merge(opts))
end
end
jeremyevans-rodauth-b53f402/lib/rodauth/version.rb 0000664 0000000 0000000 00000001176 15157255142 0022277 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
module Rodauth
# The major version of Rodauth, updated only for major changes that are
# likely to require modification to apps using Rodauth.
MAJOR = 2
# The minor version of Rodauth, updated for new feature releases of Rodauth.
MINOR = 43
# The patch version of Rodauth, updated only for bug fixes from the last
# feature release.
TINY = 0
# The full version of Rodauth as a string
VERSION = "#{MAJOR}.#{MINOR}.#{TINY}".freeze
# The full version of Rodauth as a number (1.17.0 => 11700)
VERSION_NUMBER = MAJOR*10000 + MINOR*100 + TINY
def self.version
VERSION
end
end
jeremyevans-rodauth-b53f402/rodauth.gemspec 0000664 0000000 0000000 00000005724 15157255142 0021067 0 ustar 00root root 0000000 0000000 require File.expand_path("../lib/rodauth/version", __FILE__)
Gem::Specification.new do |s|
s.name = 'rodauth'
s.version = Rodauth.version
s.platform = Gem::Platform::RUBY
s.extra_rdoc_files = ["MIT-LICENSE"]
s.rdoc_options += ["--quiet", "--line-numbers", "--inline-source", '--title', "Rodauth: Ruby's Most Advanced Authentication Framework", '--main', 'README.rdoc']
s.license = "MIT"
s.summary = "Authentication and Account Management Framework for Rack Applications"
s.author = "Jeremy Evans"
s.email = "code@jeremyevans.net"
s.homepage = "https://rodauth.jeremyevans.net"
s.required_ruby_version = ">= 1.9.2"
s.files = %w(MIT-LICENSE) + Dir["dict/*.txt"] + Dir["lib/**/*.rb"] + Dir["templates/*.str"] + Dir["javascript/*.js"]
s.metadata = {
'bug_tracker_uri' => 'https://github.com/jeremyevans/rodauth/issues',
'changelog_uri' => 'https://rodauth.jeremyevans.net/rdoc/files/CHANGELOG.html',
'documentation_uri' => 'https://rodauth.jeremyevans.net/documentation.html',
'mailing_list_uri' => 'https://github.com/jeremyevans/rodauth/discussions',
'source_code_uri' => 'https://github.com/jeremyevans/rodauth',
}
s.description = <= 4"])
s.add_dependency('roda', [">= 2.6.0"])
s.add_development_dependency('tilt')
s.add_development_dependency('rack_csrf')
s.add_development_dependency('bcrypt')
s.add_development_dependency('argon2', '>=2')
s.add_development_dependency('mail')
s.add_development_dependency('rotp')
s.add_development_dependency('rqrcode')
s.add_development_dependency('jwt')
s.add_development_dependency('webauthn', '>=2')
s.add_development_dependency("minitest", '>=5.0.0')
s.add_development_dependency("minitest-global_expectations")
s.add_development_dependency("minitest-hooks", '>=1.1.0')
s.add_development_dependency("capybara", '>=2.1.0')
end
jeremyevans-rodauth-b53f402/spec/ 0000775 0000000 0000000 00000000000 15157255142 0016776 5 ustar 00root root 0000000 0000000 jeremyevans-rodauth-b53f402/spec/account_expiration_spec.rb 0000664 0000000 0000000 00000017473 15157255142 0024247 0 ustar 00root root 0000000 0000000 require_relative 'spec_helper'
describe 'Rodauth account expiration feature' do
it "should force account expiration after x number of days since last login" do
rodauth do
enable :login, :logout, :account_expiration
end
roda do |r|
r.rodauth
r.root{view :content=>rodauth.logged_in? ? "Logged In#{rodauth.last_account_login_at.strftime('%m%d%y')}" : "Not Logged"}
end
now = Time.now
2.times do
login
page.body.must_include "Logged In#{now.strftime('%m%d%y')}"
logout
end
DB[:account_activity_times].update(:last_login_at => Time.now - 181*86400)
2.times do
login
page.body.must_include 'Not Logged'
page.find('#error_flash').text.must_equal "You cannot log into this account as it has expired"
end
end
[true, false].each do |before|
it "should not allow resetting of passwords for expired accounts, when loading account_expiration #{before ? "before" : "after"}" do
rodauth do
features = [:reset_password, :account_expiration]
features.reverse! if before
enable :login, :logout, *features
reset_password_email_last_sent_column nil
end
roda do |r|
r.rodauth
r.root{view :content=>rodauth.logged_in? ? "Logged In#{rodauth.last_account_login_at.strftime('%m%d%y')}" : "Not Logged"}
end
now = Time.now
login
page.body.must_include "Logged In#{now.strftime('%m%d%y')}"
logout
visit '/login'
click_link 'Forgot Password?'
fill_in 'Login', :with=>'foo@example.com'
click_button 'Request Password Reset'
link = email_link(/(\/reset-password\?key=.+)$/)
visit link
fill_in 'Password', :with=>'0123456'
fill_in 'Confirm Password', :with=>'0123456'
click_button 'Reset Password'
page.find('#notice_flash').text.must_equal "Your password has been reset"
page.current_path.must_equal '/'
visit '/login'
click_link 'Forgot Password?'
fill_in 'Login', :with=>'foo@example.com'
click_button 'Request Password Reset'
link = email_link(/(\/reset-password\?key=.+)$/)
DB[:account_activity_times].update(:last_login_at => Time.now - 181*86400)
visit link
page.title.must_equal 'Reset Password'
fill_in 'Password', :with=>'01234567'
fill_in 'Confirm Password', :with=>'01234567'
click_button 'Reset Password'
page.find('#error_flash').text.must_equal "You cannot log into this account as it has expired"
page.body.must_include 'Not Logged'
page.current_path.must_equal '/'
visit '/login'
click_link 'Forgot Password?'
fill_in 'Login', :with=>'foo@example.com'
click_button 'Request Password Reset'
page.find('#error_flash').text.must_equal "You cannot log into this account as it has expired"
page.body.must_include 'Not Logged'
page.current_path.must_equal '/'
end
it "should not allow account unlocks for expired accounts, when loading account_expiration #{before ? "before" : "after"}" do
rodauth do
features = [:lockout, :account_expiration]
features.reverse! if before
enable :logout, *features
max_invalid_logins 2
unlock_account_autologin? false
end
roda do |r|
r.rodauth
r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
end
login
logout
visit '/login'
fill_in 'Login', :with=>'foo@example.com'
3.times do
fill_in 'Password', :with=>'012345678910'
click_button 'Login'
end
page.body.must_include("This account is currently locked out")
click_button 'Request Account Unlock'
page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
link = email_link(/(\/unlock-account\?key=.+)$/)
visit link
click_button 'Unlock Account'
page.find('#notice_flash').text.must_equal 'Your account has been unlocked'
page.body.must_include('Not Logged')
visit '/login'
fill_in 'Login', :with=>'foo@example.com'
3.times do
fill_in 'Password', :with=>'012345678910'
click_button 'Login'
end
page.body.must_include("This account is currently locked out")
click_button 'Request Account Unlock'
page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
link = email_link(/(\/unlock-account\?key=.+)$/)
DB[:account_activity_times].update(:last_login_at => Time.now - 181*86400)
visit link
click_button 'Unlock Account'
page.find('#error_flash').text.must_equal "You cannot log into this account as it has expired"
page.body.must_include 'Not Logged'
page.current_path.must_equal '/'
end
end
it "should not allow account unlock requests for expired accounts" do
rodauth do
enable :lockout, :account_expiration, :logout
max_invalid_logins 2
unlock_account_autologin? false
end
roda do |r|
r.rodauth
r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
end
login
logout
visit '/login'
fill_in 'Login', :with=>'foo@example.com'
3.times do
fill_in 'Password', :with=>'012345678910'
click_button 'Login'
end
DB[:account_activity_times].update(:last_login_at => Time.now - 181*86400)
page.body.must_include("This account is currently locked out")
click_button 'Request Account Unlock'
page.find('#error_flash').text.must_equal "You cannot log into this account as it has expired"
page.body.must_include 'Not Logged'
page.current_path.must_equal '/'
end
it "should use last activity time if configured" do
rodauth do
enable :login, :logout, :account_expiration
expire_account_on_last_activity? true
account_expiration_error_flash{"Account expired on #{account_expired_at.strftime('%m%d%y')}"}
end
roda do |r|
r.is("a"){view :content=>"Logged In#{rodauth.last_account_activity_at.strftime('%m%d%y')}"}
rodauth.update_last_activity
r.rodauth
r.root{view :content=>rodauth.logged_in? ? "Logged In#{rodauth.last_account_activity_at.strftime('%m%d%y')}" : 'Not Logged'}
end
now = Time.now
login
page.body.must_include "Logged In#{now.strftime('%m%d%y')}"
DB[:account_activity_times].count.must_equal 1
DB[:account_activity_times].delete
visit '/'
DB[:account_activity_times].count.must_equal 1
t1 = now - 179*86400
DB[:account_activity_times].update(:last_activity_at => t1)
visit '/a'
page.body.must_include "Logged In#{t1.strftime('%m%d%y')}"
logout
t2 = now - 181*86400
DB[:account_activity_times].update(:last_activity_at => t2).must_equal 1
login
page.body.must_include 'Not Logged'
page.find('#error_flash').text.must_equal "Account expired on #{now.strftime('%m%d%y')}"
DB[:account_activity_times].update(:expired_at=>t1).must_equal 1
login
page.body.must_include 'Not Logged'
page.find('#error_flash').text.must_equal "Account expired on #{t1.strftime('%m%d%y')}"
end
[true, false].each do |before|
it "should remove account activity data when closing accounts, when loading account_expiration #{before ? "before" : "after"}" do
rodauth do
features = [:close_account, :account_expiration]
features.reverse! if before
enable :login, *features
close_account_requires_password? false
end
roda do |r|
r.rodauth
r.root{view :content=>rodauth.logged_in? ? "Logged In#{rodauth.last_account_login_at.strftime('%m%d%y')}" : "Not Logged"}
end
login
DB[:account_activity_times].count.must_equal 1
visit '/close-account'
click_button 'Close Account'
DB[:account_activity_times].count.must_equal 0
end
end
end
jeremyevans-rodauth-b53f402/spec/active_sessions_spec.rb 0000664 0000000 0000000 00000043351 15157255142 0023544 0 ustar 00root root 0000000 0000000 require_relative 'spec_helper'
describe 'Rodauth active sessions feature' do
[true, false].each do |before|
it "should check that session is active, when loading active_sessions #{before ? "before" : "after"}" do
rodauth do
features = [:logout, :active_sessions]
features.reverse! if before
enable :login, *features
hmac_secret '123'
end
roda do |r|
r.is("precheck"){rodauth.currently_active_session? ? "Active" : "Inactive"}
rodauth.check_active_session
r.rodauth
r.is("clear"){rodauth.clear_session; r.redirect '/'}
r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
end
visit '/precheck'
page.body.must_equal "Inactive"
login
page.body.must_include "Logged In"
visit '/precheck'
page.body.must_equal "Active"
session1 = get_cookie('rack.session')
logout
visit '/'
page.body.must_include "Not Logged"
remove_cookie('rack.session')
set_cookie('rack.session', session1)
visit '/foo'
page.current_path.must_equal '/'
page.body.must_include "Not Logged"
page.find('#error_flash').text.must_equal "This session has been logged out"
login
page.body.must_include "Logged In"
session2 = get_cookie('rack.session')
remove_cookie('rack.session')
set_cookie('rack.session', session1)
visit '/'
page.body.must_include "Not Logged"
page.find('#error_flash').text.must_equal "This session has been logged out"
remove_cookie('rack.session')
set_cookie('rack.session', session2)
visit '/'
page.body.must_include "Logged In"
visit '/clear'
page.current_path.must_equal '/'
page.body.must_include "Not Logged"
set_cookie('rack.session', session2)
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].delete
visit '/precheck'
page.body.must_equal "Inactive"
visit '/'
page.body.must_include "Not Logged"
end
end
it "should support secret rotation via hmac_old_secret" do
secret = '123'
old_secret = nil
rodauth do
enable :login, :active_sessions
hmac_secret{secret}
hmac_old_secret{old_secret}
active_sessions_redirect '/login'
end
roda do |r|
r.rodauth
rodauth.check_active_session
r.root{view :content=>""}
end
login
secret = '234'
visit '/'
page.current_path.must_equal '/login'
DB[:account_active_session_keys].delete
secret = '123'
login
DB[:account_active_session_keys].count.must_equal 1
key1 = DB[:account_active_session_keys].get(:session_id)
secret = '234'
old_secret = '123'
visit '/'
page.current_path.must_equal '/'
DB[:account_active_session_keys].count.must_equal 1
key2 = DB[:account_active_session_keys].get(:session_id)
key2.wont_equal key1
old_secret = nil
visit '/'
page.current_path.must_equal '/'
DB[:account_active_session_keys].count.must_equal 1
DB[:account_active_session_keys].get(:session_id).must_equal key2
end
it "should clear all active sessions except current when resetting password without a logged in session" do
rodauth do
enable :login, :reset_password, :active_sessions
require_password_confirmation? false
hmac_secret '123'
end
roda do |r|
r.rodauth
rodauth.check_active_session
r.root{view :content=>rodauth.logged_in? ? "Logged In!" : "Not Logged"}
end
login
visit '/login'
login(:pass=>'01234567', :visit=>false)
click_button 'Request Password Reset'
page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to reset the password for your account"
remove_cookie('rack.session')
visit email_link(/(\/reset-password\?key=.+)$/)
fill_in 'Password', :with=>'012345678911'
DB[:account_active_session_keys].count.must_equal 1
click_button "Reset Password"
page.find('#notice_flash').text.must_equal "Your password has been reset"
page.body.must_include "Not Logged"
DB[:account_active_session_keys].count.must_equal 0
end
it "should clear all active sessions when changing login in a logged in session" do
rodauth do
enable :login, :change_login, :active_sessions
require_login_confirmation? false
change_login_requires_password? false
hmac_secret '123'
end
roda do |r|
r.rodauth
rodauth.check_active_session
r.root{view :content=>rodauth.logged_in? ? "Logged In!" : "Not Logged"}
end
login
session1 = get_cookie('rack.session')
remove_cookie('rack.session')
login
visit '/change-login'
fill_in 'Login', :with=>'foo3@example.com'
DB[:account_active_session_keys].count.must_equal 2
click_button 'Change Login'
page.find('#notice_flash').text.must_equal "Your login has been changed"
DB[:account_active_session_keys].count.must_equal 1
visit '/'
page.body.must_include "Logged In!"
set_cookie('rack.session', session1)
visit '/'
page.body.must_include "Not Logged"
end
it "should remove previous active session when updating session" do
rodauth do
enable :create_account, :verify_account_grace_period, :active_sessions
create_account_autologin? true
verify_account_autologin? true
hmac_secret '123'
end
roda do |r|
r.rodauth
r.root{view :content=>""}
end
visit "/create-account"
fill_in "Login", with: "foo@example2.com"
fill_in "Password", with: "secret"
fill_in "Confirm Password", with: "secret"
click_on "Create Account"
DB[:account_active_session_keys].count.must_equal 1
visit email_link(/(\/verify-account\?key=.+)$/, "foo@example2.com")
click_on "Verify Account"
DB[:account_active_session_keys].count.must_equal 1
end
it "should handle session inactivity and lifetime deadlines" do
session_inactivity_deadline = 86400
session_lifetime_deadline = 86400*30
rodauth do
enable :login, :logout, :active_sessions
hmac_secret '123'
session_inactivity_deadline{session_inactivity_deadline}
session_lifetime_deadline{session_lifetime_deadline}
end
roda do |r|
rodauth.check_active_session
r.rodauth
r.is("clear"){rodauth.clear_session; r.redirect '/'}
r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
end
login
page.body.must_include "Logged In"
past_time = lambda do |seconds|
Sequel.date_sub(Sequel::CURRENT_TIMESTAMP, :seconds=>seconds)
end
DB[:account_active_session_keys].update(:last_use=>past_time.call(86400/2))
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].update(:last_use=>past_time.call(86400*2))
visit '/'
page.body.must_include "Not Logged"
login
DB[:account_active_session_keys].update(:created_at=>past_time.call(86400*29))
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].update(:created_at=>past_time.call(86400*31))
visit '/'
page.body.must_include "Not Logged"
session_inactivity_deadline = 50
login
DB[:account_active_session_keys].update(:last_use=>past_time.call(25))
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].update(:last_use=>past_time.call(75))
visit '/'
page.body.must_include "Not Logged"
session_lifetime_deadline = 100
login
DB[:account_active_session_keys].update(:created_at=>past_time.call(50))
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].update(:created_at=>past_time.call(150))
visit '/'
page.body.must_include "Not Logged"
session_inactivity_deadline = 50
session_lifetime_deadline = nil
login
DB[:account_active_session_keys].update(:last_use=>past_time.call(25))
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].update(:last_use=>past_time.call(75))
visit '/'
page.body.must_include "Not Logged"
session_inactivity_deadline = nil
session_lifetime_deadline = 100
login
DB[:account_active_session_keys].update(:created_at=>past_time.call(50))
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].update(:created_at=>past_time.call(150))
visit '/'
page.body.must_include "Not Logged"
session_inactivity_deadline = 10
session_lifetime_deadline = 100
login
DB[:account_active_session_keys].update(:last_use=>past_time.call(5), :created_at=>past_time.call(50))
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].update(:last_use=>past_time.call(15), :created_at=>past_time.call(150))
visit '/'
page.body.must_include "Not Logged"
session_inactivity_deadline = nil
session_lifetime_deadline = nil
login
DB[:account_active_session_keys].update(:last_use=>past_time.call(5), :created_at=>past_time.call(50))
visit '/'
page.body.must_include "Logged In"
DB[:account_active_session_keys].update(:last_use=>past_time.call(86400), :created_at=>past_time.call(150))
visit '/'
page.body.must_include "Logged In"
t = DB[:account_active_session_keys].get(:last_use)
t = Time.parse(t) if t.is_a?(String)
t.must_be(:<, Time.now - 10)
end
it "should logout all sessions for account on logout if that option is selected" do
rodauth do
enable :login, :active_sessions
hmac_secret '123'
end
roda do |r|
rodauth.check_active_session
r.rodauth
r.is("clear"){rodauth.clear_session; r.redirect '/'}
rodauth.session[rodauth.session_id_session_key] || ''
end
login
session_id1 = page.body
session1 = get_cookie('rack.session')
visit '/clear'
login
session_id2 = page.body
session2 = get_cookie('rack.session')
session_id1.wont_equal session_id2
remove_cookie('rack.session')
set_cookie('rack.session', session1)
visit '/'
page.body.must_equal session_id1
remove_cookie('rack.session')
set_cookie('rack.session', session2)
visit '/'
page.body.must_equal session_id2
visit '/logout'
check 'Logout all Logged In Sessions?'
click_button 'Logout'
remove_cookie('rack.session')
set_cookie('rack.session', session1)
visit '/'
page.body.must_equal ''
remove_cookie('rack.session')
set_cookie('rack.session', session2)
visit '/'
page.body.must_equal ''
end
it "should support logging out given session" do
rodauth do
enable :login, :active_sessions
hmac_secret '123'
end
roda do |r|
r.is("clear"){rodauth.clear_session; ''}
r.is("except_for", String){|i| rodauth.remove_all_active_sessions_except_for(i); ''}
rodauth.check_active_session
r.rodauth
rodauth.session[rodauth.session_id_session_key] || ''
end
login
session_id1 = page.body
session1 = get_cookie('rack.session')
visit '/clear'
login
session_id2 = page.body
session2 = get_cookie('rack.session')
session_id1.wont_equal session_id2
visit "/except_for/#{session_id2}"
visit '/clear'
remove_cookie('rack.session')
set_cookie('rack.session', session1)
visit '/'
page.body.wont_equal session_id1
page.body.wont_equal session_id2
visit '/clear'
remove_cookie('rack.session')
set_cookie('rack.session', session2)
visit '/'
page.body.must_equal session_id2
end
it "should support logging out current session" do
rodauth do
enable :login, :active_sessions
hmac_secret '123'
end
roda do |r|
r.is("clear"){rodauth.clear_session; ''}
r.is("except_current"){rodauth.remove_all_active_sessions_except_current; ''}
r.is("except_no_current"){rodauth.session.delete(rodauth.session_id_session_key); rodauth.remove_all_active_sessions_except_current; ''}
rodauth.check_active_session
r.rodauth
rodauth.session[rodauth.session_id_session_key] || ''
end
login
session_id1 = page.body
session1 = get_cookie('rack.session')
visit '/clear'
login
session_id2 = page.body
session2 = get_cookie('rack.session')
session_id1.wont_equal session_id2
visit "/except_current"
visit '/clear'
remove_cookie('rack.session')
set_cookie('rack.session', session1)
visit '/'
page.body.wont_equal session_id1
page.body.wont_equal session_id2
visit '/clear'
remove_cookie('rack.session')
set_cookie('rack.session', session2)
visit '/'
page.body.must_equal session_id2
DB[:account_active_session_keys].count.must_equal 1
visit "/except_no_current"
DB[:account_active_session_keys].count.must_equal 0
end
it "should support logging out all sessions for not-logged in users" do
rodauth do
enable :login, :active_sessions, :reset_password
hmac_secret '123'
after_reset_password do
remove_all_active_sessions
end
end
roda do |r|
rodauth.check_active_session
r.rodauth
r.is("clear"){rodauth.clear_session; r.redirect '/'}
r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
end
login
page.body.must_include "Logged In"
visit '/clear'
page.body.must_include "Not Logged"
DB[:account_active_session_keys].count.must_equal 1
visit '/reset-password-request'
fill_in 'Login', :with=>'foo@example.com'
click_button 'Request Password Reset'
visit email_link(/(\/reset-password\?key=.+)$/)
fill_in 'Password', :with=>'01234567'
fill_in 'Confirm Password', :with=>'01234567'
click_button 'Reset Password'
DB[:account_active_session_keys].count.must_equal 0
end
it "should handle duplicate session ids by sharing them by default" do
random_key = nil
rodauth do
enable :login, :active_sessions
hmac_secret '123'
random_key{random_key ||= super()}
end
roda do |r|
rodauth.check_active_session
r.rodauth
r.is("clear"){rodauth.clear_session; r.redirect '/'}
rodauth.session[rodauth.session_id_session_key] || ''
end
login
session_id1 = page.body
session1 = get_cookie('rack.session')
visit '/clear'
login
session_id2 = page.body
session2 = get_cookie('rack.session')
session_id1.must_equal session_id2
remove_cookie('rack.session')
set_cookie('rack.session', session1)
visit '/'
page.body.must_equal session_id1
remove_cookie('rack.session')
set_cookie('rack.session', session2)
visit '/'
page.body.must_equal session_id2
visit '/logout'
click_button 'Logout'
remove_cookie('rack.session')
set_cookie('rack.session', session1)
visit '/'
page.body.must_equal ''
remove_cookie('rack.session')
set_cookie('rack.session', session2)
visit '/'
page.body.must_equal ''
end
[true, false].each do |before|
it "should remove active session keys when closing accounts, when active_sessions is loaded #{before ? "before" : "after"}" do
rodauth do
features = [:close_account, :active_sessions]
features.reverse! if before
enable :login, *features
close_account_requires_password? false
hmac_secret '123'
end
roda do |r|
rodauth.check_active_session
r.rodauth
r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
end
login
DB[:account_active_session_keys].count.must_equal 1
visit '/close-account'
click_button 'Close Account'
DB[:account_active_session_keys].count.must_equal 0
end
end
it "should handle cases where active session id is not set during logout, to handle cases where active_sessions was added after session creation" do
rodauth do
enable :login, :active_sessions
hmac_secret '123'
end
roda do |r|
r.rodauth
rodauth.check_active_session
r.get('remove_session_id'){session.delete(rodauth.session_id_session_key); r.redirect '/logout'}
r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
end
login
visit '/remove_session_id'
click_button 'Logout'
page.title.must_equal 'Login'
end
it "should limit accounts to a single logged in session when using jwt" do
rodauth do
enable :login, :active_sessions
hmac_secret '123'
end
roda(:jwt) do |r|
rodauth.check_active_session
r.rodauth
r.post("clear"){rodauth.clear_session; [3]}
rodauth.logged_in? ? [1] : [2]
end
json_login
authorization1 = @authorization
json_logout
json_request.must_equal [200, [2]]
@authorization = authorization1
json_request.must_equal [401, {'reason'=>'inactive_session', 'error'=>"This session has been logged out"}]
json_login
json_request.must_equal [200, [1]]
authorization2 = @authorization
@authorization = authorization1
json_request.must_equal [401, {'reason'=>'inactive_session', 'error'=>"This session has been logged out"}]
@authorization = authorization2
json_request.must_equal [200, [1]]
json_request('/clear').must_equal [200, [3]]
json_login
authorization3 = @authorization
json_request.must_equal [200, [1]]
@authorization = authorization2
json_request.must_equal [200, [1]]
res = json_request("/logout", 'global_logout'=>'t')
res.must_equal [200, {"success"=>'You have been logged out'}]
@authorization = authorization2
json_request.must_equal [401, {'reason'=>'inactive_session', 'error'=>"This session has been logged out"}]
json_request.must_equal [200, [2]]
@authorization = authorization3
json_request.must_equal [401, {'reason'=>'inactive_session', 'error'=>"This session has been logged out"}]
json_request.must_equal [200, [2]]
end
end
jeremyevans-rodauth-b53f402/spec/all.rb 0000664 0000000 0000000 00000000054 15157255142 0020072 0 ustar 00root root 0000000 0000000 Dir['./spec/*_spec.rb'].each{|f| require f}
jeremyevans-rodauth-b53f402/spec/audit_logging_spec.rb 0000664 0000000 0000000 00000005253 15157255142 0023156 0 ustar 00root root 0000000 0000000 require_relative 'spec_helper'
describe 'Rodauth audit_logging feature' do
ds = DB[:account_authentication_audit_logs].order(Sequel.desc(:at), Sequel.desc(:id))
it "should handle audit logging of all actions" do
rodauth do
enable :login, :logout, :audit_logging
end
roda do |r|
r.rodauth
view :content=>"Logged In"
end
login
account_id, at, message, metadata = ds.get([:account_id, :at, :message, :metadata])
account_id.must_equal DB[:accounts].get(:id)
at = Time.parse(at) unless at.is_a?(Time)
at.must_be(:>, Time.now - 86400)
message.must_equal 'login'
metadata.must_be_nil
logout
ds.where(message: 'logout').count.must_equal 1
login(:pass=>'012345678')
ds.where(message: 'login_failure').count.must_equal 1
end
it "should allow customizing of audit log messages and metadata" do
rodauth do
enable :login, :logout, :audit_logging
audit_log_message_for :login, "Login Ahoy!"
audit_log_message_for :login_failure do
"Login failure for #{param(login_param)}"
end
audit_log_metadata_for :logout, {'details'=>'A wild logout appears!'}
audit_log_metadata_for :login_failure do
{'never_do_this'=>param(password_param)}
end
audit_log_message_default do |action|
action.to_s.upcase
end
audit_log_metadata_default('nothing'=>'specific')
end
roda do |r|
r.rodauth
view :content=>"Logged In"
end
login
account_id, at, message, metadata = ds.get([:account_id, :at, :message, :metadata])
account_id.must_equal DB[:accounts].get(:id)
at = Time.parse(at) unless at.is_a?(Time)
at.must_be(:>, Time.now - 86400)
message.must_equal 'Login Ahoy!'
metadata = JSON.parse(metadata) if metadata.is_a?(String)
metadata.must_equal('nothing'=>'specific')
logout
message, metadata = ds.where(message: 'LOGOUT').get([:message, :metadata])
message.wont_equal nil
metadata = JSON.parse(metadata) if metadata.is_a?(String)
metadata.must_equal('details'=>'A wild logout appears!')
login(:pass=>'012345678')
message, metadata = ds.where(message: 'Login failure for foo@example.com').get([:message, :metadata])
message.wont_equal nil
metadata = JSON.parse(metadata) if metadata.is_a?(String)
metadata = JSON.parse(metadata) if metadata.is_a?(String)
metadata.must_equal('never_do_this'=>'012345678')
end
it "should skip audit logging if there is no message" do
rodauth do
enable :login, :audit_logging
audit_log_message_for :login, nil
end
roda do |r|
r.rodauth
view :content=>"Logged In"
end
login
ds.must_be_empty
end
end
jeremyevans-rodauth-b53f402/spec/change_login_spec.rb 0000664 0000000 0000000 00000024252 15157255142 0022757 0 ustar 00root root 0000000 0000000 require_relative 'spec_helper'
describe 'Rodauth change_login feature' do
it "should support changing logins for accounts" do
DB[:accounts].insert(:email=>'foo2@example.com')
require_password = false
require_email = true
rodauth do
enable :login, :logout, :change_login
change_login_requires_password?{require_password}
require_email_address_logins?{require_email}
end
roda do |r|
r.rodauth
r.root{view :content=>""}
end
login
page.current_path.must_equal '/'
visit '/change-login'
page.title.must_equal 'Change Login'
fill_in 'Login', :with=>'foobar'
fill_in 'Confirm Login', :with=>'foobar'
click_button 'Change Login'
page.find('#error_flash').text.must_equal "There was an error changing your login"
page.html.must_include("invalid login, not a valid email address")
page.current_path.must_equal '/change-login'
require_email = false
fill_in 'Login', :with=>'fb'
fill_in 'Confirm Login', :with=>'fb'
click_button 'Change Login'
page.find('#error_flash').text.must_equal "There was an error changing your login"
page.html.must_include("invalid login, minimum 3 characters")
page.current_path.must_equal '/change-login'
fill_in 'Login', :with=>'f'*256
fill_in 'Confirm Login', :with=>'f'*256
click_button 'Change Login'
page.find('#error_flash').text.must_equal "There was an error changing your login"
page.html.must_include("invalid login, maximum 255 characters")
page.current_path.must_equal '/change-login'
fill_in 'Login', :with=>"\u1234"*200
fill_in 'Confirm Login', :with=>"\u1234"*200
click_button 'Change Login'
page.find('#error_flash').text.must_equal "There was an error changing your login"
page.html.must_include("invalid login, maximum 255 bytes")
page.current_path.must_equal '/change-login'
fill_in 'Login', :with=>'foo@example.com'
fill_in 'Confirm Login', :with=>'foo2@example.com'
click_button 'Change Login'
page.find('#error_flash').text.must_equal "There was an error changing your login"
page.html.must_include("logins do not match")
page.current_path.must_equal '/change-login'
fill_in 'Login', :with=>'foo2@example.com'
click_button 'Change Login'
page.find('#error_flash').text.must_equal "There was an error changing your login"
page.html.must_include("invalid login, already an account with this login")
page.current_path.must_equal '/change-login'
fill_in 'Login', :with=>'foo@example.com'
fill_in 'Confirm Login', :with=>'foo@example.com'
click_button 'Change Login'
page.find('#error_flash').text.must_equal "There was an error changing your login"
page.html.must_include("invalid login, same as current login")
page.current_path.must_equal '/change-login'
fill_in 'Login', :with=>'foo3@example.com'
fill_in 'Confirm Login', :with=>'foo3@example.com'
click_button 'Change Login'
page.find('#notice_flash').text.must_equal "Your login has been changed"
page.current_path.must_equal '/'
logout
login(:login=>'foo3@example.com')
page.current_path.must_equal '/'
require_password = true
visit '/change-login'
fill_in 'Password', :with=>'012345678'
fill_in 'Login', :with=>'foo4@example.com'
fill_in 'Confirm Login', :with=>'foo4@example.com'
click_button 'Change Login'
page.find('#error_flash').text.must_equal "There was an error changing your login"
page.html.must_include("invalid password")
page.current_path.must_equal '/change-login'
fill_in 'Password', :with=>'0123456789'
click_button 'Change Login'
page.find('#notice_flash').text.must_equal "Your login has been changed"
page.current_path.must_equal '/'
logout
login(:login=>'foo4@example.com')
page.current_path.must_equal '/'
end
it "should support changing logins for accounts without login confirmation" do
rodauth do
enable :login, :change_login
change_login_requires_password? false
require_login_confirmation? false
login_meets_requirements?{|login| login.length > 4}
end
roda do |r|
r.rodauth
r.root{view :content=>""}
end
login
visit '/change-login'
fill_in 'Login', :with=>'foo'
click_button 'Change Login'
page.html.must_include "invalid login"
page.html.wont_include "invalid login,"
visit '/change-login'
fill_in 'Login', :with=>'foo3@example.com'
click_button 'Change Login'
page.find('#notice_flash').text.must_equal "Your login has been changed"
end
it "invalides reset password links after login change" do
rodauth do
enable :login, :change_login, :reset_password
change_login_requires_password? false
require_login_confirmation? false
login_meets_requirements?{|login| login.length > 4}
end
roda do |r|
r.rodauth
r.root{view :content=>""}
end
login
visit '/login'
login(:pass=>'01234567', :visit=>false)
click_button 'Request Password Reset'
page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to reset the password for your account"
link = email_link(/(\/reset-password\?key=.+)$/)
visit '/change-login'
fill_in 'Login', :with=>'foo3@example.com'
click_button 'Change Login'
page.find('#notice_flash').text.must_equal "Your login has been changed"
visit link
page.find('#error_flash').text.must_equal "There was an error resetting your password: invalid or expired password reset key"
end
[:jwt, :json].each do |json|
it "should support changing logins via #{json}" do
DB[:accounts].insert(:email=>'foo2@example.com')
require_password = false
rodauth do
enable :login, :logout, :change_login
change_login_requires_password?{require_password}
end
roda(json) do |r|
r.rodauth
end
json_login
res = json_request('/change-login', :login=>'foobar', "login-confirm"=>'foobar')
res.must_equal [422, {'reason'=>"login_not_valid_email",'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, not a valid email address"]}]
res = json_request('/change-login', :login=>'foo@example.com', "login-confirm"=>'foo2@example.com')
res.must_equal [422, {'reason'=>"logins_do_not_match",'error'=>"There was an error changing your login", "field-error"=>["login", "logins do not match"]}]
res = json_request('/change-login', :login=>'foo2@example.com', "login-confirm"=>'foo2@example.com')
res.must_equal [422, {'reason'=>"already_an_account_with_this_login",'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, already an account with this login"]}]
res = json_request('/change-login', :login=>'f', "login-confirm"=>'f')
res.must_equal [422, {'reason'=>"login_too_short",'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, minimum 3 characters"]}]
res = json_request('/change-login', :login=>'f'*256, "login-confirm"=>'f'*256)
res.must_equal [422, {'reason'=>"login_too_long",'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, maximum 255 characters"]}]
res = json_request('/change-login', :login=>'foo3@example.com', "login-confirm"=>'foo3@example.com')
res.must_equal [200, {'success'=>"Your login has been changed"}]
json_logout
json_login(:login=>'foo3@example.com')
require_password = true
res = json_request('/change-login', :login=>'foo4@example.com', "login-confirm"=>'foo4@example.com', :password=>'012345678')
res.must_equal [401, {'reason'=>"invalid_password",'error'=>"There was an error changing your login", "field-error"=>["password", "invalid password"]}]
res = json_request('/change-login', :login=>'foo4@example.com', "login-confirm"=>'foo4@example.com', :password=>'0123456789')
res.must_equal [200, {'success'=>"Your login has been changed"}]
json_logout
json_login(:login=>'foo4@example.com')
end
end
it "should support changing logins using an internal request" do
rodauth do
enable :login, :change_login, :internal_request
login_meets_requirements?{|login| login.length > 4}
end
roda do |r|
r.rodauth
r.root{rodauth.logged_in?.nil?.to_s}
end
proc do
app.rodauth.change_login(:account_login=>'foo@example.com', :login=>'foo')
end.must_raise Rodauth::InternalRequestError
app.rodauth.change_login(:account_login=>'foo@example.com', :login=>'foo3@example.com').must_be_nil
visit '/'
page.body.must_equal 'true'
login
page.current_path.must_equal '/login'
login(:login=>'foo3@example.com', :visit=>false)
page.current_path.must_equal '/'
page.body.must_equal 'false'
end
it "should support overriding the flash-then-redirect response" do
DB[:accounts].insert(:email=>'foo2@example.com')
rodauth do
enable :login, :logout, :change_login
change_login_requires_password? false
require_email_address_logins? true
change_login_response{ return_response("Change is gonna come") }
end
roda do |r|
r.rodauth
r.root{view :content=>""}
end
login
page.current_path.must_equal '/'
visit '/change-login'
page.title.must_equal 'Change Login'
fill_in 'Login', :with=>'foo3@example.com'
fill_in 'Confirm Login', :with=>'foo3@example.com'
click_button 'Change Login'
page.body.must_equal "Change is gonna come"
logout
login(:login=>'foo3@example.com')
page.current_path.must_equal '/'
end
it "should raise error if a *_response method does not return a response" do
DB[:accounts].insert(:email=>'foo2@example.com')
rodauth do
enable :login, :logout, :change_login
change_login_requires_password? false
require_email_address_logins? true
change_login_response{ "Change is gonna come" }
end
roda do |r|
r.rodauth
r.root{view :content=>""}
end
login
visit '/change-login'
fill_in 'Login', :with=>'foo3@example.com'
fill_in 'Confirm Login', :with=>'foo3@example.com'
proc{click_button 'Change Login'}.must_raise Rodauth::ConfigurationError
end
end
jeremyevans-rodauth-b53f402/spec/change_password_notify_spec.rb 0000664 0000000 0000000 00000001561 15157255142 0025077 0 ustar 00root root 0000000 0000000 require_relative 'spec_helper'
describe 'Rodauth change_password_notify feature' do
it "should email when using change password" do
rodauth do
enable :login, :logout, :change_password_notify
change_password_requires_password? false
end
roda do |r|
r.rodauth
r.root{view :content=>""}
end
login
page.current_path.must_equal '/'
visit '/change-password'
fill_in 'New Password', :with=>'0123456'
fill_in 'Confirm Password', :with=>'0123456'
click_button 'Change Password'
page.find('#notice_flash').text.must_equal "Your password has been changed"
page.current_path.must_equal '/'
email = email_sent
email.subject.must_equal "Password Changed"
email.body.to_s.must_equal <