codalware-auth
Version:
Complete authentication system with enterprise security, attack protection, team workspaces, waitlist, billing, UI components, 2FA, and account recovery - production-ready in 5 minutes. Enhanced CLI with verification, rollback, and App Router scaffolding.
120 lines (99 loc) • 4 kB
text/typescript
import { prisma } from './db'
import crypto from 'crypto'
import dns from 'dns/promises'
import { Organization } from '@prisma/client'
const DOMAIN_REGEX = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*$/
export function normalizeDomain(domain: string) {
return domain.toLowerCase().trim()
}
export function isValidDomain(domain: string) {
if (!domain) return false
if (domain.includes('://') || domain.includes('/')) return false
if (!DOMAIN_REGEX.test(domain)) return false
return domain.includes('.')
}
export async function generateDomainVerificationToken(organizationId: string, domain: string) {
// Normalize domain
const normalized = normalizeDomain(domain)
if (!isValidDomain(normalized)) {
throw new Error('Invalid domain')
}
// Check if domain is already taken by another org
// using bracket access to avoid type issues until prisma client is regenerated
const existing = await prisma.organizationDomain.findUnique({ where: { domain: normalized } })
if (existing && existing.organizationId !== organizationId) {
throw new Error('Domain already claimed')
}
// Generate token
const token = crypto.randomBytes(16).toString('hex')
// Upsert the domain row
const row = await prisma.organizationDomain.upsert({
where: { domain: normalized },
update: { verificationToken: token, verified: false },
create: { organizationId, domain: normalized, verificationToken: token, verified: false }
})
return { token, domain: row.domain }
}
export async function verifyDomainByDns(organizationId: string, domain: string) {
const normalized = normalizeDomain(domain)
if (!isValidDomain(normalized)) {
throw new Error('Invalid domain')
}
const row = await prisma.organizationDomain.findUnique({ where: { domain: normalized } })
if (!row) {
throw new Error('Domain not requested')
}
if (row.organizationId !== organizationId) {
throw new Error('Domain owned by another organization')
}
if (!row.verificationToken) {
throw new Error('No verification token found')
}
try {
const records = await dns.resolveTxt(normalized)
const flat = records.flat().join(' ')
if (flat.includes(row.verificationToken)) {
const updated = await prisma.organizationDomain.update({ where: { domain: normalized }, data: { verified: true, verifiedAt: new Date() } })
return { verified: true, domain: updated.domain }
}
return { verified: false }
} catch {
return { verified: false, error: 'Failed to resolve DNS records' }
}
}
export async function resolveOrgByHost(host: string) {
// Strip port
const hostname = host.split(':')[0].toLowerCase()
// 1) exact match in OrganizationDomain
const domainRow = await prisma.organizationDomain.findUnique({ where: { domain: hostname }, include: { organization: true } })
if (domainRow && domainRow.verified) return domainRow.organization
// 2) fallback: check Organization.customDomain (legacy) — for backward compatibility
// Use a raw query to avoid TypeScript errors if the legacy field doesn't exist
try {
const legacyRows = await prisma.$queryRaw<Organization[]>`
SELECT * FROM organizations WHERE customDomain = ${hostname} LIMIT 1
`
if (legacyRows.length > 0) {
return legacyRows[0]
}
} catch {
// legacy field may not exist; ignore
}
// 3) fallback: subdomain -> slug mapping (e.g., orgslug.yourapp.com)
// Try to split hostname and probe first label
const parts = hostname.split('.')
if (parts.length > 2) {
const slug = parts[0]
const bySlug = await prisma.organization.findFirst({ where: { slug } })
if (bySlug) return bySlug
}
return null
}
const customDomain = {
generateDomainVerificationToken,
verifyDomainByDns,
resolveOrgByHost,
normalizeDomain,
isValidDomain,
}
export default customDomain