Recovery option, fix profile card menu placement

This commit is contained in:
austinkelsay 2025-01-10 14:39:45 -06:00
parent cace835c84
commit 417159aad9
3 changed files with 112 additions and 2 deletions

View File

@ -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)}

View File

@ -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: {

View File

@ -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>
)
}