Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions webapp/src/app/(root)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { prisma } from '@/lib/prisma';
import { getSession } from '@/lib/auth';
import { getAuthSession } from '@/lib/auth';
import TodoItemComponent from './components/TodoItem';
import CreateTodoForm from './components/CreateTodoForm';
import { TodoItemStatus } from '@prisma/client';
import Header from '@/components/Header';

export default async function Home() {
const { userId } = await getSession();
const { userId } = await getAuthSession();

const todos = await prisma.todoItem.findMany({
where: {
Expand Down
23 changes: 7 additions & 16 deletions webapp/src/app/auth-callback/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import { redirect } from 'next/navigation';
import { getSession, UserNotCreatedError } from '@/lib/auth';
import { getAuthSession } from '@/lib/auth';
import { prisma } from '@/lib/prisma';

export const dynamic = 'force-dynamic';

export default async function AuthCallbackPage() {
try {
await getSession();
} catch (e) {
console.log(e);
if (e instanceof UserNotCreatedError) {
const userId = e.userId;
console.log(userId);
await prisma.user.create({
data: {
id: userId,
},
});
} else {
throw e;
}
const { userId } = await getAuthSession();

const user = await prisma.user.findUnique({ where: { id: userId } });
if (user == null) {
await prisma.user.create({ data: { id: userId } });
}
Comment thread
konokenj marked this conversation as resolved.

redirect('/');
}
11 changes: 6 additions & 5 deletions webapp/src/app/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Link from 'next/link';

export default function SignInPage() {
return (
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
Expand All @@ -15,15 +13,18 @@ export default function SignInPage() {
Please sign in with your Cognito account to continue
</p>

<Link
{/* Use <a> instead of <Link> 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 */}
<a
href="/api/auth/sign-in"
// you can add a query string to change the locale of cognito managed login page.
// href="/api/auth/sign-in?lang=ja"
className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
prefetch={false} // prevent CORS error
>
Sign in with Cognito
</Link>
</a>
</div>
</div>
</div>
Expand Down
14 changes: 6 additions & 8 deletions webapp/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
'use client';

import Link from 'next/link';
import { useRouter } from 'next/navigation';

export default function Header() {
const router = useRouter();

return (
<header className="bg-indigo-600 text-white shadow-md">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
Expand All @@ -16,13 +11,16 @@ export default function Header() {
</Link>
</div>
<div>
<Link
{/* Use <a> instead of <Link> 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 */}
<a
href="/api/auth/sign-out"
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-indigo-600 bg-white hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
prefetch={false} // prevent CORS error
>
Sign Out
</Link>
</a>
</div>
</div>
</div>
Expand Down
56 changes: 38 additions & 18 deletions webapp/src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,58 @@
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),
});
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') {
Comment thread
konokenj marked this conversation as resolved.
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 where you need to distinguish 401 from 500.
*/
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) {}
}
15 changes: 3 additions & 12 deletions webapp/src/lib/safe-action.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { getAuthSession } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { runWithAmplifyServerContext } from '@/lib/amplifyServerUtils';
import { getCurrentUser } from 'aws-amplify/auth/server';
import { createSafeActionClient, DEFAULT_SERVER_ERROR_MESSAGE } from 'next-safe-action';
import { cookies } from 'next/headers';

export class MyCustomError extends Error {
constructor(message: string) {
Expand All @@ -28,18 +26,11 @@ 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 { userId } = await getAuthSession();

const user = await prisma.user.findUnique({
where: {
id: currentUser.userId,
id: userId,
},
});

Expand Down
Loading