Skip to content

Commit 9bc04a7

Browse files
authored
Merge pull request #32 from Sohan-Rout/main1
Feat : added auth route and fixes
2 parents 007163d + c21149a commit 9bc04a7

File tree

3 files changed

+107
-61
lines changed

3 files changed

+107
-61
lines changed

EnvExample.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
EMAIL_USER=Your App Email
2+
EMAIL_PASSWORD=Your Google App Password
3+
NEXT_PUBLIC_GA_ID=Your Google Analytics ID
4+
5+
NEXT_PUBLIC_SUPABASE_URL=Your supabase Url
6+
NEXT_PUBLIC_SUPABASE_ANON_KEY=Your Anon Key
7+
NEXT_PUBLIC_TURNSTILE_SITE_KEY=Your Cloudfare Captcha Key
8+
9+
TURNSTILE_SECRET_KEY=Your Cloudfare backend route api key
10+
SUPABASE_SERVICE_KEY=Your supabase service key

app/api/auth/route.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { createClient } from '@supabase/supabase-js'
2+
3+
const supabase = createClient(
4+
process.env.NEXT_PUBLIC_SUPABASE_URL,
5+
process.env.SUPABASE_SERVICE_KEY
6+
)
7+
8+
export async function POST(req) {
9+
try {
10+
const { email, password, captchaToken, action } = await req.json()
11+
12+
if (!email || !password)
13+
return new Response(JSON.stringify({ message: 'Email and password required' }), { status: 400 })
14+
15+
if (action === 'signup') {
16+
if (!captchaToken)
17+
return new Response(JSON.stringify({ message: 'Captcha token missing' }), { status: 400 })
18+
19+
// Verify Turnstile token
20+
const verifyRes = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
21+
method: 'POST',
22+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
23+
body: new URLSearchParams({
24+
secret: process.env.TURNSTILE_SECRET_KEY,
25+
response: captchaToken,
26+
}),
27+
})
28+
29+
const data = await verifyRes.json()
30+
if (!data.success)
31+
return new Response(JSON.stringify({ message: 'Captcha verification failed' }), { status: 400 })
32+
33+
// Create Supabase user
34+
const { user, error } = await supabase.auth.admin.createUser({ email, password })
35+
if (error)
36+
return new Response(JSON.stringify({ message: error.message }), { status: 400 })
37+
38+
return new Response(JSON.stringify({ message: 'Signup successful! Check your email.' }), { status: 200 })
39+
}
40+
41+
else if (action === 'login') {
42+
return new Response(JSON.stringify({ message: 'Use frontend login with anon key' }), { status: 400 })
43+
}
44+
45+
else {
46+
return new Response(JSON.stringify({ message: 'Invalid action' }), { status: 400 })
47+
}
48+
} catch (err) {
49+
console.error(err)
50+
return new Response(JSON.stringify({ message: 'Internal server error' }), { status: 500 })
51+
}
52+
}

app/login/page.jsx

Lines changed: 45 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { useRouter } from 'next/navigation'
55
import { FiMail, FiLock, FiUser, FiLogIn, FiUserPlus, FiSun, FiMoon } from 'react-icons/fi'
66
import { motion } from 'framer-motion'
77
import Link from 'next/link'
8-
import dynamic from "next/dynamic";
8+
import dynamic from 'next/dynamic'
99

1010
const Turnstile = dynamic(
11-
() => import("@marsidev/react-turnstile").then((mod) => mod.Turnstile),
11+
() => import('@marsidev/react-turnstile').then((mod) => mod.Turnstile),
1212
{ ssr: false }
13-
);
13+
)
1414

