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 && (
+
+ )}
)
}
\ No newline at end of file