feat: Implement identity lock screen for sensitive user actions and introduce avatar generation.

This commit is contained in:
Christomatt
2026-01-30 04:44:57 +01:00
parent 4c9afa42fe
commit 10a54a0ea9
16 changed files with 519 additions and 134 deletions
+12 -3
View File
@@ -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 });
+23 -6
View File
@@ -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');
+3 -3
View File
@@ -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);
}