1515
export default function LoginPage() {
1616
const [email, setEmail] = useState('')
@@ -38,17 +38,28 @@ export default function LoginPage() {
3838
const handleAuth = async () => {
3939
setLoading(true)
4040
setError('')
41-
41+
4242
try {
4343
if (isLogin) {
44+
// Login with frontend anon key
4445
const { error } = await supabase.auth.signInWithPassword({ email, password })
4546
if (error) throw error
4647
router.push('/dashboard')
4748
} else {
49+
// Signup via API route with Turnstile
4850
if (!captchaToken) throw new Error('Please complete captcha')
49-
const { error } = await supabase.auth.signUp({ email, password, options: { captchaToken } })
50-
if (error) throw error
51-
alert('Check your email for confirmation!')
51+
52+
const res = await fetch('/auth', {
53+
method: 'POST',
54+
headers: { 'Content-Type': 'application/json' },
55+
body: JSON.stringify({ email, password, captchaToken, action: 'signup' }),
56+
})
57+
58+
const data = await res.json()
59+
if (!res.ok) throw new Error(data.message)
60+
61+
alert(data.message)
62+
setIsLogin(true) // switch to login after signup
5263
}
5364
} catch (err) {
5465
setError(err.message)
@@ -57,60 +68,47 @@ export default function LoginPage() {
5768
}
5869
}
5970

60-
const handleGoogleSignIn = async () => {
61-
const { error } = await supabase.auth.signInWithOAuth({
62-
provider: 'google',
63-
})
64-
if (error) console.error('Error signing in with Google:', error.message)
71+
const handleGoogleSignIn = async () => {
72+
const { error } = await supabase.auth.signInWithOAuth({ provider: 'google' })
73+
if (error) console.error('Google sign-in error:', error.message)
6574
}
6675

6776
return (
68-
<div className="min-h-screen dark:bg-gray-950 bg-gray-50 flex items-center justify-center p-4">
69-
<motion.div
77+
<div className="min-h-screen flex items-center justify-center p-4 bg-gray-50 dark:bg-gray-950">
78+
<motion.div
7079
initial={{ opacity: 0, y: 20 }}
7180
animate={{ opacity: 1, y: 0 }}
7281
className="w-full max-w-md bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden border border-gray-200 dark:border-gray-700"
7382
>
74-
{/* Header with theme toggle */}
83+
{/* Header */}
7584
<div className="bg-blue-600 p-6 text-white flex justify-between items-center">
7685
<div>
7786
<h1 className="text-2xl font-bold">{isLogin ? 'Welcome Back' : 'Create Account'}</h1>
7887
<p className="text-blue-100 dark:text-blue-200">
7988
{isLogin ? 'Sign in to access your dashboard' : 'Join us to get started'}
8089
</p>
8190
</div>
82-
<button
83-
onClick={toggleTheme}
84-
className="p-2 rounded-full hover:bg-blue-700 transition duration-200"
85-
aria-label="Toggle theme"
86-
>
87-
{theme === 'light' ? (
88-
<FiMoon className="w-5 h-5 text-white" />
89-
) : (
90-
<FiSun className="w-5 h-5 text-white" />
91-
)}
91+
<button onClick={toggleTheme} className="p-2 rounded-full hover:bg-blue-700" aria-label="Toggle theme">
92+
{theme === 'light' ? <FiMoon className="w-5 h-5" /> : <FiSun className="w-5 h-5" />}
9293
</button>
9394
</div>
9495

9596
<div className="p-6 space-y-4">
9697
{error && (
97-
<motion.div
98-
initial={{ opacity: 0 }}
99-
animate={{ opacity: 1 }}
100-
className="bg-red-100 dark:bg-red-900/30 border-l-4 border-red-500 text-red-700 dark:text-red-300 p-3 rounded"
101-
>
102-
<p>{error}</p>
98+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="bg-red-100 dark:bg-red-900/30 border-l-4 border-red-500 text-red-700 dark:text-red-300 p-3 rounded">
99+
{error}
103100
</motion.div>
104101
)}
105102

103+
{/* Form */}
106104
<div className="space-y-4">
107105
<div className="relative">
108106
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
109107
<FiMail className="text-gray-400 dark:text-gray-500" />
110108
</div>
111109
<input
112110
type="email"
113-
className="w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200"
111+
className="w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-200"
114112
placeholder="Email address"
115113
value={email}
116114
onChange={(e) => setEmail(e.target.value)}
@@ -123,7 +121,7 @@ export default function LoginPage() {
123121
</div>
124122
<input
125123
type="password"
126-
className="w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200"
124+
className="w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-200"
127125
placeholder="Password"
128126
value={password}
129127
onChange={(e) => setPassword(e.target.value)}
@@ -137,7 +135,7 @@ export default function LoginPage() {
137135
</div>
138136
<input
139137
type="text"
140-
className="w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200"
138+
className="w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-200"
141139
placeholder="Full name (optional)"
142140
/>
143141
</div>
@@ -156,69 +154,55 @@ export default function LoginPage() {
156154
onClick={handleAuth}
157155
disabled={loading}
158156
className={`w-full flex items-center justify-center py-3 px-4 rounded-lg text-white font-medium transition-all ${
159-
loading
160-
? 'bg-gray-400 dark:bg-gray-600 cursor-not-allowed'
161-
: 'bg-blue-600 hover:from-blue-700 hover:to-purple-700 dark:hover:from-blue-800 dark:hover:to-purple-800 shadow-md hover:shadow-lg'
157+
loading
158+
? 'bg-gray-400 dark:bg-gray-600 cursor-not-allowed'
159+
: 'bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-800 shadow-md hover:shadow-lg'
162160
}`}
163161
>
164162
{loading ? (
165163
'Processing...'
164+
) : isLogin ? (
165+
<>
166+
<FiLogIn className="mr-2" /> Sign In
167+
</>
166168
) : (
167169
<>
168-
{isLogin ? (
169-
<>
170-
<FiLogIn className="mr-2" /> Sign In
171-
</>
172-
) : (
173-
<>
174-
<FiUserPlus className="mr-2" /> Sign Up
175-
</>
176-
)}
170+
<FiUserPlus className="mr-2" /> Sign Up
177171
</>
178172
)}
179173
</button>
180174
</div>
181175

176+
{/* Switch forms */}
182177
<div className="text-center text-sm text-gray-600 dark:text-gray-400">
183178
{isLogin ? (
184179
<p>
185180
Don't have an account?{' '}
186-
<button
187-
onClick={() => setIsLogin(false)}
188-
className="text-blue-600 dark:text-blue-400 hover:underline font-medium"
189-
>
181+
<button onClick={() => setIsLogin(false)} className="text-blue-600 dark:text-blue-400 hover:underline">
190182
Sign up
191183
</button>
192184
</p>
193185
) : (
194186
<p>
195187
Already have an account?{' '}
196-
<button
197-
onClick={() => setIsLogin(true)}
198-
className="text-blue-600 dark:text-blue-400 hover:underline font-medium"
199-
>
188+
<button onClick={() => setIsLogin(true)} className="text-blue-600 dark:text-blue-400 hover:underline">
200189
Sign in
201190
</button>
202191
</p>
203192
)}
204193
</div>
205194

195+
{/* Google OAuth */}
206196
<div className="relative flex items-center py-4">
207197
<div className="flex-grow border-t border-gray-300 dark:border-gray-600"></div>
208198
<span className="flex-shrink mx-4 text-gray-500 dark:text-gray-400">or</span>
209199
<div className="flex-grow border-t border-gray-300 dark:border-gray-600"></div>
210200
</div>
211201

212202
<button
213-
onClick={() => supabase.auth.signInWithOAuth({ provider: 'google' })}
203+
onClick={handleGoogleSignIn}
214204
className="w-full flex items-center justify-center py-3 px-4 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 font-medium hover:bg-gray-50 dark:hover:bg-gray-600 transition-all"
215205
>
216-
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
217-
<path 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" fill="#4285F4"/>
218-
<path 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" fill="#34A853"/>
219-
<path 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" fill="#FBBC05"/>
220-
<path 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" fill="#EA4335"/>
221-
</svg>
222206
Continue with Google
223207
</button>
224208

0 commit comments

Comments
 (0)