first commit
Made-with: Cursor
This commit is contained in:
151
frontend/hooks/useAuth.ts
Normal file
151
frontend/hooks/useAuth.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
"use client";
|
||||
import { useState, useEffect, useCallback, createContext, useContext } from "react";
|
||||
import { api } from "@/lib/api";
|
||||
import {
|
||||
hasNostrExtension,
|
||||
getPublicKey,
|
||||
signEvent,
|
||||
createAuthEvent,
|
||||
fetchNostrProfile,
|
||||
createBunkerSigner,
|
||||
type BunkerSignerInterface,
|
||||
type NostrProfile,
|
||||
} from "@/lib/nostr";
|
||||
|
||||
export interface User {
|
||||
pubkey: string;
|
||||
role: string;
|
||||
username?: string;
|
||||
name?: string;
|
||||
picture?: string;
|
||||
about?: string;
|
||||
nip05?: string;
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
login: () => Promise<User>;
|
||||
loginWithBunker: (input: string) => Promise<User>;
|
||||
loginWithConnectedSigner: (signer: BunkerSignerInterface) => Promise<User>;
|
||||
logout: () => void;
|
||||
isAdmin: boolean;
|
||||
isModerator: boolean;
|
||||
}
|
||||
|
||||
export const AuthContext = createContext<AuthContextType>({
|
||||
user: null,
|
||||
loading: true,
|
||||
login: async () => ({ pubkey: "", role: "USER" }),
|
||||
loginWithBunker: async () => ({ pubkey: "", role: "USER" }),
|
||||
loginWithConnectedSigner: async () => ({ pubkey: "", role: "USER" }),
|
||||
logout: () => {},
|
||||
isAdmin: false,
|
||||
isModerator: false,
|
||||
});
|
||||
|
||||
export function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
|
||||
export function useAuthProvider(): AuthContextType {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem("bbe_user");
|
||||
const token = localStorage.getItem("bbe_token");
|
||||
if (stored && token) {
|
||||
try {
|
||||
setUser(JSON.parse(stored));
|
||||
} catch {
|
||||
localStorage.removeItem("bbe_user");
|
||||
localStorage.removeItem("bbe_token");
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
const completeAuth = useCallback(
|
||||
async (
|
||||
getPubKey: () => Promise<string>,
|
||||
sign: (event: any) => Promise<any>
|
||||
): Promise<User> => {
|
||||
const pubkey = await getPubKey();
|
||||
const { challenge } = await api.getChallenge(pubkey);
|
||||
const event = createAuthEvent(pubkey, challenge);
|
||||
const signedEvent = await sign(event);
|
||||
const { token, user: userData } = await api.verify(pubkey, signedEvent);
|
||||
|
||||
let profile: NostrProfile = {};
|
||||
try {
|
||||
profile = await fetchNostrProfile(pubkey);
|
||||
} catch {
|
||||
// Profile fetch is best-effort
|
||||
}
|
||||
|
||||
const fullUser: User = {
|
||||
...userData,
|
||||
name: profile.name,
|
||||
displayName: profile.displayName,
|
||||
picture: profile.picture,
|
||||
about: profile.about,
|
||||
nip05: profile.nip05,
|
||||
username: userData.username,
|
||||
};
|
||||
|
||||
localStorage.setItem("bbe_token", token);
|
||||
localStorage.setItem("bbe_user", JSON.stringify(fullUser));
|
||||
setUser(fullUser);
|
||||
return fullUser;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const login = useCallback(async (): Promise<User> => {
|
||||
if (!hasNostrExtension()) {
|
||||
throw new Error("Please install a Nostr extension (e.g., Alby, nos2x)");
|
||||
}
|
||||
return completeAuth(getPublicKey, signEvent);
|
||||
}, [completeAuth]);
|
||||
|
||||
const loginWithConnectedSigner = useCallback(
|
||||
async (signer: BunkerSignerInterface): Promise<User> => {
|
||||
return completeAuth(
|
||||
() => signer.getPublicKey(),
|
||||
(event) => signer.signEvent(event)
|
||||
);
|
||||
},
|
||||
[completeAuth]
|
||||
);
|
||||
|
||||
const loginWithBunker = useCallback(
|
||||
async (input: string): Promise<User> => {
|
||||
const { signer } = await createBunkerSigner(input);
|
||||
try {
|
||||
return await loginWithConnectedSigner(signer);
|
||||
} finally {
|
||||
await signer.close().catch(() => {});
|
||||
}
|
||||
},
|
||||
[loginWithConnectedSigner]
|
||||
);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
localStorage.removeItem("bbe_token");
|
||||
localStorage.removeItem("bbe_user");
|
||||
setUser(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
user,
|
||||
loading,
|
||||
login,
|
||||
loginWithBunker,
|
||||
loginWithConnectedSigner,
|
||||
logout,
|
||||
isAdmin: user?.role === "ADMIN",
|
||||
isModerator: user?.role === "MODERATOR" || user?.role === "ADMIN",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user