feat(integrations): add per-user OAuth wizard with user-mode for Dropbox, OneDrive, Google Drive
- Add user_mode to dropbox/onedrive/google_drive setup views that loads integration config - Show user-friendly auth wizard when integration_id is provided (user mode) - In user mode: show integration name, current folder, back-to-integrations link - In callback templates: only save credentials (not config) for user integrations - In integrations dashboard: show Authorize/Re-Authorize button for all OAuth types - Add WATCH_FOLDER OAuth support: detect source_type in config for auth button - isOAuthType() and oauthLink() now accept full integration object Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,37 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="mb-8">
|
||||
{% if user_mode %}
|
||||
<!-- ── User-mode header ──────────────────────────────────────────── -->
|
||||
<div class="mb-4">
|
||||
<a href="/integrations" class="inline-flex items-center text-sm text-indigo-600 hover:text-indigo-800">
|
||||
<i class="fas fa-arrow-left mr-1" aria-hidden="true"></i> Back to Integrations
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mb-2">
|
||||
Connect Google Drive
|
||||
{% if integration_name %}<span class="text-gray-500 text-2xl font-normal">— {{ integration_name }}</span>{% endif %}
|
||||
</h1>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Authorize DocuElevate to access your Google Drive account. Your credentials are stored securely in your personal integration record.
|
||||
</p>
|
||||
{% if is_configured %}
|
||||
<div class="bg-green-50 border-l-4 border-green-500 text-green-700 p-4 my-4" role="alert">
|
||||
<p class="font-semibold">✓ Already authorized</p>
|
||||
<p class="text-sm mt-1">This integration already has credentials. You can re-authorize below to refresh or update them.
|
||||
{% if folder_id %}<br><strong>Folder ID:</strong> {{ folder_id }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="bg-blue-50 border-l-4 border-blue-500 text-blue-700 p-4 my-4" role="alert">
|
||||
<p class="font-semibold">Authorization required</p>
|
||||
<p class="text-sm mt-1">Complete the wizard below to grant access to your Google Drive account.
|
||||
{% if folder_id %}<br><strong>Target folder ID:</strong> {{ folder_id }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- ── Admin-mode header ─────────────────────────────────────────── -->
|
||||
<h1 class="text-3xl font-bold mb-2">Google Drive Integration Setup</h1>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Configure the Google Drive integration for DocuElevate using our setup wizard.
|
||||
@@ -42,8 +73,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not user_mode %}
|
||||
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<h2 class="text-2xl font-semibold">Authentication Method</h2>
|
||||
@@ -224,7 +257,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Token validation and status -->
|
||||
</div>
|
||||
{% endif %}{# end if not user_mode for admin auth-method block #}
|
||||
|
||||
<!-- ── User-mode OAuth wizard ────────────────────────────────────────── -->
|
||||
{% if user_mode %}
|
||||
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||
<h2 class="text-2xl font-semibold mb-4">OAuth Wizard</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="client-id" class="block text-sm font-medium text-gray-700">Client ID <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input type="text" id="client-id" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="Enter your OAuth Client ID" value="{{ client_id_value }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="client-secret" class="block text-sm font-medium text-gray-700">Client Secret <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input type="password" id="client-secret" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="Enter your OAuth Client Secret" value="{{ client_secret_value }}">
|
||||
</div>
|
||||
{% if folder_id %}
|
||||
<div class="bg-gray-50 border border-gray-200 rounded-md px-3 py-2">
|
||||
<p class="text-xs text-gray-500">Target folder ID (from integration settings)</p>
|
||||
<p class="text-sm font-mono text-gray-700">{{ folder_id }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<button id="start-oauth-flow" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Start Authentication Flow
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Token validation and status (admin mode only) -->
|
||||
{% if not user_mode %}
|
||||
<div id="token-status" class="mt-6 bg-white shadow-md rounded-lg p-6 {{ 'hidden' if not is_configured else '' }}">
|
||||
<h2 class="text-xl font-semibold mb-4">Connection Status</h2>
|
||||
|
||||
@@ -262,7 +327,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Configuration for Worker Nodes section -->
|
||||
<!-- Configuration for Worker Nodes section (admin only) -->
|
||||
<div id="oauth-env-section" class="mt-6 p-4 bg-gray-100 rounded-md {{ 'hidden' if not use_oauth else '' }}">
|
||||
<h3 class="font-medium text-lg mb-2">OAuth Configuration for Worker Nodes</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
@@ -307,6 +372,7 @@ GOOGLE_DRIVE_FOLDER_ID={{ folder_id|default('YOUR_FOLDER_ID', true) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="bg-white shadow-md rounded-lg p-6 mt-8 mb-8">
|
||||
<div class="flex items-start">
|
||||
@@ -326,9 +392,15 @@ GOOGLE_DRIVE_FOLDER_ID={{ folder_id|default('YOUR_FOLDER_ID', true) }}
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
{% if user_mode %}
|
||||
<a href="/integrations" class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md shadow-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
<i class="fas fa-arrow-left mr-2" aria-hidden="true"></i> Back to Integrations
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="/status" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Back to Status
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Result Modal -->
|
||||
@@ -358,18 +430,14 @@ GOOGLE_DRIVE_FOLDER_ID={{ folder_id|default('YOUR_FOLDER_ID', true) }}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const userMode = {{ 'true' if user_mode else 'false' }};
|
||||
|
||||
// Store integration_id if provided (for per-user OAuth flow)
|
||||
const integrationId = "{{ integration_id or '' }}";
|
||||
if (integrationId) {
|
||||
sessionStorage.setItem('oauth_integration_id', integrationId);
|
||||
}
|
||||
|
||||
// Tab elements
|
||||
const oauthTabBtn = document.getElementById('oauth-tab-btn');
|
||||
const saTabBtn = document.getElementById('sa-tab-btn');
|
||||
const oauthTab = document.getElementById('oauth-tab');
|
||||
const saTab = document.getElementById('sa-tab');
|
||||
|
||||
// Form elements
|
||||
const clientIdInput = document.getElementById('client-id');
|
||||
const clientSecretInput = document.getElementById('client-secret');
|
||||
@@ -379,12 +447,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const saveSaSettingsBtn = document.getElementById('save-sa-settings');
|
||||
const selectFolderBtn = document.getElementById('select-folder-btn');
|
||||
|
||||
// Status and test elements
|
||||
// Admin-mode elements (may be null in user mode)
|
||||
const oauthTabBtn = document.getElementById('oauth-tab-btn');
|
||||
const saTabBtn = document.getElementById('sa-tab-btn');
|
||||
const oauthTab = document.getElementById('oauth-tab');
|
||||
const saTab = document.getElementById('sa-tab');
|
||||
const tokenStatus = document.getElementById('token-status');
|
||||
const testConnectionBtn = document.getElementById('test-connection');
|
||||
const refreshTokenBtn = document.getElementById('refresh-token-btn');
|
||||
|
||||
// Environment sections
|
||||
const oauthEnvSection = document.getElementById('oauth-env-section');
|
||||
const saEnvSection = document.getElementById('sa-env-section');
|
||||
|
||||
@@ -485,7 +555,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const folderName = folder[google.picker.Document.NAME];
|
||||
|
||||
// Update the folder ID input
|
||||
folderIdInput.value = folderId;
|
||||
if (folderIdInput) folderIdInput.value = folderId;
|
||||
if (saFolderIdInput) {
|
||||
saFolderIdInput.value = folderId;
|
||||
}
|
||||
@@ -501,7 +571,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// New function to save folder ID after picker selection
|
||||
function saveFolderId(folderId) {
|
||||
// Check which tab is active to determine if we're using OAuth or Service Account
|
||||
const isOauthActive = !oauthTab.classList.contains('hidden');
|
||||
const isOauthActive = !oauthTab || !oauthTab.classList.contains('hidden');
|
||||
|
||||
// Prepare form data
|
||||
const formData = new FormData();
|
||||
@@ -509,9 +579,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
formData.append('use_oauth', isOauthActive ? 'true' : 'false');
|
||||
|
||||
// If using OAuth, also include client credentials if available
|
||||
if (isOauthActive) {
|
||||
if (isOauthActive && clientIdInput) {
|
||||
const clientId = clientIdInput.value.trim();
|
||||
const clientSecret = clientSecretInput.value.trim();
|
||||
const clientSecret = clientSecretInput ? clientSecretInput.value.trim() : '';
|
||||
|
||||
if (clientId) formData.append('client_id', clientId);
|
||||
if (clientSecret) formData.append('client_secret', clientSecret);
|
||||
@@ -535,8 +605,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
console.error('Error saving folder ID:', data.message);
|
||||
} else {
|
||||
console.log('Folder ID saved successfully');
|
||||
// Make the token status visible if it was hidden
|
||||
tokenStatus.classList.remove('hidden');
|
||||
// Make the token status visible if it was hidden (admin mode)
|
||||
if (tokenStatus) tokenStatus.classList.remove('hidden');
|
||||
|
||||
// Update environment variables display if they exist
|
||||
updateEnvVarsDisplay();
|
||||
@@ -549,13 +619,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Function to update environment variables display
|
||||
function updateEnvVarsDisplay() {
|
||||
const folderId = folderIdInput.value || saFolderIdInput.value || 'YOUR_FOLDER_ID';
|
||||
const folderId = (folderIdInput && folderIdInput.value) || (saFolderIdInput && saFolderIdInput.value) || 'YOUR_FOLDER_ID';
|
||||
|
||||
// Update OAuth env vars if the element exists
|
||||
const oauthEnvVarsCode = document.querySelector('#oauth-env-vars code');
|
||||
if (oauthEnvVarsCode) {
|
||||
const clientId = clientIdInput.value || 'YOUR_CLIENT_ID';
|
||||
const clientSecret = clientSecretInput.value ? 'YOUR_CLIENT_SECRET' : 'YOUR_CLIENT_SECRET';
|
||||
const clientId = (clientIdInput && clientIdInput.value) || 'YOUR_CLIENT_ID';
|
||||
const clientSecret = 'YOUR_CLIENT_SECRET';
|
||||
|
||||
oauthEnvVarsCode.textContent = `GOOGLE_DRIVE_USE_OAUTH=true
|
||||
GOOGLE_DRIVE_CLIENT_ID=${clientId}
|
||||
@@ -615,33 +685,33 @@ GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
||||
selectFolderBtn.addEventListener('click', createPicker);
|
||||
}
|
||||
|
||||
// Tab switching
|
||||
oauthTabBtn.addEventListener('click', function() {
|
||||
oauthTabBtn.className = 'px-4 py-2 rounded-md text-white bg-blue-600';
|
||||
saTabBtn.className = 'px-4 py-2 rounded-md text-gray-700 bg-gray-200';
|
||||
oauthTab.classList.remove('hidden');
|
||||
saTab.classList.add('hidden');
|
||||
oauthEnvSection.classList.remove('hidden');
|
||||
saEnvSection.classList.add('hidden');
|
||||
});
|
||||
// Tab switching (admin mode only)
|
||||
if (oauthTabBtn && saTabBtn && oauthTab && saTab) {
|
||||
oauthTabBtn.addEventListener('click', function() {
|
||||
oauthTabBtn.className = 'px-4 py-2 rounded-md text-white bg-blue-600';
|
||||
saTabBtn.className = 'px-4 py-2 rounded-md text-gray-700 bg-gray-200';
|
||||
oauthTab.classList.remove('hidden');
|
||||
saTab.classList.add('hidden');
|
||||
if (oauthEnvSection) oauthEnvSection.classList.remove('hidden');
|
||||
if (saEnvSection) saEnvSection.classList.add('hidden');
|
||||
});
|
||||
|
||||
saTabBtn.addEventListener('click', function() {
|
||||
saTabBtn.className = 'px-4 py-2 rounded-md text-white bg-blue-600';
|
||||
oauthTabBtn.className = 'px-4 py-2 rounded-md text-gray-700 bg-gray-200';
|
||||
saTab.classList.remove('hidden');
|
||||
oauthTab.classList.add('hidden');
|
||||
saEnvSection.classList.remove('hidden');
|
||||
oauthEnvSection.classList.add('hidden');
|
||||
});
|
||||
saTabBtn.addEventListener('click', function() {
|
||||
saTabBtn.className = 'px-4 py-2 rounded-md text-white bg-blue-600';
|
||||
oauthTabBtn.className = 'px-4 py-2 rounded-md text-gray-700 bg-gray-200';
|
||||
saTab.classList.remove('hidden');
|
||||
oauthTab.classList.add('hidden');
|
||||
if (saEnvSection) saEnvSection.classList.remove('hidden');
|
||||
if (oauthEnvSection) oauthEnvSection.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Sync folder IDs between tabs
|
||||
folderIdInput.addEventListener('input', function() {
|
||||
if (saFolderIdInput) {
|
||||
// Sync folder IDs between tabs (admin mode only)
|
||||
if (folderIdInput && saFolderIdInput) {
|
||||
folderIdInput.addEventListener('input', function() {
|
||||
saFolderIdInput.value = folderIdInput.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (saFolderIdInput) {
|
||||
saFolderIdInput.addEventListener('input', function() {
|
||||
folderIdInput.value = saFolderIdInput.value;
|
||||
});
|
||||
@@ -652,7 +722,7 @@ GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
||||
startOauthFlowBtn.addEventListener('click', function() {
|
||||
const clientId = clientIdInput.value.trim();
|
||||
const clientSecret = clientSecretInput.value.trim();
|
||||
const folderId = folderIdInput.value.trim();
|
||||
const folderId = folderIdInput ? folderIdInput.value.trim() : '';
|
||||
|
||||
if (!clientId) {
|
||||
showModal('error', 'Validation Error', 'Please enter your Client ID');
|
||||
@@ -664,12 +734,12 @@ GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't require folder ID, make it optional
|
||||
|
||||
// Save values to session storage for use after redirect
|
||||
sessionStorage.setItem('google_drive_client_id', clientId);
|
||||
sessionStorage.setItem('google_drive_client_secret', clientSecret);
|
||||
if (folderId) {
|
||||
|
||||
// In admin mode store folder_id; in user mode the config is already set
|
||||
if (!userMode && folderId) {
|
||||
sessionStorage.setItem('google_drive_folder_id', folderId);
|
||||
}
|
||||
sessionStorage.setItem('google_drive_use_oauth', 'true');
|
||||
@@ -682,7 +752,7 @@ GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
||||
});
|
||||
}
|
||||
|
||||
// Save service account settings button
|
||||
// Save service account settings button (admin mode only)
|
||||
if (saveSaSettingsBtn) {
|
||||
saveSaSettingsBtn.addEventListener('click', function() {
|
||||
const folderId = saFolderIdInput.value.trim();
|
||||
@@ -717,7 +787,7 @@ GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showModal('success', 'Settings Saved', 'Google Drive service account settings have been saved');
|
||||
tokenStatus.classList.remove('hidden');
|
||||
if (tokenStatus) tokenStatus.classList.remove('hidden');
|
||||
|
||||
// Update environment variables display
|
||||
const saEnvVarsCode = document.querySelector('#sa-env-vars code');
|
||||
@@ -740,7 +810,7 @@ GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
||||
});
|
||||
}
|
||||
|
||||
// Test connection button
|
||||
// Test connection button (admin mode only)
|
||||
if (testConnectionBtn) {
|
||||
testConnectionBtn.addEventListener('click', function() {
|
||||
const originalText = testConnectionBtn.textContent;
|
||||
@@ -770,7 +840,7 @@ GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh token button
|
||||
// Refresh token button (admin mode only)
|
||||
if (refreshTokenBtn) {
|
||||
refreshTokenBtn.addEventListener('click', function() {
|
||||
showModal('info', 'Confirm', 'This will start a new OAuth flow to obtain a fresh token. Continue?');
|
||||
@@ -808,7 +878,7 @@ GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
||||
});
|
||||
}
|
||||
|
||||
// Copy environment variables buttons
|
||||
// Copy environment variables buttons (admin mode only)
|
||||
const copyOAuthEnvVarsBtn = document.getElementById('copy-oauth-env-vars');
|
||||
const copySAEnvVarsBtn = document.getElementById('copy-sa-env-vars');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user