feat: refactor auth into singleton and improve UI with custom fonts/colors/animations
This commit is contained in:
parent
345ee6a5e6
commit
cb10fd657f
@ -1,94 +1,5 @@
|
|||||||
import NextAuth, { NextAuthOptions } from "next-auth";
|
import NextAuth from "next-auth";
|
||||||
import GoogleProvider from "next-auth/providers/google";
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaAdapter } from "@auth/prisma-adapter";
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
|
||||||
import { compare } from "bcryptjs";
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
|
||||||
adapter: PrismaAdapter(prisma) as any,
|
|
||||||
providers: [
|
|
||||||
GoogleProvider({
|
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID || "",
|
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
|
|
||||||
authorization: {
|
|
||||||
params: {
|
|
||||||
prompt: "consent",
|
|
||||||
access_type: "offline",
|
|
||||||
response_type: "code"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
// Apple Sign In can be added when credentials are available
|
|
||||||
// AppleProvider({
|
|
||||||
// clientId: process.env.APPLE_ID || "",
|
|
||||||
// clientSecret: process.env.APPLE_SECRET || "",
|
|
||||||
// }),
|
|
||||||
CredentialsProvider({
|
|
||||||
name: "credentials",
|
|
||||||
credentials: {
|
|
||||||
email: { label: "Email", type: "email" },
|
|
||||||
password: { label: "Password", type: "password" }
|
|
||||||
},
|
|
||||||
async authorize(credentials) {
|
|
||||||
if (!credentials?.email || !credentials?.password) {
|
|
||||||
throw new Error("Invalid credentials");
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: {
|
|
||||||
email: credentials.email
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user || !user.passwordHash) {
|
|
||||||
throw new Error("Invalid credentials");
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPasswordValid = await compare(
|
|
||||||
credentials.password,
|
|
||||||
user.passwordHash
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isPasswordValid) {
|
|
||||||
throw new Error("Invalid credentials");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name || null,
|
|
||||||
image: user.image || null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
session: {
|
|
||||||
strategy: "jwt"
|
|
||||||
},
|
|
||||||
pages: {
|
|
||||||
signIn: "/auth/login",
|
|
||||||
signOut: "/auth/login",
|
|
||||||
error: "/auth/login",
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
async jwt({ token, user }) {
|
|
||||||
if (user) {
|
|
||||||
token.id = user.id;
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
},
|
|
||||||
async session({ session, token }) {
|
|
||||||
if (session.user) {
|
|
||||||
(session.user as any).id = token.id;
|
|
||||||
}
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
secret: process.env.NEXTAUTH_SECRET
|
|
||||||
};
|
|
||||||
|
|
||||||
const handler = NextAuth(authOptions);
|
const handler = NextAuth(authOptions);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import { createCalendarEvent, updateCalendarEvent, deleteCalendarEvent, CalendarConnection } from '@/lib/calendar-events';
|
import { createCalendarEvent, updateCalendarEvent, deleteCalendarEvent, CalendarConnection } from '@/lib/calendar-events';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { google } from 'googleapis';
|
import { google } from 'googleapis';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { google } from 'googleapis';
|
import { google } from 'googleapis';
|
||||||
|
|
||||||
// Initiate Google Calendar OAuth flow
|
// Initiate Google Calendar OAuth flow
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { prisma } from '@/lib/prisma';
|
import { prisma } from '@/lib/prisma';
|
||||||
import { getTokens, getUserCalendars } from '@/lib/outlook-calendar';
|
import { getTokens, getUserCalendars } from '@/lib/outlook-calendar';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { getAuthUrl } from '@/lib/outlook-calendar';
|
import { getAuthUrl } from '@/lib/outlook-calendar';
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '../../auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import { getCalendarEvents, CalendarConnection } from '@/lib/calendar-events';
|
import { getCalendarEvents, CalendarConnection } from '@/lib/calendar-events';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '../auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient, Task } from '@prisma/client';
|
import { PrismaClient, Task } from '@prisma/client';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from "@/lib/auth";
|
||||||
import { prisma } from '@/lib/prisma';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '../../auth/[...nextauth]/route';
|
import { authOptions } from '@/lib/auth';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
|
|
||||||
|
|||||||
@ -654,7 +654,7 @@ h3 {
|
|||||||
.weekly-day-date {
|
.weekly-day-date {
|
||||||
font-size: var(--weekly-date-size, 0.65rem);
|
font-size: var(--weekly-date-size, 0.65rem);
|
||||||
font-weight: var(--weekly-date-weight, 400);
|
font-weight: var(--weekly-date-weight, 400);
|
||||||
color: var(--weekly-text-light);
|
color: var(--weekly-date-color, var(--weekly-text-light));
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
@ -664,9 +664,8 @@ h3 {
|
|||||||
.weekly-day-name {
|
.weekly-day-name {
|
||||||
font-size: var(--weekly-headline-size, 1.25rem);
|
font-size: var(--weekly-headline-size, 1.25rem);
|
||||||
font-weight: var(--weekly-headline-weight, 900);
|
font-weight: var(--weekly-headline-weight, 900);
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
color: var(--weekly-text);
|
color: var(--weekly-weekday-color, var(--weekly-text));
|
||||||
font-family: var(--weekly-font-headline) !important;
|
font-family: var(--weekly-font-headline) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,7 +716,7 @@ h3 {
|
|||||||
font-size: var(--weekly-task-size, 0.9rem);
|
font-size: var(--weekly-task-size, 0.9rem);
|
||||||
font-weight: var(--weekly-task-weight, 400);
|
font-weight: var(--weekly-task-weight, 400);
|
||||||
line-height: var(--weekly-task-line-height, 1.5);
|
line-height: var(--weekly-task-line-height, 1.5);
|
||||||
color: var(--weekly-text);
|
color: var(--weekly-task-color, var(--weekly-text));
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -1612,9 +1611,9 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.event-title {
|
.event-title {
|
||||||
font-weight: 600;
|
font-weight: var(--weekly-event-weight, 600);
|
||||||
font-size: 0.85rem;
|
font-size: var(--weekly-event-size, 0.85rem);
|
||||||
font-family: var(--weekly-font);
|
font-family: var(--weekly-event-font, var(--weekly-font));
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-slot-event .event-title {
|
.time-slot-event .event-title {
|
||||||
@ -1622,14 +1621,15 @@ h3 {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-weight: 500;
|
font-weight: var(--weekly-event-weight, 500);
|
||||||
font-family: var(--weekly-font);
|
font-family: var(--weekly-event-font, var(--weekly-font));
|
||||||
|
font-size: var(--weekly-event-size, 0.85rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-slot-event .event-time-row {
|
.time-slot-event .event-time-row {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
font-family: var(--weekly-font);
|
font-family: var(--weekly-event-font, var(--weekly-font));
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-slot-event:hover {
|
.time-slot-event:hover {
|
||||||
@ -2274,28 +2274,42 @@ h3 {
|
|||||||
|
|
||||||
/* --- VIEW TRANSITION ANIMATION (Weekly-like) --- */
|
/* --- VIEW TRANSITION ANIMATION (Weekly-like) --- */
|
||||||
|
|
||||||
/* Only apply Container transitions (Full Slide) when navType is 'week' */
|
/* Base Slide Animations */
|
||||||
[data-nav-type="week"] .weekly-days-grid {
|
|
||||||
view-transition-name: week-grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* When navType="day", NO name on container, so columns animate individually via inline view-transition-name */
|
|
||||||
|
|
||||||
@keyframes slideOutToLeft {
|
@keyframes slideOutToLeft {
|
||||||
to {
|
to { transform: translateX(-20%); opacity: 0; }
|
||||||
transform: translateX(-100%);
|
}
|
||||||
opacity: 0.8;
|
@keyframes slideInFromRight {
|
||||||
}
|
from { transform: translateX(20%); opacity: 0; }
|
||||||
|
to { transform: translateX(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
@keyframes slideOutToRight {
|
||||||
|
to { transform: translateX(20%); opacity: 0; }
|
||||||
|
}
|
||||||
|
@keyframes slideInFromLeft {
|
||||||
|
from { transform: translateX(-20%); opacity: 0; }
|
||||||
|
to { transform: translateX(0); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideInFromRight {
|
/* Week Grid Transition Group */
|
||||||
from {
|
::view-transition-group(week-grid) {
|
||||||
transform: translateX(100%);
|
animation-duration: 0.4s;
|
||||||
}
|
animation-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
|
||||||
|
}
|
||||||
|
|
||||||
to {
|
/* Slide Next (Left) */
|
||||||
transform: translateX(0);
|
[data-slide-direction="next"]::view-transition-old(week-grid) {
|
||||||
}
|
animation-name: slideOutToLeft;
|
||||||
|
}
|
||||||
|
[data-slide-direction="next"]::view-transition-new(week-grid) {
|
||||||
|
animation-name: slideInFromRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide Prev (Right) */
|
||||||
|
[data-slide-direction="prev"]::view-transition-old(week-grid) {
|
||||||
|
animation-name: slideOutToRight;
|
||||||
|
}
|
||||||
|
[data-slide-direction="prev"]::view-transition-new(week-grid) {
|
||||||
|
animation-name: slideInFromLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideOutToRight {
|
@keyframes slideOutToRight {
|
||||||
|
|||||||
@ -450,7 +450,7 @@ export default function WeeklyView() {
|
|||||||
const [cellDuration, setCellDuration] = useState<CellDuration>(60);
|
const [cellDuration, setCellDuration] = useState<CellDuration>(60);
|
||||||
const [draggedTask, setDraggedTask] = useState<Task | null>(null);
|
const [draggedTask, setDraggedTask] = useState<Task | null>(null);
|
||||||
const [showTimeGrid, setShowTimeGrid] = useState(true);
|
const [showTimeGrid, setShowTimeGrid] = useState(true);
|
||||||
const [slideDirection, setSlideDirection] = useState<'left' | 'right' | 'out-left' | 'out-right' | 'in-left' | 'in-right' | null>(null);
|
const [slideDirection, setSlideDirection] = useState<'next' | 'prev' | null>(null);
|
||||||
const [activeSlot, setActiveSlot] = useState<{ day: number; slot: string } | null>(null);
|
const [activeSlot, setActiveSlot] = useState<{ day: number; slot: string } | null>(null);
|
||||||
const [newSlotTask, setNewSlotTask] = useState('');
|
const [newSlotTask, setNewSlotTask] = useState('');
|
||||||
const [selectedTaskForNotes, setSelectedTaskForNotes] = useState<Task | null>(null);
|
const [selectedTaskForNotes, setSelectedTaskForNotes] = useState<Task | null>(null);
|
||||||
@ -1149,12 +1149,13 @@ export default function WeeklyView() {
|
|||||||
const navigate = (newDate: Date, direction: 'left' | 'right', type: 'day' | 'week') => {
|
const navigate = (newDate: Date, direction: 'left' | 'right', type: 'day' | 'week') => {
|
||||||
if (typeof document !== 'undefined' && 'startViewTransition' in document) {
|
if (typeof document !== 'undefined' && 'startViewTransition' in document) {
|
||||||
const doc = document as any;
|
const doc = document as any;
|
||||||
doc.documentElement.dataset.transitionDirection = direction === 'left' ? 'next' : 'prev';
|
doc.documentElement.dataset.slideDirection = direction === 'left' ? 'next' : 'prev';
|
||||||
doc.documentElement.dataset.navType = 'week'; // Always use full-grid slide for both day and week
|
doc.documentElement.dataset.navType = 'week'; // Always use full-grid slide for both day and week
|
||||||
|
|
||||||
doc.startViewTransition(() => {
|
doc.startViewTransition(() => {
|
||||||
setCurrentWeekStart(newDate);
|
setCurrentWeekStart(newDate);
|
||||||
setSlideDirection(null);
|
// setSlideDirection state is not strictly needed for global transition but keeping it clean
|
||||||
|
setSlideDirection(direction === 'left' ? 'next' : 'prev');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setCurrentWeekStart(newDate);
|
setCurrentWeekStart(newDate);
|
||||||
@ -1987,8 +1988,10 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
{/* Day Columns */}
|
{/* Day Columns */}
|
||||||
<main
|
<main
|
||||||
className={`weekly-days-grid cols-${viewDays} ${slideDirection ? `slide-${slideDirection}` : ''}`}
|
className={`weekly-days-grid cols-${viewDays}`}
|
||||||
style={{ viewTransitionName: 'week-grid' } as React.CSSProperties}
|
data-slide-direction={slideDirection}
|
||||||
|
data-nav-type={viewDays > 1 ? 'week' : 'day'}
|
||||||
|
style={{ viewTransitionName: viewDays > 1 ? 'week-grid' : 'none' } as React.CSSProperties}
|
||||||
>
|
>
|
||||||
{getVisibleDays().map((date, colIndex) => (
|
{getVisibleDays().map((date, colIndex) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
117
src/lib/auth.ts
117
src/lib/auth.ts
@ -1,39 +1,84 @@
|
|||||||
// Mock authentication utilities
|
import { NextAuthOptions } from "next-auth";
|
||||||
|
import GoogleProvider from "next-auth/providers/google";
|
||||||
|
import { PrismaAdapter } from "@auth/prisma-adapter";
|
||||||
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
|
import { compare } from "bcryptjs";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export interface UserSession {
|
export const authOptions: NextAuthOptions = {
|
||||||
id: string;
|
adapter: PrismaAdapter(prisma) as any,
|
||||||
email: string;
|
providers: [
|
||||||
}
|
GoogleProvider({
|
||||||
|
clientId: process.env.GOOGLE_CLIENT_ID || "",
|
||||||
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
|
||||||
|
authorization: {
|
||||||
|
params: {
|
||||||
|
prompt: "consent",
|
||||||
|
access_type: "offline",
|
||||||
|
response_type: "code"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
CredentialsProvider({
|
||||||
|
name: "credentials",
|
||||||
|
credentials: {
|
||||||
|
email: { label: "Email", type: "email" },
|
||||||
|
password: { label: "Password", type: "password" }
|
||||||
|
},
|
||||||
|
async authorize(credentials) {
|
||||||
|
if (!credentials?.email || !credentials?.password) {
|
||||||
|
throw new Error("Invalid credentials");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
const user = await prisma.user.findUnique({
|
||||||
* Verify authentication token and return user session or null
|
where: {
|
||||||
*/
|
email: credentials.email
|
||||||
export async function verifyAuth(): Promise<UserSession | null> {
|
}
|
||||||
// In a real implementation, this would decode and validate the JWT token
|
});
|
||||||
// This is a simplified mock implementation
|
|
||||||
const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null;
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock verification - in reality this would validate the JWT signature
|
|
||||||
// and check expiration etc.
|
|
||||||
return {
|
|
||||||
id: 'mock-user-id',
|
|
||||||
email: 'user@example.com'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (!user || !user.passwordHash) {
|
||||||
* Require authentication - throw error if no valid session, return session if valid
|
throw new Error("Invalid credentials");
|
||||||
*/
|
}
|
||||||
export async function requireAuth(): Promise<UserSession> {
|
|
||||||
const session = await verifyAuth();
|
const isPasswordValid = await compare(
|
||||||
|
credentials.password,
|
||||||
if (!session) {
|
user.passwordHash
|
||||||
throw new Error('Authentication required');
|
);
|
||||||
}
|
|
||||||
|
if (!isPasswordValid) {
|
||||||
return session;
|
throw new Error("Invalid credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name || null,
|
||||||
|
image: user.image || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
session: {
|
||||||
|
strategy: "jwt"
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: "/auth/login",
|
||||||
|
signOut: "/auth/login",
|
||||||
|
error: "/auth/login",
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
async jwt({ token, user }) {
|
||||||
|
if (user) {
|
||||||
|
token.id = user.id;
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
},
|
||||||
|
async session({ session, token }) {
|
||||||
|
if (session.user) {
|
||||||
|
(session.user as any).id = token.id;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
secret: process.env.NEXTAUTH_SECRET
|
||||||
|
};
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user