From bde98be752a6e147a17727d74278b50f990c8789 Mon Sep 17 00:00:00 2001 From: boilerrat <128boilerrat@gmail.com> Date: Tue, 18 Mar 2025 22:27:59 -0400 Subject: [PATCH] Moved authentication credentials to environment variables and added settings page for password management --- .env.example | 7 +- src/app/api/auth/change-password/route.ts | 52 +++++++ src/app/api/auth/login/route.ts | 8 +- src/app/contacts/import/page.tsx | 3 + src/app/contacts/new/page.tsx | 3 + src/app/contacts/page.tsx | 3 + src/app/dashboard/page.tsx | 3 + src/app/settings/page.tsx | 85 ++++++++++++ src/components/settings/settings-form.tsx | 157 ++++++++++++++++++++++ src/components/ui/alert.tsx | 58 ++++++++ src/lib/auth.ts | 10 +- 11 files changed, 383 insertions(+), 6 deletions(-) create mode 100644 src/app/api/auth/change-password/route.ts create mode 100644 src/app/settings/page.tsx create mode 100644 src/components/settings/settings-form.tsx create mode 100644 src/components/ui/alert.tsx diff --git a/.env.example b/.env.example index fd85a3f..24afb09 100644 --- a/.env.example +++ b/.env.example @@ -13,4 +13,9 @@ NODE_ENV="development" PORT=3000 # Next.js -NEXT_PUBLIC_API_URL="http://localhost:3000/api" \ No newline at end of file +NEXT_PUBLIC_API_URL="http://localhost:3000/api" + +# Authentication +AUTH_SECRET="your-secure-auth-secret-key" +ADMIN_USERNAME="admin" +ADMIN_PASSWORD="strong-password-here" \ No newline at end of file diff --git a/src/app/api/auth/change-password/route.ts b/src/app/api/auth/change-password/route.ts new file mode 100644 index 0000000..cf9a790 --- /dev/null +++ b/src/app/api/auth/change-password/route.ts @@ -0,0 +1,52 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getUser } from "@/lib/auth"; + +// Get authentication credentials from environment variables +const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'stones1234'; + +export async function POST(request: NextRequest) { + try { + // Check if user is authenticated + const user = await getUser(); + if (!user) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + + // Parse request body + const body = await request.json(); + const { currentPassword, newPassword } = body; + + // Validate inputs + if (!currentPassword || !newPassword) { + return NextResponse.json( + { success: false, message: "Missing required fields" }, + { status: 400 } + ); + } + + // Verify current password + if (currentPassword !== ADMIN_PASSWORD) { + return NextResponse.json( + { success: false, message: "Current password is incorrect" }, + { status: 400 } + ); + } + + // Password validation is successful, but we don't actually change it here + // since it needs to be changed in environment variables + + return NextResponse.json({ + success: true, + message: "Password validation successful. Please update your environment files." + }); + } catch (error) { + console.error("Change password error:", error); + return NextResponse.json( + { success: false, message: "Internal server error" }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 1136d13..545b7eb 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -1,14 +1,18 @@ import { NextRequest, NextResponse } from "next/server"; import { cookies } from "next/headers"; +// Get authentication credentials from environment variables +const ADMIN_USERNAME = process.env.ADMIN_USERNAME || 'admin' +const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'stones1234' + // Mock user data - in a real app this would come from a database const USERS = [ { id: "1", name: "Admin", role: "admin", - username: "admin", - password: "stones1234" // In production, use hashed passwords + username: ADMIN_USERNAME, + password: ADMIN_PASSWORD } ]; diff --git a/src/app/contacts/import/page.tsx b/src/app/contacts/import/page.tsx index cc521d6..68f56e7 100644 --- a/src/app/contacts/import/page.tsx +++ b/src/app/contacts/import/page.tsx @@ -39,6 +39,9 @@ export default async function ImportContactsPage() { Hello, {user.name} + + Settings + )} diff --git a/src/app/contacts/new/page.tsx b/src/app/contacts/new/page.tsx index f4687f4..a66e969 100644 --- a/src/app/contacts/new/page.tsx +++ b/src/app/contacts/new/page.tsx @@ -39,6 +39,9 @@ export default async function NewContactPage() { Hello, {user.name} + + Settings + )} diff --git a/src/app/contacts/page.tsx b/src/app/contacts/page.tsx index 9959781..245d838 100644 --- a/src/app/contacts/page.tsx +++ b/src/app/contacts/page.tsx @@ -86,6 +86,9 @@ export default async function ContactsPage({ searchParams }: ContactsPageProps) Hello, {user.name} + + Settings + )} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index e2dd249..5703526 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -41,6 +41,9 @@ export default async function DashboardPage() { Hello, {user.name} + + Settings + )} diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx new file mode 100644 index 0000000..4b9a07b --- /dev/null +++ b/src/app/settings/page.tsx @@ -0,0 +1,85 @@ +import { Metadata } from "next"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { getUser } from "@/lib/auth"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { LogoutButton } from "@/components/auth/logout-button"; +import { SettingsForm } from "@/components/settings/settings-form"; + +export const metadata: Metadata = { + title: "Settings - Stones Database", + description: "Manage your account settings", +}; + +export default async function SettingsPage() { + const user = await getUser(); + + if (!user) { + notFound(); + } + + return ( +
+
+
+
+ + Stones Database + +
+ +
+
+
+
+
+ + + +

