@@ -61,6 +61,7 @@ import auth, {
61
61
verifyBeforeUpdateEmail ,
62
62
getAdditionalUserInfo ,
63
63
getCustomAuthDomain ,
64
+ validatePassword ,
64
65
AppleAuthProvider ,
65
66
EmailAuthProvider ,
66
67
FacebookAuthProvider ,
@@ -72,6 +73,8 @@ import auth, {
72
73
TwitterAuthProvider ,
73
74
} from '../lib' ;
74
75
76
+ const PasswordPolicyImpl = require ( '../lib/password-policy/PasswordPolicyImpl' ) . default ;
77
+
75
78
// @ts -ignore test
76
79
import FirebaseModule from '../../app/lib/internal/FirebaseModule' ;
77
80
// @ts -ignore - We don't mind missing types here
@@ -133,13 +136,13 @@ describe('Auth', function () {
133
136
const result = auth ( ) . useEmulator ( 'http://my-host:9099' ) ;
134
137
expect ( result ) . toEqual ( [ 'my-host' , 9099 ] ) ;
135
138
} ) ;
136
- } ) ;
137
139
138
- describe ( 'tenantId' , function ( ) {
139
- it ( 'should be able to set tenantId ' , function ( ) {
140
- const auth = firebase . app ( ) . auth ( ) ;
141
- auth . setTenantId ( 'test-id' ) . then ( ( ) => {
142
- expect ( auth . tenantId ) . toBe ( 'test-id' ) ;
140
+ describe ( 'tenantId' , function ( ) {
141
+ it ( 'should be able to set tenantId ' , function ( ) {
142
+ const auth = firebase . app ( ) . auth ( ) ;
143
+ auth . setTenantId ( 'test-id' ) . then ( ( ) => {
144
+ expect ( auth . tenantId ) . toBe ( 'test-id' ) ;
145
+ } ) ;
143
146
} ) ;
144
147
} ) ;
145
148
@@ -201,6 +204,84 @@ describe('Auth', function () {
201
204
expect ( actual . _auth ) . not . toBeNull ( ) ;
202
205
} ) ;
203
206
} ) ;
207
+
208
+ describe ( 'ActionCodeSettings' , function ( ) {
209
+ beforeAll ( function ( ) {
210
+ // @ts -ignore test
211
+ jest . spyOn ( FirebaseModule . prototype , 'native' , 'get' ) . mockImplementation ( ( ) => {
212
+ return new Proxy (
213
+ { } ,
214
+ {
215
+ get : ( ) => jest . fn ( ) . mockResolvedValue ( { } as never ) ,
216
+ } ,
217
+ ) ;
218
+ } ) ;
219
+ } ) ;
220
+
221
+ it ( 'should allow linkDomain as `ActionCodeSettings.linkDomain`' , function ( ) {
222
+ const auth = firebase . app ( ) . auth ( ) ;
223
+ const actionCodeSettings : FirebaseAuthTypes . ActionCodeSettings = {
224
+ url : 'https://example.com' ,
225
+ handleCodeInApp : true ,
226
+ linkDomain : 'example.com' ,
227
+ } ;
228
+ const email = 'fake@example.com' ;
229
+ auth . sendSignInLinkToEmail ( email , actionCodeSettings ) ;
230
+ auth . sendPasswordResetEmail ( email , actionCodeSettings ) ;
231
+ sendPasswordResetEmail ( auth , email , actionCodeSettings ) ;
232
+ sendSignInLinkToEmail ( auth , email , actionCodeSettings ) ;
233
+
234
+ const user : FirebaseAuthTypes . User = new User ( auth , { } ) ;
235
+
236
+ user . sendEmailVerification ( actionCodeSettings ) ;
237
+ user . verifyBeforeUpdateEmail ( email , actionCodeSettings ) ;
238
+ sendEmailVerification ( user , actionCodeSettings ) ;
239
+ verifyBeforeUpdateEmail ( user , email , actionCodeSettings ) ;
240
+ } ) ;
241
+
242
+ it ( 'should warn using `ActionCodeSettings.dynamicLinkDomain`' , function ( ) {
243
+ const auth = firebase . app ( ) . auth ( ) ;
244
+ const actionCodeSettings : FirebaseAuthTypes . ActionCodeSettings = {
245
+ url : 'https://example.com' ,
246
+ handleCodeInApp : true ,
247
+ linkDomain : 'example.com' ,
248
+ dynamicLinkDomain : 'example.com' ,
249
+ } ;
250
+ const email = 'fake@example.com' ;
251
+ let warnings = 0 ;
252
+ const consoleWarnSpy = jest . spyOn ( console , 'warn' ) ;
253
+ consoleWarnSpy . mockReset ( ) ;
254
+ consoleWarnSpy . mockImplementation ( warnMessage => {
255
+ if (
256
+ warnMessage . includes (
257
+ 'Instead, use ActionCodeSettings.linkDomain to set up a custom domain' ,
258
+ )
259
+ ) {
260
+ warnings ++ ;
261
+ }
262
+ } ) ;
263
+ auth . sendSignInLinkToEmail ( email , actionCodeSettings ) ;
264
+ expect ( warnings ) . toBe ( 1 ) ;
265
+ auth . sendPasswordResetEmail ( email , actionCodeSettings ) ;
266
+ expect ( warnings ) . toBe ( 2 ) ;
267
+ sendPasswordResetEmail ( auth , email , actionCodeSettings ) ;
268
+ expect ( warnings ) . toBe ( 3 ) ;
269
+ sendSignInLinkToEmail ( auth , email , actionCodeSettings ) ;
270
+ expect ( warnings ) . toBe ( 4 ) ;
271
+ const user : FirebaseAuthTypes . User = new User ( auth , { } ) ;
272
+
273
+ user . sendEmailVerification ( actionCodeSettings ) ;
274
+ expect ( warnings ) . toBe ( 5 ) ;
275
+ user . verifyBeforeUpdateEmail ( email , actionCodeSettings ) ;
276
+ expect ( warnings ) . toBe ( 6 ) ;
277
+ sendEmailVerification ( user , actionCodeSettings ) ;
278
+ expect ( warnings ) . toBe ( 7 ) ;
279
+ verifyBeforeUpdateEmail ( user , email , actionCodeSettings ) ;
280
+ expect ( warnings ) . toBe ( 8 ) ;
281
+ consoleWarnSpy . mockReset ( ) ;
282
+ consoleWarnSpy . mockRestore ( ) ;
283
+ } ) ;
284
+ } ) ;
204
285
} ) ;
205
286
206
287
describe ( 'modular' , function ( ) {
@@ -420,6 +501,10 @@ describe('Auth', function () {
420
501
expect ( getCustomAuthDomain ) . toBeDefined ( ) ;
421
502
} ) ;
422
503
504
+ it ( '`validatePassword` function is properly exposed to end user' , function ( ) {
505
+ expect ( validatePassword ) . toBeDefined ( ) ;
506
+ } ) ;
507
+
423
508
it ( '`AppleAuthProvider` class is properly exposed to end user' , function ( ) {
424
509
expect ( AppleAuthProvider ) . toBeDefined ( ) ;
425
510
} ) ;
@@ -456,81 +541,79 @@ describe('Auth', function () {
456
541
expect ( TwitterAuthProvider ) . toBeDefined ( ) ;
457
542
} ) ;
458
543
459
- describe ( 'ActionCodeSettings' , function ( ) {
460
- beforeAll ( function ( ) {
461
- // @ts -ignore test
462
- jest . spyOn ( FirebaseModule . prototype , 'native' , 'get' ) . mockImplementation ( ( ) => {
463
- return new Proxy (
464
- { } ,
465
- {
466
- get : ( ) => jest . fn ( ) . mockResolvedValue ( { } as never ) ,
467
- } ,
468
- ) ;
469
- } ) ;
544
+ describe ( 'PasswordPolicyImpl' , function ( ) {
545
+ const TEST_MIN_PASSWORD_LENGTH = 6 ;
546
+ const TEST_SCHEMA_VERSION = 1 ;
547
+
548
+ const testPolicy = {
549
+ customStrengthOptions : {
550
+ minPasswordLength : 6 ,
551
+ maxPasswordLength : 4096 ,
552
+ containsLowercaseCharacter : true ,
553
+ containsUppercaseCharacter : true ,
554
+ containsNumericCharacter : true ,
555
+ containsNonAlphanumericCharacter : true ,
556
+ } ,
557
+ allowedNonAlphanumericCharacters : [ '$' , '*' ] ,
558
+ schemaVersion : 1 ,
559
+ enforcementState : 'OFF' ,
560
+ } ;
561
+
562
+ it ( 'should create a password policy' , async ( ) => {
563
+ let passwordPolicy = new PasswordPolicyImpl ( testPolicy ) ;
564
+ expect ( passwordPolicy ) . toBeDefined ( ) ;
565
+ expect ( passwordPolicy . customStrengthOptions . minPasswordLength ) . toEqual (
566
+ TEST_MIN_PASSWORD_LENGTH ,
567
+ ) ;
568
+ expect ( passwordPolicy . schemaVersion ) . toEqual ( TEST_SCHEMA_VERSION ) ;
470
569
} ) ;
471
570
472
- it ( 'should allow linkDomain as `ActionCodeSettings.linkDomain`' , function ( ) {
473
- const auth = firebase . app ( ) . auth ( ) ;
474
- const actionCodeSettings : FirebaseAuthTypes . ActionCodeSettings = {
475
- url : 'https://example.com' ,
476
- handleCodeInApp : true ,
477
- linkDomain : 'example.com' ,
478
- } ;
479
- const email = 'fake@example.com' ;
480
- auth . sendSignInLinkToEmail ( email , actionCodeSettings ) ;
481
- auth . sendPasswordResetEmail ( email , actionCodeSettings ) ;
482
- sendPasswordResetEmail ( auth , email , actionCodeSettings ) ;
483
- sendSignInLinkToEmail ( auth , email , actionCodeSettings ) ;
571
+ it ( 'should return statusValid: true when the password satisfies the password policy' , async ( ) => {
572
+ const passwordPolicy = new PasswordPolicyImpl ( testPolicy ) ;
573
+ let password = 'Password$123' ;
574
+ let status = passwordPolicy . validatePassword ( password ) ;
575
+ expect ( status ) . toBeDefined ( ) ;
576
+ expect ( status . isValid ) . toEqual ( true ) ;
577
+ } ) ;
484
578
485
- const user : FirebaseAuthTypes . User = new User ( auth , { } ) ;
579
+ it ( 'should return statusValid: false when the password is too short' , async ( ) => {
580
+ const passwordPolicy = new PasswordPolicyImpl ( testPolicy ) ;
581
+ let password = 'Pa1$' ;
582
+ let status = passwordPolicy . validatePassword ( password ) ;
583
+ expect ( status ) . toBeDefined ( ) ;
584
+ expect ( status . isValid ) . toEqual ( false ) ;
585
+ } ) ;
486
586
487
- user . sendEmailVerification ( actionCodeSettings ) ;
488
- user . verifyBeforeUpdateEmail ( email , actionCodeSettings ) ;
489
- sendEmailVerification ( user , actionCodeSettings ) ;
490
- verifyBeforeUpdateEmail ( user , email , actionCodeSettings ) ;
587
+ it ( 'should return statusValid: false when the password has no capital characters' , async ( ) => {
588
+ const passwordPolicy = new PasswordPolicyImpl ( testPolicy ) ;
589
+ let password = 'password123$' ;
590
+ let status = passwordPolicy . validatePassword ( password ) ;
591
+ expect ( status ) . toBeDefined ( ) ;
592
+ expect ( status . isValid ) . toEqual ( false ) ;
491
593
} ) ;
492
594
493
- it ( 'should warn using `ActionCodeSettings.dynamicLinkDomain`' , function ( ) {
494
- const auth = firebase . app ( ) . auth ( ) ;
495
- const actionCodeSettings : FirebaseAuthTypes . ActionCodeSettings = {
496
- url : 'https://example.com' ,
497
- handleCodeInApp : true ,
498
- linkDomain : 'example.com' ,
499
- dynamicLinkDomain : 'example.com' ,
500
- } ;
501
- const email = 'fake@example.com' ;
502
- let warnings = 0 ;
503
- const consoleWarnSpy = jest . spyOn ( console , 'warn' ) ;
504
- consoleWarnSpy . mockReset ( ) ;
505
- consoleWarnSpy . mockImplementation ( warnMessage => {
506
- if (
507
- warnMessage . includes (
508
- 'Instead, use ActionCodeSettings.linkDomain to set up a custom domain' ,
509
- )
510
- ) {
511
- warnings ++ ;
512
- }
513
- } ) ;
514
- auth . sendSignInLinkToEmail ( email , actionCodeSettings ) ;
515
- expect ( warnings ) . toBe ( 1 ) ;
516
- auth . sendPasswordResetEmail ( email , actionCodeSettings ) ;
517
- expect ( warnings ) . toBe ( 2 ) ;
518
- sendPasswordResetEmail ( auth , email , actionCodeSettings ) ;
519
- expect ( warnings ) . toBe ( 3 ) ;
520
- sendSignInLinkToEmail ( auth , email , actionCodeSettings ) ;
521
- expect ( warnings ) . toBe ( 4 ) ;
522
- const user : FirebaseAuthTypes . User = new User ( auth , { } ) ;
595
+ it ( 'should return statusValid: false when the password has no lowercase characters' , async ( ) => {
596
+ const passwordPolicy = new PasswordPolicyImpl ( testPolicy ) ;
597
+ let password = 'PASSWORD123$' ;
598
+ let status = passwordPolicy . validatePassword ( password ) ;
599
+ expect ( status ) . toBeDefined ( ) ;
600
+ expect ( status . isValid ) . toEqual ( false ) ;
601
+ } ) ;
523
602
524
- user . sendEmailVerification ( actionCodeSettings ) ;
525
- expect ( warnings ) . toBe ( 5 ) ;
526
- user . verifyBeforeUpdateEmail ( email , actionCodeSettings ) ;
527
- expect ( warnings ) . toBe ( 6 ) ;
528
- sendEmailVerification ( user , actionCodeSettings ) ;
529
- expect ( warnings ) . toBe ( 7 ) ;
530
- verifyBeforeUpdateEmail ( user , email , actionCodeSettings ) ;
531
- expect ( warnings ) . toBe ( 8 ) ;
532
- consoleWarnSpy . mockReset ( ) ;
533
- consoleWarnSpy . mockRestore ( ) ;
603
+ it ( 'should return statusValid: false when the password has no numbers' , async ( ) => {
604
+ const passwordPolicy = new PasswordPolicyImpl ( testPolicy ) ;
605
+ let password = 'Password$' ;
606
+ let status = passwordPolicy . validatePassword ( password ) ;
607
+ expect ( status ) . toBeDefined ( ) ;
608
+ expect ( status . isValid ) . toEqual ( false ) ;
609
+ } ) ;
610
+
611
+ it ( 'should return statusValid: false when the password has no special characters' , async ( ) => {
612
+ const passwordPolicy = new PasswordPolicyImpl ( testPolicy ) ;
613
+ let password = 'Password123' ;
614
+ let status = passwordPolicy . validatePassword ( password ) ;
615
+ expect ( status ) . toBeDefined ( ) ;
616
+ expect ( status . isValid ) . toEqual ( false ) ;
534
617
} ) ;
535
618
} ) ;
536
619
} ) ;
0 commit comments