@@ -15,15 +13,18 @@ export default function SignInPage() {
Please sign in with your Cognito account to continue
- instead of to trigger a full-page navigation.
+ The sign-in route returns a 302 redirect to Cognito, which
+ would cause a CORS error if fetched via client-side navigation. */}
+ {/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
+
Sign in with Cognito
-
+
diff --git a/webapp/src/components/Header.tsx b/webapp/src/components/Header.tsx
index 53c1ccc..dd2626c 100644
--- a/webapp/src/components/Header.tsx
+++ b/webapp/src/components/Header.tsx
@@ -1,11 +1,6 @@
-'use client';
-
import Link from 'next/link';
-import { useRouter } from 'next/navigation';
export default function Header() {
- const router = useRouter();
-
return (
@@ -16,13 +11,16 @@ export default function Header() {
- instead of to trigger a full-page navigation.
+ The sign-out route returns a 302 redirect to Cognito, which
+ would cause a CORS error if fetched via client-side navigation. */}
+ {/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
+
Sign Out
-
+
diff --git a/webapp/src/lib/auth.ts b/webapp/src/lib/auth.ts
index 2231648..bf60472 100644
--- a/webapp/src/lib/auth.ts
+++ b/webapp/src/lib/auth.ts
@@ -1,13 +1,15 @@
+import { cache } from 'react';
import { cookies } from 'next/headers';
import { fetchAuthSession } from 'aws-amplify/auth/server';
import { runWithAmplifyServerContext } from '@/lib/amplifyServerUtils';
import { prisma } from '@/lib/prisma';
-export class UserNotCreatedError {
- constructor(public readonly userId: string) {}
-}
-
-export async function getSession() {
+/**
+ * Get the authenticated session without DB access.
+ * Use when only userId/email/accessToken is needed.
+ * Memoized per request via React cache().
+ */
+export const getAuthSession = cache(async () => {
const session = await runWithAmplifyServerContext({
nextServerContext: { cookies },
operation: (contextSpec) => fetchAuthSession(contextSpec),
@@ -15,24 +17,42 @@ export async function getSession() {
if (session.userSub == null || session.tokens?.idToken == null || session.tokens?.accessToken == null) {
throw new Error('session not found');
}
- const userId = session.userSub;
const email = session.tokens.idToken.payload.email;
if (typeof email != 'string') {
- throw new Error(`invalid email ${userId}.`);
- }
- const user = await prisma.user.findUnique({
- where: {
- id: userId,
- },
- });
- if (user == null) {
- throw new UserNotCreatedError(userId);
+ throw new Error(`invalid email ${session.userSub}.`);
}
-
return {
- userId: user.id,
+ userId: session.userSub,
email,
accessToken: session.tokens.accessToken.toString(),
- user,
};
+});
+
+/**
+ * Try to get the authenticated session, returning null on failure.
+ * Use in API Routes to avoid try/catch boilerplate for auth checks.
+ */
+export async function tryGetAuthSession() {
+ try {
+ return await getAuthSession();
+ } catch {
+ return null;
+ }
+}
+
+/**
+ * Get the authenticated session with the User record from DB.
+ * Memoized per request via React cache().
+ */
+export const getSessionWithUser = cache(async () => {
+ const auth = await getAuthSession();
+ const user = await prisma.user.findUnique({ where: { id: auth.userId } });
+ if (user == null) {
+ throw new UserNotFoundError(auth.userId);
+ }
+ return { ...auth, user };
+});
+
+export class UserNotFoundError {
+ constructor(public readonly userId: string) {}
}
diff --git a/webapp/src/lib/safe-action.ts b/webapp/src/lib/safe-action.ts
index e0d2ef0..d4bd698 100644
--- a/webapp/src/lib/safe-action.ts
+++ b/webapp/src/lib/safe-action.ts
@@ -1,8 +1,5 @@
-import { prisma } from '@/lib/prisma';
-import { runWithAmplifyServerContext } from '@/lib/amplifyServerUtils';
-import { getCurrentUser } from 'aws-amplify/auth/server';
+import { getSessionWithUser } from '@/lib/auth';
import { createSafeActionClient, DEFAULT_SERVER_ERROR_MESSAGE } from 'next-safe-action';
-import { cookies } from 'next/headers';
export class MyCustomError extends Error {
constructor(message: string) {
@@ -28,24 +25,6 @@ const actionClient = createSafeActionClient({
});
export const authActionClient = actionClient.use(async ({ next }) => {
- const currentUser = await runWithAmplifyServerContext({
- nextServerContext: { cookies },
- operation: (contextSpec) => getCurrentUser(contextSpec),
- });
-
- if (!currentUser) {
- throw new Error('Session is not valid!');
- }
-
- const user = await prisma.user.findUnique({
- where: {
- id: currentUser.userId,
- },
- });
-
- if (user == null) {
- throw new Error('user not found');
- }
-
+ const { user } = await getSessionWithUser();
return next({ ctx: { userId: user.id } });
});