11package integration_tests
22
33import (
4- "fmt"
54 "testing"
65
76 "github.com/google/uuid"
87 "github.com/stretchr/testify/assert"
98 "github.com/stretchr/testify/require"
109
11- "github.com/authorizerdev/authorizer/internal/constants"
12- "github.com/authorizerdev/authorizer/internal/crypto"
10+ "github.com/authorizerdev/authorizer/internal/config"
1311 "github.com/authorizerdev/authorizer/internal/graph/model"
1412 "github.com/authorizerdev/authorizer/internal/refs"
1513)
1614
17- // TestRedirectURIValidation verifies that all endpoints accepting redirect_uri
18- // validate it against AllowedOrigins to prevent open-redirect token theft.
19- func TestRedirectURIValidation (t * testing.T ) {
20- cfg := getTestConfig ()
21- cfg .AllowedOrigins = []string {"http://localhost:3000" }
22- cfg .EnableMagicLinkLogin = true
23- cfg .EnableEmailVerification = true
24- cfg .IsEmailServiceEnabled = true
25- cfg .IsSMSServiceEnabled = true
26- cfg .SMTPHost = "localhost"
27- cfg .SMTPPort = 1025
28- cfg .SMTPSenderEmail = "test@authorizer.dev"
29- cfg .SMTPSenderName = "Test"
30- cfg .SMTPLocalName = "Test"
31- cfg .SMTPSkipTLSVerification = true
32-
33- ts := initTestSetup (t , cfg )
34- _ , ctx := createContext (ts )
35-
36- attackerURL := "https://attacker.com/steal"
37- validURL := "http://localhost:3000/callback"
38-
39- t .Run ("ForgotPassword should reject invalid redirect_uri" , func (t * testing.T ) {
40- // First create a user
41- email := "fp_redirect_test_" + uuid .New ().String () + "@authorizer.dev"
42- password := "Password@123"
43- signupRes , err := ts .GraphQLProvider .SignUp (ctx , & model.SignUpRequest {
44- Email : & email ,
45- Password : password ,
46- ConfirmPassword : password ,
47- })
48- require .NoError (t , err )
49- require .NotNil (t , signupRes )
15+ // TestRedirectURIRejectsAttacker verifies that forgot_password rejects
16+ // attacker-controlled redirect_uri values with explicit AllowedOrigins.
17+ func TestRedirectURIRejectsAttacker (t * testing.T ) {
18+ runForEachDB (t , func (t * testing.T , cfg * config.Config ) {
19+ cfg .AllowedOrigins = []string {"http://localhost:3000" }
20+ cfg .EnableBasicAuthentication = true
21+ cfg .EnableEmailVerification = false
22+ cfg .EnableSignup = true
5023
51- // Attacker-controlled redirect_uri should be rejected
52- res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
53- Email : refs .NewStringRef (email ),
54- RedirectURI : refs .NewStringRef (attackerURL ),
55- })
56- assert .Error (t , err )
57- assert .Nil (t , res )
58- assert .Contains (t , err .Error (), "invalid redirect URI" )
59- })
24+ ts := initTestSetup (t , cfg )
25+ _ , ctx := createContext (ts )
6026
61- t .Run ("ForgotPassword should accept valid redirect_uri" , func (t * testing.T ) {
62- email := "fp_valid_redirect_" + uuid .New ().String () + "@authorizer.dev"
27+ email := "redirect_test_" + uuid .New ().String () + "@authorizer.dev"
6328 password := "Password@123"
6429 signupRes , err := ts .GraphQLProvider .SignUp (ctx , & model.SignUpRequest {
6530 Email : & email ,
@@ -69,98 +34,96 @@ func TestRedirectURIValidation(t *testing.T) {
6934 require .NoError (t , err )
7035 require .NotNil (t , signupRes )
7136
72- res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
73- Email : refs .NewStringRef (email ),
74- RedirectURI : refs .NewStringRef (validURL ),
37+ t .Run ("rejects attacker redirect_uri" , func (t * testing.T ) {
38+ res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
39+ Email : refs .NewStringRef (email ),
40+ RedirectURI : refs .NewStringRef ("https://attacker.com/steal" ),
41+ })
42+ assert .Error (t , err )
43+ assert .Nil (t , res )
44+ assert .Contains (t , err .Error (), "invalid redirect URI" )
7545 })
76- assert .NoError (t , err )
77- assert .NotNil (t , res )
78- })
7946
80- t .Run ("MagicLinkLogin should reject invalid redirect_uri" , func (t * testing.T ) {
81- email := "ml_redirect_test_" + uuid .New ().String () + "@authorizer.dev"
82- res , err := ts .GraphQLProvider .MagicLinkLogin (ctx , & model.MagicLinkLoginRequest {
83- Email : email ,
84- RedirectURI : & attackerURL ,
47+ t .Run ("accepts valid redirect_uri" , func (t * testing.T ) {
48+ res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
49+ Email : refs .NewStringRef (email ),
50+ RedirectURI : refs .NewStringRef ("http://localhost:3000/reset" ),
51+ })
52+ assert .NoError (t , err )
53+ assert .NotNil (t , res )
8554 })
86- assert .Error (t , err )
87- assert .Nil (t , res )
88- assert .Contains (t , err .Error (), "invalid redirect URI" )
8955 })
56+ }
9057
91- t .Run ("MagicLinkLogin should accept valid redirect_uri" , func (t * testing.T ) {
92- email := "ml_valid_redirect_" + uuid .New ().String () + "@authorizer.dev"
93- res , err := ts .GraphQLProvider .MagicLinkLogin (ctx , & model.MagicLinkLoginRequest {
94- Email : email ,
95- RedirectURI : & validURL ,
96- })
97- assert .NoError (t , err )
98- assert .NotNil (t , res )
99- })
58+ // TestRedirectURIWildcardOrigins is a regression test for the open redirect
59+ // vulnerability (issue #540). When allowed_origins=["*"] (the default config),
60+ // attacker-controlled redirect_uri values must still be rejected.
61+ func TestRedirectURIWildcardOrigins (t * testing.T ) {
62+ runForEachDB (t , func (t * testing.T , cfg * config.Config ) {
63+ cfg .AllowedOrigins = []string {"*" }
64+ cfg .EnableBasicAuthentication = true
65+ cfg .EnableEmailVerification = false
66+ cfg .EnableSignup = true
67+ ts := initTestSetup (t , cfg )
68+ _ , ctx := createContext (ts )
10069
101- t .Run ("Signup should reject invalid redirect_uri" , func (t * testing.T ) {
102- email := "signup_redirect_test_" + uuid .New ().String () + "@authorizer.dev"
70+ email := "wildcard_redirect_" + uuid .New ().String () + "@authorizer.dev"
10371 password := "Password@123"
104- res , err := ts .GraphQLProvider .SignUp (ctx , & model.SignUpRequest {
105- Email : & email ,
106- Password : password ,
107- ConfirmPassword : password ,
108- RedirectURI : & attackerURL ,
109- })
110- assert .Error (t , err )
111- assert .Nil (t , res )
112- assert .Contains (t , err .Error (), "invalid redirect URI" )
113- })
11472
115- t .Run ("Signup should accept valid redirect_uri" , func (t * testing.T ) {
116- email := "signup_valid_redirect_" + uuid .New ().String () + "@authorizer.dev"
117- password := "Password@123"
118- res , err := ts .GraphQLProvider .SignUp (ctx , & model.SignUpRequest {
73+ signupRes , err := ts .GraphQLProvider .SignUp (ctx , & model.SignUpRequest {
11974 Email : & email ,
12075 Password : password ,
12176 ConfirmPassword : password ,
122- RedirectURI : & validURL ,
12377 })
124- assert .NoError (t , err )
125- assert .NotNil (t , res )
126- })
127-
128- t .Run ("InviteMembers should reject invalid redirect_uri" , func (t * testing.T ) {
129- cfg .IsEmailServiceEnabled = true
130- cfg .EnableBasicAuthentication = true
131- cfg .EnableMagicLinkLogin = true
132-
133- req , _ := createContext (ts )
134- h , err := crypto .EncryptPassword (cfg .AdminSecret )
13578 require .NoError (t , err )
136- req . Header . Set ( "Cookie" , fmt . Sprintf ( "%s=%s" , constants . AdminCookieName , h ) )
79+ require . NotNil ( t , signupRes )
13780
138- emailTo := "invite_redirect_test_" + uuid .New ().String () + "@authorizer.dev"
139- res , err := ts .GraphQLProvider .InviteMembers (ctx , & model.InviteMemberRequest {
140- Emails : []string {emailTo },
141- RedirectURI : & attackerURL ,
81+ t .Run ("rejects attacker redirect_uri with wildcard origins" , func (t * testing.T ) {
82+ res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
83+ Email : refs .NewStringRef (email ),
84+ RedirectURI : refs .NewStringRef ("https://attacker.com/capture" ),
85+ })
86+ assert .Error (t , err )
87+ assert .Nil (t , res )
88+ assert .Contains (t , err .Error (), "invalid redirect URI" )
14289 })
143- assert .Error (t , err )
144- assert .Nil (t , res )
145- assert .Contains (t , err .Error (), "invalid redirect URI" )
146- })
14790
148- t .Run ("InviteMembers should accept valid redirect_uri" , func (t * testing.T ) {
149- cfg .IsEmailServiceEnabled = true
150- cfg .EnableBasicAuthentication = true
151- cfg .EnableMagicLinkLogin = true
91+ t .Run ("allows self-origin redirect_uri with wildcard origins" , func (t * testing.T ) {
92+ selfURI := "http://" + ts .HttpServer .Listener .Addr ().String () + "/app/reset-password"
93+ res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
94+ Email : refs .NewStringRef (email ),
95+ RedirectURI : refs .NewStringRef (selfURI ),
96+ })
97+ assert .NoError (t , err )
98+ assert .NotNil (t , res )
99+ assert .NotEmpty (t , res .Message )
100+ })
152101
153- req , _ := createContext (ts )
154- h , err := crypto .EncryptPassword (cfg .AdminSecret )
155- require .NoError (t , err )
156- req .Header .Set ("Cookie" , fmt .Sprintf ("%s=%s" , constants .AdminCookieName , h ))
102+ t .Run ("works without redirect_uri (uses default)" , func (t * testing.T ) {
103+ res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
104+ Email : refs .NewStringRef (email ),
105+ })
106+ assert .NoError (t , err )
107+ assert .NotNil (t , res )
108+ assert .NotEmpty (t , res .Message )
109+ })
110+
111+ t .Run ("rejects javascript scheme" , func (t * testing.T ) {
112+ res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
113+ Email : refs .NewStringRef (email ),
114+ RedirectURI : refs .NewStringRef ("javascript:alert(1)" ),
115+ })
116+ assert .Error (t , err )
117+ assert .Nil (t , res )
118+ })
157119
158- emailTo := "invite_valid_redirect_" + uuid .New ().String () + "@authorizer.dev"
159- res , err := ts .GraphQLProvider .InviteMembers (ctx , & model.InviteMemberRequest {
160- Emails : []string {emailTo },
161- RedirectURI : & validURL ,
120+ t .Run ("rejects data scheme" , func (t * testing.T ) {
121+ res , err := ts .GraphQLProvider .ForgotPassword (ctx , & model.ForgotPasswordRequest {
122+ Email : refs .NewStringRef (email ),
123+ RedirectURI : refs .NewStringRef ("data:text/html,<h1>evil</h1>" ),
124+ })
125+ assert .Error (t , err )
126+ assert .Nil (t , res )
162127 })
163- assert .NoError (t , err )
164- assert .NotNil (t , res )
165128 })
166129}
0 commit comments