Add OAuth login support and streamline authentication flows
- Introduced `oauthLogin` method in `electron/preload.ts` and backend IPC handlers for OAuth via `BrowserWindow`. - Replaced web-based OAuth redirection with Electron-specific implementation for Google, Facebook, and Apple. - Refactored `SocialForm.tsx` to handle OAuth login success and token management via Electron. - Updated `User`, `QuillSense`, and context methods to include `quill-trial` subscriptions and extended login logic. - Cleaned up code, removed unused imports, and improved error handling for authentication scenarios.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faApple, faFacebookF, faGoogle} from "@fortawesome/free-brands-svg-icons";
|
||||
import React, {useContext, useEffect} from "react";
|
||||
import Link from "next/link";
|
||||
import System from "@/lib/models/System";
|
||||
import {AlertContext} from "@/context/AlertContext";
|
||||
import {configs} from "@/lib/configs";
|
||||
@@ -14,8 +13,12 @@ export default function SocialForm() {
|
||||
const {setSession} = useContext<SessionContextProps>(SessionContext)
|
||||
const t = useTranslations();
|
||||
const {lang} = useContext<LangContextProps>(LangContext)
|
||||
|
||||
const isElectron = typeof window !== 'undefined' && !!window.electron;
|
||||
|
||||
useEffect((): void => {
|
||||
// Skip URL parsing in Electron (OAuth is handled via BrowserWindow)
|
||||
if (isElectron) return;
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const provider: string | null = params.get('provider');
|
||||
if (!provider) {
|
||||
@@ -47,6 +50,16 @@ export default function SocialForm() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function handleLoginSuccess(token: string): Promise<void> {
|
||||
if (window.electron) {
|
||||
await window.electron.setToken(token);
|
||||
window.electron.loginSuccess(token);
|
||||
} else {
|
||||
System.setCookie('token', token, 30);
|
||||
setSession({isConnected: true, user: null, accessToken: token});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFacebookLogin(code: string, state: string): Promise<void> {
|
||||
if (code && state) {
|
||||
const response: string = await System.postToServer<string>(`auth/facebook`, {
|
||||
@@ -57,16 +70,10 @@ export default function SocialForm() {
|
||||
errorMessage(t('socialForm.error.connection'));
|
||||
return;
|
||||
}
|
||||
System.setCookie('token', response, 30);
|
||||
const token: string | null = System.getCookie('token');
|
||||
if (!token) {
|
||||
errorMessage(t('socialForm.error.connection'));
|
||||
return;
|
||||
}
|
||||
setSession({isConnected: true, user: null, accessToken: token});
|
||||
await handleLoginSuccess(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleGoogleLogin(code: string): Promise<void> {
|
||||
if (code) {
|
||||
const response: string = await System.postToServer<string>(`auth/google`, {
|
||||
@@ -74,17 +81,12 @@ export default function SocialForm() {
|
||||
}, lang);
|
||||
if (!response) {
|
||||
errorMessage(t('socialForm.error.connection'));
|
||||
}
|
||||
System.setCookie('token', response, 30);
|
||||
const token: string | null = System.getCookie('token');
|
||||
if (!token) {
|
||||
errorMessage(t('socialForm.error.connection'));
|
||||
return;
|
||||
}
|
||||
setSession({isConnected: true, user: null, accessToken: token});
|
||||
await handleLoginSuccess(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleAppleLogin(code: string, state: string): Promise<void> {
|
||||
if (code && state) {
|
||||
const response: string = await System.postToServer<string>(`auth/apple`, {
|
||||
@@ -95,41 +97,62 @@ export default function SocialForm() {
|
||||
errorMessage(t('socialForm.error.connection'));
|
||||
return;
|
||||
}
|
||||
System.setCookie('token', response, 30);
|
||||
const token: string | null = System.getCookie('token');
|
||||
if (!token) {
|
||||
errorMessage(t('socialForm.error.connection'));
|
||||
return;
|
||||
}
|
||||
setSession({isConnected: true, user: null, accessToken: token});
|
||||
await handleLoginSuccess(response);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOAuthClick(provider: 'google' | 'facebook' | 'apple'): Promise<void> {
|
||||
if (!window.electron) return;
|
||||
|
||||
try {
|
||||
const result = await window.electron.oauthLogin(provider, configs.baseUrl);
|
||||
|
||||
if (!result.success) {
|
||||
if (result.error !== 'Window closed by user') {
|
||||
errorMessage(t('socialForm.error.connection'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.code) {
|
||||
if (provider === 'google') {
|
||||
await handleGoogleLogin(result.code);
|
||||
} else if (provider === 'facebook' && result.state) {
|
||||
await handleFacebookLogin(result.code, result.state);
|
||||
} else if (provider === 'apple' && result.state) {
|
||||
await handleAppleLogin(result.code, result.state);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage(t('socialForm.error.connection'));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-center gap-3">
|
||||
<Link
|
||||
href={`https://www.facebook.com/v18.0/dialog/oauth?client_id=1015270470233591&redirect_uri=${configs.baseUrl}login?provider=facebook&scope=email&response_type=code&state=abc123`}
|
||||
className="flex items-center justify-center w-14 h-14 bg-[#1877F2] hover:bg-opacity-90 text-textPrimary rounded-xl transition-colors"
|
||||
<button
|
||||
onClick={() => handleOAuthClick('facebook')}
|
||||
className="flex items-center justify-center w-14 h-14 bg-[#1877F2] hover:bg-opacity-90 text-textPrimary rounded-xl transition-colors cursor-pointer"
|
||||
aria-label="Login with Facebook"
|
||||
>
|
||||
<FontAwesomeIcon icon={faFacebookF} className="w-6 h-6 text-textPrimary"/>
|
||||
</Link>
|
||||
</button>
|
||||
|
||||
<Link
|
||||
href={`https://accounts.google.com/o/oauth2/v2/auth?client_id=911482317931-pvjog1br22r6l8k1afq0ki94em2fsoen.apps.googleusercontent.com&redirect_uri=${configs.baseUrl}login?provider=google&response_type=code&scope=openid email profile&access_type=offline`}
|
||||
className="flex items-center justify-center w-14 h-14 bg-white hover:bg-opacity-90 text-[#4285F4] rounded-xl transition-colors"
|
||||
<button
|
||||
onClick={() => handleOAuthClick('google')}
|
||||
className="flex items-center justify-center w-14 h-14 bg-white hover:bg-opacity-90 text-[#4285F4] rounded-xl transition-colors cursor-pointer"
|
||||
aria-label="Login with Google"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGoogle} className="w-6 h-6 text-[#4285F4]"/>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href={`https://appleid.apple.com/auth/authorize?client_id=eritors.apple.login&redirect_uri=https://eritors.com/login?provider=apple&response_type=code&scope=email name&response_mode=form_post&state=abc123`}
|
||||
className="flex items-center justify-center w-14 h-14 bg-black hover:bg-opacity-90 text-white rounded-xl transition-colors"
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleOAuthClick('apple')}
|
||||
className="flex items-center justify-center w-14 h-14 bg-black hover:bg-opacity-90 text-white rounded-xl transition-colors cursor-pointer"
|
||||
aria-label="Login with Apple"
|
||||
>
|
||||
<FontAwesomeIcon icon={faApple} className="w-6 h-6 text-white"/>
|
||||
</Link>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
11
app/page.tsx
11
app/page.tsx
@@ -456,6 +456,7 @@ function ScribeContent() {
|
||||
user: user,
|
||||
accessToken: token,
|
||||
});
|
||||
console.log(user)
|
||||
setCurrentCredits(user.creditsBalance)
|
||||
setAmountSpent(user.aiUsage)
|
||||
} catch (e: unknown) {
|
||||
@@ -601,14 +602,8 @@ function ScribeContent() {
|
||||
<AutoSyncOnReconnect/>
|
||||
<BookContext.Provider value={{book: currentBook, setBook: setCurrentBook}}>
|
||||
<ChapterContext.Provider value={{chapter: currentChapter, setChapter: setCurrentChapter}}>
|
||||
<AIUsageContext.Provider value={{
|
||||
totalCredits: currentCredits,
|
||||
setTotalCredits: setCurrentCredits,
|
||||
totalPrice: amountSpent,
|
||||
setTotalPrice: setAmountSpent
|
||||
}}>
|
||||
<div
|
||||
className="bg-background text-text-primary h-screen flex flex-col font-['Lora']">
|
||||
<AIUsageContext.Provider value={{totalCredits: currentCredits, setTotalCredits: setCurrentCredits, totalPrice: amountSpent, setTotalPrice: setAmountSpent}}>
|
||||
<div className="bg-background text-text-primary h-screen flex flex-col font-['Lora']">
|
||||
<ScribeTopBar/>
|
||||
<EditorContext.Provider value={{editor: editor}}>
|
||||
<ScribeControllerBar/>
|
||||
|
||||
Reference in New Issue
Block a user