@alinsafawi/aegis-auth 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +442 -16
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23616,23 +23616,27 @@ export default function LoginPage() {
|
|
|
23616
23616
|
setError('')
|
|
23617
23617
|
setLoading(true)
|
|
23618
23618
|
|
|
23619
|
-
|
|
23620
|
-
.
|
|
23621
|
-
|
|
23622
|
-
|
|
23623
|
-
|
|
23624
|
-
const res = await fetch('/api/auth/login', {
|
|
23625
|
-
method: 'POST',
|
|
23626
|
-
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf ?? '' },
|
|
23627
|
-
body: JSON.stringify({ identifier, password }),
|
|
23628
|
-
})
|
|
23619
|
+
try {
|
|
23620
|
+
const csrf = document.cookie
|
|
23621
|
+
.split('; ')
|
|
23622
|
+
.find((r) => r.startsWith('${appName.toLowerCase().replace(/[^a-z]/g, "")}_csrf='))
|
|
23623
|
+
?.split('=')[1]
|
|
23629
23624
|
|
|
23630
|
-
|
|
23631
|
-
|
|
23625
|
+
const res = await fetch('/api/auth/login', {
|
|
23626
|
+
method: 'POST',
|
|
23627
|
+
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf ?? '' },
|
|
23628
|
+
body: JSON.stringify({ identifier, password }),
|
|
23629
|
+
})
|
|
23632
23630
|
|
|
23633
|
-
|
|
23634
|
-
|
|
23635
|
-
|
|
23631
|
+
const data = await res.json()
|
|
23632
|
+
if (!res.ok) { setError(data.error ?? 'Login failed'); return }
|
|
23633
|
+
if (data.requiresTwoFactor) { router.push('/two-factor'); return }
|
|
23634
|
+
router.push(\`/\${data.role}/dashboard\`)
|
|
23635
|
+
} catch {
|
|
23636
|
+
setError('Network error \u2014 please try again.')
|
|
23637
|
+
} finally {
|
|
23638
|
+
setLoading(false)
|
|
23639
|
+
}
|
|
23636
23640
|
}
|
|
23637
23641
|
|
|
23638
23642
|
return (
|
|
@@ -23747,6 +23751,402 @@ function generatePackageJson(name, packageManager) {
|
|
|
23747
23751
|
2
|
|
23748
23752
|
);
|
|
23749
23753
|
}
|
|
23754
|
+
function generatePrismaLib() {
|
|
23755
|
+
return `import { PrismaClient } from '@prisma/client'
|
|
23756
|
+
|
|
23757
|
+
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
|
|
23758
|
+
|
|
23759
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
|
23760
|
+
|
|
23761
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
23762
|
+
`;
|
|
23763
|
+
}
|
|
23764
|
+
function generateLoginRoute() {
|
|
23765
|
+
return `import { createLoginHandler } from '@alinsafawi/aegis-auth-next'
|
|
23766
|
+
import { prisma } from '@/lib/prisma'
|
|
23767
|
+
import config from '../../../../../auth.config'
|
|
23768
|
+
|
|
23769
|
+
export const POST = createLoginHandler(config, prisma)
|
|
23770
|
+
`;
|
|
23771
|
+
}
|
|
23772
|
+
function generateLogoutRoute() {
|
|
23773
|
+
return `import { createLogoutHandler } from '@alinsafawi/aegis-auth-next'
|
|
23774
|
+
import config from '../../../../../auth.config'
|
|
23775
|
+
|
|
23776
|
+
export const POST = createLogoutHandler(config)
|
|
23777
|
+
`;
|
|
23778
|
+
}
|
|
23779
|
+
function generateTwoFactorRoute() {
|
|
23780
|
+
return `import { createTwoFactorHandler } from '@alinsafawi/aegis-auth-next'
|
|
23781
|
+
import { prisma } from '@/lib/prisma'
|
|
23782
|
+
import config from '../../../../../auth.config'
|
|
23783
|
+
|
|
23784
|
+
export const POST = createTwoFactorHandler(config, prisma)
|
|
23785
|
+
`;
|
|
23786
|
+
}
|
|
23787
|
+
function generateChangePasswordRoute() {
|
|
23788
|
+
return `import { createChangePasswordHandler } from '@alinsafawi/aegis-auth-next'
|
|
23789
|
+
import { prisma } from '@/lib/prisma'
|
|
23790
|
+
import config from '../../../../../auth.config'
|
|
23791
|
+
|
|
23792
|
+
export const POST = createChangePasswordHandler(config, prisma)
|
|
23793
|
+
`;
|
|
23794
|
+
}
|
|
23795
|
+
function generateForgotPasswordPage(language) {
|
|
23796
|
+
const ts = language === "typescript";
|
|
23797
|
+
return `'use client'
|
|
23798
|
+
|
|
23799
|
+
import { useState } from 'react'
|
|
23800
|
+
|
|
23801
|
+
export default function ForgotPasswordPage() {
|
|
23802
|
+
const [email, setEmail] = useState('')
|
|
23803
|
+
const [sent, setSent] = useState(false)
|
|
23804
|
+
const [error, setError] = useState('')
|
|
23805
|
+
const [loading, setLoading] = useState(false)
|
|
23806
|
+
|
|
23807
|
+
async function handleSubmit(e${ts ? ": React.FormEvent" : ""}) {
|
|
23808
|
+
e.preventDefault()
|
|
23809
|
+
setLoading(true)
|
|
23810
|
+
setError('')
|
|
23811
|
+
const res = await fetch('/api/auth/forgot-password', {
|
|
23812
|
+
method: 'POST',
|
|
23813
|
+
headers: { 'Content-Type': 'application/json' },
|
|
23814
|
+
body: JSON.stringify({ email }),
|
|
23815
|
+
})
|
|
23816
|
+
setLoading(false)
|
|
23817
|
+
if (!res.ok) { setError((await res.json()).error ?? 'Something went wrong'); return }
|
|
23818
|
+
setSent(true)
|
|
23819
|
+
}
|
|
23820
|
+
|
|
23821
|
+
if (sent) {
|
|
23822
|
+
return (
|
|
23823
|
+
<main className="min-h-screen flex items-center justify-center p-4">
|
|
23824
|
+
<div className="w-full max-w-sm text-center space-y-3">
|
|
23825
|
+
<h1 className="text-2xl font-bold">Check your email</h1>
|
|
23826
|
+
<p className="text-[var(--muted)] text-sm">
|
|
23827
|
+
If an account exists for <strong>{email}</strong>, a reset link has been sent.
|
|
23828
|
+
</p>
|
|
23829
|
+
<a href="/login" className="block text-sm text-[var(--muted)] hover:underline mt-4">
|
|
23830
|
+
Back to login
|
|
23831
|
+
</a>
|
|
23832
|
+
</div>
|
|
23833
|
+
</main>
|
|
23834
|
+
)
|
|
23835
|
+
}
|
|
23836
|
+
|
|
23837
|
+
return (
|
|
23838
|
+
<main className="min-h-screen flex items-center justify-center p-4">
|
|
23839
|
+
<div className="w-full max-w-sm">
|
|
23840
|
+
<div className="text-center mb-8">
|
|
23841
|
+
<h1 className="text-2xl font-bold">Forgot password</h1>
|
|
23842
|
+
<p className="text-[var(--muted)] text-sm mt-1">We'll send you a reset link.</p>
|
|
23843
|
+
</div>
|
|
23844
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
23845
|
+
{error && <div className="text-sm text-red-500 bg-red-50 border border-red-200 rounded-md p-3">{error}</div>}
|
|
23846
|
+
<div>
|
|
23847
|
+
<label className="block text-sm font-medium mb-1">Email address</label>
|
|
23848
|
+
<input
|
|
23849
|
+
type="email"
|
|
23850
|
+
value={email}
|
|
23851
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
23852
|
+
required
|
|
23853
|
+
autoComplete="email"
|
|
23854
|
+
className="w-full px-3 py-2 rounded-md border bg-[var(--input)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
|
|
23855
|
+
/>
|
|
23856
|
+
</div>
|
|
23857
|
+
<button
|
|
23858
|
+
type="submit"
|
|
23859
|
+
disabled={loading}
|
|
23860
|
+
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}
|
|
23861
|
+
className="w-full py-2 px-4 rounded-md font-medium disabled:opacity-50 transition-opacity"
|
|
23862
|
+
>
|
|
23863
|
+
{loading ? 'Sending\u2026' : 'Send reset link'}
|
|
23864
|
+
</button>
|
|
23865
|
+
<div className="text-center">
|
|
23866
|
+
<a href="/login" className="text-sm text-[var(--muted)] hover:underline">Back to login</a>
|
|
23867
|
+
</div>
|
|
23868
|
+
</form>
|
|
23869
|
+
</div>
|
|
23870
|
+
</main>
|
|
23871
|
+
)
|
|
23872
|
+
}
|
|
23873
|
+
`;
|
|
23874
|
+
}
|
|
23875
|
+
function generateResetPasswordPage(language) {
|
|
23876
|
+
const ts = language === "typescript";
|
|
23877
|
+
return `'use client'
|
|
23878
|
+
|
|
23879
|
+
import { useState } from 'react'
|
|
23880
|
+
import { useSearchParams, useRouter } from 'next/navigation'
|
|
23881
|
+
|
|
23882
|
+
export default function ResetPasswordPage() {
|
|
23883
|
+
const params = useSearchParams()
|
|
23884
|
+
const router = useRouter()
|
|
23885
|
+
const token = params.get('token') ?? ''
|
|
23886
|
+
const [password, setPassword] = useState('')
|
|
23887
|
+
const [confirm, setConfirm] = useState('')
|
|
23888
|
+
const [error, setError] = useState('')
|
|
23889
|
+
const [loading, setLoading] = useState(false)
|
|
23890
|
+
|
|
23891
|
+
async function handleSubmit(e${ts ? ": React.FormEvent" : ""}) {
|
|
23892
|
+
e.preventDefault()
|
|
23893
|
+
if (password !== confirm) { setError('Passwords do not match'); return }
|
|
23894
|
+
setLoading(true)
|
|
23895
|
+
setError('')
|
|
23896
|
+
const res = await fetch('/api/auth/reset-password', {
|
|
23897
|
+
method: 'POST',
|
|
23898
|
+
headers: { 'Content-Type': 'application/json' },
|
|
23899
|
+
body: JSON.stringify({ token, newPassword: password }),
|
|
23900
|
+
})
|
|
23901
|
+
setLoading(false)
|
|
23902
|
+
if (!res.ok) { setError((await res.json()).error ?? 'Something went wrong'); return }
|
|
23903
|
+
router.push('/login?reset=1')
|
|
23904
|
+
}
|
|
23905
|
+
|
|
23906
|
+
return (
|
|
23907
|
+
<main className="min-h-screen flex items-center justify-center p-4">
|
|
23908
|
+
<div className="w-full max-w-sm">
|
|
23909
|
+
<div className="text-center mb-8">
|
|
23910
|
+
<h1 className="text-2xl font-bold">Reset password</h1>
|
|
23911
|
+
</div>
|
|
23912
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
23913
|
+
{error && <div className="text-sm text-red-500 bg-red-50 border border-red-200 rounded-md p-3">{error}</div>}
|
|
23914
|
+
<div>
|
|
23915
|
+
<label className="block text-sm font-medium mb-1">New password</label>
|
|
23916
|
+
<input
|
|
23917
|
+
type="password"
|
|
23918
|
+
value={password}
|
|
23919
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
23920
|
+
required
|
|
23921
|
+
autoComplete="new-password"
|
|
23922
|
+
className="w-full px-3 py-2 rounded-md border bg-[var(--input)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
|
|
23923
|
+
/>
|
|
23924
|
+
</div>
|
|
23925
|
+
<div>
|
|
23926
|
+
<label className="block text-sm font-medium mb-1">Confirm new password</label>
|
|
23927
|
+
<input
|
|
23928
|
+
type="password"
|
|
23929
|
+
value={confirm}
|
|
23930
|
+
onChange={(e) => setConfirm(e.target.value)}
|
|
23931
|
+
required
|
|
23932
|
+
autoComplete="new-password"
|
|
23933
|
+
className="w-full px-3 py-2 rounded-md border bg-[var(--input)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
|
|
23934
|
+
/>
|
|
23935
|
+
</div>
|
|
23936
|
+
<button
|
|
23937
|
+
type="submit"
|
|
23938
|
+
disabled={loading}
|
|
23939
|
+
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}
|
|
23940
|
+
className="w-full py-2 px-4 rounded-md font-medium disabled:opacity-50 transition-opacity"
|
|
23941
|
+
>
|
|
23942
|
+
{loading ? 'Resetting\u2026' : 'Reset password'}
|
|
23943
|
+
</button>
|
|
23944
|
+
</form>
|
|
23945
|
+
</div>
|
|
23946
|
+
</main>
|
|
23947
|
+
)
|
|
23948
|
+
}
|
|
23949
|
+
`;
|
|
23950
|
+
}
|
|
23951
|
+
function generateTwoFactorPage(language) {
|
|
23952
|
+
const ts = language === "typescript";
|
|
23953
|
+
return `'use client'
|
|
23954
|
+
|
|
23955
|
+
import { useState } from 'react'
|
|
23956
|
+
import { useRouter } from 'next/navigation'
|
|
23957
|
+
|
|
23958
|
+
export default function TwoFactorPage() {
|
|
23959
|
+
const [code, setCode] = useState('')
|
|
23960
|
+
const [error, setError] = useState('')
|
|
23961
|
+
const [loading, setLoading] = useState(false)
|
|
23962
|
+
const router = useRouter()
|
|
23963
|
+
|
|
23964
|
+
async function handleSubmit(e${ts ? ": React.FormEvent" : ""}) {
|
|
23965
|
+
e.preventDefault()
|
|
23966
|
+
setLoading(true)
|
|
23967
|
+
setError('')
|
|
23968
|
+
const res = await fetch('/api/auth/two-factor', {
|
|
23969
|
+
method: 'POST',
|
|
23970
|
+
headers: { 'Content-Type': 'application/json' },
|
|
23971
|
+
body: JSON.stringify({ code }),
|
|
23972
|
+
})
|
|
23973
|
+
const data = await res.json()
|
|
23974
|
+
setLoading(false)
|
|
23975
|
+
if (!res.ok) { setError(data.error ?? 'Invalid code'); return }
|
|
23976
|
+
router.push(\`/\${data.role}/dashboard\`)
|
|
23977
|
+
}
|
|
23978
|
+
|
|
23979
|
+
return (
|
|
23980
|
+
<main className="min-h-screen flex items-center justify-center p-4">
|
|
23981
|
+
<div className="w-full max-w-sm">
|
|
23982
|
+
<div className="text-center mb-8">
|
|
23983
|
+
<h1 className="text-2xl font-bold">Two-factor authentication</h1>
|
|
23984
|
+
<p className="text-[var(--muted)] text-sm mt-1">Enter the 6-digit code from your authenticator app.</p>
|
|
23985
|
+
</div>
|
|
23986
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
23987
|
+
{error && <div className="text-sm text-red-500 bg-red-50 border border-red-200 rounded-md p-3">{error}</div>}
|
|
23988
|
+
<div>
|
|
23989
|
+
<label className="block text-sm font-medium mb-1">Authentication code</label>
|
|
23990
|
+
<input
|
|
23991
|
+
type="text"
|
|
23992
|
+
inputMode="numeric"
|
|
23993
|
+
pattern="[0-9]*"
|
|
23994
|
+
maxLength={6}
|
|
23995
|
+
value={code}
|
|
23996
|
+
onChange={(e) => setCode(e.target.value.replace(/D/g, ''))}
|
|
23997
|
+
required
|
|
23998
|
+
autoComplete="one-time-code"
|
|
23999
|
+
placeholder="000000"
|
|
24000
|
+
className="w-full px-3 py-2 rounded-md border bg-[var(--input)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] text-center tracking-widest text-lg"
|
|
24001
|
+
/>
|
|
24002
|
+
</div>
|
|
24003
|
+
<button
|
|
24004
|
+
type="submit"
|
|
24005
|
+
disabled={loading || code.length !== 6}
|
|
24006
|
+
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}
|
|
24007
|
+
className="w-full py-2 px-4 rounded-md font-medium disabled:opacity-50 transition-opacity"
|
|
24008
|
+
>
|
|
24009
|
+
{loading ? 'Verifying\u2026' : 'Verify'}
|
|
24010
|
+
</button>
|
|
24011
|
+
<div className="text-center">
|
|
24012
|
+
<a href="/login" className="text-sm text-[var(--muted)] hover:underline">Back to login</a>
|
|
24013
|
+
</div>
|
|
24014
|
+
</form>
|
|
24015
|
+
</div>
|
|
24016
|
+
</main>
|
|
24017
|
+
)
|
|
24018
|
+
}
|
|
24019
|
+
`;
|
|
24020
|
+
}
|
|
24021
|
+
function generateVerifyEmailNoticePage() {
|
|
24022
|
+
return `export default function VerifyEmailNoticePage() {
|
|
24023
|
+
return (
|
|
24024
|
+
<main className="min-h-screen flex items-center justify-center p-4">
|
|
24025
|
+
<div className="w-full max-w-sm text-center space-y-3">
|
|
24026
|
+
<h1 className="text-2xl font-bold">Verify your email</h1>
|
|
24027
|
+
<p className="text-[var(--muted)] text-sm">
|
|
24028
|
+
We sent a verification code to your email address. Enter it below to activate your account.
|
|
24029
|
+
</p>
|
|
24030
|
+
<a href="/verify-email" className="block mt-4 text-sm underline" style={{ color: 'var(--primary)' }}>
|
|
24031
|
+
Enter verification code \u2192
|
|
24032
|
+
</a>
|
|
24033
|
+
</div>
|
|
24034
|
+
</main>
|
|
24035
|
+
)
|
|
24036
|
+
}
|
|
24037
|
+
`;
|
|
24038
|
+
}
|
|
24039
|
+
function generateVerifyEmailPage(language) {
|
|
24040
|
+
const ts = language === "typescript";
|
|
24041
|
+
return `'use client'
|
|
24042
|
+
|
|
24043
|
+
import { useState } from 'react'
|
|
24044
|
+
import { useRouter } from 'next/navigation'
|
|
24045
|
+
|
|
24046
|
+
export default function VerifyEmailPage() {
|
|
24047
|
+
const [code, setCode] = useState('')
|
|
24048
|
+
const [error, setError] = useState('')
|
|
24049
|
+
const [loading, setLoading] = useState(false)
|
|
24050
|
+
const router = useRouter()
|
|
24051
|
+
|
|
24052
|
+
async function handleSubmit(e${ts ? ": React.FormEvent" : ""}) {
|
|
24053
|
+
e.preventDefault()
|
|
24054
|
+
setLoading(true)
|
|
24055
|
+
setError('')
|
|
24056
|
+
const res = await fetch('/api/auth/verify-email', {
|
|
24057
|
+
method: 'POST',
|
|
24058
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24059
|
+
body: JSON.stringify({ code }),
|
|
24060
|
+
})
|
|
24061
|
+
const data = await res.json()
|
|
24062
|
+
setLoading(false)
|
|
24063
|
+
if (!res.ok) { setError(data.error ?? 'Invalid code'); return }
|
|
24064
|
+
router.push(\`/\${data.role}/dashboard\`)
|
|
24065
|
+
}
|
|
24066
|
+
|
|
24067
|
+
return (
|
|
24068
|
+
<main className="min-h-screen flex items-center justify-center p-4">
|
|
24069
|
+
<div className="w-full max-w-sm">
|
|
24070
|
+
<div className="text-center mb-8">
|
|
24071
|
+
<h1 className="text-2xl font-bold">Enter verification code</h1>
|
|
24072
|
+
<p className="text-[var(--muted)] text-sm mt-1">Check your email for the 6-digit code.</p>
|
|
24073
|
+
</div>
|
|
24074
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
24075
|
+
{error && <div className="text-sm text-red-500 bg-red-50 border border-red-200 rounded-md p-3">{error}</div>}
|
|
24076
|
+
<div>
|
|
24077
|
+
<label className="block text-sm font-medium mb-1">Verification code</label>
|
|
24078
|
+
<input
|
|
24079
|
+
type="text"
|
|
24080
|
+
inputMode="numeric"
|
|
24081
|
+
pattern="[0-9]*"
|
|
24082
|
+
maxLength={6}
|
|
24083
|
+
value={code}
|
|
24084
|
+
onChange={(e) => setCode(e.target.value.replace(/D/g, ''))}
|
|
24085
|
+
required
|
|
24086
|
+
autoComplete="one-time-code"
|
|
24087
|
+
placeholder="000000"
|
|
24088
|
+
className="w-full px-3 py-2 rounded-md border bg-[var(--input)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] text-center tracking-widest text-lg"
|
|
24089
|
+
/>
|
|
24090
|
+
</div>
|
|
24091
|
+
<button
|
|
24092
|
+
type="submit"
|
|
24093
|
+
disabled={loading || code.length !== 6}
|
|
24094
|
+
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}
|
|
24095
|
+
className="w-full py-2 px-4 rounded-md font-medium disabled:opacity-50 transition-opacity"
|
|
24096
|
+
>
|
|
24097
|
+
{loading ? 'Verifying\u2026' : 'Verify email'}
|
|
24098
|
+
</button>
|
|
24099
|
+
<div className="text-center">
|
|
24100
|
+
<a href="/login" className="text-sm text-[var(--muted)] hover:underline">Back to login</a>
|
|
24101
|
+
</div>
|
|
24102
|
+
</form>
|
|
24103
|
+
</div>
|
|
24104
|
+
</main>
|
|
24105
|
+
)
|
|
24106
|
+
}
|
|
24107
|
+
`;
|
|
24108
|
+
}
|
|
24109
|
+
function generateForgotPasswordRoute(language) {
|
|
24110
|
+
const ts = language === "typescript";
|
|
24111
|
+
return `import { NextRequest, NextResponse } from 'next/server'
|
|
24112
|
+
import { prisma } from '@/lib/prisma'
|
|
24113
|
+
import config from '../../../../../auth.config'
|
|
24114
|
+
|
|
24115
|
+
export async function POST(request${ts ? ": NextRequest" : ""}) {
|
|
24116
|
+
// TODO: implement \u2014 find user by email, generate reset token, send email
|
|
24117
|
+
// You can use: import { sendEmail } from '@alinsafawi/aegis-auth-core'
|
|
24118
|
+
const { email } = await request.json()
|
|
24119
|
+
return NextResponse.json({ success: true })
|
|
24120
|
+
}
|
|
24121
|
+
`;
|
|
24122
|
+
}
|
|
24123
|
+
function generateResetPasswordRoute(language) {
|
|
24124
|
+
const ts = language === "typescript";
|
|
24125
|
+
return `import { NextRequest, NextResponse } from 'next/server'
|
|
24126
|
+
import { prisma } from '@/lib/prisma'
|
|
24127
|
+
import { hashPassword } from '@alinsafawi/aegis-auth-core'
|
|
24128
|
+
import config from '../../../../../auth.config'
|
|
24129
|
+
|
|
24130
|
+
export async function POST(request${ts ? ": NextRequest" : ""}) {
|
|
24131
|
+
// TODO: implement \u2014 verify token, update password hash
|
|
24132
|
+
const { token, newPassword } = await request.json()
|
|
24133
|
+
return NextResponse.json({ success: true })
|
|
24134
|
+
}
|
|
24135
|
+
`;
|
|
24136
|
+
}
|
|
24137
|
+
function generateVerifyEmailRoute(language) {
|
|
24138
|
+
const ts = language === "typescript";
|
|
24139
|
+
return `import { NextRequest, NextResponse } from 'next/server'
|
|
24140
|
+
import { prisma } from '@/lib/prisma'
|
|
24141
|
+
import config from '../../../../../auth.config'
|
|
24142
|
+
|
|
24143
|
+
export async function POST(request${ts ? ": NextRequest" : ""}) {
|
|
24144
|
+
// TODO: implement \u2014 verify code, mark emailVerified = true, return role
|
|
24145
|
+
const { code } = await request.json()
|
|
24146
|
+
return NextResponse.json({ success: true, role: 'user' })
|
|
24147
|
+
}
|
|
24148
|
+
`;
|
|
24149
|
+
}
|
|
23750
24150
|
|
|
23751
24151
|
// src/commands/init.ts
|
|
23752
24152
|
var DEFAULT_COLOR = "oklch(0.6 0.2 240)";
|
|
@@ -23958,9 +24358,35 @@ dist
|
|
|
23958
24358
|
await step("Creating session helpers", async () => {
|
|
23959
24359
|
await writeFile(`src/lib/auth.${language === "typescript" ? "ts" : "js"}`, generateAuthLib(language));
|
|
23960
24360
|
});
|
|
23961
|
-
await step("Scaffolding auth pages", async () => {
|
|
24361
|
+
await step("Scaffolding auth pages and API routes", async () => {
|
|
23962
24362
|
const e = language === "typescript" ? "tsx" : "jsx";
|
|
24363
|
+
const r = language === "typescript" ? "ts" : "js";
|
|
24364
|
+
await writeFile(`src/lib/prisma.${r}`, generatePrismaLib());
|
|
24365
|
+
await writeFile(`src/app/api/auth/login/route.${r}`, generateLoginRoute());
|
|
24366
|
+
await writeFile(`src/app/api/auth/logout/route.${r}`, generateLogoutRoute());
|
|
24367
|
+
await writeFile(`src/app/api/auth/change-password/route.${r}`, generateChangePasswordRoute());
|
|
24368
|
+
if (features.twoFactor) {
|
|
24369
|
+
await writeFile(`src/app/api/auth/two-factor/route.${r}`, generateTwoFactorRoute());
|
|
24370
|
+
}
|
|
24371
|
+
if (features.passwordReset) {
|
|
24372
|
+
await writeFile(`src/app/api/auth/forgot-password/route.${r}`, generateForgotPasswordRoute(language));
|
|
24373
|
+
await writeFile(`src/app/api/auth/reset-password/route.${r}`, generateResetPasswordRoute(language));
|
|
24374
|
+
}
|
|
24375
|
+
if (features.emailVerification) {
|
|
24376
|
+
await writeFile(`src/app/api/auth/verify-email/route.${r}`, generateVerifyEmailRoute(language));
|
|
24377
|
+
}
|
|
23963
24378
|
await writeFile(`src/app/(auth)/login/page.${e}`, generateLoginPage(app.appName, DEFAULT_COLOR, language));
|
|
24379
|
+
if (features.passwordReset) {
|
|
24380
|
+
await writeFile(`src/app/(auth)/forgot-password/page.${e}`, generateForgotPasswordPage(language));
|
|
24381
|
+
await writeFile(`src/app/(auth)/reset-password/page.${e}`, generateResetPasswordPage(language));
|
|
24382
|
+
}
|
|
24383
|
+
if (features.twoFactor) {
|
|
24384
|
+
await writeFile(`src/app/(auth)/two-factor/page.${e}`, generateTwoFactorPage(language));
|
|
24385
|
+
}
|
|
24386
|
+
if (features.emailVerification) {
|
|
24387
|
+
await writeFile(`src/app/(auth)/verify-email-notice/page.${e}`, generateVerifyEmailNoticePage());
|
|
24388
|
+
await writeFile(`src/app/(auth)/verify-email/page.${e}`, generateVerifyEmailPage(language));
|
|
24389
|
+
}
|
|
23964
24390
|
});
|
|
23965
24391
|
await step("Creating .env and .env.example", async () => {
|
|
23966
24392
|
await writeFile(".env", generateEnvFile(app.cookiePrefix, infra, features));
|