Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
# Replace with your actual OAuth provider credentials

# Google OAuth2
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# GOOGLE_CLIENT_ID=your-google-client-id
# GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_CLIENT_ID=760575932383-1ah7nvt9vi02pr5r20h5nm5g8p1pl87j.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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

REDIRECT_URL=http://localhost/api/login/oauth2/code
GOOGLE_REDIRECT_URI=http://localhost:8080/login/oauth2/code/{registrationId}
# Facebook OAuth2
FACEBOOK_CLIENT_ID=your-facebook-app-id-here
FACEBOOK_CLIENT_SECRET=your-facebook-app-secret-here
Expand All @@ -24,21 +26,27 @@ APPLE_CLIENT_SECRET=your-apple-client-secret-jwt-here

CONTENT_MODERATION_WEBHOOK_URL=https://inscriptions.cdacb.in/n8n/webhook/content-moderation
CONTENT_MODERATION_INSECURE_SSL=true

CONTENT_MODERATION_SAFE_THRESHOLD=0.7
CONTENT_MODERATION_CONNECT_TIMEOUT_MS=5000
CONTENT_MODERATION_READ_TIMEOUT_MS=10000
ADMIN_APPROVAL_INTERNAL_EMAIL=your-internal-approver@example.com
APP_CORS_URL=http://localhost:3000
APP_BACKEND_URL=http://localhost:8080
APP_FRONTEND_OAUTH_CALLBACK_URL=http://localhost:8080/api/v1/noauth/check
APP_FRONTEND_ADMIN_APPROVAL_RESULT_URL=http://localhost:8080/api/v1/noauth/check


ADMIN_APPROVAL_INTERNAL_EMAIL=bavithbabu25@gmail.com
APP_CORS_URL=https://inscriptions.cdacb.in
APP_FRONTEND_OAUTH_CALLBACK_URL=https://inscriptions.cdacb.in/oauth/callback
APP_FRONTEND_OAUTH_ADMIN_CALLBACK_URL=https://inscriptions.cdacb.in/admin/oauth/callback

APP_BACKEND_URL=https://inscriptions.cdacb.in/api
APP_FRONTEND_ADMIN_APPROVAL_RESULT_URL=https://inscriptions.cdacb.in/admin/approval-result

# APP_BACKEND_URL=http://localhost:8080
# APP_FRONTEND_ADMIN_APPROVAL_RESULT_URL=http://localhost:8080/oauth2/admin/approve

# Spring Mail
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=your-email@example.com
spring.mail.password=your-app-password
spring.mail.username=bavithbabu25@gmail.com
spring.mail.password=ugfj wcfo cbah hfau
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

Expand Down
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- OpenAPi Swager -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.9</version>
</dependency>

<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
170 changes: 170 additions & 0 deletions rough.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from "react";
import { useLocation } from "react-router-dom";
import { setPostLoginRedirect } from "@/utils/postLoginRedirect";
// import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google";
// import { jwtDecode } from "jwt-decode";
// import { redirect } from "react-router-dom";

const redirectURL = window._env_?.VITE_REDIRECT_URL
|| import.meta.env.VITE_REDIRECT_URL
|| "/api/oauth2/login";
const adminLoginRedirectURL = window._env_?.VITE_ADMIN_LOGIN_REDIRECT_URL
|| import.meta.env.VITE_ADMIN_LOGIN_REDIRECT_URL
|| "/api/oauth2/admin/login";
const adminRegisterRedirectURL = window._env_?.VITE_ADMIN_REGISTER_REDIRECT_URL
|| import.meta.env.VITE_ADMIN_REGISTER_REDIRECT_URL
|| "/api/oauth2/admin/register";
const OAUTH_CALLBACK_GUARD_KEY = "auth:oauth-callback-processed";

const AuthPage: React.FC = () => {
const location = useLocation();

const getSafeRedirectPath = () => {
const next = new URLSearchParams(location.search).get("next") || "";
if (next.startsWith("/") && !next.startsWith("//")) {
return next;
}

const from = location.state && typeof (location.state as { from?: unknown }).from === "string"
? (location.state as { from: string }).from
: "";

if (from.startsWith("/") && !from.startsWith("//")) {
return from;
}

return null;
};

// const handleLoginSuccess = (credentialResponse: any) => {
// if (credentialResponse.credential) {
// const decoded: any = jwtDecode(credentialResponse.credential);
// console.log("User Info:", decoded);
// // You can send credentialResponse.credential to your backend for verification
// }
// };

// const handleLoginFailure = () => {
// console.error("Login Failed");
// };
const prepareOAuthRedirect = () => {
const redirectPath = getSafeRedirectPath();

if (redirectPath) {
setPostLoginRedirect(redirectPath);
}

// Reset callback guard before initiating a new OAuth round-trip.
sessionStorage.removeItem(OAUTH_CALLBACK_GUARD_KEY);
};

const handleGoogleLogin = () => {
prepareOAuthRedirect();
window.location.href = redirectURL;
}

const handleAdminLogin = () => {
prepareOAuthRedirect();
window.location.href = adminLoginRedirectURL;
};

const handleAdminRegister = () => {
prepareOAuthRedirect();
window.location.href = adminRegisterRedirectURL;
};

return (
<div className="flex items-center justify-center" style={{ minHeight: "62vh" }}>
<div className="bg-white shadow-lg rounded-2xl p-10 w-full max-w-md text-center">
<h2 className="text-2xl font-bold text-gray-800 mb-6">
Welcome Back 👋
</h2>
<p className="text-gray-600 mb-6">
Sign in or create an account with Google
</p>
{/* <GoogleOAuthProvider clientId="962264895991-93et5a8stepe4osg77oj9gh0am4cc897.apps.googleusercontent.com">
<GoogleLogin
onSuccess={handleLoginSuccess}
onError={handleLoginFailure}
shape="pill"
text="signin_with"
width="100%"
/>
</GoogleOAuthProvider> */}

<button
onClick={handleGoogleLogin}


className={`
w-full flex items-center justify-center gap-3 px-6 py-3
border border-gray-300 rounded-full text-gray-700 font-medium
hover:bg-gray-50 hover:shadow-md transition-all duration-200
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
cursor-pointer
`}
>
{/* Google Icon */}
<svg
className="w-5 h-5"
viewBox="0 0 24 24"
>
<path
fill="#4285F4"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="#34A853"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="#FBBC05"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="#EA4335"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>

{(
<div className="flex items-center gap-2">
{/* <div className="animate-spin rounded-full h-4 w-4 border-2 border-gray-300 border-t-gray-600"></div> */}
<span>Continue With Google</span>
</div>
)}

</button>

<div className="mt-4 space-y-3">
<button
onClick={handleAdminLogin}
className="w-full rounded-full border border-gray-300 px-6 py-3 font-medium text-gray-700 transition-all duration-200 hover:bg-gray-50 hover:shadow-md"
>
Continue As Admin
</button>

<button
onClick={handleAdminRegister}
className="w-full rounded-full border border-dashed border-gray-300 px-6 py-3 font-medium text-gray-700 transition-all duration-200 hover:bg-gray-50 hover:shadow-md"
>
Request Admin Access
</button>
</div>

<div className="mt-6 text-sm text-gray-500">
By continuing, you agree to our{" "}
<a href="#" className="text-blue-500 underline">
Terms of Service
</a>{" "}
and{" "}
<a href="#" className="text-blue-500 underline">
Privacy Policy
</a>
</div>
</div>
</div>
);
};

