first commit
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
import { NextAuthOptions } from 'next-auth'
|
||||
import CredentialsProvider from 'next-auth/providers/credentials'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: 'credentials',
|
||||
credentials: {
|
||||
username: { label: 'Username', type: 'text' },
|
||||
password: { label: 'Password', type: 'password' },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.username || !credentials?.password) return null
|
||||
|
||||
const adminUsername = process.env.ADMIN_USERNAME
|
||||
let adminPasswordHash = process.env.ADMIN_PASSWORD_HASH
|
||||
|
||||
if (!adminUsername || !adminPasswordHash) {
|
||||
throw new Error('Admin credentials not configured')
|
||||
}
|
||||
|
||||
// Handle base64 encoded hash to avoid Next.js .env $ expansion issues
|
||||
if (adminPasswordHash.startsWith('JDJ') || (!adminPasswordHash.startsWith('$2') && !adminPasswordHash.startsWith('$2a'))) {
|
||||
try {
|
||||
const decoded = Buffer.from(adminPasswordHash, 'base64').toString('utf8')
|
||||
if (decoded.startsWith('$2')) {
|
||||
adminPasswordHash = decoded
|
||||
}
|
||||
} catch {
|
||||
// keep original if decoding fails
|
||||
}
|
||||
}
|
||||
|
||||
if (credentials.username !== adminUsername) return null
|
||||
|
||||
const isValid = await bcrypt.compare(credentials.password, adminPasswordHash)
|
||||
if (!isValid) return null
|
||||
|
||||
return { id: '1', name: adminUsername, email: `${adminUsername}@configvault` }
|
||||
},
|
||||
}),
|
||||
],
|
||||
session: { strategy: 'jwt' },
|
||||
pages: {
|
||||
signIn: '/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
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import postgres from 'postgres'
|
||||
|
||||
// DATABASE_URL örneği:
|
||||
// postgresql://user:password@localhost:5432/configvault
|
||||
// postgresql://user:password@your-server.com:5432/configvault?sslmode=require
|
||||
|
||||
const sql = postgres(process.env.DATABASE_URL!, {
|
||||
max: 10, // max connection pool size
|
||||
idle_timeout: 20, // saniye
|
||||
connect_timeout: 10,
|
||||
// SSL: production'da genellikle gerekli, lokal'de kapalı
|
||||
ssl: process.env.DATABASE_SSL === 'true' ? 'require' : false,
|
||||
})
|
||||
|
||||
export default sql
|
||||
@@ -0,0 +1,80 @@
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function slugify(text: string): string {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^\w-]+/g, '')
|
||||
.replace(/--+/g, '-')
|
||||
.trim()
|
||||
}
|
||||
|
||||
export function maskSecret(value: string): string {
|
||||
if (!value) return '••••••••'
|
||||
if (value.length <= 8) return '••••••••••••'
|
||||
return value.slice(0, 4) + '•'.repeat(Math.min(value.length - 8, 16)) + value.slice(-4)
|
||||
}
|
||||
|
||||
export function envBadgeStyles(env: string) {
|
||||
switch (env) {
|
||||
case 'production':
|
||||
return {
|
||||
bg: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20 hover:border-emerald-500/40',
|
||||
active: 'bg-emerald-500 text-slate-950 font-semibold shadow-lg shadow-emerald-500/25 border-emerald-400',
|
||||
dot: 'bg-emerald-400',
|
||||
}
|
||||
case 'staging':
|
||||
return {
|
||||
bg: 'bg-amber-500/10 text-amber-400 border-amber-500/20 hover:border-amber-500/40',
|
||||
active: 'bg-amber-500 text-slate-950 font-semibold shadow-lg shadow-amber-500/25 border-amber-400',
|
||||
dot: 'bg-amber-400',
|
||||
}
|
||||
case 'development':
|
||||
return {
|
||||
bg: 'bg-cyan-500/10 text-cyan-400 border-cyan-500/20 hover:border-cyan-500/40',
|
||||
active: 'bg-cyan-500 text-slate-950 font-semibold shadow-lg shadow-cyan-500/25 border-cyan-400',
|
||||
dot: 'bg-cyan-400',
|
||||
}
|
||||
default:
|
||||
return {
|
||||
bg: 'bg-slate-500/10 text-slate-400 border-slate-500/20 hover:border-slate-500/40',
|
||||
active: 'bg-slate-500 text-slate-950 font-semibold shadow-lg shadow-slate-500/25 border-slate-400',
|
||||
dot: 'bg-slate-400',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function envColor(env: string) {
|
||||
switch (env) {
|
||||
case 'production':
|
||||
return 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20'
|
||||
case 'staging':
|
||||
return 'bg-amber-500/10 text-amber-400 border-amber-500/20'
|
||||
case 'development':
|
||||
return 'bg-cyan-500/10 text-cyan-400 border-cyan-500/20'
|
||||
default:
|
||||
return 'bg-slate-500/10 text-slate-400 border-slate-500/20'
|
||||
}
|
||||
}
|
||||
|
||||
export function typeColor(type: string) {
|
||||
switch (type) {
|
||||
case 'secret':
|
||||
return 'bg-rose-500/10 text-rose-400 border-rose-500/20'
|
||||
case 'url':
|
||||
return 'bg-sky-500/10 text-sky-400 border-sky-500/20'
|
||||
case 'json':
|
||||
return 'bg-purple-500/10 text-purple-400 border-purple-500/20'
|
||||
case 'boolean':
|
||||
return 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20'
|
||||
case 'number':
|
||||
return 'bg-amber-500/10 text-amber-400 border-amber-500/20'
|
||||
default:
|
||||
return 'bg-slate-500/10 text-slate-300 border-slate-500/20'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user