From 417159aad92258792b9c0b9c01572cf6a0821753 Mon Sep 17 00:00:00 2001 From: austinkelsay <austinkelsay@yahoo.com> Date: Fri, 10 Jan 2025 14:39:45 -0600 Subject: [PATCH] Recovery option, fix profile card menu placement --- src/components/profile/UserProfileCard.js | 4 +- src/pages/api/auth/[...nextauth].js | 50 +++++++++++++++++++ src/pages/auth/signin.js | 60 +++++++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/components/profile/UserProfileCard.js b/src/components/profile/UserProfileCard.js index 0c93869..4301cc9 100644 --- a/src/components/profile/UserProfileCard.js +++ b/src/components/profile/UserProfileCard.js @@ -48,7 +48,7 @@ const UserProfileCard = ({ user }) => { const MobileProfileCard = () => ( <div className="w-full bg-gray-800 rounded-lg p-2 py-1 border border-gray-700 shadow-md h-[420px] flex flex-col justify-center items-start"> <div className="flex flex-col gap-2 pt-4 w-full relative"> - <div className="absolute top-8 right-[10px]"> + <div className="absolute top-8 right-[14px]"> <i className="pi pi-ellipsis-h text-2xl cursor-pointer" onClick={(e) => menu.current.toggle(e)} @@ -145,7 +145,7 @@ const UserProfileCard = ({ user }) => { className="rounded-full my-4" /> <div className="flex flex-col gap-2 pt-4 w-fit relative"> - <div className="absolute top-[-4px] right-[-30px]"> + <div className="absolute top-[-1px] right-[-24px]"> <i className="pi pi-ellipsis-h text-2xl cursor-pointer" onClick={(e) => menu.current.toggle(e)} diff --git a/src/pages/api/auth/[...nextauth].js b/src/pages/api/auth/[...nextauth].js index 2ed0013..32b93f1 100644 --- a/src/pages/api/auth/[...nextauth].js +++ b/src/pages/api/auth/[...nextauth].js @@ -12,6 +12,7 @@ import { updateUser, getUserByPubkey, createUser, getUserById, getUserByEmail } import { createRole } from "@/db/models/roleModels"; import appConfig from "@/config/appConfig"; import NDK from "@nostr-dev-kit/ndk"; +import { nip19 } from 'nostr-tools'; // Initialize NDK for Nostr interactions const ndk = new NDK({ @@ -162,6 +163,55 @@ export const authOptions = { avatar: profile.avatar_url }; } + }), + // Recovery provider + CredentialsProvider({ + id: "recovery", + name: "Recovery", + credentials: { + nsec: { label: "Recovery Key (nsec or hex)", type: "text" } + }, + authorize: async (credentials) => { + if (!credentials?.nsec) return null; + + try { + // Convert nsec to hex if needed + let privkeyHex = credentials.nsec; + if (credentials.nsec.startsWith('nsec')) { + try { + const { data: decodedPrivkey } = nip19.decode(credentials.nsec); + privkeyHex = Buffer.from(decodedPrivkey).toString('hex'); + } catch (error) { + console.error("Invalid nsec format:", error); + return null; + } + } + + // Find user with matching privkey + const user = await prisma.user.findFirst({ + where: { privkey: privkeyHex }, + include: { + role: true, + purchased: true, + userCourses: true, + userLessons: true, + nip05: true, + lightningAddress: true, + userBadges: true + } + }); + + if (!user) { + console.error("No user found with provided recovery key"); + return null; + } + + return user; + } catch (error) { + console.error("Recovery authorization error:", error); + return null; + } + } }) ], callbacks: { diff --git a/src/pages/auth/signin.js b/src/pages/auth/signin.js index 1b7b079..b7d546d 100644 --- a/src/pages/auth/signin.js +++ b/src/pages/auth/signin.js @@ -7,7 +7,9 @@ import { InputText } from 'primereact/inputtext'; export default function SignIn() { const [email, setEmail] = useState("") + const [nsec, setNsec] = useState("") const [showEmailInput, setShowEmailInput] = useState(false) + const [showRecoveryInput, setShowRecoveryInput] = useState(false) const {ndk, addSigner} = useNDKContext(); const { data: session, status } = useSession(); const router = useRouter(); @@ -68,6 +70,25 @@ export default function SignIn() { } }; + const handleRecoverySignIn = async (e) => { + e.preventDefault() + try { + const result = await signIn("recovery", { + nsec, + redirect: false, + callbackUrl: '/' + }); + + if (result?.ok) { + router.push('/'); + } else { + console.error("Recovery login failed:", result?.error); + } + } catch (error) { + console.error("Recovery sign in error:", error); + } + } + useEffect(() => { // Redirect if already signed in if (session?.user) { @@ -124,6 +145,45 @@ export default function SignIn() { rounded onClick={handleAnonymousSignIn} /> + <GenericButton + label={"recover account"} + icon="pi pi-key" + className="text-[#f8f8ff] w-[250px] my-4 mx-auto" + rounded + onClick={() => setShowRecoveryInput(!showRecoveryInput)} + /> + {showRecoveryInput && ( + <form onSubmit={handleRecoverySignIn} className="flex flex-col items-center bg-gray-700 w-fit mx-auto p-4 rounded-lg"> + <div className="text-center mb-4 max-w-[350px]"> + <p className="text-yellow-400 mb-2">⚠️ Recovery Notice</p> + <p className="text-gray-200 mb-2"> + 🔑 This recovery option is only for accounts created through: + </p> + <ul className="text-gray-300 mb-2 text-left list-none"> + <li>📧 Email Login</li> + <li>👤 Anonymous Login</li> + <li>🐙 GitHub Login</li> + </ul> + <p className="text-red-400 text-sm"> + ⛔ Do NOT enter your personal Nostr nsec here! Only use the recovery key provided by PlebDevs (available on your profile page). + </p> + </div> + <InputText + type="password" + value={nsec} + onChange={(e) => setNsec(e.target.value)} + placeholder="Enter recovery key (nsec or hex)" + className="w-[250px] my-4" + /> + <GenericButton + type="submit" + label={"Recover Account"} + icon="pi pi-lock-open" + className="text-[#f8f8ff] w-fit my-4" + rounded + /> + </form> + )} </div> ) } \ No newline at end of file