Make user media uploads session-backed
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { authenticateUser, createSession } from '@/lib/auth';
|
||||
import { createStorageSession } from '@/lib/storage/session';
|
||||
import { verifyTurnstileToken } from '@/lib/turnstile';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -30,6 +31,7 @@ export async function POST(request: Request) {
|
||||
|
||||
// Create session
|
||||
await createSession(user.id);
|
||||
await createStorageSession(user, data.password);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { destroySession } from '@/lib/auth';
|
||||
import { clearStorageSession } from '@/lib/storage/session';
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
await clearStorageSession();
|
||||
await destroySession();
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { registerUser, createSession } from '@/lib/auth';
|
||||
import { createStorageSession } from '@/lib/storage/session';
|
||||
import { db, nodes, users } from '@/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { verifyTurnstileToken } from '@/lib/turnstile';
|
||||
@@ -97,6 +98,7 @@ export async function POST(request: Request) {
|
||||
|
||||
// Create session for new user
|
||||
await createSession(user.id);
|
||||
await createStorageSession(user, data.password);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
||||
+15
-18
@@ -17,7 +17,8 @@ import {
|
||||
BotHandleTakenError,
|
||||
BotValidationError,
|
||||
} from '@/lib/bots/botManager';
|
||||
import { generateAndUploadAvatarToUserStorage, decryptS3Credentials } from '@/lib/storage/s3';
|
||||
import { generateAndUploadAvatarToUserStorage } from '@/lib/storage/s3';
|
||||
import { createStorageSession, getStorageSession } from '@/lib/storage/session';
|
||||
|
||||
// Schema for creating a bot
|
||||
const createBotSchema = z.object({
|
||||
@@ -43,7 +44,6 @@ const createBotSchema = z.object({
|
||||
timezone: z.string().optional(),
|
||||
}).optional(),
|
||||
autonomousMode: z.boolean().optional(),
|
||||
// Optional: password to generate avatar using owner's S3 storage
|
||||
ownerPassword: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -61,28 +61,25 @@ export async function POST(request: Request) {
|
||||
const body = await request.json();
|
||||
const data = createBotSchema.parse(body);
|
||||
|
||||
// Generate bot avatar using owner's S3 storage if password provided and no avatar URL
|
||||
const storageSession =
|
||||
(await getStorageSession(user.id)) ||
|
||||
(data.ownerPassword ? await createStorageSession(user, data.ownerPassword) : null);
|
||||
|
||||
// Generate bot avatar using owner's S3 storage if no avatar URL
|
||||
let botAvatarUrl: string | null | undefined = data.avatarUrl;
|
||||
if (!botAvatarUrl && data.ownerPassword && user.storageAccessKeyEncrypted && user.storageSecretKeyEncrypted && user.storageBucket) {
|
||||
if (!botAvatarUrl && storageSession) {
|
||||
try {
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const botHandle = `${data.handle.toLowerCase()}@${nodeDomain}`;
|
||||
|
||||
// Decrypt the storage credentials
|
||||
const { accessKeyId, secretAccessKey } = decryptS3Credentials(
|
||||
user.storageAccessKeyEncrypted,
|
||||
user.storageSecretKeyEncrypted,
|
||||
data.ownerPassword
|
||||
);
|
||||
|
||||
|
||||
botAvatarUrl = await generateAndUploadAvatarToUserStorage(
|
||||
botHandle,
|
||||
user.storageEndpoint || undefined,
|
||||
user.storagePublicBaseUrl || undefined,
|
||||
user.storageRegion || 'auto',
|
||||
user.storageBucket,
|
||||
accessKeyId,
|
||||
secretAccessKey
|
||||
storageSession.endpoint || undefined,
|
||||
storageSession.publicBaseUrl || undefined,
|
||||
storageSession.region || 'us-east-1',
|
||||
storageSession.bucket,
|
||||
storageSession.accessKeyId,
|
||||
storageSession.secretAccessKey
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('[Bot API] Failed to generate bot avatar:', err);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db, media } from '@/db';
|
||||
import { requireAuth } from '@/lib/auth';
|
||||
import { uploadToUserStorage } from '@/lib/storage/s3';
|
||||
import { getStorageSession, createStorageSession } from '@/lib/storage/session';
|
||||
import { uploadWithStorageCredentials } from '@/lib/storage/s3';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10MB for images
|
||||
@@ -47,10 +48,13 @@ export async function POST(req: NextRequest) {
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
// Require password to decrypt storage credentials
|
||||
if (!password) {
|
||||
return NextResponse.json({
|
||||
error: 'Password required to upload media. Your storage credentials are encrypted and need your password to decrypt.'
|
||||
const storageSession =
|
||||
(await getStorageSession(user.id)) ||
|
||||
(password ? await createStorageSession(user, password) : null);
|
||||
|
||||
if (!storageSession) {
|
||||
return NextResponse.json({
|
||||
error: 'Upload session expired. Please sign in again.'
|
||||
}, { status: 401 });
|
||||
}
|
||||
|
||||
@@ -58,18 +62,17 @@ export async function POST(req: NextRequest) {
|
||||
const filename = `${uuidv4()}-${file.name.replace(/[^a-zA-Z0-9.-]/g, '')}`;
|
||||
|
||||
// Upload to user's own S3-compatible storage
|
||||
const uploadResult = await uploadToUserStorage(
|
||||
const uploadResult = await uploadWithStorageCredentials(
|
||||
buffer,
|
||||
filename,
|
||||
file.type,
|
||||
user.storageProvider as any,
|
||||
user.storageEndpoint,
|
||||
user.storagePublicBaseUrl,
|
||||
user.storageRegion || 'us-east-1',
|
||||
user.storageBucket || '',
|
||||
user.storageAccessKeyEncrypted,
|
||||
user.storageSecretKeyEncrypted,
|
||||
password
|
||||
storageSession.provider,
|
||||
storageSession.endpoint,
|
||||
storageSession.publicBaseUrl,
|
||||
storageSession.region,
|
||||
storageSession.bucket,
|
||||
storageSession.accessKeyId,
|
||||
storageSession.secretAccessKey
|
||||
);
|
||||
|
||||
// Store media record with S3 URL
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
import { requireAuth, verifyPassword } from '@/lib/auth';
|
||||
import { clearStorageSession, createStorageSession } from '@/lib/storage/session';
|
||||
|
||||
const bodySchema = z.object({
|
||||
password: z.string().min(1),
|
||||
});
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const user = await requireAuth();
|
||||
const body = await request.json();
|
||||
const data = bodySchema.parse(body);
|
||||
|
||||
if (!user.passwordHash) {
|
||||
return NextResponse.json({ error: 'Account has no password set' }, { status: 400 });
|
||||
}
|
||||
|
||||
const isValid = await verifyPassword(data.password, user.passwordHash);
|
||||
|
||||
if (!isValid) {
|
||||
return NextResponse.json({ error: 'Incorrect password' }, { status: 401 });
|
||||
}
|
||||
|
||||
await createStorageSession(user, data.password);
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 });
|
||||
}
|
||||
if (error instanceof Error && error.message === 'Authentication required') {
|
||||
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
|
||||
}
|
||||
|
||||
console.error('Storage session error:', error);
|
||||
return NextResponse.json({ error: 'Failed to refresh upload session' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE() {
|
||||
await clearStorageSession();
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
Reference in New Issue
Block a user