Security Comprehensive security guide for ShipKit applications covering authentication, data protection, API security, and infrastructure security
Security
This guide covers security best practices and implementations for ShipKit applications, including authentication, data protection, API security, and infrastructure security.
Authentication
NextAuth.js Configuration
// src/lib/auth/config.ts
import { NextAuthConfig } from 'next-auth'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { db } from '@/server/db'
import { env } from '@/env.mjs'
export const authConfig: NextAuthConfig = {
adapter: PrismaAdapter(db),
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: '/auth/signin',
error: '/auth/error',
verifyRequest: '/auth/verify',
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard')
if (isOnDashboard) {
if (isLoggedIn) return true
return false
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl))
}
return true
},
jwt({ token, user, account, profile }) {
if (user) {
token.id = user.id
token.role = user.role
}
return token
},
session({ session, token }) {
if (token && session.user) {
session.user.id = token.id as string
session.user.role = token.role as string
}
return session
},
},
providers: [], // Configured in auth.ts
}
Role-Based Access Control (RBAC)
// src/lib/auth/rbac.ts
import { getServerSession } from 'next-auth'
import { redirect } from 'next/navigation'
export enum Role {
USER = 'USER',
ADMIN = 'ADMIN',
SUPER_ADMIN = 'SUPER_ADMIN',
}
export const permissions = {
[Role.USER]: ['read:own_data', 'write:own_data'],
[Role.ADMIN]: ['read:all_data', 'write:all_data', 'manage:users'],
[Role.SUPER_ADMIN]: ['read:all_data', 'write:all_data', 'manage:users', 'manage:system'],
} as const
export async function checkPermission(permission: string) {
const session = await getServerSession()
if (!session?.user) {
redirect('/auth/signin')
}
const userRole = session.user.role as Role
const userPermissions = permissions[userRole] || []
return userPermissions.includes(permission)
}
export function withPermission(permission: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value
descriptor.value = async function (...args: any[]) {
const hasPermission = await checkPermission(permission)
if (!hasPermission) {
throw new Error('Unauthorized')
}
return originalMethod.apply(this, args)
}
return descriptor
}
}
Password Security
// src/lib/auth/password.ts
import { hash, compare } from 'bcryptjs'
import { z } from 'zod'
const passwordSchema = z
.string()
.min(8)
.max(100)
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/)
export async function hashPassword(password: string): Promise<string> {
if (!passwordSchema.safeParse(password).success) {
throw new Error(
'Password must be 8-100 characters and include uppercase, lowercase, number, and special character'
)
}
return hash(password, 12)
}
export async function verifyPassword(
password: string,
hashedPassword: string
): Promise<boolean> {
return compare(password, hashedPassword)
}
export function validatePassword(password: string): boolean {
return passwordSchema.safeParse(password).success
}
Data Protection
Encryption at Rest
// src/lib/security/encryption.ts
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'
import { env } from '@/env.mjs'
const algorithm = 'aes-256-gcm'
const keyLength = 32
const ivLength = 16
const authTagLength = 16
interface EncryptedData {
encrypted: string
iv: string
authTag: string
}
export function encrypt(text: string): EncryptedData {
const iv = randomBytes(ivLength)
const cipher = createCipheriv(algorithm, Buffer.from(env.ENCRYPTION_KEY, 'hex'), iv)
let encrypted = cipher.update(text, 'utf8', 'hex')
encrypted += cipher.final('hex')
return {
encrypted,
iv: iv.toString('hex'),
authTag: cipher.getAuthTag().toString('hex'),
}
}
export function decrypt(data: EncryptedData): string {
const decipher = createDecipheriv(
algorithm,
Buffer.from(env.ENCRYPTION_KEY, 'hex'),
Buffer.from(data.iv, 'hex')
)
decipher.setAuthTag(Buffer.from(data.authTag, 'hex'))
let decrypted = decipher.update(data.encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}
Data Sanitization
// src/lib/security/sanitization.ts
import { z } from 'zod'
import DOMPurify from 'isomorphic-dompurify'
export function sanitizeHtml(html: string): string {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
})
}
export function sanitizeFilename(filename: string): string {
return filename.replace(/[^a-zA-Z0-9.-]/g, '_')
}
export const userInputSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(50).regex(/^[a-zA-Z0-9\s-]*$/),
bio: z.string().max(500).transform(sanitizeHtml),
})
export function validateAndSanitizeInput<T>(
schema: z.ZodSchema<T>,
data: unknown
): T {
return schema.parse(data)
}
API Security
Rate Limiting
// src/lib/security/rate-limit.ts
import { Redis } from '@upstash/redis'
import { Ratelimit } from '@upstash/ratelimit'
import { headers } from 'next/headers'
import { env } from '@/env.mjs'
const redis = new Redis({
url: env.UPSTASH_REDIS_URL,
token: env.UPSTASH_REDIS_TOKEN,
})
export const rateLimiter = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '10 s'),
analytics: true,
})
export async function checkRateLimit() {
const headersList = headers()
const ip = headersList.get('x-forwarded-for') ?? 'anonymous'
const { success, limit, reset, remaining } = await rateLimiter.limit(ip)
if (!success) {
throw new Error(
`Rate limit exceeded. Try again in ${Math.ceil(
(reset - Date.now()) / 1000
)} seconds`
)
}
return { limit, remaining, reset }
}
API Authentication
// src/lib/security/api-auth.ts
import { type NextRequest } from 'next/server'
import { getToken } from 'next-auth/jwt'
import { env } from '@/env.mjs'
export async function authenticateApiRequest(req: NextRequest) {
// Check for API key
const apiKey = req.headers.get('x-api-key')
if (apiKey === env.API_SECRET_KEY) {
return { type: 'api-key' as const }
}
// Check for JWT token
const token = await getToken({ req })
if (token) {
return { type: 'jwt' as const, token }
}
throw new Error('Unauthorized')
}
export function validateApiKey(apiKey: string): boolean {
return apiKey === env.API_SECRET_KEY
}
CORS Configuration
// src/middleware.ts
import { type NextRequest } from 'next/server'
import { corsHeaders } from './lib/security/cors'
export function middleware(request: NextRequest) {
// Check if request is for API
if (request.nextUrl.pathname.startsWith('/api/')) {
// Handle preflight requests
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: corsHeaders,
})
}
// Add CORS headers to response
const response = NextResponse.next()
Object.entries(corsHeaders).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
return NextResponse.next()
}
export const config = {
matcher: '/api/:path*',
}
Infrastructure Security
Headers Configuration
// next.config.mjs
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
},
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
}
]
const ContentSecurityPolicy = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline' *.vercel-insights.com;
style-src 'self' 'unsafe-inline';
img-src * blob: data:;
media-src 'none';
connect-src *;
font-src 'self';
`
export default {
headers: async () => [
{
source: '/:path*',
headers: securityHeaders,
},
],
}
Environment Variables
// src/env.mjs
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
NEXTAUTH_URL: z.string().url(),
NEXTAUTH_SECRET: z.string().min(32),
ENCRYPTION_KEY: z.string().length(64), // 32 bytes in hex
API_SECRET_KEY: z.string().min(32),
UPSTASH_REDIS_URL: z.string().url(),
UPSTASH_REDIS_TOKEN: z.string(),
},
client: {
NEXT_PUBLIC_APP_URL: z.string().url(),
},
runtimeEnv: process.env,
})
File Upload Security
// src/lib/security/upload.ts
import { type File } from '@web-std/file'
import { z } from 'zod'
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB
const ALLOWED_FILE_TYPES = [
'image/jpeg',
'image/png',
'image/webp',
'application/pdf',
]
export const uploadSchema = z.object({
file: z
.instanceof(File)
.refine((file) => file.size <= MAX_FILE_SIZE, 'File size must be 5MB or less')
.refine(
(file) => ALLOWED_FILE_TYPES.includes(file.type),
'File type not supported'
),
})
export async function scanFile(file: File): Promise<boolean> {
// Implement virus scanning here
// This is a placeholder
return true
}
export function sanitizeFile(file: File): Promise<File> {
// Implement file sanitization here
// This is a placeholder
return Promise.resolve(file)
}
Security Best Practices
Authentication
Use secure session management
Implement proper password policies
Enable multi-factor authentication
Use secure token handling
Data Protection
Encrypt sensitive data
Implement proper access controls
Sanitize user input
Use secure data transmission
API Security
Implement rate limiting
Use proper authentication
Enable CORS protection
Validate all inputs
Infrastructure
Configure security headers
Use secure environment variables
Implement file upload security
Enable SSL/TLS
Security Checklist
Authentication
[ ] Configure NextAuth.js
[ ] Set up RBAC
[ ] Implement password policies
[ ] Enable MFA
Data Protection
[ ] Set up encryption
[ ] Configure access controls
[ ] Implement sanitization
[ ] Secure data storage
API Security
[ ] Enable rate limiting
[ ] Set up API authentication
[ ] Configure CORS
[ ] Validate requests
Infrastructure
[ ] Set security headers
[ ] Secure environment variables
[ ] Configure file upload security
[ ] Enable monitoring