export default AuthPage;
121 changes: 121 additions & 0 deletions rough2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useContext, useEffect } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { authClient } from "@/utils/http/clients/authClient.client";
import AuthContext from "@/context/AuthContext";
import cdacRoundLogo from '@/assets/cdacroundlogo.png';
import { getPostLoginRedirect } from "@/utils/postLoginRedirect";

const MAX_REFRESH_RETRIES = 3;
const RETRY_DELAY_MS = 700;
const OAUTH_CALLBACK_GUARD_KEY = "auth:oauth-callback-processed";

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const OAuthCallback = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { loginSuccess } = useContext(AuthContext);

useEffect(() => {
// React StrictMode runs effects twice in development.
// Guard prevents a second callback pass from overriding the first successful redirect.
if (sessionStorage.getItem(OAUTH_CALLBACK_GUARD_KEY) === "1") {
return;
}
sessionStorage.setItem(OAUTH_CALLBACK_GUARD_KEY, "1");

const navigateToLoginWithNext = () => {
sessionStorage.removeItem(OAUTH_CALLBACK_GUARD_KEY);
const next = getPostLoginRedirect();
if (next) {
navigate(`/login?next=${encodeURIComponent(next)}`, { replace: true });
} else {
navigate("/login", { replace: true });
}
};

const completeLogin = async () => {
try {
const status = searchParams.get("status");
const flow = searchParams.get("flow");

if (status === "pending" && flow === "admin_register") {
navigate("/login?admin_request=pending", { replace: true });
return;
}

if (status === "denied" && flow === "admin_login") {
navigate("/login?admin_access=denied", { replace: true });
return;
}

if (status && status !== "success") {
throw new Error(`OAuth callback returned unsupported status: ${status}`);
}

let accessToken: string | null = null;
let lastError: unknown = null;

for (let attempt = 1; attempt <= MAX_REFRESH_RETRIES; attempt++) {
try {
console.log(`OAuthCallback: refresh attempt ${attempt}/${MAX_REFRESH_RETRIES}`);
const res = await authClient.post("/oauth2/authenticated/refresh-token");
console.log("OAuthCallback: refresh response:", res && res.data);

accessToken = res?.data?.data?.accessToken || res?.data?.auth_token || res?.data?.token || null;
console.log("OAuthCallback: computed accessToken:", accessToken);

if (accessToken) break;
lastError = new Error("No access token found in refresh response");
} catch (error) {
lastError = error;
console.warn(`OAuthCallback: refresh attempt ${attempt} failed`, {
message: (error as any)?.message,
status: (error as any)?.response?.status,
});
}

if (attempt < MAX_REFRESH_RETRIES) {
await wait(RETRY_DELAY_MS * attempt);
}
}

if (accessToken) {
loginSuccess(accessToken);

const savedRedirect = getPostLoginRedirect();

if (savedRedirect && savedRedirect.startsWith("/") && !savedRedirect.startsWith("//")) {
navigate(savedRedirect, { replace: true });
} else {
navigate("/home", { replace: true });
}
} else {
throw lastError || new Error("OAuth callback failed without an access token");
}
} catch (error) {
console.error("Error completing OAuth login:", {
message: (error as any)?.message,
response: (error as any)?.response?.data,
status: (error as any)?.response?.status,
});
navigateToLoginWithNext();
}
};

completeLogin();
}, [loginSuccess, navigate, searchParams]);

return (
<div className="min-h-screen bg-secondary-background flex items-center justify-center">
<div className='flex flex-col items-center'>
{/* <FaSpinner className="animate-spin text-4xl text-[#66B0FF]" /> */}
<img src={cdacRoundLogo} className="mr-3 mb-4 size-20 cdacSpinner" />
<div className="text-[#000000] text-lg">Logging in...</div>
</div>
</div>
)

};

export default OAuthCallback;
Loading