@qikdev/sdk 0.1.200 → 0.1.202

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qikdev/sdk",
3
- "version": "0.1.200",
3
+ "version": "0.1.202",
4
4
  "type": "module",
5
5
  "description": "Promise based Javascript SDK",
6
6
  "main": "./dist/main.js",
@@ -10,6 +10,7 @@
10
10
  "dev": "vite build --watch",
11
11
  "test": "mocha --colors ./test/*.spec.js",
12
12
  "test:watch": "mocha --colors -w ./test/*.spec.js",
13
+ "test:auth": "yarn build && node test/server.js",
13
14
  "document": "jsdoc -r . -d public/docs -c jsdoc.conf README.md"
14
15
  },
15
16
  "exports": {
@@ -1,5 +1,6 @@
1
1
  import axios from 'axios';
2
2
  import { EventDispatcher } from './qik.utils.js';
3
+ import { createStorageAdapter } from './qik.storage.js';
3
4
 
4
5
  ///////////////////////////////////////////////////
5
6
 
@@ -23,19 +24,27 @@ var QikAuth = function(qik) {
23
24
 
24
25
  ///////////////////////////////////////////////////
25
26
 
26
- var sessionStorage = {};
27
- var store = sessionStorage;
27
+ // Create storage adapter based on configuration
28
+ const storageAdapter = createStorageAdapter({
29
+ useHttpOnlyCookies: qik.useHttpOnlyCookies,
30
+ cookieConfig: qik.cookieConfig
31
+ });
32
+
28
33
  const tokenBufferSeconds = 10;
29
34
 
30
35
  ///////////////////////////////////////////////////
31
36
 
32
37
  const service = {
33
38
  debug: false,
39
+ storageAdapter: storageAdapter,
34
40
  }
35
41
 
42
+ // Backwards compatibility - keep store reference
36
43
  Object.defineProperty(service, 'store', {
37
- value: store,
38
- writable: false,
44
+ get: function() {
45
+ return { user: storageAdapter.getUser() };
46
+ },
47
+ enumerable: true
39
48
  });
40
49
 
41
50
  //Create a new dispatcher
@@ -47,7 +56,7 @@ var QikAuth = function(qik) {
47
56
  function dispatch(parameters) {
48
57
 
49
58
  //Get the current user
50
- var user = store.user;
59
+ var user = storageAdapter.getUser();
51
60
 
52
61
  //Dispatch the change to the listeners
53
62
  if (service.onChange) {
@@ -75,8 +84,9 @@ var QikAuth = function(qik) {
75
84
 
76
85
  service.set = function(user, parameters, stopDispatch) {
77
86
 
78
- if (JSON.stringify(store.user) != JSON.stringify(user)) {
79
- store.user = user;
87
+ const currentUser = storageAdapter.getUser();
88
+ if (JSON.stringify(currentUser) != JSON.stringify(user)) {
89
+ storageAdapter.setUser(user);
80
90
  if (!stopDispatch) {
81
91
  return dispatch(parameters)
82
92
  }
@@ -95,7 +105,7 @@ var QikAuth = function(qik) {
95
105
  */
96
106
 
97
107
  service.logout = function() {
98
- delete store.user;
108
+ storageAdapter.clearUser();
99
109
  qik.cache.reset();
100
110
  return dispatch()
101
111
  }
@@ -288,6 +298,15 @@ var QikAuth = function(qik) {
288
298
  bypassInterceptor: true
289
299
  }
290
300
 
301
+ // Check if we're in cookie mode
302
+ const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
303
+
304
+ if (isCookieMode) {
305
+ // Signal to backend that we want cookie-based auth
306
+ credentials.useCookies = true;
307
+ postOptions.withCredentials = true;
308
+ }
309
+
291
310
  /////////////////////////////////////////////
292
311
 
293
312
  var url = `${qik.apiURL}/user/login`;
@@ -305,7 +324,6 @@ var QikAuth = function(qik) {
305
324
 
306
325
  if (autoAuthenticate) {
307
326
  service.set(res.data);
308
-
309
327
  }
310
328
 
311
329
  resolve(res);
@@ -391,6 +409,15 @@ var QikAuth = function(qik) {
391
409
  bypassInterceptor: true
392
410
  }
393
411
 
412
+ // Check if we're in cookie mode
413
+ const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
414
+
415
+ if (isCookieMode) {
416
+ // Signal to backend that we want cookie-based auth
417
+ credentials.useCookies = true;
418
+ postOptions.withCredentials = true;
419
+ }
420
+
394
421
  /////////////////////////////////////////////
395
422
 
396
423
  var url = `${qik.apiURL}/user/signup`;
@@ -581,18 +608,29 @@ var QikAuth = function(qik) {
581
608
 
582
609
  var currentUser = service.getCurrentUser();
583
610
  if (!currentUser) {
584
-
585
611
  return Promise.reject('No user');
586
612
  }
587
613
 
614
+ // Check if we're in cookie mode
615
+ const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
616
+
617
+ if (isCookieMode) {
618
+ // In cookie mode, we can't check token expiry from JavaScript
619
+ // Just attempt a refresh if forced, otherwise let the server handle it
620
+ if (forceRefresh) {
621
+ return await service.refreshAccessToken();
622
+ } else {
623
+ // Return a placeholder - server will handle token validation
624
+ return 'cookie_mode_token';
625
+ }
626
+ }
627
+
628
+ // localStorage mode - existing token validation logic
588
629
  var { token } = currentUser;
589
630
  if (!token) {
590
-
591
631
  return Promise.reject('No token');
592
632
  }
593
633
 
594
- /////////////////////////////////////////////////////
595
-
596
634
  //Check our date
597
635
  var now = new Date();
598
636
 
@@ -600,25 +638,18 @@ var QikAuth = function(qik) {
600
638
  //retiring the token
601
639
  now.setSeconds(now.getSeconds() + tokenBufferSeconds);
602
640
 
603
- /////////////////////////////////////////////////////
604
-
605
641
  var expires = new Date(token.expires);
606
642
 
607
643
  if (forceRefresh) {
608
- console.log('force refresh valid token', token.refreshToken)
609
644
  return await service.refreshAccessToken(token.refreshToken);
610
645
  }
611
646
 
612
647
  //If the token is still fresh
613
648
  if (now < expires) {
614
-
615
649
  return token;
616
650
  } else {
617
-
618
651
  return await service.refreshAccessToken(token.refreshToken);
619
652
  }
620
-
621
-
622
653
  }
623
654
 
624
655
  ///////////////////////////////////////////////////
@@ -627,78 +658,77 @@ var QikAuth = function(qik) {
627
658
 
628
659
  service.refreshAccessToken = async function(refreshToken) {
629
660
 
630
- // /////////////////////////////////////////////
631
-
632
- // if (appContext) {
633
-
634
- // } else {
635
-
636
- // }
637
-
638
- // /////////////////////////////////////////////
639
-
640
661
  //If there is already a request in progress
641
662
  if (refreshContext.inflightRefreshRequest) {
642
-
643
663
  return refreshContext.inflightRefreshRequest;
644
664
  }
645
665
 
646
666
  /////////////////////////////////////////////////////
647
667
 
648
- //Create an refresh request
668
+ // Check if we're in cookie mode
669
+ const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
649
670
 
671
+ //Create a refresh request
650
672
  refreshContext.inflightRefreshRequest = new Promise(function(resolve, reject) {
651
673
 
652
- //Bypass the interceptor on all token refresh calls
653
- //Because we don't need to add the access token etc onto it
674
+ let requestBody = {};
675
+ let requestOptions = {
676
+ bypassInterceptor: true,
677
+ withoutToken: true,
678
+ };
654
679
 
655
- qik.api.post(`/user/refresh`, {
656
- refreshToken: refreshToken
657
- }, {
658
- bypassInterceptor: true,
659
- withoutToken: true,
660
- })
680
+ if (isCookieMode) {
681
+ // In cookie mode, refresh token is sent via httpOnly cookie
682
+ // No need to include it in the request body
683
+ requestOptions.withCredentials = true;
684
+ } else {
685
+ // In localStorage mode, send refresh token in request body
686
+ if (!refreshToken) {
687
+ refreshContext.inflightRefreshRequest = null;
688
+ return reject(new Error('No refresh token provided for localStorage mode'));
689
+ }
690
+ requestBody.refreshToken = refreshToken;
691
+ }
692
+
693
+ qik.api.post(`/user/refresh`, requestBody, requestOptions)
661
694
  .then(function tokenRefreshComplete(res) {
662
695
 
663
696
  //Update the user with any changes
664
697
  //returned back from the refresh request
665
- if (!res) {
666
-
698
+ if (!res || !res.data) {
667
699
  refreshContext.inflightRefreshRequest = null;
668
- return reject();
669
-
670
- } else {
671
- //Update with our new session
672
- service.set(res.data);
700
+ return reject(new Error('Invalid refresh response'));
701
+ }
673
702
 
703
+ //Update with our new session
704
+ service.set(res.data);
705
+ dispatch();
674
706
 
675
- dispatch();
707
+ // Return appropriate response based on mode
708
+ if (isCookieMode) {
709
+ // In cookie mode, just indicate success - tokens are in cookies
710
+ resolve('cookie_token_refreshed');
711
+ } else {
712
+ // In localStorage mode, return the actual access token
713
+ const newToken = res.data.token?.accessToken;
714
+ if (newToken) {
715
+ resolve(newToken);
716
+ } else {
717
+ reject(new Error('No access token in refresh response'));
718
+ }
676
719
  }
677
720
 
678
- //Resolve with the new token
679
- resolve(res.data.token);
680
-
681
721
  //Remove the inflight request
682
722
  setTimeout(function() {
683
723
  refreshContext.inflightRefreshRequest = null;
684
- })
685
-
724
+ });
686
725
 
687
726
  })
688
727
  .catch(function(err) {
689
-
690
-
691
- //TODO Check if invalid_refresh_token
692
-
693
-
694
-
695
728
  setTimeout(function() {
696
729
  refreshContext.inflightRefreshRequest = null;
697
730
  });
698
731
  reject(err);
699
-
700
-
701
-
702
732
  });
703
733
  });
704
734
 
@@ -723,18 +753,26 @@ var QikAuth = function(qik) {
723
753
  return qik.api.get('/user')
724
754
  .then(function(res) {
725
755
 
726
-
727
756
  if (res.data) {
728
- if (store.user) {
729
- Object.assign(store.user.session, res.data);
757
+ const currentUser = storageAdapter.getUser();
758
+ if (currentUser) {
759
+ // Update the session data within the current user object
760
+ const updatedUser = { ...currentUser };
761
+ if (updatedUser.session) {
762
+ Object.assign(updatedUser.session, res.data);
763
+ } else {
764
+ updatedUser.session = res.data;
765
+ }
766
+ service.set(updatedUser);
767
+ } else {
768
+ // No current user, set the response data as new user
769
+ service.set(res.data);
730
770
  }
731
771
  } else {
732
772
  service.set(null);
733
773
  }
734
774
 
735
-
736
775
  retryCount = 0;
737
-
738
776
  dispatch();
739
777
  })
740
778
  .catch(function(err) {
@@ -749,7 +787,6 @@ var QikAuth = function(qik) {
749
787
  // service.sync();
750
788
  // }
751
789
 
752
-
753
790
  });
754
791
  }
755
792
 
@@ -763,7 +800,7 @@ var QikAuth = function(qik) {
763
800
  * const me = sdk.auth.getCurrentUser();
764
801
  */
765
802
  service.getCurrentUser = function() {
766
- return store.user;
803
+ return storageAdapter.getUser();
767
804
  }
768
805
 
769
806
 
@@ -777,7 +814,13 @@ var QikAuth = function(qik) {
777
814
  */
778
815
  service.getCurrentToken = function() {
779
816
 
817
+ // First try to get token from storage adapter
818
+ var accessToken = storageAdapter.getAccessToken();
819
+ if (accessToken) {
820
+ return accessToken;
821
+ }
780
822
 
823
+ // For localStorage mode, check user object for token
781
824
  var user = service.getCurrentUser();
782
825
 
783
826
  //User is not logged in
@@ -819,7 +862,21 @@ var QikAuth = function(qik) {
819
862
 
820
863
  //////////////////////////////
821
864
 
822
- var userDetails = await service.getCurrentUser();
865
+ // Check if we're in cookie mode
866
+ const isCookieMode = storageAdapter.isCookieMode && storageAdapter.isCookieMode();
867
+
868
+ if (isCookieMode) {
869
+ // In cookie mode, browser automatically includes httpOnly cookies
870
+ // We don't need to manually add Authorization headers or handle token refresh
871
+ // Just ensure withCredentials is set for cross-origin requests
872
+ if (!originalRequest.withCredentials) {
873
+ originalRequest.withCredentials = true;
874
+ }
875
+ return originalRequest;
876
+ }
877
+
878
+ // localStorage mode - existing logic for manual token management
879
+ var userDetails = service.getCurrentUser();
823
880
  var accessToken;
824
881
  var refreshToken;
825
882
  var expiryDate;
@@ -839,11 +896,8 @@ var QikAuth = function(qik) {
839
896
  if (accessToken) {
840
897
  //Set the token of the request as the user's access token
841
898
  originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
842
-
843
-
844
899
  } else {
845
900
  //Return the original request without a token
846
-
847
901
  return originalRequest;
848
902
  }
849
903
 
@@ -851,8 +905,6 @@ var QikAuth = function(qik) {
851
905
 
852
906
  //If no refresh token
853
907
  if (!refreshToken) {
854
-
855
-
856
908
  //Continue with the original request
857
909
  return originalRequest;
858
910
  }
@@ -935,4 +987,4 @@ var QikAuth = function(qik) {
935
987
  }
936
988
 
937
989
 
938
- export default QikAuth;
990
+ export default QikAuth;
@@ -21,15 +21,26 @@ import { EventDispatcher } from './qik.utils.js';
21
21
  * @constructor
22
22
  * @param {Object} options
23
23
  * @param {String} options.apiURL The remote URL of the Qik API you want to connect to. Options are 'staging', 'production' or you may set a specific URL eg. 'https://api.qik.io' (do not include trailing slash). If no value is provided, will default to 'production'.
24
- * @param {String} options.applicationToken When running as a static application, (for example a website) you may set the application's access token before you initialize the Qik instance here.
24
+ * @param {String} options.applicationToken When running as a static application, (for example a website) you may set the application's access token before you initialize the Qik instance here.
25
+ * @param {Boolean} options.useHttpOnlyCookies When set to true, authentication tokens will be stored in secure httpOnly cookies instead of localStorage. This provides enhanced security against XSS attacks. Requires backend support for cookie-based authentication.
26
+ * @param {Object} options.cookieConfig Configuration options for cookie storage when useHttpOnlyCookies is true.
27
+ * @param {String} options.cookieConfig.domain Cookie domain (auto-detected if not specified).
28
+ * @param {Boolean} options.cookieConfig.secure Whether cookies should only be sent over HTTPS (default: true).
29
+ * @param {String} options.cookieConfig.sameSite SameSite cookie attribute ('strict', 'lax', or 'none', default: 'lax').
25
30
  * @example
26
31
  *
27
32
  * //Import the Qik package
28
33
  * import Qik from '@qikdev/sdk';
29
34
  *
30
- * //Create a new Qik instance
35
+ * //Create a new Qik instance with localStorage (default)
31
36
  * var qik = new Qik();
32
37
  *
38
+ * //Create a new Qik instance with httpOnly cookies
39
+ * var qik = new Qik({
40
+ * apiURL: 'production',
41
+ * useHttpOnlyCookies: true
42
+ * });
43
+ *
33
44
  * //Request the current user session endpoint from the Qik API
34
45
  * qik.api.get('/session').then(function(res) {
35
46
  * console.log('User session is ', res.data);
@@ -243,4 +254,4 @@ export default function(options) {
243
254
 
244
255
  return core;
245
256
 
246
- }
257
+ }