saasufy-components 2.2.0 → 2.3.0

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.
@@ -0,0 +1,98 @@
1
+ import { SocketConsumer } from './socket.js';
2
+ import { generateRandomHexString } from './utils.js';
3
+
4
+ const DEFAULT_AUTH_TIMEOUT = 10000;
5
+
6
+ class OAuthHandler extends SocketConsumer {
7
+ static get observedAttributes() {
8
+ return [
9
+ 'provider',
10
+ 'code-param-name',
11
+ 'state-param-name',
12
+ 'state-session-storage-key',
13
+ 'success-url',
14
+ 'success-location-hash',
15
+ 'navigate-event-path',
16
+ 'auth-timeout'
17
+ ];
18
+ }
19
+
20
+ successRedirect() {
21
+ let successURL = this.getAttribute('success-url');
22
+ let successLocationHash = this.getAttribute('success-location-hash');
23
+ let navigateEventPath = this.getAttribute('navigate-event-path');
24
+
25
+ if (successLocationHash) {
26
+ location.hash = successLocationHash;
27
+ } else if (successURL) {
28
+ location.href = successURL;
29
+ } else {
30
+ this.dispatchEvent(
31
+ new CustomEvent('navigate', {
32
+ detail: {
33
+ path: navigateEventPath
34
+ },
35
+ bubbles: true
36
+ })
37
+ );
38
+ }
39
+ }
40
+
41
+ async connectedCallback() {
42
+ this.innerHTML = '';
43
+
44
+ let provider = this.getAttribute('provider');
45
+ if (!provider) {
46
+ throw new Error('The provider attribute of oauth-handler was not specified');
47
+ }
48
+
49
+ let sessionStorageKey = this.getAttribute('state-session-storage-key') || `oauth.${provider}.state`;
50
+ let expectedOAuthState = sessionStorage.getItem(sessionStorageKey);
51
+ if (!expectedOAuthState) return;
52
+
53
+ let codeParamName = this.getAttribute('code-param-name') || 'code';
54
+ let stateParamName = this.getAttribute('state-param-name') || 'state';
55
+
56
+ let authTimeout = Number(this.getAttribute('auth-timeout') || DEFAULT_AUTH_TIMEOUT);
57
+
58
+ let urlSearchParams = new URLSearchParams(location.search);
59
+ let code = urlSearchParams.get(codeParamName);
60
+
61
+ if (code) {
62
+ let state = urlSearchParams.get(stateParamName);
63
+ let socket = this.getSocket();
64
+
65
+ history.replaceState({}, document.title, location.pathname);
66
+
67
+ if (state === expectedOAuthState) {
68
+ try {
69
+ await socket.invoke('log-in-oauth', { provider, data: { code } });
70
+ } catch (error) {
71
+ this.innerHTML = `
72
+ <div class="error">OAuth authentication failed. ${error.message}</div>
73
+ `;
74
+ return;
75
+ }
76
+ if (socket.authState === 'authenticated') {
77
+ this.successRedirect();
78
+ } else {
79
+ try {
80
+ await socket.listener('authenticate').once(authTimeout);
81
+ } catch (error) {
82
+ this.innerHTML = `
83
+ <div class="error">OAuth socket authentication timed out</div>
84
+ `;
85
+ return;
86
+ }
87
+ this.successRedirect();
88
+ }
89
+ } else {
90
+ this.innerHTML = `
91
+ <div class="error">OAuth state query parameter was invalid</div>
92
+ `;
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ window.customElements.define('oauth-handler', OAuthHandler);
package/oauth-link.js ADDED
@@ -0,0 +1,84 @@
1
+ import { renderTemplate, generateRandomHexString } from './utils.js';
2
+
3
+ class OAuthLink extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ this.isReady = false;
7
+ this.attachShadow({ mode: 'open' });
8
+
9
+ this.handleSlotChangeEvent = () => {
10
+ this.renderItem();
11
+ };
12
+ }
13
+
14
+ static get observedAttributes() {
15
+ return [
16
+ 'provider',
17
+ 'client-id',
18
+ 'state-size',
19
+ 'state-session-storage-key'
20
+ ];
21
+ }
22
+
23
+ attributeChangedCallback(name, oldValue, newValue) {
24
+ if (!this.isReady) return;
25
+ this.render();
26
+ }
27
+
28
+ connectedCallback() {
29
+ this.isReady = true;
30
+ this.render();
31
+ this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
32
+ }
33
+
34
+ disconnectedCallback() {
35
+ this.shadowRoot.removeEventListener('slotchange', this.handleSlotChangeEvent);
36
+ }
37
+
38
+ renderItem() {
39
+ let provider = this.getAttribute('provider');
40
+ if (!provider) {
41
+ throw new Error('The provider attribute of oauth-link was not specified');
42
+ }
43
+ let viewportNode = this.shadowRoot.querySelector('slot[name="viewport"]').assignedNodes()[0];
44
+ if (!viewportNode) return;
45
+ let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
46
+ if (!itemTemplate) return;
47
+
48
+ let clientId = this.getAttribute('client-id');
49
+ let stateSize = Number(this.getAttribute('state-size') || 20);
50
+ let sessionStorageKey = this.getAttribute('state-session-storage-key') || `oauth.${provider}.state`;
51
+ let state = generateRandomHexString(stateSize);
52
+ sessionStorage.setItem(sessionStorageKey, state);
53
+
54
+ let itemString = renderTemplate(
55
+ itemTemplate.innerHTML,
56
+ {
57
+ oauth: {
58
+ clientId,
59
+ state
60
+ }
61
+ }
62
+ );
63
+ viewportNode.innerHTML = itemString;
64
+ }
65
+
66
+ render() {
67
+ let urlSearchParams = new URLSearchParams(location.search);
68
+ let code = urlSearchParams.get('code');
69
+
70
+ if (code) {
71
+ this.shadowRoot.innerHTML = '';
72
+ return;
73
+ }
74
+
75
+ this.shadowRoot.innerHTML = `
76
+ <slot name="item"></slot>
77
+ <slot name="viewport"></slot>
78
+ `;
79
+
80
+ this.renderItem();
81
+ }
82
+ }
83
+
84
+ window.customElements.define('oauth-link', OAuthLink);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasufy-components",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/utils.js CHANGED
@@ -268,6 +268,18 @@ export function updateConsumerElements(parentElement, consumers, value) {
268
268
  }
269
269
  }
270
270
 
271
+ export function generateRandomHexString(byteLength) {
272
+ let byteArray = new Uint8Array(byteLength);
273
+ crypto.getRandomValues(byteArray);
274
+ return Array.from(byteArray, byte => {
275
+ let firstChar = byte >> 4;
276
+ firstChar = firstChar < 10 ? String(firstChar) : String.fromCharCode(firstChar + 87);
277
+ let secondChar = byte & 0x0f;
278
+ secondChar = secondChar < 10 ? String(secondChar) : String.fromCharCode(secondChar + 87);
279
+ return `${firstChar}${secondChar}`;
280
+ }).join('');
281
+ }
282
+
271
283
  export function wait(duration) {
272
284
  return new Promise((resolve) => setTimeout(resolve, duration));
273
285
  }