|
| 1 | +import { NextRequest, NextResponse } from 'next/server'; |
| 2 | +import {Signer, NilauthClient, Builder as NucTokenBuilder, Did, Builder, Command} from '@nillion/nuc'; |
| 3 | +import {NucCmd, SecretVaultBuilderClient } from '@nillion/secretvaults'; |
| 4 | +import { validateOrigin } from '@/lib/origin-validator'; |
| 5 | +import { NILDB_CONFIG } from '@/lib/nildb-config'; |
| 6 | + |
| 7 | +// Simple in-memory rate limiter |
| 8 | +const rateLimitMap = new Map<string, { count: number; resetTime: number }>(); |
| 9 | +const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute |
| 10 | +const MAX_REQUESTS_PER_WINDOW = 5; // 5 tokens per minute per IP |
| 11 | + |
| 12 | +function getRateLimitKey(request: NextRequest): string { |
| 13 | + // Try to get real IP from common headers (for proxies/load balancers) |
| 14 | + const forwarded = request.headers.get('x-forwarded-for'); |
| 15 | + const realIp = request.headers.get('x-real-ip'); |
| 16 | + const ip = forwarded?.split(',')[0] || realIp || 'unknown'; |
| 17 | + return ip; |
| 18 | +} |
| 19 | + |
| 20 | +function isRateLimited(key: string): boolean { |
| 21 | + const now = Date.now(); |
| 22 | + const record = rateLimitMap.get(key); |
| 23 | + |
| 24 | + if (!record || now > record.resetTime) { |
| 25 | + // New window |
| 26 | + rateLimitMap.set(key, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS }); |
| 27 | + return false; |
| 28 | + } |
| 29 | + |
| 30 | + if (record.count >= MAX_REQUESTS_PER_WINDOW) { |
| 31 | + return true; |
| 32 | + } |
| 33 | + |
| 34 | + record.count++; |
| 35 | + return false; |
| 36 | +} |
| 37 | + |
| 38 | +export async function POST(request: NextRequest) { |
| 39 | + try { |
| 40 | + // Validate origin |
| 41 | + const originError = validateOrigin(request); |
| 42 | + if (originError) return originError; |
| 43 | + |
| 44 | + // Rate limiting check |
| 45 | + const rateLimitKey = getRateLimitKey(request); |
| 46 | + if (isRateLimited(rateLimitKey)) { |
| 47 | + console.warn(`Rate limit exceeded for IP: ${rateLimitKey}`); |
| 48 | + return NextResponse.json( |
| 49 | + { error: 'Too many requests. Please try again later.' }, |
| 50 | + { status: 429 } |
| 51 | + ); |
| 52 | + } |
| 53 | + |
| 54 | + const { userDid } = await request.json(); |
| 55 | + |
| 56 | + if (!userDid) { |
| 57 | + return NextResponse.json( |
| 58 | + { error: 'User DID is required' }, |
| 59 | + { status: 400 } |
| 60 | + ); |
| 61 | + } |
| 62 | + |
| 63 | + // Check for Nillion API key |
| 64 | + const apiKey = process.env.NILLION_API_KEY; |
| 65 | + if (!apiKey) { |
| 66 | + return NextResponse.json( |
| 67 | + { error: 'Nillion API key not configured. Please set NILLION_API_KEY in your environment variables.' }, |
| 68 | + { status: 503 } |
| 69 | + ); |
| 70 | + } |
| 71 | + |
| 72 | + console.log('Creating nilDB delegation token for user:', userDid); |
| 73 | + |
| 74 | + // Create signer from API key |
| 75 | + const builderSigner = await Signer.fromPrivateKey(apiKey, "nil"); |
| 76 | + const builderDid = await builderSigner.getDid(); |
| 77 | + |
| 78 | + // Create Nilauth client manually using the provided public key |
| 79 | + // (avoiding network call to /about endpoint) |
| 80 | + // @ts-expect-error - Constructor is private but works fine at runtime; waiting for Nillion API fix |
| 81 | + const nilauthClient = new NilauthClient({ |
| 82 | + payer: builderSigner, |
| 83 | + nilauth: { |
| 84 | + baseUrl: NILDB_CONFIG.nilauthUrl, |
| 85 | + publicKey: NILDB_CONFIG.nilauthPublicKey, |
| 86 | + did: Did.fromPublicKey(NILDB_CONFIG.nilauthPublicKey, 'key') |
| 87 | + } |
| 88 | + }); |
| 89 | + |
| 90 | + // Create builder client |
| 91 | + console.log('Creating SecretVaultBuilderClient with', NILDB_CONFIG.nodes.length, 'nodes'); |
| 92 | + const builderClient = await SecretVaultBuilderClient.from({ |
| 93 | + signer: builderSigner, |
| 94 | + nilauthClient, |
| 95 | + dbs: [...NILDB_CONFIG.nodes] |
| 96 | + }); |
| 97 | + console.log('SecretVaultBuilderClient created successfully'); |
| 98 | + |
| 99 | + // Refresh authentication to get root token |
| 100 | + console.log('Refreshing root token from nilauth...'); |
| 101 | + try { |
| 102 | + await builderClient.refreshRootToken(); |
| 103 | + console.log('Root token refreshed successfully'); |
| 104 | + } catch (err) { |
| 105 | + console.error('Failed to refresh root token:', err); |
| 106 | + throw new Error(`Failed to refresh root token: ${err instanceof Error ? err.message : String(err)}`); |
| 107 | + } |
| 108 | + |
| 109 | + // Get the root token |
| 110 | + const rootToken = builderClient.rootToken; |
| 111 | + if (!rootToken) { |
| 112 | + throw new Error('No root token available from builderClient'); |
| 113 | + } |
| 114 | + |
| 115 | + // Create delegation token from builderClient to user |
| 116 | + // Token expires in 10 minutes - enough time for user to submit data |
| 117 | + const expiresInSeconds = 10 * 60; |
| 118 | + const expiresAt = Math.floor(Date.now() / 1000) + expiresInSeconds; |
| 119 | + |
| 120 | + /*// Create delegation token from builderClient to user |
| 121 | + // Don't specify command - let the user client add it |
| 122 | + // Note: Using signer.privateKey() since builderClient.keypair is not exposed in current API |
| 123 | + const delegationToken = new NucTokenBuilder() |
| 124 | + .extending(rootToken) |
| 125 | + .audience(userDid) |
| 126 | + .expiresAt(expiresAt) |
| 127 | + .build(signer.privateKey());*/ |
| 128 | + |
| 129 | + const delegationToken = await Builder.delegationFrom(builderClient.rootToken) |
| 130 | + .command(NucCmd.nil.db.data.create as Command) |
| 131 | + .audience(userDid) |
| 132 | + .expiresIn(3600) |
| 133 | + .signAndSerialize(builderSigner); |
| 134 | + |
| 135 | + console.log('nilDB delegation token created successfully'); |
| 136 | + |
| 137 | + return NextResponse.json({ |
| 138 | + success: true, |
| 139 | + delegationToken, |
| 140 | + collectionId: NILDB_CONFIG.collectionId, |
| 141 | + builderDid: builderDid.didString |
| 142 | + }); |
| 143 | + |
| 144 | + } catch (error) { |
| 145 | + console.error('Error creating nilDB delegation token:', error); |
| 146 | + return NextResponse.json( |
| 147 | + { error: error instanceof Error ? error.message : 'Failed to create delegation token' }, |
| 148 | + { status: 500 } |
| 149 | + ); |
| 150 | + } |
| 151 | +} |
0 commit comments