Initial reader-api backend extracted from reader
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { OAuth2Client } from "google-auth-library"
|
||||
import { SignJWT, importPKCS8, generateKeyPair } from "jose"
|
||||
import * as crypto from "crypto"
|
||||
|
||||
const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID)
|
||||
|
||||
function generateTokens(userId: string) {
|
||||
const secret = process.env.MOBILE_JWT_SECRET || process.env.NEXTAUTH_SECRET || ""
|
||||
const key = crypto.createHmac("sha256", secret)
|
||||
const payload = Buffer.from(JSON.stringify({ sub: userId, iat: Math.floor(Date.now() / 1000) })).toString("base64url")
|
||||
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url")
|
||||
const sig = key.update(`${header}.${payload}`).digest("base64url")
|
||||
const accessToken = `${header}.${payload}.${sig}`
|
||||
|
||||
// refresh token: random 40-byte hex, stored hashed in DB if needed; for now return raw
|
||||
const refreshToken = crypto.randomBytes(40).toString("hex")
|
||||
return { accessToken, refreshToken }
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { googleIdToken } = body
|
||||
|
||||
if (!googleIdToken || typeof googleIdToken !== "string") {
|
||||
return NextResponse.json({ error: "googleIdToken is required" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Verify the Google ID token
|
||||
let ticket
|
||||
try {
|
||||
ticket = await googleClient.verifyIdToken({
|
||||
idToken: googleIdToken,
|
||||
audience: process.env.GOOGLE_CLIENT_ID,
|
||||
})
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid Google token" }, { status: 401 })
|
||||
}
|
||||
|
||||
const payload = ticket.getPayload()
|
||||
if (!payload?.email) {
|
||||
return NextResponse.json({ error: "Unable to extract email from token" }, { status: 401 })
|
||||
}
|
||||
|
||||
const { email, name, picture, sub: googleSub } = payload
|
||||
|
||||
// Upsert user — match NextAuth behaviour
|
||||
let user = await prisma.user.findUnique({ where: { email } })
|
||||
if (!user) {
|
||||
user = await prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
name: name ?? null,
|
||||
image: picture ?? null,
|
||||
emailVerified: new Date(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Upsert Google Account link
|
||||
const existingAccount = await prisma.account.findUnique({
|
||||
where: { provider_providerAccountId: { provider: "google", providerAccountId: googleSub } },
|
||||
})
|
||||
if (!existingAccount) {
|
||||
await prisma.account.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
type: "oauth",
|
||||
provider: "google",
|
||||
providerAccountId: googleSub,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const { accessToken, refreshToken } = generateTokens(user.id)
|
||||
|
||||
return NextResponse.json({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn: 3600,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
image: user.image,
|
||||
role: user.role,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Mobile login error:", error)
|
||||
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user