feat: Implement identity lock screen for sensitive user actions and introduce avatar generation.
This commit is contained in:
@@ -10,6 +10,7 @@ import { requireAuth, verifyPassword, hashPassword } from '@/lib/auth';
|
||||
import { db, users } from '@/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import * as crypto from 'crypto';
|
||||
import { requireSignedAction, type SignedAction } from '@/lib/auth/verify-signature';
|
||||
|
||||
/**
|
||||
* Decrypt the private key using the OLD password
|
||||
@@ -70,9 +71,17 @@ function encryptPrivateKey(privateKey: string, password: string): { encrypted: s
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const user = await requireAuth();
|
||||
const body = await req.json();
|
||||
const { currentPassword, newPassword } = body;
|
||||
// Parse signed action
|
||||
const signedAction: SignedAction = await req.json();
|
||||
|
||||
// Verify signature and get user
|
||||
const user = await requireSignedAction(signedAction);
|
||||
|
||||
if (signedAction.action !== 'change_password') {
|
||||
return NextResponse.json({ error: 'Invalid action type' }, { status: 400 });
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = signedAction.data;
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getSession, requireAuth } from '@/lib/auth';
|
||||
import { getSession } from '@/lib/auth';
|
||||
import { db, users } from '@/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { requireSignedAction, type SignedAction } from '@/lib/auth/verify-signature';
|
||||
|
||||
const updateProfileSchema = z.object({
|
||||
displayName: z.string().min(1).max(50).optional(),
|
||||
@@ -52,9 +53,20 @@ export async function PATCH(request: Request) {
|
||||
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
|
||||
}
|
||||
|
||||
const currentUser = await requireAuth();
|
||||
const body = await request.json();
|
||||
const data = updateProfileSchema.parse(body);
|
||||
// Parse signed action
|
||||
const signedAction: SignedAction = await request.json();
|
||||
|
||||
// Verify signature and get user
|
||||
// This ensures the request was signed by the user's private key
|
||||
const currentUser = await requireSignedAction(signedAction);
|
||||
|
||||
// Ensure the action type is correct for profile updates
|
||||
if (signedAction.action !== 'update_profile') {
|
||||
return NextResponse.json({ error: 'Invalid action type' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Parse inner data
|
||||
const data = updateProfileSchema.parse(signedAction.data);
|
||||
|
||||
const updateData: {
|
||||
displayName?: string;
|
||||
@@ -119,8 +131,13 @@ export async function PATCH(request: Request) {
|
||||
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 });
|
||||
if (error instanceof Error) {
|
||||
if (error.message === 'Authentication required') {
|
||||
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
|
||||
}
|
||||
if (error.message === 'Invalid signature' || error.message === 'User not found') {
|
||||
return NextResponse.json({ error: 'Invalid signature or identity' }, { status: 403 });
|
||||
}
|
||||
}
|
||||
console.error('Profile update error:', error);
|
||||
return NextResponse.json({ error: 'Failed to update profile' }, { status: 500 });
|
||||
|
||||
@@ -71,8 +71,8 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
// LAZY LOAD: If remote and not cached, try to fetch it now
|
||||
if (!cachedUser && isRemote) {
|
||||
// LAZY LOAD: If remote and (not cached OR missing avatar), try to fetch it now
|
||||
if (isRemote && (!cachedUser || !cachedUser.avatarUrl)) {
|
||||
try {
|
||||
const [rHandle, rDomain] = participant2Handle.split('@');
|
||||
const { fetchSwarmUserProfile } = await import('@/lib/swarm/interactions');
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db, chatConversations, chatMessages, users } from '@/db';
|
||||
import { eq, desc, and, lt, isNull, sql } from 'drizzle-orm';
|
||||
import { eq, desc, and, lt, isNull, sql, inArray } from 'drizzle-orm';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
if (senderDids.size > 0) {
|
||||
const found = await db.query.users.findMany({
|
||||
where: sql`${users.did} IN ${Array.from(senderDids)}`
|
||||
where: inArray(users.did, Array.from(senderDids))
|
||||
});
|
||||
found.forEach(u => usersByDid[u.did] = u);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export async function GET(request: NextRequest) {
|
||||
// Also fetch local users by handle if needed
|
||||
if (senderHandles.size > 0) {
|
||||
const found = await db.query.users.findMany({
|
||||
where: sql`${users.handle} IN ${Array.from(senderHandles)}`
|
||||
where: inArray(users.handle, Array.from(senderHandles))
|
||||
});
|
||||
found.forEach(u => usersByHandle[u.handle] = u);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user