Account Settings

+
+
+ + + + Change Password + + +
+

Important Note

+

+ Since this application uses environment variables for authentication, updating the password here will + guide you on how to update the relevant environment files for the changes to take effect. +

+
+ +
+
+
+
+
+

+ © 2025 BoilerHaus. All rights reserved. +

+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/settings/settings-form.tsx b/src/components/settings/settings-form.tsx new file mode 100644 index 0000000..3907cfb --- /dev/null +++ b/src/components/settings/settings-form.tsx @@ -0,0 +1,157 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { AlertCircle, Check } from "lucide-react"; + +export function SettingsForm() { + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + const [instructions, setInstructions] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + setSuccess(false); + setInstructions(false); + + // Validate passwords + if (!currentPassword || !newPassword || !confirmPassword) { + setError("All fields are required"); + return; + } + + if (newPassword !== confirmPassword) { + setError("New passwords don't match"); + return; + } + + if (newPassword.length < 8) { + setError("Password must be at least 8 characters long"); + return; + } + + setIsLoading(true); + + try { + const response = await fetch("/api/auth/change-password", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + currentPassword, + newPassword, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || "Something went wrong"); + } + + setSuccess(true); + setInstructions(true); + setCurrentPassword(""); + setNewPassword(""); + setConfirmPassword(""); + } catch (error: any) { + setError(error.message); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+ + setCurrentPassword(e.target.value)} + disabled={isLoading} + /> +
+ +
+ + setNewPassword(e.target.value)} + disabled={isLoading} + /> +
+ +
+ + setConfirmPassword(e.target.value)} + disabled={isLoading} + /> +
+ + {error && ( + + + Error + {error} + + )} + + {success && ( + + + Success + Password validation successful! + + )} + +
+ +
+
+ + {instructions && ( +
+

Next Steps to Change Your Password

+

+ Since this application uses environment variables for authentication, you need to update the password in your environment files: +

+
    +
  1. Open the .env file in your project's root directory
  2. +
  3. Find the line with ADMIN_PASSWORD="current-password"
  4. +
  5. Replace current-password with your new password: ADMIN_PASSWORD="{newPassword}"
  6. +
  7. Save the file
  8. +
  9. Restart the application for the changes to take effect
  10. +
+

+ Note: If you're running in production, make sure to also update your production environment variables. +

+
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..26a60d3 --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,58 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } \ No newline at end of file diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 3f08d4c..520e85b 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -9,17 +9,21 @@ export interface User { role: string } -// Set to environment variable in production +// Use environment variable for auth secret const AUTH_SECRET = process.env.AUTH_SECRET || 'stones-database-secret' +// Get authentication credentials from environment variables +const ADMIN_USERNAME = process.env.ADMIN_USERNAME || 'admin' +const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'stones1234' + // In a real app, this would be stored in a database const USERS = [ { id: '1', name: 'Admin', role: 'admin' as const, - username: 'admin', - password: 'stones1234' // In production, use hashed passwords + username: ADMIN_USERNAME, + password: ADMIN_PASSWORD } ]