Auto-commit 2026-04-29 16:31

This commit is contained in:
2026-04-29 16:31:27 -04:00
parent e8687bb6b2
commit 0495ee5bd2
19691 changed files with 3272886 additions and 138 deletions

View File

@@ -0,0 +1,114 @@
import { NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';
import AppleProvider from 'next-auth/providers/apple';
import { z } from 'zod';
// Environment variables
const envSchema = z.object({
NEXTAUTH_URL: z.string().url(),
NEXTAUTH_SECRET: z.string().min(32),
GOOGLE_CLIENT_ID: z.string(),
GOOGLE_CLIENT_SECRET: z.string(),
APPLE_CLIENT_ID: z.string(),
APPLE_CLIENT_SECRET: z.string(),
DATABASE_URL: z.string().url(),
});
export const authEnv = envSchema.parse({
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
APPLE_CLIENT_ID: process.env.APPLE_CLIENT_ID,
APPLE_CLIENT_SECRET: process.env.APPLE_CLIENT_SECRET,
DATABASE_URL: process.env.DATABASE_URL,
});
// Role-based access control
export type UserRole = 'user' | 'family_admin' | 'family_member' | 'support';
export const userRoles: UserRole[] = ['user', 'family_admin', 'family_member', 'support'];
// Family group types
export type FamilyGroup = {
id: string;
name: string;
members: string[]; // user IDs
createdAt: Date;
updatedAt: Date;
};
// NextAuth options
export const authOptions: NextAuthOptions = {
providers: [
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('Email and password required');
}
// TODO: Validate against database
const user = {
id: '1',
email: credentials.email,
name: credentials.email.split('@')[0],
role: 'user' as UserRole,
};
return user;
},
}),
GoogleProvider({
clientId: authEnv.GOOGLE_CLIENT_ID,
clientSecret: authEnv.GOOGLE_CLIENT_SECRET,
}),
AppleProvider({
clientId: authEnv.APPLE_CLIENT_ID,
clientSecret: authEnv.APPLE_CLIENT_SECRET,
}),
],
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: '/auth/signin',
signOut: '/auth/signout',
error: '/auth/error',
},
callbacks: {
async jwt({ token, user, account }) {
if (user) {
token.id = user.id;
token.role = (user as any).role;
}
if (account) {
token.provider = account.provider;
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
session.user.role = token.role as UserRole;
}
return session;
},
},
events: {
async createUser({ user }) {
// TODO: Create default family group
console.log('New user created:', user.email);
},
},
};

View File

@@ -0,0 +1,25 @@
// Config
export { authOptions, authEnv, userRoles } from './config/auth.config';
export type { UserRole, FamilyGroup } from './config/auth.config';
// Middleware
export { withAuth, withRole, protectApiRoute } from './middleware/auth.middleware';
// Models
export {
userSchema,
familyGroupSchema,
familyMemberSchema,
sessionSchema,
accountSchema,
createUserSchema,
createFamilyGroupSchema,
addFamilyMemberSchema,
} from './models/auth.models';
export type {
User,
FamilyGroup as AuthFamilyGroup,
FamilyMember,
Session,
Account,
} from './models/auth.models';

View File

@@ -0,0 +1,62 @@
import { NextRequest, NextResponse } from 'next-auth/react';
import { UserRole } from '../config/auth.config';
/**
* Middleware to protect routes that require authentication
*/
export function withAuth(
request: NextRequest,
options?: {
signInPath?: string;
}
): NextResponse {
const token = request.cookies.get('next-auth.session-token')?.value;
const signInPath = options?.signInPath ?? '/auth/signin';
if (!token) {
const signInUrl = new URL(signInPath, request.url);
signInUrl.searchParams.set('callbackUrl', request.nextUrl.pathname);
return NextResponse.redirect(signInUrl);
}
return NextResponse.next();
}
/**
* Middleware to check if user has required role
*/
export function withRole(
response: NextResponse,
request: NextRequest,
requiredRoles: UserRole[]
): NextResponse {
const token = request.cookies.get('next-auth.session-token')?.value;
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// TODO: Decode JWT and check role
// For now, allow all authenticated users
return response;
}
/**
* Middleware to protect API routes
*/
export function protectApiRoute(request: NextRequest): NextResponse {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Missing or invalid token' }, { status: 401 });
}
const token = authHeader.split(' ')[1];
try {
// TODO: Verify JWT token
return NextResponse.next();
} catch (error) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
}

View File

@@ -0,0 +1,81 @@
import { z } from 'zod';
// User schema
export const userSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1),
image: z.string().url().optional(),
role: z.enum(['user', 'family_admin', 'family_member', 'support']),
emailVerified: z.date().optional(),
createdAt: z.date(),
updatedAt: z.date(),
});
export type User = z.infer<typeof userSchema>;
// Family group schema
export const familyGroupSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
ownerId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
});
export type FamilyGroup = z.infer<typeof familyGroupSchema>;
// Family member schema
export const familyMemberSchema = z.object({
id: z.string().uuid(),
groupId: z.string().uuid(),
userId: z.string().uuid(),
role: z.enum(['owner', 'admin', 'member']),
joinedAt: z.date(),
});
export type FamilyMember = z.infer<typeof familyMemberSchema>;
// Session schema
export const sessionSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
sessionToken: z.string(),
expires: z.date(),
createdAt: z.date(),
});
export type Session = z.infer<typeof sessionSchema>;
// Account schema (for OAuth)
export const accountSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
provider: z.string(),
providerAccountId: z.string(),
access_token: z.string().optional(),
refresh_token: z.string().optional(),
expires_at: z.number().optional(),
token_type: z.string().optional(),
scope: z.string().optional(),
});
export type Account = z.infer<typeof accountSchema>;
// Validation schemas for API
export const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1),
});
export const createFamilyGroupSchema = z.object({
name: z.string().min(1).max(100),
ownerId: z.string().uuid(),
});
export const addFamilyMemberSchema = z.object({
groupId: z.string().uuid(),
userId: z.string().uuid(),
role: z.enum(['admin', 'member']).default('member'),
});