Epic Doc
π― Ready for Production
Database Integration
// Easy migration from JSON mock to real database
export class DatabaseCourseAdapter {
async findById(id: string): Promise<Course | null> {
// Replace JSON file access with database query
const result = await db.course.findUnique({ where: { id } })
if (!result) return null
// Keep Nostr integration exactly the same
if (result.noteId) {
const nostrEvent = await fetchNostrEvent(result.noteId)
return { ...result, note: nostrEvent }
}
return result
}
}
π Dual Authentication Architecture: Nostr-First vs OAuth-First
Revolutionary Identity-Source System with Complete Profile Collection
Latest Enhancement: Complete NIP-01 Profile Support
// Session now includes comprehensive user data
const { data: session } = useSession()
// Basic fields (all users)
session?.user?.name // Display name
session?.user?.email // Email address
session?.user?.image // Avatar/profile picture
session?.user?.pubkey // Nostr public key
// Enhanced Nostr profile fields (all users)
session?.user?.nip05 // Nostr address verification
session?.user?.lud16 // Lightning address
session?.user?.banner // Profile banner image
// Complete Nostr profile (Nostr-first accounts)
session?.user?.nostrProfile?.about // Bio/description
session?.user?.nostrProfile?.website // Personal website
session?.user?.nostrProfile?.location // Geographic location
session?.user?.nostrProfile?.github // GitHub username
session?.user?.nostrProfile?.twitter // Twitter handle
// ... plus any other fields from user's NIP-01 profile
Revolutionary Identity-Source System
/**
* DUAL AUTHENTICATION ARCHITECTURE - Two distinct paradigms
*
* π΅ NOSTR-FIRST ACCOUNTS (Nostr as identity source):
* --------------------------------------------------
* β’ NIP07 Authentication (nostr provider) - User custody of keys
* β’ Anonymous Authentication (anonymous provider) - Platform custody for experimentation
*
* Behavior:
* - Nostr profile is the SOURCE OF TRUTH for user data
* - Profile sync happens on every login from Nostr relays
* - Database user fields are updated if Nostr profile differs
* - User's Nostr identity drives their platform identity
*
* π OAUTH-FIRST ACCOUNTS (Platform as identity source):
* -----------------------------------------------------
* β’ Email Authentication (email provider) - May not know about Nostr
* β’ GitHub Authentication (github provider) - May not know about Nostr
*
* Behavior:
* - OAuth profile is the SOURCE OF TRUTH for user data
* - Ephemeral Nostr keypairs generated for background Nostr functionality
* - No profile sync from Nostr - OAuth data takes precedence
* - Platform identity drives their Nostr identity (not vice versa)
*/
// OAUTH-FIRST: Ephemeral keypair generation for transparent Nostr access
events: {
async createUser({ user }) {
// Only OAuth-first accounts get ephemeral keys automatically
if (!user.pubkey) {
const keys = await generateKeypair()
await prisma.user.update({
where: { id: user.id },
data: {
pubkey: keys.publicKey,
privkey: keys.privateKey, // Background Nostr capabilities
}
})
}
},
async signIn({ user, account }) {
// NOSTR-FIRST: Sync profile from Nostr relays (source of truth)
const isNostrFirst = ['nostr', 'anonymous', 'recovery'].includes(account?.provider)
if (user.pubkey && isNostrFirst) {
await syncUserProfileFromNostr(user.id, user.pubkey)
}
// OAUTH-FIRST: Skip Nostr sync, OAuth profile is authoritative
}
}
// Universal session with proper key handling
async session({ session, token }) {
if (session.user.pubkey) {
// Include privkey for ephemeral accounts (anonymous, email, GitHub)
// NIP07 users never have privkey stored (user-controlled keys)
const dbUser = await prisma.user.findUnique({
where: { id: token.userId },
select: { privkey: true }
})
if (dbUser?.privkey) {
session.user.privkey = dbUser.privkey // Enable client-side signing
}
}
}
Four Authentication Methods with Universal Nostr Capabilities
const authOptions = {
adapter: PrismaAdapter(prisma),
providers: [
// π OAUTH-FIRST: Email Magic Links (User may not know about Nostr)
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: parseInt(process.env.EMAIL_SERVER_PORT || '587'),
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD
}
},
from: process.env.EMAIL_FROM
// β Gets ephemeral keypair for background Nostr functionality
// β Email profile is source of truth, no Nostr profile sync
}),
// π OAUTH-FIRST: GitHub OAuth (User may not know about Nostr)
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
profile(profile) {
// Simplified GitHub profile mapping (essential fields only)
return {
id: profile.id.toString(),
email: profile.email,
name: profile.name || profile.login,
image: profile.avatar_url,
// β Gets ephemeral keypair for background Nostr functionality
// β GitHub profile is source of truth, no Nostr profile sync
}
}
}),
// π΅ NOSTR-FIRST: Anonymous (User trying things out)
CredentialsProvider({
id: 'anonymous',
async authorize() {
const keys = await generateKeypair()
// Create anonymous user with fresh keypair (platform custody)
const user = await prisma.user.create({
data: {
pubkey: keys.publicKey,
privkey: keys.privateKey, // Platform manages keys for experiments
username: `anon_${keys.publicKey.substring(0, 8)}`
}
})
// β Attempts to sync with any existing Nostr profile
await syncUserProfileFromNostr(user.id, keys.publicKey)
return user
}
}),
// π΅ NOSTR-FIRST: NIP07 Browser Extension (User in custody)
CredentialsProvider({
id: 'nostr',
async authorize(credentials) {
// User provides pubkey via browser extension (user controls keys)
let user = await prisma.user.findUnique({
where: { pubkey: credentials.pubkey }
})
if (!user) {
user = await prisma.user.create({
data: {
pubkey: credentials.pubkey
// No privkey stored - user has custody via browser extension
}
})
}
// β ALWAYS sync profile from Nostr (source of truth)
await syncUserProfileFromNostr(user.id, credentials.pubkey)
return user
}
})
]
}
Identity-Source Architecture Benefits
π΅ Nostr-First Accounts (NIP07 & Anonymous):
- Profile Sovereignty: Nostr profile always overrides database values
- Real-time Sync: Profile changes on Nostr immediately reflect in platform
- Key Management: Clear separation of user custody (NIP07) vs platform custody (Anonymous)
- Identity Flow: Nostr β Database (Nostr profile drives platform identity)
π OAuth-First Accounts (Email & GitHub):
- Familiar Experience: Standard OAuth flow, no Nostr knowledge required
- Transparent Web3: Background Nostr capabilities without user awareness
- Profile Stability: OAuth profile data remains authoritative and stable
- Identity Flow: OAuth Provider β Database (Platform identity drives Nostr keys)
Universal Benefits:
- 100% Nostr Access: All users can participate in Nostr functionality regardless of login method
- Appropriate Custody: User-controlled keys for Nostr users, platform-managed for others
- Future-Ready: Seamless upgrade path to NIP46 remote signing
- Client-Side Signing: Ephemeral account users can sign Nostr events in browser
- Complete Profile Data: All users get comprehensive profile information appropriate to their authentication method
π Enhanced Profile Collection System
Complete NIP-01 Profile Support
/**
* COMPREHENSIVE PROFILE COLLECTION
* ===============================
*
* π΅ NOSTR-FIRST ACCOUNTS: Complete profile from Nostr relays
* - Fetches ALL fields from NIP-01 kind 0 events (not just basic fields)
* - Includes: name, picture, about, nip05, lud16, banner, website, location,
* github, twitter, telegram, mastodon, youtube, linkedin, pronouns,
* occupation, company, skills, interests, and any other custom fields
* - Real-time sync on every login ensures profile stays current
* - Stored both in database (key fields) and session (complete profile)
*
* π OAUTH-FIRST ACCOUNTS: Essential provider data + background Nostr
* - GitHub: name, email, image (streamlined, no extended fields)
* - Email: email, name (from provider)
* - Gets ephemeral Nostr keypair for protocol participation
* - Can access complete Nostr profile via session.user.nostrProfile if desired
*/
// Enhanced fetchNostrProfile - returns complete profile object
async function fetchNostrProfile(pubkey: string): Promise<Record<string, unknown> | null> {
const profileEvent = await relayPool.get(
relays,
{ kinds: [0], authors: [pubkey] }
)
if (profileEvent?.kind === 0) {
// Return ALL fields from Nostr profile (not filtered)
return JSON.parse(profileEvent.content)
}
return null
}
// Enhanced session callback - includes complete profile data
async session({ session, token }) {
session.user.id = token.userId
session.user.pubkey = token.pubkey
session.user.username = token.username
session.user.email = token.email
session.user.image = token.avatar
session.user.name = token.username
// Enhanced profile fields
Object.assign(session.user, {
nip05: token.nip05,
lud16: token.lud16,
banner: token.banner
})
// For Nostr-first accounts, fetch complete profile
if (session.user.pubkey) {
const completeNostrProfile = await fetchNostrProfile(session.user.pubkey)
if (completeNostrProfile) {
session.user.nostrProfile = completeNostrProfile
}
}
}
Profile Data Access Patterns
// In your React components
const { data: session } = useSession()
// β
Always available (all authentication methods)
session?.user?.name // Display name
session?.user?.email // Email address
session?.user?.image // Avatar/profile picture
session?.user?.pubkey // Nostr public key (all users get one)
// β
Enhanced fields (synced from appropriate source)
session?.user?.nip05 // Nostr address (from Nostr or empty)
session?.user?.lud16 // Lightning address (from Nostr or empty)
session?.user?.banner // Banner image (from Nostr or empty)
// β
Complete Nostr profile (available for all users)
session?.user?.nostrProfile?.about // Biography/description
session?.user?.nostrProfile?.website // Personal website URL
session?.user?.nostrProfile?.location // Geographic location
session?.user?.nostrProfile?.github // GitHub username
session?.user?.nostrProfile?.twitter // Twitter handle
session?.user?.nostrProfile?.telegram // Telegram username
session?.user?.nostrProfile?.mastodon // Mastodon address
session?.user?.nostrProfile?.youtube // YouTube channel
session?.user?.nostrProfile?.linkedin // LinkedIn profile
session?.user?.nostrProfile?.pronouns // Preferred pronouns
session?.user?.nostrProfile?.occupation // Job title/occupation
session?.user?.nostrProfile?.company // Company/organization
session?.user?.nostrProfile?.skills // Technical skills
session?.user?.nostrProfile?.interests // Personal interests
// Plus any other custom fields from the user's Nostr profile
// β
Authentication context
session?.user?.privkey // Private key (ephemeral accounts only)
const isNostrFirst = !session?.user?.privkey // True for NIP07 users
const canSignEvents = !!session?.user?.privkey // True for ephemeral accounts
Profile Collection Benefits
π΅ For Nostr-First Users (NIP07 & Anonymous):
- Complete Profile Access: Every field from their Nostr profile is available
- Real-time Sync: Profile updates on Nostr immediately reflect in the platform
- No Data Loss: Platform preserves all custom fields and metadata
- Source of Truth: Nostr profile always takes precedence over database values
π For OAuth-First Users (Email & GitHub):
- Clean Integration: Simple, familiar OAuth flow without Nostr complexity
- Essential Data: Name, email, image from provider - no unnecessary fields
- Background Nostr: Transparent access to Nostr protocol features when needed
- Stable Profiles: OAuth provider data remains consistent and authoritative
Universal Features:
- Type Safety: Full TypeScript support for all profile fields
- Flexible Access: Use basic fields or dive deep into complete Nostr profiles
- Performance: Intelligent caching of profile data with 5-minute refresh
- Future-Proof: Ready for any new NIP-01 profile fields that emerge
π Documentation
Comprehensive documentation is available in the docs directory:
- Profile System Architecture - Complete architectural overview
- Profile API Reference - Detailed API documentation
- Profile Implementation Guide - Step-by-step implementation
- Documentation Index - Complete documentation directory
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Acknowledgments
- Vercel for Next.js and deployment platform
- shadcn for the beautiful UI components
- Tailwind CSS for the utility-first approach
- Radix UI for accessible component primitives
- Zod for runtime validation
Built with π by PlebDevs
From build issues to production-ready in one focused session. This platform demonstrates that proper architecture cleanup and type safety can be achieved while maintaining system functionality and providing immediate value to developers.
π Ready to build the next generation of web applications? This platform gives you everything you need to ship fast and scale efficiently with enterprise-grade architecture and zero build errors.
- Reference: https://nostr.com