Introduce the Proxmox.OAuth2 singleton supporting Google and Microsoft
OAuth2. The flow is handled by opening a new window with the
authorization URL, and expects to receive the resulting authorization
code from the redirect handler via a [BroadcastChannel].

[BroadcastChannel]
https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel

Signed-off-by: Arthur Bied-Charreton <[email protected]>
---
 src/Utils.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 84 insertions(+)

diff --git a/src/Utils.js b/src/Utils.js
index 5457ffa..f59c4a5 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -1723,6 +1723,90 @@ Ext.define('Proxmox.Utils', {
     },
 });
 
+Ext.define('Proxmox.OAuth2', {
+    singleton: true,
+
+    handleGoogleFlow: function (clientId, clientSecret) {
+        return this._handleFlow({
+            clientId,
+            clientSecret,
+            authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
+            tokenUrl: 'https://oauth2.googleapis.com/token',
+            scope: 'https://mail.google.com',
+            extraAuthParams: {
+                access_type: 'offline',
+                prompt: 'consent',
+            },
+        });
+    },
+
+    handleMicrosoftFlow: function(clientId, clientSecret, tenantId) {
+        return this._handleFlow({
+            clientId,
+            clientSecret,
+            authUrl: 
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
+            tokenUrl: 
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
+            scope: 'https://outlook.office.com/SMTP.Send offline_access',
+            extraAuthParams: {
+                prompt: 'consent',
+            },
+        });
+    },
+
+    _handleFlow: function (config) {
+        return new Promise((resolve, reject) => {
+            const redirectUri = window.location.origin;
+            const channelName = `oauth2_${crypto.randomUUID()}`;
+            const state = encodeURIComponent(JSON.stringify({ channelName }));
+
+            const authParams = new URLSearchParams({
+                client_id: config.clientId,
+                response_type: 'code',
+                redirect_uri: redirectUri,
+                scope: config.scope,
+                state,
+                ...config.extraAuthParams,
+            });
+
+            const authUrl = `${config.authUrl}?${authParams}`;
+
+            // Opens OAuth2 authentication window. The app's redirect handler 
must  
+            // extract the authorization code from the callback URL and send 
it via:
+            // new BroadcastChannel(state.channelName).postMessage({ code })
+            const channel = new BroadcastChannel(channelName);
+            const popup = window.open(authUrl);
+
+            channel.addEventListener('message', async (event) => {
+                if (popup && !popup.closed) {
+                    popup.close();
+                }
+                channel.close();
+
+                try {
+                    const response = await fetch(config.tokenUrl, {
+                        method: 'POST',
+                        headers: {
+                            'Content-Type': 
'application/x-www-form-urlencoded',
+                        },
+                        body: new URLSearchParams({
+                            grant_type: 'authorization_code',
+                            code: event.data.code,
+                            client_id: config.clientId,
+                            client_secret: config.clientSecret,
+                            redirect_uri: redirectUri,
+                        }),
+                    });
+
+                    const tokens = await response.json();
+                    resolve(tokens.refresh_token);
+                } catch (error) {
+                    reject(error);
+                }
+            });
+        })
+    }
+})
+
 Ext.define('Proxmox.Async', {
     singleton: true,
 
-- 
2.47.3



Reply via email to