Fixed auth security
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { generateToken } from "../../utils/jwt";
|
||||
|
||||
interface AuthRequest {
|
||||
code: string;
|
||||
@ -15,11 +16,15 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
const secretCode = process.env.SECRET_CODE || import.meta.env.SECRET_CODE;
|
||||
const adminCode = process.env.ADMIN_CODE || import.meta.env.ADMIN_CODE;
|
||||
|
||||
let role = "guest";
|
||||
let role: "guest" | "admin" | null = null;
|
||||
|
||||
if (code === adminCode) {
|
||||
role = "admin";
|
||||
} else if (code !== secretCode) {
|
||||
} else if (code === secretCode) {
|
||||
role = "guest";
|
||||
}
|
||||
|
||||
if (!role) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
@ -45,10 +50,19 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ success: true, role }), {
|
||||
status: 200,
|
||||
headers,
|
||||
});
|
||||
const token = generateToken(role);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
token,
|
||||
role
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers,
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Authentication failed:", error);
|
||||
return new Response(
|
||||
|
@ -1,11 +1,12 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { getS3Data, putS3Data } from "../../../lib/s3";
|
||||
import type { RegistryItem } from "../../../lib/types";
|
||||
import { createProtectedAPIRoute } from "../../../utils/auth-middleware";
|
||||
|
||||
const REGISTRY_FILE_KEY = "registry.json";
|
||||
|
||||
// GET: Get a specific registry item by ID
|
||||
export const GET: APIRoute = async ({ params }) => {
|
||||
// GET: Get a specific registry item by ID (requires guest role)
|
||||
const handleGet: APIRoute = async ({ params }) => {
|
||||
try {
|
||||
const { id } = params;
|
||||
if (!id) {
|
||||
@ -38,8 +39,8 @@ export const GET: APIRoute = async ({ params }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// PUT: Update an existing registry item
|
||||
export const PUT: APIRoute = async ({ request, params }) => {
|
||||
// PUT: Update an existing registry item (requires guest role)
|
||||
const handlePut: APIRoute = async ({ request, params }) => {
|
||||
try {
|
||||
const { id } = params;
|
||||
if (!id) {
|
||||
@ -83,9 +84,8 @@ export const PUT: APIRoute = async ({ request, params }) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// DELETE: Delete a registry item
|
||||
export const DELETE: APIRoute = async ({ params }) => {
|
||||
// DELETE: Delete a registry item (requires admin role)
|
||||
const handleDelete: APIRoute = async ({ params }) => {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
@ -134,3 +134,8 @@ export const DELETE: APIRoute = async ({ params }) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Export protected routes
|
||||
export const GET = createProtectedAPIRoute(handleGet, "guest");
|
||||
export const PUT = createProtectedAPIRoute(handlePut, "guest");
|
||||
export const DELETE = createProtectedAPIRoute(handleDelete, "admin");
|
||||
|
@ -2,11 +2,12 @@ import type { APIRoute } from "astro";
|
||||
import { getS3Data, putS3Data } from "../../../lib/s3";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import type { RegistryItem } from "../../../lib/types";
|
||||
import { createProtectedAPIRoute } from "../../../utils/auth-middleware";
|
||||
|
||||
const REGISTRY_FILE_KEY = "registry.json";
|
||||
|
||||
// GET: List all registry items
|
||||
export const GET: APIRoute = async () => {
|
||||
// GET: List all registry items (requires guest role)
|
||||
const handleGet: APIRoute = async () => {
|
||||
try {
|
||||
const registry = await getS3Data<RegistryItem[]>(REGISTRY_FILE_KEY) || [];
|
||||
return new Response(JSON.stringify(registry), {
|
||||
@ -22,8 +23,8 @@ export const GET: APIRoute = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// POST: Create a new registry item
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
// POST: Create a new registry item (requires admin role)
|
||||
const handlePost: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { name, link } = body;
|
||||
@ -58,3 +59,7 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Export protected routes
|
||||
export const GET = createProtectedAPIRoute(handleGet, "guest");
|
||||
export const POST = createProtectedAPIRoute(handlePost, "admin");
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { getS3Data, putS3Data } from "../../lib/s3";
|
||||
import type { RSVPItem } from "../../lib/types";
|
||||
import { createProtectedAPIRoute } from "../../utils/auth-middleware";
|
||||
|
||||
const objectsToCSV = (data: RSVPItem[]): string => {
|
||||
const headers = ["name", "dietaryRestrictions", "notes", "timestamp"];
|
||||
@ -44,8 +45,8 @@ const csvToObjects = (csv: string): RSVPItem[] => {
|
||||
});
|
||||
};
|
||||
|
||||
// GET: Retrieve all RSVPs
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
// GET: Retrieve all RSVPs (requires admin role)
|
||||
const handleGet: APIRoute = async ({ request }) => {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
@ -81,8 +82,8 @@ export const GET: APIRoute = async ({ request }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// POST: Submit a new RSVP
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
// POST: Submit a new RSVP (requires guest role)
|
||||
const handlePost: APIRoute = async ({ request }) => {
|
||||
console.log("API endpoint hit - starting request processing");
|
||||
|
||||
const headers = {
|
||||
@ -136,3 +137,7 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Export protected routes
|
||||
export const GET = createProtectedAPIRoute(handleGet, "admin");
|
||||
export const POST = createProtectedAPIRoute(handlePost, "guest");
|
||||
|
@ -15,40 +15,33 @@ import SignOut from "../../components/SignOut.tsx";
|
||||
|
||||
<div id="manager-container" class="hidden">
|
||||
<RegistryManager client:load />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 justify-center items-center">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<div class="flex flex-row gap-2 justify-center items-center mt-4">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<SignOut client:load />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
|
||||
<script>
|
||||
const checkAndUpdateVisibility = (role: string | null) => {
|
||||
if (role === "admin") {
|
||||
document.getElementById("auth-container")?.classList.add("hidden");
|
||||
document
|
||||
.getElementById("manager-container")
|
||||
?.classList.remove("hidden");
|
||||
import { hasRole } from "../../utils/auth-client";
|
||||
|
||||
function updateVisibility() {
|
||||
const authContainer = document.getElementById("auth-container");
|
||||
const managerContainer = document.getElementById("manager-container");
|
||||
|
||||
if (hasRole("admin")) {
|
||||
authContainer?.classList.add("hidden");
|
||||
managerContainer?.classList.remove("hidden");
|
||||
} else {
|
||||
document
|
||||
.getElementById("auth-container")
|
||||
?.classList.remove("hidden");
|
||||
document
|
||||
.getElementById("manager-container")
|
||||
?.classList.add("hidden");
|
||||
authContainer?.classList.remove("hidden");
|
||||
managerContainer?.classList.add("hidden");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check auth state on page load
|
||||
const isAuthenticated =
|
||||
sessionStorage.getItem("isAuthenticated") === "true";
|
||||
const role = sessionStorage.getItem("role");
|
||||
checkAndUpdateVisibility(role);
|
||||
updateVisibility();
|
||||
|
||||
// Add event listener for custom event from SignIn component
|
||||
document.addEventListener("auth-success", ((event: CustomEvent) => {
|
||||
const newRole = event.detail?.role || sessionStorage.getItem("role");
|
||||
checkAndUpdateVisibility(newRole);
|
||||
}) as EventListener);
|
||||
document.addEventListener("auth-success", updateVisibility);
|
||||
</script>
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import RegistryList from "../../components/RegistryList.tsx";
|
||||
import SignIn from "../../components/SignIn.tsx";
|
||||
import SignOut from "../../components/SignOut.tsx";
|
||||
---
|
||||
|
||||
@ -10,37 +11,44 @@ import SignOut from "../../components/SignOut.tsx";
|
||||
View and Claim Items from the Registry:
|
||||
</div>
|
||||
|
||||
<div id="registry-container">
|
||||
<div id="auth-container">
|
||||
<SignIn client:load onSuccess={() => {}} requiredRole="guest" />
|
||||
</div>
|
||||
|
||||
<div id="content-container" class="hidden">
|
||||
<RegistryList client:load />
|
||||
<div id="empty-registry-message" class="text-center p-8 hidden">
|
||||
<p class="text-xl">Nothing here yet! Please check back in a week!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 justify-center items-center">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<div class="flex flex-row gap-2 justify-center items-center mt-4">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<SignOut client:load />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
// Check auth state on page load
|
||||
const isAuthenticated =
|
||||
sessionStorage.getItem("isAuthenticated") === "true";
|
||||
if (isAuthenticated) {
|
||||
document.getElementById("auth-container")?.classList.add("hidden");
|
||||
document
|
||||
.getElementById("registry-container")
|
||||
?.classList.remove("hidden");
|
||||
import { hasRole, isAuthenticated } from "../../utils/auth-client";
|
||||
|
||||
function updateVisibility() {
|
||||
const authContainer = document.getElementById("auth-container");
|
||||
const contentContainer = document.getElementById("content-container");
|
||||
|
||||
if (isAuthenticated() && hasRole("guest")) {
|
||||
authContainer?.classList.add("hidden");
|
||||
contentContainer?.classList.remove("hidden");
|
||||
} else {
|
||||
authContainer?.classList.remove("hidden");
|
||||
contentContainer?.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// Check auth state on page load
|
||||
updateVisibility();
|
||||
|
||||
// Add event listener for custom event from SignIn component
|
||||
document.addEventListener("auth-success", () => {
|
||||
document.getElementById("auth-container")?.classList.add("hidden");
|
||||
document
|
||||
.getElementById("registry-container")
|
||||
?.classList.remove("hidden");
|
||||
});
|
||||
document.addEventListener("auth-success", updateVisibility);
|
||||
|
||||
// Check for empty registry
|
||||
document.addEventListener("registry-empty", () => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import RSVP from "../components/RSVP.tsx";
|
||||
import SignIn from "../components/SignIn.tsx";
|
||||
import SignOut from "../components/SignOut.tsx";
|
||||
---
|
||||
|
||||
@ -10,26 +11,39 @@ import SignOut from "../components/SignOut.tsx";
|
||||
Please RSVP using the form below:
|
||||
</div>
|
||||
|
||||
<RSVP client:load />
|
||||
<div id="auth-container">
|
||||
<SignIn client:load onSuccess={() => {}} requiredRole="guest" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 justify-center items-center">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<div id="content-container" class="hidden">
|
||||
<RSVP client:load />
|
||||
<div class="flex flex-row gap-2 justify-center items-center mt-4">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<SignOut client:load />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
// Check auth state on page load
|
||||
const isAuthenticated =
|
||||
sessionStorage.getItem("isAuthenticated") === "true";
|
||||
if (isAuthenticated) {
|
||||
document.getElementById("auth-container")?.classList.add("hidden");
|
||||
document.getElementById("rsvp-container")?.classList.remove("hidden");
|
||||
import { hasRole, isAuthenticated } from "../utils/auth-client";
|
||||
|
||||
function updateVisibility() {
|
||||
const authContainer = document.getElementById("auth-container");
|
||||
const contentContainer = document.getElementById("content-container");
|
||||
|
||||
if (isAuthenticated() && hasRole("guest")) {
|
||||
authContainer?.classList.add("hidden");
|
||||
contentContainer?.classList.remove("hidden");
|
||||
} else {
|
||||
authContainer?.classList.remove("hidden");
|
||||
contentContainer?.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// Check auth state on page load
|
||||
updateVisibility();
|
||||
|
||||
// Add event listener for custom event from SignIn component
|
||||
document.addEventListener("auth-success", () => {
|
||||
document.getElementById("auth-container")?.classList.add("hidden");
|
||||
document.getElementById("rsvp-container")?.classList.remove("hidden");
|
||||
});
|
||||
document.addEventListener("auth-success", updateVisibility);
|
||||
</script>
|
||||
|
@ -15,40 +15,33 @@ import SignOut from "../../components/SignOut.tsx";
|
||||
|
||||
<div id="manager-container" class="hidden">
|
||||
<RSVPManager client:load />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 justify-center items-center">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<div class="flex flex-row gap-2 justify-center items-center mt-4">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<SignOut client:load />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
|
||||
<script>
|
||||
const checkAndUpdateVisibility = (role: string | null) => {
|
||||
if (role === "admin") {
|
||||
document.getElementById("auth-container")?.classList.add("hidden");
|
||||
document
|
||||
.getElementById("manager-container")
|
||||
?.classList.remove("hidden");
|
||||
import { hasRole } from "../../utils/auth-client";
|
||||
|
||||
function updateVisibility() {
|
||||
const authContainer = document.getElementById("auth-container");
|
||||
const managerContainer = document.getElementById("manager-container");
|
||||
|
||||
if (hasRole("admin")) {
|
||||
authContainer?.classList.add("hidden");
|
||||
managerContainer?.classList.remove("hidden");
|
||||
} else {
|
||||
document
|
||||
.getElementById("auth-container")
|
||||
?.classList.remove("hidden");
|
||||
document
|
||||
.getElementById("manager-container")
|
||||
?.classList.add("hidden");
|
||||
authContainer?.classList.remove("hidden");
|
||||
managerContainer?.classList.add("hidden");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check auth state on page load
|
||||
const isAuthenticated =
|
||||
sessionStorage.getItem("isAuthenticated") === "true";
|
||||
const role = sessionStorage.getItem("role");
|
||||
checkAndUpdateVisibility(role);
|
||||
updateVisibility();
|
||||
|
||||
// Add event listener for custom event from SignIn component
|
||||
document.addEventListener("auth-success", ((event: CustomEvent) => {
|
||||
const newRole = event.detail?.role || sessionStorage.getItem("role");
|
||||
checkAndUpdateVisibility(newRole);
|
||||
}) as EventListener);
|
||||
document.addEventListener("auth-success", updateVisibility);
|
||||
</script>
|
49
src/pages/rsvp/index.astro
Normal file
49
src/pages/rsvp/index.astro
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import RSVP from "../../components/RSVP.tsx";
|
||||
import SignIn from "../../components/SignIn.tsx";
|
||||
import SignOut from "../../components/SignOut.tsx";
|
||||
---
|
||||
|
||||
<Layout title="RSVP">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="text-center text-4xl">
|
||||
Please RSVP using the form below:
|
||||
</div>
|
||||
|
||||
<div id="auth-container">
|
||||
<SignIn client:load onSuccess={() => {}} requiredRole="guest" />
|
||||
</div>
|
||||
|
||||
<div id="content-container" class="hidden">
|
||||
<RSVP client:load />
|
||||
<div class="flex flex-row gap-2 justify-center items-center mt-4">
|
||||
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||
<SignOut client:load />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
import { hasRole } from "../../utils/auth-client";
|
||||
|
||||
function updateVisibility() {
|
||||
const authContainer = document.getElementById("auth-container");
|
||||
const contentContainer = document.getElementById("content-container");
|
||||
|
||||
if (hasRole("guest")) {
|
||||
authContainer?.classList.add("hidden");
|
||||
contentContainer?.classList.remove("hidden");
|
||||
} else {
|
||||
authContainer?.classList.remove("hidden");
|
||||
contentContainer?.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// Check auth state on page load
|
||||
updateVisibility();
|
||||
|
||||
// Add event listener for custom event from SignIn component
|
||||
document.addEventListener("auth-success", updateVisibility);
|
||||
</script>
|
Reference in New Issue
Block a user