Better auth
This commit is contained in:
+19
-30
@@ -5,10 +5,10 @@ import { supabase } from "../supa";
|
||||
/**
|
||||
* POST /api/user
|
||||
* Register new user
|
||||
* Body: { name, username, email, password }
|
||||
* Body: { username, email, password }
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const { name, username, email, password } = await req.json();
|
||||
const { username, email, password } = await req.json();
|
||||
|
||||
if (!email || !password || !username)
|
||||
return NextResponse.json(
|
||||
@@ -28,29 +28,35 @@ export async function POST(req: NextRequest) {
|
||||
{ status: StatusCodes.CONFLICT }
|
||||
);
|
||||
|
||||
const { data: authData, error: authError } =
|
||||
await supabase.auth.admin.createUser({
|
||||
const { data: authData, error: authError } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
email_confirm: true,
|
||||
user_metadata: { name, username },
|
||||
});
|
||||
|
||||
if (authError)
|
||||
return NextResponse.json(
|
||||
{ message: authError.message },
|
||||
{ status: StatusCodes.BAD_REQUEST }
|
||||
{ status: StatusCodes.INTERNAL_SERVER_ERROR }
|
||||
);
|
||||
|
||||
if (!authData.user)
|
||||
return NextResponse.json(
|
||||
{ message: "User is null" },
|
||||
{ status: StatusCodes.INTERNAL_SERVER_ERROR }
|
||||
);
|
||||
|
||||
await supabase.from("profiles").insert({
|
||||
id: authData.user.id,
|
||||
name,
|
||||
username,
|
||||
display_name: username,
|
||||
avatar_url: "",
|
||||
node_address: "",
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
message: "ok",
|
||||
user_id: authData.user.id,
|
||||
message: "User registered. Please check your email to verify your account.",
|
||||
user_id: authData.user?.id,
|
||||
email_sent: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,30 +67,14 @@ export async function POST(req: NextRequest) {
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
const params = new URL(req.url).searchParams;
|
||||
const [username, password] = [params.get("username"), params.get("password")];
|
||||
const [email, password] = [params.get("email"), params.get("password")];
|
||||
|
||||
if (!username || !password)
|
||||
if (!email || !password)
|
||||
return NextResponse.json(
|
||||
{ message: "Params username and password are required" },
|
||||
{ message: "Params email and password are required" },
|
||||
{ status: StatusCodes.BAD_REQUEST }
|
||||
);
|
||||
|
||||
let email = username;
|
||||
if (!username.includes("@")) {
|
||||
const { data } = await supabase
|
||||
.from("profiles")
|
||||
.select("email")
|
||||
.eq("email", username)
|
||||
.maybeSingle();
|
||||
email = data?.email;
|
||||
}
|
||||
|
||||
if (!email)
|
||||
return NextResponse.json(
|
||||
{ message: "Invalid username" },
|
||||
{ status: StatusCodes.UNAUTHORIZED }
|
||||
);
|
||||
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
@@ -104,7 +94,6 @@ export async function GET(req: NextRequest) {
|
||||
user_id: session?.user?.id,
|
||||
});
|
||||
|
||||
// Optionally set cookie
|
||||
res.cookies.set("token", session?.access_token ?? "", { httpOnly: true });
|
||||
|
||||
return res;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { supabase } from "../supa";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const email = req.nextUrl.searchParams.get("email");
|
||||
const password = req.nextUrl.searchParams.get("password");
|
||||
|
||||
if (!email || !password)
|
||||
return NextResponse.json(
|
||||
{ message: "Missing email or password" },
|
||||
{ status: StatusCodes.BAD_REQUEST }
|
||||
);
|
||||
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error)
|
||||
return NextResponse.json(
|
||||
{ message: error.message },
|
||||
{ status: StatusCodes.INTERNAL_SERVER_ERROR }
|
||||
);
|
||||
|
||||
if (!data)
|
||||
return NextResponse.json(
|
||||
{ verified: false, message: "User not found" },
|
||||
{ status: StatusCodes.NOT_FOUND }
|
||||
);
|
||||
|
||||
const res = NextResponse.json({
|
||||
verified: !!data.user.email_confirmed_at,
|
||||
user_id: data.user.id,
|
||||
access_token: data.session.access_token,
|
||||
refresh_token: data.session.refresh_token,
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -106,9 +106,7 @@ export default function AppSidebar({
|
||||
];
|
||||
|
||||
setServers(updatedServers);
|
||||
Cookies.set("servers", updatedServers.map((x) => x.join("@")).join(","), {
|
||||
expires: 7,
|
||||
});
|
||||
Cookies.set("servers", updatedServers.map((x) => x.join("@")).join(","));
|
||||
setNewServer({ name: "", ip: "" });
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Cookies from "js-cookie";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { CardContent, CardHeader, CardTitle } from "../ui/card";
|
||||
import { Label } from "../ui/label";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function CreateAccount() {
|
||||
const [username, setUsername] = useState("");
|
||||
@@ -20,9 +21,40 @@ export default function CreateAccount() {
|
||||
const [feedback, setFeedback] = useState<
|
||||
undefined | { kind: "error" | "info"; message: string }
|
||||
>();
|
||||
const [verify, setVerify] = useState<string | undefined>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// Verify interval
|
||||
useEffect(() => {
|
||||
if (verify) {
|
||||
const i = setInterval(async () => {
|
||||
const { data } = await axios.get("/api/verify", {
|
||||
params: { email, password },
|
||||
});
|
||||
const response = data as {
|
||||
verified: boolean;
|
||||
user_id: string;
|
||||
access_token?: string;
|
||||
refresh_token?: string;
|
||||
};
|
||||
if (response.verified) {
|
||||
clearInterval(i);
|
||||
setFeedback({
|
||||
message: "Email verified, redirecting...",
|
||||
kind: "info",
|
||||
});
|
||||
if (response.access_token) {
|
||||
Cookies.set("token", response.access_token);
|
||||
setTimeout(() => router.push("/chat"), 500);
|
||||
} else {
|
||||
console.log(response);
|
||||
}
|
||||
}
|
||||
}, 8000);
|
||||
}
|
||||
}, [verify]);
|
||||
|
||||
const validateInput = ():
|
||||
| { kind: "error" | "info"; message: string }
|
||||
| undefined => {
|
||||
@@ -36,7 +68,7 @@ export default function CreateAccount() {
|
||||
return {
|
||||
kind: "error",
|
||||
message:
|
||||
"Username must be 3–20 characters and only letters, numbers, or underscores.",
|
||||
"Username must be 3-20 characters and only letters, numbers, or underscores.",
|
||||
};
|
||||
}
|
||||
if (password.length < 6) {
|
||||
@@ -67,11 +99,25 @@ export default function CreateAccount() {
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await axios.post("/api/user", { username, password, email });
|
||||
Cookies.set("token", (res.data as any).token);
|
||||
|
||||
setFeedback({ kind: "info", message: "Account created! Redirecting..." });
|
||||
setTimeout(() => router.push("/chat"), 1000);
|
||||
const { data } = await axios.post("/api/user", {
|
||||
username,
|
||||
password,
|
||||
email,
|
||||
});
|
||||
const response = data as {
|
||||
message: string;
|
||||
email_sent: boolean;
|
||||
user_id: string;
|
||||
};
|
||||
if (response.email_sent) {
|
||||
toast.info(
|
||||
"We sent you a verification email. Please verify to continue."
|
||||
);
|
||||
setFeedback(undefined);
|
||||
setVerify(response.user_id);
|
||||
} else {
|
||||
setFeedback({ message: response.message, kind: "error" });
|
||||
}
|
||||
} catch (err: any) {
|
||||
setFeedback({
|
||||
kind: "error",
|
||||
@@ -87,9 +133,15 @@ export default function CreateAccount() {
|
||||
return (
|
||||
<>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl mx-auto">Create an Account</CardTitle>
|
||||
<CardTitle className="text-3xl mx-auto">
|
||||
{verify ? "Verify" : "Create an Account"}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="gap-3 flex flex-col">
|
||||
{verify ? (
|
||||
`We sent you a verification email at (${email}). Please verify to continue.`
|
||||
) : (
|
||||
<>
|
||||
<span>
|
||||
Email
|
||||
<Input
|
||||
@@ -150,6 +202,8 @@ export default function CreateAccount() {
|
||||
<Button onClick={signup} disabled={loading}>
|
||||
{loading ? "Signing Up..." : "Sign Up"}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,18 +6,10 @@ import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import Cookies from "js-cookie";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../ui/card";
|
||||
import { CardContent, CardHeader, CardTitle } from "../ui/card";
|
||||
|
||||
export default function Login() {
|
||||
const [username, setUsername] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [feedback, setFeedback] = useState<
|
||||
undefined | { kind: "error" | "info"; message: string }
|
||||
@@ -29,7 +21,7 @@ export default function Login() {
|
||||
setFeedback(undefined);
|
||||
try {
|
||||
const res = await axios.get("/api/user", {
|
||||
params: { username, password },
|
||||
params: { email, password },
|
||||
});
|
||||
|
||||
Cookies.set("token", (res.data as any).token);
|
||||
@@ -57,7 +49,7 @@ export default function Login() {
|
||||
<CardContent className="gap-3 flex flex-col">
|
||||
<span>
|
||||
Email
|
||||
<Input onChange={(e) => setUsername(e.target.value)} type="email" />
|
||||
<Input onChange={(e) => setEmail(e.target.value)} type="email" />
|
||||
</span>
|
||||
<span>
|
||||
Password
|
||||
|
||||
@@ -17,12 +17,6 @@ export default function useUser() {
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchUser() {
|
||||
const token = Cookies.get("token");
|
||||
if (!token) {
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setUser((await get("/api/profile")).data);
|
||||
} catch (e: any) {
|
||||
|
||||
Reference in New Issue
Block a user