Skip to content

Commit f821158

Browse files
committed
Route all content provider requests through api
1 parent a22b08c commit f821158

File tree

2 files changed

+67
-61
lines changed

2 files changed

+67
-61
lines changed

src/serving/components/ContentProviderAuthManager.tsx

Lines changed: 65 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useCallback, useEffect, useMemo } from "react";
22
import { Box, Button, Card, CardContent, CircularProgress, IconButton, Stack, Typography } from "@mui/material";
33
import { Link as LinkIcon, LinkOff as LinkOffIcon, Refresh as RefreshIcon, Add as AddIcon } from "@mui/icons-material";
4-
import { Locale } from "@churchapps/apphelper";
4+
import { ApiHelper, Locale } from "@churchapps/apphelper";
55
import { getProvider, getAvailableProviders, type IProvider, type DeviceAuthorizationResponse } from "@churchapps/content-providers";
66
import { type ContentProviderAuthInterface } from "../../helpers";
77
import { ContentProviderAuthHelper } from "../../helpers/ContentProviderAuthHelper";
@@ -31,7 +31,7 @@ export const ContentProviderAuthManager: React.FC<Props> = ({ ministryId, onAuth
3131
const [pkceWindow, setPkceWindow] = useState<Window | null>(null);
3232

3333
const availableProviders = useMemo(() => {
34-
const providers = getAvailableProviders(["lessonschurch", "signpresenter", "bibleproject"]);
34+
const providers = getAvailableProviders(["lessonschurch", "signpresenter", "bibleproject", "dropbox", "jesusfilm"]);
3535
console.log("[ContentProviderAuthManager] availableProviders:", providers);
3636
return providers;
3737
}, []);
@@ -201,15 +201,24 @@ export const ContentProviderAuthManager: React.FC<Props> = ({ ministryId, onAuth
201201
setAuthStatus("error");
202202
return;
203203
}
204-
// Generate code verifier
204+
205+
// Step 1: Create a relay session on the API
206+
const relayData = await ApiHelper.post("/oauth/relay/sessions", { provider: providerId }, "MembershipApi");
207+
if (!relayData?.sessionCode || !relayData?.redirectUri) {
208+
setAuthError("Failed to create authorization session. Please try again.");
209+
setAuthStatus("error");
210+
return;
211+
}
212+
213+
const { sessionCode, redirectUri, expiresIn } = relayData;
214+
215+
// Step 2: Generate code verifier and build auth URL using relay redirect
205216
const verifier = (provider as any).generateCodeVerifier();
206217
setCodeVerifier(verifier);
207218

208-
// Build auth URL
209-
const redirectUri = `${window.location.origin}/oauth/callback`;
210-
const authResult = await (provider as any).buildAuthUrl(verifier, redirectUri);
219+
const authResult = await (provider as any).buildAuthUrl(verifier, redirectUri, sessionCode);
211220

212-
// Open popup window
221+
// Step 3: Open popup window
213222
const width = 600;
214223
const height = 700;
215224
const left = window.screenX + (window.outerWidth - width) / 2;
@@ -230,72 +239,69 @@ export const ContentProviderAuthManager: React.FC<Props> = ({ ministryId, onAuth
230239
setPkceWindow(popup);
231240
setAuthStatus("pkce_waiting");
232241

233-
// Listen for the callback
234-
const handleMessage = async (event: MessageEvent) => {
235-
if (event.origin !== window.location.origin) return;
242+
// Step 4: Poll the relay for the auth code
243+
const expiresAt = Date.now() + (expiresIn || 300) * 1000;
236244

237-
if (event.data?.type === "oauth_callback" && event.data?.providerId === providerId) {
238-
window.removeEventListener("message", handleMessage);
245+
const poll = async () => {
246+
if (popup.closed) {
247+
setAuthStatus("idle");
248+
setAuthProviderId(null);
249+
return;
250+
}
239251

240-
if (event.data.error) {
241-
setAuthError(event.data.error_description || event.data.error);
242-
setAuthStatus("error");
243-
return;
244-
}
252+
if (Date.now() >= expiresAt) {
253+
popup.close();
254+
setAuthError("Authorization session expired. Please try again.");
255+
setAuthStatus("error");
256+
return;
257+
}
245258

246-
if (event.data.code) {
247-
try {
248-
// Exchange code for tokens
249-
const tokens = await (provider as any).exchangeCodeForTokens(
250-
event.data.code,
251-
verifier,
252-
redirectUri
253-
);
254-
255-
if (tokens) {
256-
await ContentProviderAuthHelper.storeAuth(ministryId, providerId, tokens);
257-
setAuthStatus("success");
258-
await loadLinkedProviders();
259-
if (onAuthChange) onAuthChange();
260-
261-
// Auto-close after success
262-
setTimeout(() => {
263-
setAuthProviderId(null);
264-
setAuthStatus("idle");
265-
setCodeVerifier(null);
266-
}, 2000);
267-
} else {
268-
setAuthError("Failed to exchange code for tokens");
269-
setAuthStatus("error");
270-
}
271-
} catch (error) {
272-
console.error("Error exchanging code:", error);
273-
setAuthError("Failed to complete authentication");
259+
try {
260+
const result = await ApiHelper.getAnonymous(`/oauth/relay/sessions/${sessionCode}`, "MembershipApi");
261+
262+
if (result?.status === "completed" && result?.authCode) {
263+
popup.close();
264+
265+
// Exchange code for tokens
266+
const tokens = await (provider as any).exchangeCodeForTokens(
267+
result.authCode,
268+
verifier,
269+
redirectUri
270+
);
271+
272+
if (tokens) {
273+
await ContentProviderAuthHelper.storeAuth(ministryId, providerId, tokens);
274+
setAuthStatus("success");
275+
await loadLinkedProviders();
276+
if (onAuthChange) onAuthChange();
277+
278+
setTimeout(() => {
279+
setAuthProviderId(null);
280+
setAuthStatus("idle");
281+
setCodeVerifier(null);
282+
}, 2000);
283+
} else {
284+
setAuthError("Failed to exchange code for tokens");
274285
setAuthStatus("error");
275286
}
287+
return;
276288
}
289+
290+
// Still pending — poll again
291+
setTimeout(poll, 3000);
292+
} catch (error) {
293+
console.error("Polling error:", error);
294+
setTimeout(poll, 5000);
277295
}
278296
};
279297

280-
window.addEventListener("message", handleMessage);
281-
282-
// Check if popup is closed
283-
const checkClosed = setInterval(() => {
284-
if (popup.closed) {
285-
clearInterval(checkClosed);
286-
window.removeEventListener("message", handleMessage);
287-
if (authStatus === "pkce_waiting") {
288-
setAuthStatus("idle");
289-
setAuthProviderId(null);
290-
}
291-
}
292-
}, 500);
298+
setTimeout(poll, 3000);
293299
} catch (error) {
294300
console.error("Error starting PKCE flow:", error);
295301
setAuthError("Failed to start authentication");
296302
setAuthStatus("error");
297303
}
298-
}, [ministryId, loadLinkedProviders, onAuthChange, authStatus]);
304+
}, [ministryId, loadLinkedProviders, onAuthChange]);
299305

300306
// Handle link button click
301307
const handleLink = useCallback(async (providerId: string) => {

src/serving/hooks/useProviderBrowser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface UseProviderBrowserOptions {
1515
}
1616

1717
export function useProviderBrowser(options: UseProviderBrowserOptions) {
18-
const { ministryId, defaultProviderId, providerFilter = ["lessonschurch", "signpresenter", "bibleproject"], includeFiles = false, autoLoad = true } = options;
18+
const { ministryId, defaultProviderId, providerFilter = ["lessonschurch", "signpresenter", "bibleproject", "dropbox", "jesusfilm"], includeFiles = false, autoLoad = true } = options;
1919

2020
// Provider state
2121
const [selectedProviderId, setSelectedProviderId] = useState<string>(defaultProviderId || "");
@@ -63,7 +63,7 @@ export function useProviderBrowser(options: UseProviderBrowserOptions) {
6363
const browseRaw = useCallback(async (path: string, provId: string): Promise<ContentItem[]> => {
6464
const provider = getProvider(provId);
6565
if (!provider) return [];
66-
if (ministryId && provider.requiresAuth) {
66+
if (ministryId) {
6767
return await ApiHelper.post("/providerProxy/browse", { ministryId, providerId: provId, path: path || null }, "DoingApi");
6868
}
6969
return await provider.browse(path || null, null);

0 commit comments

Comments
 (0)