Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 9 additions & 92 deletions app/app/api/auth/legacy-login/route.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,4 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { createToken } from "@/lib/jwt";
import { z } from "zod";

const loginSchema = z.object({
walletAddress: z.string().min(1, "Wallet address is required"),
signature: z.string().min(1, "Signature is required"),
message: z.string().min(1, "Message is required"),
});

// Mock wallet signature verification - replace with actual implementation
async function verifyWalletSignature(
walletAddress: string,
signature: string,
message: string
): Promise<boolean> {
// In production, verify the signature using the wallet's public key
// This is a mock implementation for demonstration
console.log(`Verifying signature for wallet: ${walletAddress}`);
return signature.length > 10; // Simple validation for demo
}

/**
* Handles user login with wallet signature.
Expand All @@ -32,82 +11,20 @@ async function verifyWalletSignature(
*/
export async function POST(request: Request) {
try {
const body = await request.json();
const parsed = loginSchema.safeParse(body);

if (!parsed.success) {
return NextResponse.json(
{ error: "Invalid request data", details: parsed.error.issues },
{ status: 400 }
);
}

const { walletAddress, signature, message } = parsed.data;

// Verify wallet signature
const isValidSignature = await verifyWalletSignature(walletAddress, signature, message);

if (!isValidSignature) {
return NextResponse.json(
{ error: "Invalid wallet signature" },
{ status: 401 }
);
}

// Find user by wallet address
const user = await prisma.user.findUnique({
where: { walletAddress },
});

if (!user) {
return NextResponse.json(
{ error: "User not found. Please register first." },
{ status: 404 }
);
}

// Create JWT token
const token = await createToken({
userId: user.id,
walletAddress: user.walletAddress,
username: user.name,
});

// Create response with cookie
const response = NextResponse.json({
success: true,
user: {
id: user.id,
walletAddress: user.walletAddress,
username: user.name,
email: null,
avatar: user.avatarUrl,
bio: user.bio,
joinDate: user.createdAt,
},
token, // Also return token in response body for client storage
}, {
headers: {
// RFC 299: Miscellaneous persistent warning
"Warning": '299 - "Deprecated: This endpoint is legacy. Use Auth.js signIn instead."',
void request;
return NextResponse.json(
{
error: "Legacy wallet authentication retired",
message:
"Use the challenge and verify flow instead: GET /api/auth/challenge then POST /api/auth/verify.",
},
});

// Set secure cookie
response.cookies.set("auth-token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 30 * 24 * 60 * 60, // 30 days
path: "/",
});

return response;
{ status: 410 }
);
} catch (error) {
console.error("Login error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
}
125 changes: 9 additions & 116 deletions app/app/api/auth/legacy-register/route.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,4 @@
import { NextResponse } from "next/server";
import { createToken } from "@/lib/jwt";
import { prisma } from "@/lib/prisma";
import { z } from "zod";

const registerSchema = z.object({
walletAddress: z.string().min(1, "Wallet address is required"),
signature: z.string().min(1, "Signature is required"),
message: z.string().min(1, "Message is required"),
username: z.string().min(3, "Username must be at least 3 characters").max(30, "Username too long"),
email: z.string().email("Invalid email").optional(),
});

// Mock wallet signature verification - replace with actual implementation
async function verifyWalletSignature (
walletAddress: string,
signature: string,
message: string
): Promise<boolean> {
// In production, verify the signature using the wallet's public key
console.log(`Verifying signature for wallet: ${walletAddress}`);
return signature.length > 10; // Simple validation for demo
}

/**
* Handles user registration with wallet signature.
Expand All @@ -33,105 +11,20 @@ async function verifyWalletSignature (
*/
export async function POST (request: Request) {
try {
const body = await request.json();
const parsed = registerSchema.safeParse(body);

if (!parsed.success) {
return NextResponse.json(
{ error: "Invalid request data", details: parsed.error.issues },
{ status: 400 }
);
}

const { walletAddress, signature, message, username, email } = parsed.data;

// Verify wallet signature
const isValidSignature = await verifyWalletSignature(walletAddress, signature, message);

if (!isValidSignature) {
return NextResponse.json(
{ error: "Invalid wallet signature" },
{ status: 401 }
);
}

// Check if user already exists
const existingUser = await prisma.user.findUnique({
where: { walletAddress },
});

if (existingUser) {
return NextResponse.json(
{ error: "User with this wallet address already exists" },
{ status: 409 }
);
}

// Check if username is taken
const existingUsers = await prisma.user.findMany({
where: { name: username },
});

if (existingUsers.length > 0) {
return NextResponse.json(
{ error: "Username already taken" },
{ status: 409 }
);
}

// Create new user
const user = await prisma.user.create({
data: {
walletAddress,
name: username,
bio: null,
avatarUrl: `https://api.dicebear.com/7.x/identicon/svg?seed=${walletAddress}`,
xp: 0,
},
});

// Create JWT token
const token = await createToken({
userId: user.id,
walletAddress: user.walletAddress,
username: user.name,
});

// Create response with cookie
const response = NextResponse.json({
success: true,
user: {
id: user.id,
walletAddress: user.walletAddress,
username: user.name,
email: null,
avatar: user.avatarUrl,
bio: user.bio,
joinDate: user.createdAt,
},
token,
}, {
headers: {
// RFC 299: Miscellaneous persistent warning
"Warning": '299 - "Deprecated: This endpoint is legacy. Use Auth.js signIn instead."',
void request;
return NextResponse.json(
{
error: "Legacy wallet authentication retired",
message:
"Use the challenge and verify flow instead: GET /api/auth/challenge then POST /api/auth/verify.",
},
});

// Set secure cookie
response.cookies.set("auth-token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 30 * 24 * 60 * 60, // 30 days
path: "/",
});

return response;
{ status: 410 }
);
} catch (error) {
console.error("Registration error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
}
87 changes: 20 additions & 67 deletions app/app/api/auth/verify/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@
*/

import { NextRequest, NextResponse } from "next/server";
import { verifyChallenge } from "@/lib/sep10";
import { createToken } from "@/lib/jwt";
import { prisma } from "@/lib/prisma";
import { authenticateWalletWithChallenge } from "@/lib/wallet-auth";
import { z } from "zod";

// Validation schema for the request body
const verifyRequestSchema = z.object({
transaction: z.string().min(1, "Transaction XDR is required"),
publicKey: z.string().length(56, "Invalid Stellar public key"),
username: z.string().min(3).max(30).optional(),
email: z.string().email().optional(),
});

/**
Expand All @@ -53,64 +54,31 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
);
}

const { transaction: signedXDR, publicKey: clientPublicKey } = validationResult.data;

// Step 1: Check if this transaction has already been used (replay attack prevention)
const transactionHash = await getTransactionHash(signedXDR);
const existingChallenge = await prisma.usedChallenge.findUnique({
where: { transactionHash },
const {
transaction: signedXDR,
publicKey: clientPublicKey,
username,
email,
} = validationResult.data;

const authResult = await authenticateWalletWithChallenge({
walletAddress: clientPublicKey,
transaction: signedXDR,
username,
email,
});

if (existingChallenge) {
if (!authResult.success) {
return NextResponse.json(
{
error: "Replay attack detected",
message: "This signature has already been used. Please request a new challenge.",
error: authResult.error,
message: authResult.message,
},
{ status: 403 }
{ status: authResult.status }
);
}

// Step 2: Verify the signed challenge transaction
const verificationResult = verifyChallenge(signedXDR, clientPublicKey);

if (!verificationResult.valid) {
return NextResponse.json(
{
error: "Verification failed",
message: verificationResult.error || "Invalid signature",
},
{ status: 401 }
);
}

// Step 3: Mark this transaction as used to prevent replay attacks
await prisma.usedChallenge.create({
data: {
transactionHash,
publicKey: clientPublicKey,
usedAt: new Date(),
},
});

// Step 4: Find or create the user
let user = await prisma.user.findUnique({
where: { walletAddress: clientPublicKey },
});

// If user doesn't exist, create a new user with default values
if (!user) {
user = await prisma.user.create({
data: {
walletAddress: clientPublicKey,
name: `User_${clientPublicKey.slice(0, 8)}`,
username: `user_${clientPublicKey.slice(0, 8)}`,
avatarUrl: `https://api.dicebear.com/7.x/identicon/svg?seed=${clientPublicKey}`,
xp: 0,
walletBalance: 0,
},
});
}
const { user } = authResult;

// Step 5: Generate JWT token
const token = await createToken({
Expand Down Expand Up @@ -174,18 +142,3 @@ export async function OPTIONS(): Promise<NextResponse> {
});
}

/**
* Helper function to extract transaction hash from XDR
* This is a simplified version - in production, use proper XDR parsing
*/
async function getTransactionHash(signedXDR: string): Promise<string> {
// Import dynamically to avoid issues if stellar-sdk isn't available at build time
const { TransactionBuilder, Networks } = await import("@stellar/stellar-sdk");

const transaction = TransactionBuilder.fromXDR(
signedXDR,
Networks.PUBLIC
);

return transaction.hash().toString("hex");
}
Loading
Loading