@@ -26,7 +26,7 @@ describe("runMiddleware", () => {
2626 fetchMock . mockResolvedValue ( mockResponse ) ;
2727
2828 const request = new NextRequest (
29- "http://localhost:3000/__cookies__?finalTarget=https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword" ,
29+ "http://localhost:3000/__cookies__?finalTarget=https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword&appName=app " ,
3030 {
3131 method : "POST" ,
3232 body : JSON . stringify ( { returnSecureToken : true } ) ,
@@ -73,7 +73,7 @@ describe("runMiddleware", () => {
7373 fetchMock . mockResolvedValue ( mockResponse ) ;
7474
7575 const request = new NextRequest (
76- "http://localhost:3000/__cookies__?finalTarget=https://securetoken.googleapis.com/v1/token" ,
76+ "http://localhost:3000/__cookies__?finalTarget=https://securetoken.googleapis.com/v1/token&appName=app " ,
7777 {
7878 method : "POST" ,
7979 body : "grant_type=refresh_token" ,
@@ -115,7 +115,7 @@ describe("runMiddleware", () => {
115115 } ) ;
116116
117117 const request = new NextRequest (
118- "http://localhost:3000/__cookies__?finalTarget=https://securetoken.googleapis.com/v1/token" ,
118+ "http://localhost:3000/__cookies__?finalTarget=https://securetoken.googleapis.com/v1/token&appName=app " ,
119119 {
120120 method : "POST" ,
121121 body : "grant_type=refresh_token&refresh_token=REDACTED" ,
@@ -140,7 +140,7 @@ describe("runMiddleware", () => {
140140
141141 it ( "throws an error when request type to proxy cannot be determined" , async ( ) => {
142142 const request = new NextRequest (
143- "http://localhost:3000/__cookies__?finalTarget=https://example. com/unknown" ,
143+ "http://localhost:3000/__cookies__?finalTarget=https://securetoken.googleapis. com/unknown&appName=app " ,
144144 { method : "POST" } ,
145145 ) ;
146146 const options = {
@@ -156,6 +156,38 @@ describe("runMiddleware", () => {
156156 ) ;
157157 } ) ;
158158
159+ it ( "throws an error when proxy target host is unauthorized in production" , async ( ) => {
160+ const request = new NextRequest (
161+ "http://localhost:3000/__cookies__?finalTarget=https://evil-attacker.com/v1/token&appName=app" ,
162+ { method : "POST" } ,
163+ ) ;
164+ const options = {
165+ apiKey : "key" ,
166+ projectId : "proj" ,
167+ emulatorHost : undefined ,
168+ tenantId : undefined ,
169+ authDomain : "auth.domain" ,
170+ } ;
171+
172+ await expect ( runMiddleware ( "app" , options , request ) ) . rejects . toThrow (
173+ "Unauthorized proxy target host: evil-attacker.com" ,
174+ ) ;
175+ } ) ;
176+
177+ it ( "returns 400 when finalTarget parameter is missing" , async ( ) => {
178+ const request = new NextRequest ( "http://localhost:3000/__cookies__?appName=app" , { method : "POST" } ) ;
179+ const options = {
180+ apiKey : "key" ,
181+ projectId : "proj" ,
182+ emulatorHost : undefined ,
183+ tenantId : undefined ,
184+ authDomain : "auth.domain" ,
185+ } ;
186+
187+ const [ response ] = await runMiddleware ( "app" , options , request ) ;
188+ expect ( response ?. status ) . toBe ( 400 ) ;
189+ } ) ;
190+
159191 it ( "returns proxy response directly without setting cookies if fetch response is not ok" , async ( ) => {
160192 fetchMock . mockResolvedValue ( {
161193 ok : false ,
@@ -165,7 +197,7 @@ describe("runMiddleware", () => {
165197 } ) ;
166198
167199 const request = new NextRequest (
168- "http://localhost:3000/__cookies__?finalTarget=https://securetoken.googleapis.com/v1/token" ,
200+ "http://localhost:3000/__cookies__?finalTarget=https://securetoken.googleapis.com/v1/token&appName=app " ,
169201 { method : "POST" , body : "grant_type=refresh_token" } ,
170202 ) ;
171203 const options = {
@@ -179,4 +211,53 @@ describe("runMiddleware", () => {
179211 const [ response ] = await runMiddleware ( "app" , options , request ) ;
180212 expect ( response ?. status ) . toBe ( 401 ) ;
181213 } ) ;
214+
215+ it ( "skips /__cookies__ proxy handling if appName parameter does not match" , async ( ) => {
216+ const request = new NextRequest (
217+ "http://localhost:3000/__cookies__?finalTarget=https://securetoken.googleapis.com/v1/token&appName=adminApp" ,
218+ { method : "POST" , body : "grant_type=refresh_token" } ,
219+ ) ;
220+ const options = {
221+ apiKey : "key" ,
222+ projectId : "proj" ,
223+ emulatorHost : undefined ,
224+ tenantId : undefined ,
225+ authDomain : "auth.domain" ,
226+ } ;
227+
228+ // Running runMiddleware for "[DEFAULT]" when target appName is "adminApp"
229+ const [ response ] = await runMiddleware ( "[DEFAULT]" , options , request ) ;
230+ expect ( response ) . toBeUndefined ( ) ; // Should skip proxying
231+ expect ( fetchMock ) . not . toHaveBeenCalled ( ) ;
232+ } ) ;
233+
234+ it ( "handles v2/accounts endpoints (e.g. MFA finalize) correctly" , async ( ) => {
235+ fetchMock . mockResolvedValue ( {
236+ ok : true ,
237+ status : 200 ,
238+ statusText : "OK" ,
239+ json : async ( ) => ( {
240+ idToken : "mfa-id-token" ,
241+ refreshToken : "mfa-refresh-token" ,
242+ } ) ,
243+ } ) ;
244+
245+ const request = new NextRequest (
246+ "http://localhost:3000/__cookies__?finalTarget=https://identitytoolkit.googleapis.com/v2/accounts:mfaSignIn:finalize&appName=[DEFAULT]" ,
247+ { method : "POST" , body : JSON . stringify ( { } ) } ,
248+ ) ;
249+ const options = {
250+ apiKey : "key" ,
251+ projectId : "proj" ,
252+ emulatorHost : undefined ,
253+ tenantId : undefined ,
254+ authDomain : "auth.domain" ,
255+ } ;
256+
257+ const [ response ] = await runMiddleware ( "[DEFAULT]" , options , request ) ;
258+ expect ( response ) . toBeDefined ( ) ;
259+ const cookies = response ! . cookies . getAll ( ) ;
260+ const idCookie = cookies . find ( ( c ) => c . name . includes ( "FIREBASE_[DEFAULT]" ) ) ;
261+ expect ( idCookie ?. value ) . toBe ( "mfa-id-token" ) ;
262+ } ) ;
182263} ) ;
0 commit comments