Switch from Service Account to OAuth2 for Google Calendar (Personal Gmail)

- Replace JWT service account authentication with OAuth2 refresh token flow
- Service accounts cannot create Google Meet links for personal Gmail accounts
- Update edge function to use OAuth2 token exchange
- Change database column from google_service_account_json to google_oauth_config
- Add helper tool (get-google-refresh-token.html) to generate OAuth credentials
- Update IntegrasiTab UI to show OAuth config instead of service account
- Add SQL migration file for new google_oauth_config column

OAuth2 Config format:
{
  "client_id": "...",
  "client_secret": "...",
  "refresh_token": "..."
}

This approach works with personal @gmail.com accounts without requiring
Google Workspace or Domain-Wide Delegation.
This commit is contained in:
dwindown
2025-12-23 14:06:42 +07:00
parent 286ab630ea
commit 7d22a5328f
4 changed files with 239 additions and 108 deletions

View File

@@ -17,7 +17,7 @@ interface IntegrationSettings {
integration_whatsapp_number: string;
integration_whatsapp_url: string;
integration_google_calendar_id: string;
google_service_account_json?: string;
google_oauth_config?: string;
integration_email_provider: string;
integration_email_api_base_url: string;
integration_privacy_url: string;
@@ -76,7 +76,7 @@ export function IntegrasiTab() {
integration_whatsapp_number: platformData.integration_whatsapp_number || '',
integration_whatsapp_url: platformData.integration_whatsapp_url || '',
integration_google_calendar_id: platformData.integration_google_calendar_id || '',
google_service_account_json: platformData.google_service_account_json || '',
google_oauth_config: platformData.google_oauth_config || '',
integration_email_provider: platformData.integration_email_provider || 'mailketing',
integration_email_api_base_url: platformData.integration_email_api_base_url || '',
integration_privacy_url: platformData.integration_privacy_url || '/privacy',
@@ -102,7 +102,7 @@ export function IntegrasiTab() {
integration_whatsapp_number: settings.integration_whatsapp_number,
integration_whatsapp_url: settings.integration_whatsapp_url,
integration_google_calendar_id: settings.integration_google_calendar_id,
google_service_account_json: settings.google_service_account_json,
google_oauth_config: settings.google_oauth_config,
integration_email_provider: settings.integration_email_provider,
integration_email_api_base_url: settings.integration_email_api_base_url,
integration_privacy_url: settings.integration_privacy_url,
@@ -117,11 +117,11 @@ export function IntegrasiTab() {
.eq('id', settings.id);
if (platformError) {
// If schema cache error, try saving service account JSON separately via raw SQL
if (platformError.code === 'PGRST204' && settings.google_service_account_json) {
// If schema cache error, try saving OAuth config separately via raw SQL
if (platformError.code === 'PGRST204' && settings.google_oauth_config) {
console.log('Schema cache error, using fallback RPC method');
const { error: rpcError } = await supabase.rpc('exec_sql', {
sql: `UPDATE platform_settings SET google_service_account_json = '${settings.google_service_account_json.replace(/'/g, "''")}'::jsonb WHERE id = '${settings.id}'`
sql: `UPDATE platform_settings SET google_oauth_config = '${settings.google_oauth_config.replace(/'/g, "''")}'::jsonb WHERE id = '${settings.id}'`
});
if (rpcError) {
@@ -349,22 +349,24 @@ export function IntegrasiTab() {
<div className="space-y-2">
<Label className="flex items-center gap-2">
<Key className="w-4 h-4" />
Google Service Account JSON
Google OAuth Config
</Label>
<Textarea
value={settings.google_service_account_json || ''}
onChange={(e) => setSettings({ ...settings, google_service_account_json: e.target.value })}
placeholder='{"type": "service_account", "project_id": "...", "private_key": "...", "client_email": "..."}'
value={settings.google_oauth_config || ''}
onChange={(e) => setSettings({ ...settings, google_oauth_config: e.target.value })}
placeholder='{"client_id": "...", "client_secret": "...", "refresh_token": "..."}'
className="min-h-[120px] font-mono text-sm border-2"
/>
<p className="text-sm text-muted-foreground">
Paste entire service account JSON from Google Cloud Console. Calendar must be shared with the service account email.
</p>
{settings.google_service_account_json && (
<div className="space-y-1">
<p className="text-sm text-muted-foreground">
OAuth2 credentials untuk personal Gmail account. Gunakan <a href="/get-google-refresh-token.html" target="_blank" className="text-blue-600 underline">tool ini</a> untuk generate refresh token.
</p>
</div>
{settings.google_oauth_config && (
<Alert>
<AlertTriangle className="w-4 h-4" />
<AlertDescription>
Service account configured. Calendar ID: {settings.integration_google_calendar_id || 'Not set'}
OAuth configured. Calendar ID: {settings.integration_google_calendar_id || 'Not set'}
</AlertDescription>
</Alert>
)}
@@ -373,8 +375,8 @@ export function IntegrasiTab() {
<Button
variant="outline"
onClick={async () => {
if (!settings.integration_google_calendar_id || !settings.google_service_account_json) {
toast({ title: "Error", description: "Lengkapi Calendar ID dan Service Account JSON", variant: "destructive" });
if (!settings.integration_google_calendar_id || !settings.google_oauth_config) {
toast({ title: "Error", description: "Lengkapi Calendar ID dan OAuth Config", variant: "destructive" });
return;
}