feat: implement local authentication mode and update related components

This commit is contained in:
Abhimanyu Saharan
2026-02-11 19:10:23 +05:30
parent 0ff645f795
commit 06ff1a9720
23 changed files with 563 additions and 93 deletions

View File

@@ -0,0 +1,65 @@
"use client";
import { useState } from "react";
import { Lock } from "lucide-react";
import { setLocalAuthToken } from "@/auth/localAuth";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
export function LocalAuthLogin() {
const [token, setToken] = useState("");
const [error, setError] = useState<string | null>(null);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const cleaned = token.trim();
if (!cleaned) {
setError("Bearer token is required.");
return;
}
setLocalAuthToken(cleaned);
setError(null);
window.location.reload();
};
return (
<div className="flex min-h-screen items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="space-y-3">
<div className="mx-auto rounded-full bg-slate-100 p-3 text-slate-700">
<Lock className="h-5 w-5" />
</div>
<div className="space-y-1 text-center">
<h1 className="text-xl font-semibold text-slate-900">
Local Authentication
</h1>
<p className="text-sm text-slate-600">
Enter the shared local token configured as
<code className="mx-1 rounded bg-slate-100 px-1 py-0.5 text-xs">
LOCAL_AUTH_TOKEN
</code>
on the backend.
</p>
</div>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-3">
<Input
type="password"
value={token}
onChange={(event) => setToken(event.target.value)}
placeholder="Paste token"
autoFocus
/>
{error ? <p className="text-sm text-red-600">{error}</p> : null}
<Button type="submit" className="w-full">
Continue
</Button>
</form>
</CardContent>
</Card>
</div>
);
}

View File

@@ -4,6 +4,7 @@ import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { SignOutButton, useUser } from "@/auth/clerk";
import { clearLocalAuthToken, isLocalAuthMode } from "@/auth/localAuth";
import {
Activity,
Bot,
@@ -36,13 +37,15 @@ export function UserMenu({
}: UserMenuProps) {
const [open, setOpen] = useState(false);
const { user } = useUser();
if (!user) return null;
const localMode = isLocalAuthMode();
if (!user && !localMode) return null;
const avatarUrl = user.imageUrl ?? null;
const avatarLabelSource = displayNameFromDb ?? user.id ?? "U";
const avatarUrl = localMode ? null : (user?.imageUrl ?? null);
const avatarLabelSource =
displayNameFromDb ?? (localMode ? "Local User" : user?.id) ?? "U";
const avatarLabel = avatarLabelSource.slice(0, 1).toUpperCase();
const displayName = displayNameFromDb ?? "Account";
const displayEmail = displayEmailFromDb ?? "";
const displayName = displayNameFromDb ?? (localMode ? "Local User" : "Account");
const displayEmail = displayEmailFromDb ?? (localMode ? "local@localhost" : "");
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -166,16 +169,31 @@ export function UserMenu({
<div className="my-2 h-px bg-[color:var(--neutral-200,var(--border))]" />
<SignOutButton>
{localMode ? (
<button
type="button"
className="flex w-full items-center gap-2 rounded-xl px-3 py-2 text-sm font-semibold text-[color:var(--neutral-800,var(--text))] transition hover:bg-[color:var(--neutral-100,var(--surface-muted))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-teal,var(--accent))] focus-visible:ring-offset-2"
onClick={() => setOpen(false)}
onClick={() => {
clearLocalAuthToken();
setOpen(false);
window.location.reload();
}}
>
<LogOut className="h-4 w-4 text-[color:var(--neutral-700,var(--text-quiet))]" />
Sign out
</button>
</SignOutButton>
) : (
<SignOutButton>
<button
type="button"
className="flex w-full items-center gap-2 rounded-xl px-3 py-2 text-sm font-semibold text-[color:var(--neutral-800,var(--text))] transition hover:bg-[color:var(--neutral-100,var(--surface-muted))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-teal,var(--accent))] focus-visible:ring-offset-2"
onClick={() => setOpen(false)}
>
<LogOut className="h-4 w-4 text-[color:var(--neutral-700,var(--text-quiet))]" />
Sign out
</button>
</SignOutButton>
)}
</div>
</PopoverContent>
</Popover>

View File

@@ -4,8 +4,17 @@ import { ClerkProvider } from "@clerk/nextjs";
import type { ReactNode } from "react";
import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey";
import { getLocalAuthToken, isLocalAuthMode } from "@/auth/localAuth";
import { LocalAuthLogin } from "@/components/organisms/LocalAuthLogin";
export function AuthProvider({ children }: { children: ReactNode }) {
if (isLocalAuthMode()) {
if (!getLocalAuthToken()) {
return <LocalAuthLogin />;
}
return <>{children}</>;
}
const publishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
const afterSignOutUrl =
process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_OUT_URL ?? "/";