@@ -2406,3 +2406,298 @@ describe('facebook limited auth adapter', () => {
24062406 }
24072407 } ) ;
24082408} ) ;
2409+
2410+ describe ( 'OTP TOTP auth adatper' , ( ) => {
2411+ const headers = {
2412+ 'Content-Type' : 'application/json' ,
2413+ 'X-Parse-Application-Id' : 'test' ,
2414+ 'X-Parse-REST-API-Key' : 'rest' ,
2415+ } ;
2416+ beforeEach ( async ( ) => {
2417+ await reconfigureServer ( {
2418+ auth : {
2419+ mfa : {
2420+ enabled : true ,
2421+ options : [ 'TOTP' ] ,
2422+ algorithm : 'SHA1' ,
2423+ digits : 6 ,
2424+ period : 30 ,
2425+ } ,
2426+ } ,
2427+ } ) ;
2428+ } ) ;
2429+
2430+ it ( 'can enroll' , async ( ) => {
2431+ const user = await Parse . User . signUp ( 'username' , 'password' ) ;
2432+ const OTPAuth = require ( 'otpauth' ) ;
2433+ const secret = new OTPAuth . Secret ( ) ;
2434+ const totp = new OTPAuth . TOTP ( {
2435+ algorithm : 'SHA1' ,
2436+ digits : 6 ,
2437+ period : 30 ,
2438+ secret,
2439+ } ) ;
2440+ const token = totp . generate ( ) ;
2441+ await user . save (
2442+ { authData : { mfa : { secret : secret . base32 , token } } } ,
2443+ { sessionToken : user . getSessionToken ( ) }
2444+ ) ;
2445+ const response = user . get ( 'authDataResponse' ) ;
2446+ expect ( response . mfa ) . toBeDefined ( ) ;
2447+ expect ( response . mfa . recovery ) . toBeDefined ( ) ;
2448+ expect ( response . mfa . recovery . length ) . toEqual ( 2 ) ;
2449+ await user . fetch ( ) ;
2450+ expect ( user . get ( 'authData' ) . mfa ) . toEqual ( { enabled : true } ) ;
2451+ } ) ;
2452+
2453+ it ( 'can login with valid token' , async ( ) => {
2454+ const user = await Parse . User . signUp ( 'username' , 'password' ) ;
2455+ const OTPAuth = require ( 'otpauth' ) ;
2456+ const secret = new OTPAuth . Secret ( ) ;
2457+ const totp = new OTPAuth . TOTP ( {
2458+ algorithm : 'SHA1' ,
2459+ digits : 6 ,
2460+ period : 30 ,
2461+ secret,
2462+ } ) ;
2463+ const token = totp . generate ( ) ;
2464+ await user . save (
2465+ { authData : { mfa : { secret : secret . base32 , token } } } ,
2466+ { sessionToken : user . getSessionToken ( ) }
2467+ ) ;
2468+ const response = await request ( {
2469+ headers,
2470+ method : 'POST' ,
2471+ url : 'http://localhost:8378/1/login' ,
2472+ body : JSON . stringify ( {
2473+ username : 'username' ,
2474+ password : 'password' ,
2475+ authData : {
2476+ mfa : totp . generate ( ) ,
2477+ } ,
2478+ } ) ,
2479+ } ) . then ( res => res . data ) ;
2480+ expect ( response . objectId ) . toEqual ( user . id ) ;
2481+ expect ( response . sessionToken ) . toBeDefined ( ) ;
2482+ expect ( response . authData ) . toEqual ( { mfa : { enabled : true } } ) ;
2483+ expect ( Object . keys ( response ) . sort ( ) ) . toEqual (
2484+ [
2485+ 'objectId' ,
2486+ 'username' ,
2487+ 'createdAt' ,
2488+ 'updatedAt' ,
2489+ 'authData' ,
2490+ 'ACL' ,
2491+ 'sessionToken' ,
2492+ 'authDataResponse' ,
2493+ ] . sort ( )
2494+ ) ;
2495+ } ) ;
2496+
2497+ it ( 'can change OTP with valid token' , async ( ) => {
2498+ const user = await Parse . User . signUp ( 'username' , 'password' ) ;
2499+ const OTPAuth = require ( 'otpauth' ) ;
2500+ const secret = new OTPAuth . Secret ( ) ;
2501+ const totp = new OTPAuth . TOTP ( {
2502+ algorithm : 'SHA1' ,
2503+ digits : 6 ,
2504+ period : 30 ,
2505+ secret,
2506+ } ) ;
2507+ const token = totp . generate ( ) ;
2508+ await user . save (
2509+ { authData : { mfa : { secret : secret . base32 , token } } } ,
2510+ { sessionToken : user . getSessionToken ( ) }
2511+ ) ;
2512+
2513+ const new_secret = new OTPAuth . Secret ( ) ;
2514+ const new_totp = new OTPAuth . TOTP ( {
2515+ algorithm : 'SHA1' ,
2516+ digits : 6 ,
2517+ period : 30 ,
2518+ secret : new_secret ,
2519+ } ) ;
2520+ const new_token = new_totp . generate ( ) ;
2521+ await user . save (
2522+ {
2523+ authData : { mfa : { secret : new_secret . base32 , token : new_token , old : totp . generate ( ) } } ,
2524+ } ,
2525+ { sessionToken : user . getSessionToken ( ) }
2526+ ) ;
2527+ await user . fetch ( { useMasterKey : true } ) ;
2528+ expect ( user . get ( 'authData' ) . mfa . secret ) . toEqual ( new_secret . base32 ) ;
2529+ } ) ;
2530+
2531+ it ( 'future logins require TOTP token' , async ( ) => {
2532+ const user = await Parse . User . signUp ( 'username' , 'password' ) ;
2533+ const OTPAuth = require ( 'otpauth' ) ;
2534+ const secret = new OTPAuth . Secret ( ) ;
2535+ const totp = new OTPAuth . TOTP ( {
2536+ algorithm : 'SHA1' ,
2537+ digits : 6 ,
2538+ period : 30 ,
2539+ secret,
2540+ } ) ;
2541+ const token = totp . generate ( ) ;
2542+ await user . save (
2543+ { authData : { mfa : { secret : secret . base32 , token } } } ,
2544+ { sessionToken : user . getSessionToken ( ) }
2545+ ) ;
2546+ await expectAsync ( Parse . User . logIn ( 'username' , 'password' ) ) . toBeRejectedWith (
2547+ new Parse . Error ( Parse . Error . OTHER_CAUSE , 'Missing additional authData mfa' )
2548+ ) ;
2549+ } ) ;
2550+
2551+ it ( 'future logins reject incorrect TOTP token' , async ( ) => {
2552+ const user = await Parse . User . signUp ( 'username' , 'password' ) ;
2553+ const OTPAuth = require ( 'otpauth' ) ;
2554+ const secret = new OTPAuth . Secret ( ) ;
2555+ const totp = new OTPAuth . TOTP ( {
2556+ algorithm : 'SHA1' ,
2557+ digits : 6 ,
2558+ period : 30 ,
2559+ secret,
2560+ } ) ;
2561+ const token = totp . generate ( ) ;
2562+ await user . save (
2563+ { authData : { mfa : { secret : secret . base32 , token } } } ,
2564+ { sessionToken : user . getSessionToken ( ) }
2565+ ) ;
2566+ await expectAsync (
2567+ request ( {
2568+ headers,
2569+ method : 'POST' ,
2570+ url : 'http://localhost:8378/1/login' ,
2571+ body : JSON . stringify ( {
2572+ username : 'username' ,
2573+ password : 'password' ,
2574+ authData : {
2575+ mfa : 'abcd' ,
2576+ } ,
2577+ } ) ,
2578+ } ) . catch ( e => {
2579+ throw e . data ;
2580+ } )
2581+ ) . toBeRejectedWith ( { code : Parse . Error . SCRIPT_FAILED , error : 'Invalid MFA token' } ) ;
2582+ } ) ;
2583+ } ) ;
2584+
2585+ describe ( 'OTP SMS auth adatper' , ( ) => {
2586+ const headers = {
2587+ 'Content-Type' : 'application/json' ,
2588+ 'X-Parse-Application-Id' : 'test' ,
2589+ 'X-Parse-REST-API-Key' : 'rest' ,
2590+ } ;
2591+ let code ;
2592+ let mobile ;
2593+ const mfa = {
2594+ enabled : true ,
2595+ options : [ 'SMS' ] ,
2596+ sendSMS ( smsCode , number ) {
2597+ expect ( smsCode ) . toBeDefined ( ) ;
2598+ expect ( number ) . toBeDefined ( ) ;
2599+ expect ( smsCode . length ) . toEqual ( 6 ) ;
2600+ code = smsCode ;
2601+ mobile = number ;
2602+ } ,
2603+ digits : 6 ,
2604+ period : 30 ,
2605+ } ;
2606+ beforeEach ( async ( ) => {
2607+ code = '' ;
2608+ mobile = '' ;
2609+ await reconfigureServer ( {
2610+ auth : {
2611+ mfa,
2612+ } ,
2613+ } ) ;
2614+ } ) ;
2615+
2616+ it ( 'can enroll' , async ( ) => {
2617+ const user = await Parse . User . signUp ( 'username' , 'password' ) ;
2618+ const sessionToken = user . getSessionToken ( ) ;
2619+ const spy = spyOn ( mfa , 'sendSMS' ) . and . callThrough ( ) ;
2620+ await user . save ( { authData : { mfa : { mobile : '+11111111111' } } } , { sessionToken } ) ;
2621+ await user . fetch ( { sessionToken } ) ;
2622+ expect ( user . get ( 'authData' ) ) . toEqual ( { mfa : { enabled : false } } ) ;
2623+ expect ( spy ) . toHaveBeenCalledWith ( code , '+11111111111' ) ;
2624+ await user . fetch ( { useMasterKey : true } ) ;
2625+ const authData = user . get ( 'authData' ) . mfa ?. pending ;
2626+ expect ( authData ) . toBeDefined ( ) ;
2627+ expect ( authData [ '+11111111111' ] ) . toBeDefined ( ) ;
2628+ expect ( Object . keys ( authData [ '+11111111111' ] ) ) . toEqual ( [ 'token' , 'expiry' ] ) ;
2629+
2630+ await user . save ( { authData : { mfa : { mobile, token : code } } } , { sessionToken } ) ;
2631+ await user . fetch ( { sessionToken } ) ;
2632+ expect ( user . get ( 'authData' ) ) . toEqual ( { mfa : { enabled : true } } ) ;
2633+ } ) ;
2634+
2635+ it ( 'future logins require SMS code' , async ( ) => {
2636+ const user = await Parse . User . signUp ( 'username' , 'password' ) ;
2637+ const spy = spyOn ( mfa , 'sendSMS' ) . and . callThrough ( ) ;
2638+ await user . save (
2639+ { authData : { mfa : { mobile : '+11111111111' } } } ,
2640+ { sessionToken : user . getSessionToken ( ) }
2641+ ) ;
2642+
2643+ await user . save (
2644+ { authData : { mfa : { mobile, token : code } } } ,
2645+ { sessionToken : user . getSessionToken ( ) }
2646+ ) ;
2647+
2648+ spy . calls . reset ( ) ;
2649+
2650+ await expectAsync ( Parse . User . logIn ( 'username' , 'password' ) ) . toBeRejectedWith (
2651+ new Parse . Error ( Parse . Error . OTHER_CAUSE , 'Missing additional authData mfa' )
2652+ ) ;
2653+ const res = await request ( {
2654+ headers,
2655+ method : 'POST' ,
2656+ url : 'http://localhost:8378/1/login' ,
2657+ body : JSON . stringify ( {
2658+ username : 'username' ,
2659+ password : 'password' ,
2660+ authData : {
2661+ mfa : true ,
2662+ } ,
2663+ } ) ,
2664+ } ) . catch ( e => e . data ) ;
2665+ expect ( res ) . toEqual ( { code : Parse . Error . SCRIPT_FAILED , error : 'Please enter the token' } ) ;
2666+ expect ( spy ) . toHaveBeenCalledWith ( code , '+11111111111' ) ;
2667+ const response = await request ( {
2668+ headers,
2669+ method : 'POST' ,
2670+ url : 'http://localhost:8378/1/login' ,
2671+ body : JSON . stringify ( {
2672+ username : 'username' ,
2673+ password : 'password' ,
2674+ authData : {
2675+ mfa : code ,
2676+ } ,
2677+ } ) ,
2678+ } ) . then ( res => res . data ) ;
2679+ expect ( response . objectId ) . toEqual ( user . id ) ;
2680+ expect ( response . sessionToken ) . toBeDefined ( ) ;
2681+ expect ( response . authData ) . toEqual ( { mfa : { enabled : true } } ) ;
2682+ expect ( Object . keys ( response ) . sort ( ) ) . toEqual (
2683+ [
2684+ 'objectId' ,
2685+ 'username' ,
2686+ 'createdAt' ,
2687+ 'updatedAt' ,
2688+ 'authData' ,
2689+ 'ACL' ,
2690+ 'sessionToken' ,
2691+ 'authDataResponse' ,
2692+ ] . sort ( )
2693+ ) ;
2694+ } ) ;
2695+
2696+ it ( 'partially enrolled users can still login' , async ( ) => {
2697+ const user = await Parse . User . signUp ( 'username' , 'password' ) ;
2698+ await user . save ( { authData : { mfa : { mobile : '+11111111111' } } } ) ;
2699+ const spy = spyOn ( mfa , 'sendSMS' ) . and . callThrough ( ) ;
2700+ await Parse . User . logIn ( 'username' , 'password' ) ;
2701+ expect ( spy ) . not . toHaveBeenCalled ( ) ;
2702+ } ) ;
2703+ } ) ;
0 commit comments