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
