1
+ /**
2
+ * @license
3
+ * Copyright 2022 Google LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import { expect , use } from 'chai' ;
19
+ import chaiAsPromised from 'chai-as-promised' ;
20
+ import * as sinon from 'sinon' ;
21
+ import sinonChai from 'sinon-chai' ;
22
+
23
+ // eslint-disable-next-line import/no-extraneous-dependencies
24
+ import { Auth , createUserWithEmailAndPassword , User } from '@firebase/auth' ;
25
+ import { randomEmail } from '../../helpers/integration/helpers' ;
26
+
27
+ use ( chaiAsPromised ) ;
28
+ use ( sinonChai ) ;
29
+
30
+ export function generateMiddlewareTests ( authGetter : ( ) => Auth , signIn : ( ) => Promise < unknown > ) : void {
31
+ context ( 'middleware' , ( ) => {
32
+ let auth : Auth ;
33
+ let unsubscribes : Array < ( ) => void > ;
34
+
35
+ beforeEach ( ( ) => {
36
+ auth = authGetter ( ) ;
37
+ unsubscribes = [ ] ;
38
+ } ) ;
39
+
40
+ afterEach ( ( ) => {
41
+ for ( const u of unsubscribes ) {
42
+ u ( ) ;
43
+ }
44
+ } ) ;
45
+
46
+ /**
47
+ * Helper function for adding beforeAuthStateChanged that will
48
+ * automatically unsubscribe after every test (since some tests may
49
+ * perform cleanup after that would be affected by the middleware)
50
+ */
51
+ function beforeAuthStateChanged ( callback : ( user : User | null ) => void | Promise < void > ) : void {
52
+ unsubscribes . push ( auth . beforeAuthStateChanged ( callback ) ) ;
53
+ }
54
+
55
+ it ( 'can prevent user sign in' , async ( ) => {
56
+ beforeAuthStateChanged ( ( ) => {
57
+ throw new Error ( 'stop sign in' ) ;
58
+ } ) ;
59
+
60
+ await expect ( signIn ( ) ) . to . be . rejectedWith ( 'auth/login-blocked' ) ;
61
+ expect ( auth . currentUser ) . to . be . null ;
62
+ } ) ;
63
+
64
+ it ( 'can prevent user sign in as a promise' , async ( ) => {
65
+ beforeAuthStateChanged ( ( ) => {
66
+ return Promise . reject ( 'stop sign in' ) ;
67
+ } ) ;
68
+
69
+ await expect ( signIn ( ) ) . to . be . rejectedWith ( 'auth/login-blocked' ) ;
70
+ expect ( auth . currentUser ) . to . be . null ;
71
+ } ) ;
72
+
73
+ it ( 'keeps previously-logged in user if blocked' , async ( ) => {
74
+ // Use a random email/password sign in for the base user
75
+ const { user : baseUser } = await createUserWithEmailAndPassword ( auth , randomEmail ( ) , 'password' ) ;
76
+
77
+ beforeAuthStateChanged ( ( ) => {
78
+ throw new Error ( 'stop sign in' ) ;
79
+ } ) ;
80
+
81
+ await expect ( signIn ( ) ) . to . be . rejectedWith ( 'auth/login-blocked' ) ;
82
+ expect ( auth . currentUser ) . to . eq ( baseUser ) ;
83
+ } ) ;
84
+
85
+ it ( 'can allow sign in' , async ( ) => {
86
+ beforeAuthStateChanged ( ( ) => {
87
+ // Pass
88
+ } ) ;
89
+
90
+ await expect ( signIn ( ) ) . not . to . be . rejected ;
91
+ expect ( auth . currentUser ) . not . to . be . null ;
92
+ } ) ;
93
+
94
+ it ( 'can allow sign in as a promise' , async ( ) => {
95
+ beforeAuthStateChanged ( ( ) => {
96
+ return Promise . resolve ( ) ;
97
+ } ) ;
98
+
99
+ await expect ( signIn ( ) ) . not . to . be . rejected ;
100
+ expect ( auth . currentUser ) . not . to . be . null ;
101
+ } ) ;
102
+
103
+ it ( 'overrides previous user if allowed' , async ( ) => {
104
+ // Use a random email/password sign in for the base user
105
+ const { user : baseUser } = await createUserWithEmailAndPassword ( auth , randomEmail ( ) , 'password' ) ;
106
+
107
+ beforeAuthStateChanged ( ( ) => {
108
+ // Pass
109
+ } ) ;
110
+
111
+ await expect ( signIn ( ) ) . not . to . be . rejected ;
112
+ expect ( auth . currentUser ) . not . to . eq ( baseUser ) ;
113
+ } ) ;
114
+
115
+ it ( 'will reject if one callback fails' , async ( ) => {
116
+ // Also check that the function is called multiple
117
+ // times
118
+ const spy = sinon . spy ( ) ;
119
+
120
+ beforeAuthStateChanged ( spy ) ;
121
+ beforeAuthStateChanged ( spy ) ;
122
+ beforeAuthStateChanged ( spy ) ;
123
+ beforeAuthStateChanged ( ( ) => {
124
+ throw new Error ( 'stop sign in' ) ;
125
+ } ) ;
126
+
127
+ await expect ( signIn ( ) ) . to . be . rejectedWith ( 'auth/login-blocked' ) ;
128
+ expect ( auth . currentUser ) . to . be . null ;
129
+ expect ( spy ) . to . have . been . calledThrice ;
130
+ } ) ;
131
+
132
+ it ( 'keeps previously-logged in user if one rejects' , async ( ) => {
133
+ // Use a random email/password sign in for the base user
134
+ const { user : baseUser } = await createUserWithEmailAndPassword ( auth , randomEmail ( ) , 'password' ) ;
135
+
136
+ // Also check that the function is called multiple
137
+ // times
138
+ const spy = sinon . spy ( ) ;
139
+
140
+ beforeAuthStateChanged ( spy ) ;
141
+ beforeAuthStateChanged ( spy ) ;
142
+ beforeAuthStateChanged ( spy ) ;
143
+ beforeAuthStateChanged ( ( ) => {
144
+ throw new Error ( 'stop sign in' ) ;
145
+ } ) ;
146
+
147
+ await expect ( signIn ( ) ) . to . be . rejectedWith ( 'auth/login-blocked' ) ;
148
+ expect ( auth . currentUser ) . to . eq ( baseUser ) ;
149
+ expect ( spy ) . to . have . been . calledThrice ;
150
+ } ) ;
151
+
152
+ it ( 'allows sign in with multiple callbacks all pass' , async ( ) => {
153
+ // Use a random email/password sign in for the base user
154
+ const { user : baseUser } = await createUserWithEmailAndPassword ( auth , randomEmail ( ) , 'password' ) ;
155
+
156
+ // Also check that the function is called multiple
157
+ // times
158
+ const spy = sinon . spy ( ) ;
159
+
160
+ beforeAuthStateChanged ( spy ) ;
161
+ beforeAuthStateChanged ( spy ) ;
162
+ beforeAuthStateChanged ( spy ) ;
163
+
164
+ await expect ( signIn ( ) ) . not . to . be . rejected ;
165
+ expect ( auth . currentUser ) . not . to . eq ( baseUser ) ;
166
+ expect ( spy ) . to . have . been . calledThrice ;
167
+ } ) ;
168
+
169
+ it ( 'does not call subsequent callbacks after rejection' , async ( ) => {
170
+ const firstSpy = sinon . spy ( ) ;
171
+ const secondSpy = sinon . spy ( ) ;
172
+
173
+ beforeAuthStateChanged ( firstSpy ) ;
174
+ beforeAuthStateChanged ( ( ) => {
175
+ throw new Error ( 'stop sign in' ) ;
176
+ } ) ;
177
+ beforeAuthStateChanged ( secondSpy ) ;
178
+
179
+ await expect ( signIn ( ) ) . to . be . rejectedWith ( 'auth/login-blocked' ) ;
180
+ expect ( firstSpy ) . to . have . been . calledOnce ;
181
+ expect ( secondSpy ) . not . to . have . been . called ;
182
+ } ) ;
183
+
184
+ it ( 'can prevent sign-out' , async ( ) => {
185
+ await signIn ( ) ;
186
+ const user = auth . currentUser ;
187
+
188
+ beforeAuthStateChanged ( ( ) => {
189
+ throw new Error ( 'block sign out' ) ;
190
+ } ) ;
191
+
192
+ await expect ( auth . signOut ( ) ) . to . be . rejectedWith ( 'auth/login-blocked' ) ;
193
+ expect ( auth . currentUser ) . to . eq ( user ) ;
194
+ } ) ;
195
+ } ) ;
196
+ }
0 commit comments