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 = () => (
-
+
menu.current.toggle(e)} @@ -145,7 +145,7 @@ const UserProfileCard = ({ user }) => { className="rounded-full my-4" />
-
+
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} /> + setShowRecoveryInput(!showRecoveryInput)} + /> + {showRecoveryInput && ( +
+
+

⚠️ Recovery Notice

+

+ 🔑 This recovery option is only for accounts created through: +

+
    +
  • 📧 Email Login
  • +
  • 👤 Anonymous Login
  • +
  • 🐙 GitHub Login
  • +
+

+ ⛔ Do NOT enter your personal Nostr nsec here! Only use the recovery key provided by PlebDevs (available on your profile page). +

+
+ setNsec(e.target.value)} + placeholder="Enter recovery key (nsec or hex)" + className="w-[250px] my-4" + /> + + + )}
) } \ No newline at end of file