Add some cookie tests and fix revealed bugs

There is one severe bug in the user registration that must be considered
a security vulnerability. Even though registration was disabled in the
configuration, simply POSTing the registration form caused the feature
flag to be bypassed and thus the new user account to be successfully
created.

Also, we now send proper HTTP error status codes in the registration and
login handlers.

The go.mod and go.sum files were updated with `go mod tidy`.
pull/974/head
Lysander Trischler 1 month ago
parent b4edb93099
commit 1c597e5847
  1. 3
      go.mod
  2. 47
      go.sum
  3. 274
      internal/e2e_test.go
  4. 174
      internal/httpexpect_test.go
  5. 3
      internal/login_handlers.go
  6. 17
      internal/register_handler.go
  7. 64
      internal/sessionassertion_test.go

@ -83,9 +83,8 @@ require (
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7 // indirect
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/net v0.0.0-20220812174116-3211cb980234
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect

@ -28,7 +28,6 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
@ -61,8 +60,7 @@ git.mills.io/prologic/observe v0.0.0-20210712230028-fc31c7aa2bd1/go.mod h1:/rNXq
git.mills.io/prologic/read-file-last-line v0.0.0-20210710073401-af293d63a6d0 h1:7sCaYq21VNVj7VWPyFrm9uoGrUEQ0lC3uJfswWt7W1o=
git.mills.io/prologic/read-file-last-line v0.0.0-20210710073401-af293d63a6d0/go.mod h1:TJx1NvLO37zXX+X22rwv6P8ZErGSsjWB4Ekyi14Qx8Y=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -294,8 +292,6 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20220114203417-14399d5448c4 h1:6GlsnS3GQYfrJZTJEUsheoyLE6kLXQJDvQKIKxgL/9Q=
github.com/gomarkdown/markdown v0.0.0-20220114203417-14399d5448c4/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20220310201231-552c6011c0b8 h1:YVvt637ygnOO9qjLBVmPOvrUmCz/i8YECSu/8UlOQW0=
github.com/gomarkdown/markdown v0.0.0-20220310201231-552c6011c0b8/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -370,7 +366,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
@ -400,13 +395,10 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@ -485,7 +477,6 @@ github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJV
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@ -553,8 +544,6 @@ github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hz
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
github.com/nicksnyder/go-i18n/v2 v2.2.0 h1:MNXbyPvd141JJqlU6gJKrczThxJy+kdCNivxZpBQFkw=
github.com/nicksnyder/go-i18n/v2 v2.2.0/go.mod h1:4OtLfzqyAxsscyCb//3gfqSvBc81gImX91LrZzczN1o=
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 h1:Ys0rDzh8s4UMlGaDa1UTA0sfKgvF0hQZzTYX8ktjiDc=
@ -574,8 +563,6 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/petermattis/goid v0.0.0-20220111183729-e033e1e0bdb5 h1:AdVIf7YN+kn8QAapO6IQHtHKSBeSiNgkSS8Yai/xMmo=
github.com/petermattis/goid v0.0.0-20220111183729-e033e1e0bdb5/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/petermattis/goid v0.0.0-20220302125637-5f11c28912df h1:/B1Q9E4W1cmiwPQfC2vymWL7FXHCEsUzg8Rywl5avtQ=
github.com/petermattis/goid v0.0.0-20220302125637-5f11c28912df/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@ -645,7 +632,6 @@ github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNl
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
@ -678,8 +664,6 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.8.1 h1:izYHOT71f9iZ7iq37Uqjael60/vYC6vMtzedudZ0zEk=
github.com/spf13/afero v1.8.1/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@ -687,8 +671,6 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@ -699,7 +681,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
github.com/steambap/captcha v1.4.1 h1:OmMdxLCWCqJvsFaFYwRpvMckIuvI6s8s1LsBrBw97P0=
@ -713,7 +694,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -821,7 +801,6 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@ -836,15 +815,11 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20220218215828-6cf2b201936e h1:iWVPgObh6F4UDtjBLK51zsy5UHTPLQwCmsNjCsbKhQ0=
golang.org/x/exp v0.0.0-20220218215828-6cf2b201936e/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7 h1:jynE66seADJbyWMUdeOyVTvPtBZt7L6LJHupGwxPZRM=
golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4=
golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -928,10 +903,8 @@ golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1039,14 +1012,10 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1161,7 +1130,6 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1232,8 +1200,6 @@ google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ6
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -1263,7 +1229,6 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@ -1317,8 +1282,6 @@ gorm.io/driver/sqlite v1.1.3 h1:BYfdVuZB5He/u9dt4qDpZqiqDJ6KhPqs5QUqsr/Eeuc=
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.6/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.22.5 h1:lYREBgc02Be/5lSCTuysZZDb6ffL2qrat6fg9CFbvXU=
gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ=
gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

@ -18,32 +18,283 @@ import (
var (
bind string
data string
srv *Server
registeredUserCounter int
)
func makeURL(partialURL string, args ...interface{}) string {
if len(args) > 0 {
partialURL = fmt.Sprintf(partialURL, args...)
}
return fmt.Sprintf("http://%s%s", bind, partialURL)
func makeURL() string {
return fmt.Sprintf("http://%s", bind)
}
func TestInfo(t *testing.T) {
e := httpexpect.New(t, makeURL(""))
func registerUser(t *testing.T) (username, password string) {
srv.config.OpenRegistrations = true
defer func() {
srv.config.OpenRegistrations = false
}()
registeredUserCounter++
username = fmt.Sprintf("user%d", registeredUserCounter)
password = "hunter2"
res := e(t).GET("/register").
Expect().
Status(http.StatusOK).
NoCookie("yarnd_token")
csrfTokenCookie := res.Cookie("csrf_token")
csrfToken := res.HTMLForm().Field("csrf_token")
e(t).POST("/register").
WithForm(map[string]string{
"csrf_token": csrfToken,
"username": username,
"password": password,
"agree": "on",
}).
WithResponseCookie(csrfTokenCookie).
Expect().
Status(http.StatusFound).
NoCookie("yarnd_token").
Header("Location").Equal("/login")
return username, password
}
e.GET("/info").
func login(t *testing.T, username, password string) *Response {
res := e(t).GET("/login").
Expect().
Status(http.StatusOK).
NoCookie("yarnd_token")
csrfTokenCookie := res.Cookie("csrf_token")
csrfToken := res.HTMLForm().Field("csrf_token")
return e(t).POST("/login").
WithForm(map[string]string{
"csrf_token": csrfToken,
"username": username,
"password": password,
}).
WithResponseCookie(csrfTokenCookie).
Expect()
}
func loginUser(t *testing.T, username, password string) *httpexpect.Cookie {
res := login(t, username, password).Status(http.StatusFound)
res.Header("Location").Equal("/")
return res.Cookie("yarnd_token")
}
func TestInfo(t *testing.T) {
e(t).GET("/info").
Expect().
Status(http.StatusOK).
Body().Contains("yarnd")
}
func TestCookies_whenRequestingStartPageAnonymously_thenSendNoYarndSessionCookie(t *testing.T) {
e(t).GET("/").
Expect().
Status(http.StatusOK).
NoCookie("yarnd_token")
}
func TestCookies_whenRequestingStartPageLoggedIn_thenSendNoYarndSessionCookie_sinceThereIsAlreadyAnActiveSession(t *testing.T) {
username, password := registerUser(t)
yarndTokenCookie := loginUser(t, username, password)
e(t).GET("/").
WithResponseCookie(yarndTokenCookie).
Expect().
Status(http.StatusOK).
NoCookie("yarnd_token")
assertSession(t, yarndTokenCookie).
Username(username).
NoCaptchaText()
}
func TestCookies_whenRequestingCaptchaAnonymously_thenSendYarndSessionCookieForCaptcha(t *testing.T) {
yarndTokenCookie := e(t).GET("/_captcha").
Expect().
Status(http.StatusOK).
Cookie("yarnd_token")
assertSession(t, yarndTokenCookie).
NoUsername().
HasCaptchaText()
}
func TestCookies_whenRequestingCaptchaLoggedIn_thenSendNoYarndSessionCookie_sinceThereIsAlreadyAnActiveSession(t *testing.T) {
username, password := registerUser(t)
yarndTokenCookie := loginUser(t, username, password)
e(t).GET("/_captcha").
WithResponseCookie(yarndTokenCookie).
Expect().
Status(http.StatusOK).
NoCookie("yarnd_token")
assertSession(t, yarndTokenCookie).
Username(username).
HasCaptchaText()
}
func TestCookies_whenSubmittingSupportFormAnonymously_thenClearYarndSessionCookieForCaptcha(t *testing.T) {
res := e(t).GET("/support").
Expect().
Status(http.StatusOK)
csrfTokenCookie := res.Cookie("csrf_token")
csrfToken := res.HTMLForm().Field("csrf_token")
yarndTokenCookie := e(t).GET("/_captcha").
Expect().
Status(http.StatusOK).
Cookie("yarnd_token")
captcha := assertSession(t, yarndTokenCookie).
NoUsername().
CaptchaText()
e(t).POST("/support").
WithResponseCookie(csrfTokenCookie).
WithResponseCookie(yarndTokenCookie).
WithForm(map[string]string{
"csrf_token": csrfToken,
"name": "John Doe",
"email": "john.doe@example.com",
"subject": "Please get back to me",
"message": "I would like to donate some money, please send me your bank account details.",
"captchaInput": captcha,
}).
Expect().
Status(http.StatusOK).
ClearCookie("yarnd_token")
assertNoSession(t, yarndTokenCookie)
}
func TestCookies_whenSubmittingSupportFormLoggedIn_thenSendNoYarndSessionCookie_sinceThereIsStillAnActiveSession(t *testing.T) {
username, password := registerUser(t)
yarndTokenCookie := loginUser(t, username, password)
res := e(t).GET("/support").
WithResponseCookie(yarndTokenCookie).
Expect().
Status(http.StatusOK)
csrfTokenCookie := res.Cookie("csrf_token")
csrfToken := res.HTMLForm().Field("csrf_token")
e(t).GET("/_captcha").
WithResponseCookie(yarndTokenCookie).
Expect().
Status(http.StatusOK).
NoCookie("yarnd_token")
captcha := assertSession(t, yarndTokenCookie).
Username(username).
CaptchaText()
e(t).POST("/support").
WithResponseCookie(csrfTokenCookie).
WithResponseCookie(yarndTokenCookie).
WithForm(map[string]string{
"csrf_token": csrfToken,
"name": "John Doe",
"email": "john.doe@example.com",
"subject": "Please get back to me",
"message": "I would like to donate some money, please send me your bank account details.",
"captchaInput": captcha,
}).
Expect().
Status(http.StatusOK).
NoCookie("yarnd_token")
assertSession(t, yarndTokenCookie).
Username(username).
NoCaptchaText()
}
func TestCookies_whenLoggingInSucceeds_thenSendYarndSessionCookie(t *testing.T) {
// First a user needs to be registered
username, password := registerUser(t)
// Now the actual login must succeed
yarndTokenCookie := loginUser(t, username, password)
yarndTokenCookie.Value().NotEmpty()
}
func TestCookies_whenLoggingInFails_thenSendNoYarndSessionCookie(t *testing.T) {
login(t, "username", "hunter2").
Status(http.StatusUnauthorized).
NoCookie("yarnd_token").
Body().Contains("Invalid username! Hint: Register an account?")
}
func TestRegister_whenRegistrationDisabledInConfig_thenRejectRegistrationRequests(t *testing.T) {
res := e(t).GET("/register").
Expect().
Status(http.StatusForbidden).
NoCookie("yarnd_token")
csrfTokenCookie := res.Cookie("csrf_token")
res.HTMLForm().NoField("csrf_token")
res.Body().Contains("Open Registrations are disabled on this pod.")
// Unfortunately, we can't really send a POST request right away, because
// there is no CSRF token HTML field in the response body. So basically
// this fails due to the CSRF token check. But, let's do it anyways.
e(t).POST("/register").
WithForm(map[string]string{
"username": "reg-disabled-and-wrong-csrf-token",
"password": "hunter2",
"agree": "on",
}).
WithResponseCookie(csrfTokenCookie).
Expect().
Status(http.StatusBadRequest).
NoCookie("yarnd_token").
Body().Equal("Bad Request\n")
// However, we can try to get such a token from another page and…
res = e(t).GET("/login").
Expect().
Status(http.StatusOK)
csrfTokenCookie = res.Cookie("csrf_token")
csrfToken := res.HTMLForm().Field("csrf_token")
// …just submit that with the matching cookie in the registration request.
e(t).POST("/register").
WithForm(map[string]string{
"csrf_token": csrfToken,
"username": "reg-disabled",
"password": "hunter2",
"agree": "on",
}).
WithResponseCookie(csrfTokenCookie).
Expect().
Status(http.StatusForbidden).
NoCookie("yarnd_token").
Body().Contains("Open Registrations are disabled on this pod.")
// Login with that user must fail.
login(t, "reg-disabled", "hunter2").
Status(http.StatusUnauthorized).
Body().Contains("Invalid username! Hint: Register an account?")
}
func TestRegister_whenRegistrationEnabled_thenAcceptRegistrationRequests(t *testing.T) {
username, password := registerUser(t)
// verify that the new user can log in
yarndTokenCookie := loginUser(t, username, password)
yarndTokenCookie.Value().NotEmpty()
}
func TestMain(m *testing.M) {
testDir, err := os.MkdirTemp("", "*-yarn-e2e-test")
if err != nil {
log.WithError(err).Error("error creating temporary test directory")
os.Exit(-1)
}
data = testDir
defer os.RemoveAll(testDir)
listener, err := net.Listen("tcp", "0.0.0.0:0")
@ -58,7 +309,7 @@ func TestMain(m *testing.M) {
server, err := NewServer(
bind,
WithData(testDir),
WithBaseURL(makeURL("")),
WithBaseURL(makeURL()),
WithStore(testStore),
WithCookieSecret(GenerateRandomToken()),
WithMagicLinkSecret(GenerateRandomToken()),
@ -68,6 +319,7 @@ func TestMain(m *testing.M) {
log.WithError(err).Error("error starting test server")
os.Exit(-1)
}
srv = server
var eg errgroup.Group

@ -0,0 +1,174 @@
package internal
import (
"fmt"
"net/http"
"strings"
"testing"
"time"
"github.com/gavv/httpexpect/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/html"
)
// This file contains a patched httpexpect version that adds more convenience
// methods for cookies and HTML forms. Not all functions are overridden, only
// those, that are needed in the tests. So if you encounter a missing method,
// just add and implement it.
func e(t *testing.T) *Expect {
return &Expect{httpexpect.WithConfig(httpexpect.Config{
BaseURL: makeURL(),
Client: &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
},
Reporter: httpexpect.NewAssertReporter(t),
Printers: []httpexpect.Printer{
httpexpect.NewCompactPrinter(t),
// httpexpect.NewDebugPrinter(t, true),
},
}), t}
}
type Expect struct {
expect *httpexpect.Expect
t *testing.T
}
func (e *Expect) GET(path string, pathargs ...interface{}) *Request {
return &Request{e.expect.GET(path, pathargs...), e.t}
}
func (e *Expect) POST(path string, pathargs ...interface{}) *Request {
return &Request{e.expect.POST(path, pathargs...), e.t}
}
type Request struct {
*httpexpect.Request
t *testing.T
}
func (r *Request) WithResponseCookie(responseCookie *httpexpect.Cookie) *Request {
cookie := responseCookie.Raw()
require.NotNil(r.t, cookie, "cookie cannot be nil")
r.Request.WithCookie(cookie.Name, cookie.Value)
return r
}
func (r *Request) WithForm(form interface{}) *Request {
r.Request.WithForm(form)
return r
}
func (r *Request) Expect() *Response {
return &Response{r.Request.Expect(), r.t}
}
type Response struct {
*httpexpect.Response
t *testing.T
}
func (r *Response) Status(status int) *Response {
r.Response.Status(status)
return r
}
// NoCookie verifies that the response does not have a Set-Cookie header with
// the given cookie name.
func (r *Response) NoCookie(name string) *Response {
cookieNames := r.Cookies().Iter()
assert.NotContainsf(r.t, cookieNames, name, "expected response without cookie '%s', but got such a cookie", name)
return r
}
// ClearCookie verifies that the reponse does have a Set-Cookie header with the
// given cookie name, an empty signed value, max-age of 0 and expiration
// timestamp of 0001-01-01T00:00:00Z.
func (r *Response) ClearCookie(name string) *Response {
cookie := r.Cookie(name)
cookie.Value().Match(`^|\d+|[a-zA-Z0-9_-]+$`) // pipe-separated empty value, unix timestamp and signature
cookie.MaxAge().Equal(0 * time.Second)
cookie.Expires().Equal(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC))
return r
}
// HTMLForm parses an HTML form from the response body. If parsing fails, the
// test is aborted.
//
// Watch out, not all controls are supported, only <input> tags that specify
// both name and value attributes.
//
// The implementation is also very, very crude. Multiple forms would be merged
// together into a larger form and later fields override earlier fields. This
// can all be fixed once needed.
func (r *Response) HTMLForm() *HTMLForm {
doc, err := html.Parse(strings.NewReader(r.Body().Raw()))
require.NoError(r.t, err, "parsing response body HTML failed")
form := make(map[string]string)
var traverse func(*html.Node)
traverse = func(n *html.Node) {
// Currently, we only need input fields. Thus, textareas, buttons etc.
// are not supported at the moment. Also, both name and value must be
// specified. So basically only pre-filled fields are parsed.
if n.Type == html.ElementNode && n.Data == "input" {
var name, value string
var foundName, foundValue bool
for _, attr := range n.Attr {
switch attr.Key {
case "name":
name, foundName = attr.Val, true
case "value":
value, foundValue = attr.Val, true
}
}
if foundName && foundValue {
form[name] = value
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
traverse(c)
}
}
traverse(doc)
return &HTMLForm{form, r.t}
}
type HTMLForm struct {
form map[string]string
t *testing.T
}
// Field verifies that a HTML form field the with given name exists and returns
// its value.
func (f *HTMLForm) Field(name string) string {
value, ok := f.form[name]
if !ok {
if len(f.form) == 0 {
f.t.Errorf("expected HTML form field '%s' in response body, but got no supported HTML form fields", name)
} else {
var fields []string
for k, v := range f.form {
fields = append(fields, fmt.Sprintf("'%s' = '%s'", k, v))
}
f.t.Errorf("expected HTML form field '%s' in response body,\n"+
"but only got %d supported HTML form fields:\n%s",
name, len(f.form), strings.Join(fields, "\n"))
}
}
return value
}
// NoField verifies that there is no HTML form field with the given name.
func (f *HTMLForm) NoField(name string) *HTMLForm {
if value, ok := f.form[name]; ok {
f.t.Errorf("expected no HTML form field '%s' in reponse body, but got one with value '%s'", name, value)
}
return f
}

@ -51,6 +51,7 @@ func (s *Server) LoginHandler() httprouter.Handle {
if err != nil {
ctx.Error = true
ctx.Message = s.tr(ctx, "ErrorInvalidUsername")
w.WriteHeader(http.StatusUnauthorized)
s.render("error", w, ctx)
return
}
@ -59,6 +60,7 @@ func (s *Server) LoginHandler() httprouter.Handle {
if failures.Get(user.Username) > MaxFailedLogins {
ctx.Error = true
ctx.Message = s.tr(ctx, "ErrorMaxFailedLogins")
w.WriteHeader(http.StatusTooManyRequests)
s.render("error", w, ctx)
return
}
@ -72,6 +74,7 @@ func (s *Server) LoginHandler() httprouter.Handle {
ctx.Error = true
ctx.Message = s.tr(ctx, "ErrorInvalidPassword")
w.WriteHeader(http.StatusUnauthorized)
s.render("error", w, ctx)
return
}

@ -21,15 +21,16 @@ func (s *Server) RegisterHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
ctx := NewContext(s, r)
if r.Method == "GET" {
if s.config.OpenRegistrations {
s.render("register", w, ctx)
} else {
ctx.Error = true
ctx.Message = s.tr(ctx, "ErrorRegisterDisabled")
s.render("error", w, ctx)
}
if !s.config.OpenRegistrations {
ctx.Error = true
ctx.Message = s.tr(ctx, "ErrorRegisterDisabled")
w.WriteHeader(http.StatusForbidden)
s.render("error", w, ctx)
return
}
if r.Method == "GET" {
s.render("register", w, ctx)
return
}

@ -0,0 +1,64 @@
package internal
import (
"testing"
"git.mills.io/yarnsocial/yarn/internal/session"
"github.com/andreadipersio/securecookie"
"github.com/gavv/httpexpect/v2"
"github.com/stretchr/testify/assert"
)
// This file defines some assertion helpers for sessions in the end to end
// tests. It's inspired by httpexpect, but uses a more compact and direct
// call style.
func extractSID(t *testing.T, yarndTokenCookie *httpexpect.Cookie) string {
sid, err := securecookie.DecodeSignedValue(srv.config.CookieSecret, "yarnd_token", yarndTokenCookie.Value().Raw())
assert.NoError(t, err, "decoding signed yarnd_token cookie failed")
assert.NotEmpty(t, sid, "extracted session ID from signed yarnd_token cookie is empty")
return sid
}
func assertNoSession(t *testing.T, yarndTokenCookie *httpexpect.Cookie) {
sid := extractSID(t, yarndTokenCookie)
sess, err := srv.sc.GetSession(sid)
assert.ErrorIsf(t, err, session.ErrSessionNotFound,
"session '%s' should not be present in session store, but is: %#v", sid, sess)
}
func assertSession(t *testing.T, yarndTokenCookie *httpexpect.Cookie) *SessionAssertion {
sid := extractSID(t, yarndTokenCookie)
sess, err := srv.sc.GetSession(sid)
assert.NoErrorf(t, err, "expected session '%s' to be present in session store", sid)
return &SessionAssertion{t, sess}
}
type SessionAssertion struct {
t *testing.T
sess *session.Session
}
func (sa *SessionAssertion) NoUsername() *SessionAssertion {
assert.NotContains(sa.t, sa.sess.Data, "username", "username in session should not be present")
return sa
}
func (sa *SessionAssertion) Username(expectedUsername string) *SessionAssertion {
assert.Equal(sa.t, expectedUsername, sa.sess.Data["username"], "username in session does not match")
return sa
}
func (sa *SessionAssertion) NoCaptchaText() *SessionAssertion {
assert.NotContains(sa.t, sa.sess.Data, "captchaText", "captchaTest in session should not be present")
return sa
}
func (sa *SessionAssertion) HasCaptchaText() *SessionAssertion {
assert.NotEmpty(sa.t, sa.sess.Data["captchaText"], "captchaText in session should be present")
return sa
}
func (sa *SessionAssertion) CaptchaText() string {
return sa.HasCaptchaText().sess.Data["captchaText"]
}
Loading…
Cancel
Save