fix: state-replay race, share session options, idealo cheerio type
This commit is contained in:
@@ -14,8 +14,14 @@ export async function GET(req: NextRequest) {
|
|||||||
return NextResponse.json({ error: 'state mismatch' }, { status: 400 })
|
return NextResponse.json({ error: 'state mismatch' }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Consume the pending login atomically before doing the token exchange
|
||||||
|
// to prevent state replay on concurrent callbacks.
|
||||||
|
const { codeVerifier } = pending
|
||||||
|
delete session.loginInProgress
|
||||||
|
await session.save()
|
||||||
|
|
||||||
const redirectUri = `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/callback`
|
const redirectUri = `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/callback`
|
||||||
const tokens = await exchangeCode({ code, codeVerifier: pending.codeVerifier, redirectUri })
|
const tokens = await exchangeCode({ code, codeVerifier, redirectUri })
|
||||||
const claims = await verifyIdToken(tokens.id_token)
|
const claims = await verifyIdToken(tokens.id_token)
|
||||||
|
|
||||||
if (!isAllowedUser(claims.sub)) {
|
if (!isAllowedUser(claims.sub)) {
|
||||||
@@ -26,7 +32,6 @@ export async function GET(req: NextRequest) {
|
|||||||
session.userId = claims.sub
|
session.userId = claims.sub
|
||||||
session.email = claims.email
|
session.email = claims.email
|
||||||
session.name = claims.name
|
session.name = claims.name
|
||||||
delete session.loginInProgress
|
|
||||||
await session.save()
|
await session.save()
|
||||||
|
|
||||||
return NextResponse.redirect(new URL('/', req.url))
|
return NextResponse.redirect(new URL('/', req.url))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface SessionData {
|
|||||||
loginInProgress?: { state: string; codeVerifier: string }
|
loginInProgress?: { state: string; codeVerifier: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
const opts: SessionOptions = {
|
export const sessionOptions: SessionOptions = {
|
||||||
password: process.env.SESSION_PASSWORD || '',
|
password: process.env.SESSION_PASSWORD || '',
|
||||||
cookieName: 'preis_tracker_session',
|
cookieName: 'preis_tracker_session',
|
||||||
cookieOptions: {
|
cookieOptions: {
|
||||||
@@ -21,11 +21,9 @@ const opts: SessionOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getSession() {
|
export async function getSession() {
|
||||||
if (!opts.password || (typeof opts.password === 'string' && opts.password.length < 32)) {
|
if (!sessionOptions.password || (typeof sessionOptions.password === 'string' && sessionOptions.password.length < 32)) {
|
||||||
throw new Error('SESSION_PASSWORD must be set to a 32+ char value')
|
throw new Error('SESSION_PASSWORD must be set to a 32+ char value')
|
||||||
}
|
}
|
||||||
const c = await cookies()
|
const c = await cookies()
|
||||||
return getIronSession<SessionData>(c, opts)
|
return getIronSession<SessionData>(c, sessionOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sessionOptions = opts
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const idealoScraper: PriceScraper = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractJsonLdPrice($: cheerio.CheerioAPI): string | null {
|
function extractJsonLdPrice($: cheerio.Root): string | null {
|
||||||
const scripts = $('script[type="application/ld+json"]')
|
const scripts = $('script[type="application/ld+json"]')
|
||||||
for (let i = 0; i < scripts.length; i++) {
|
for (let i = 0; i < scripts.length; i++) {
|
||||||
const raw = $(scripts[i]).contents().text()
|
const raw = $(scripts[i]).contents().text()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { getIronSession } from 'iron-session'
|
import { getIronSession } from 'iron-session'
|
||||||
import type { SessionData } from '@/lib/auth/session'
|
import { sessionOptions, type SessionData } from '@/lib/auth/session'
|
||||||
|
|
||||||
const PUBLIC_PREFIXES = ['/api/auth/', '/api/cron/', '/_next/', '/favicon']
|
const PUBLIC_PREFIXES = ['/api/auth/', '/api/cron/', '/_next/', '/favicon']
|
||||||
|
|
||||||
@@ -9,10 +9,7 @@ export async function middleware(req: NextRequest) {
|
|||||||
if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) return NextResponse.next()
|
if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) return NextResponse.next()
|
||||||
|
|
||||||
const res = NextResponse.next()
|
const res = NextResponse.next()
|
||||||
const session = await getIronSession<SessionData>(req, res, {
|
const session = await getIronSession<SessionData>(req, res, sessionOptions)
|
||||||
password: process.env.SESSION_PASSWORD || '',
|
|
||||||
cookieName: 'preis_tracker_session',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!session.userId) {
|
if (!session.userId) {
|
||||||
if (pathname.startsWith('/api/')) {
|
if (pathname.startsWith('/api/')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user