feat: implement album-level password protection

This commit is contained in:
ralksta
2026-04-07 23:04:03 +02:00
parent e739fdb518
commit 17398dea39
16 changed files with 168 additions and 98 deletions

View File

@@ -21,14 +21,13 @@ export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { slug, password } = body;
const { slug, password, type = 'subpage' } = body;
if (!slug || !password) {
return NextResponse.json({ error: 'Missing slug or password' }, { status: 400 });
}
// 🛡️ SECURITY: Validate input types and limit length to prevent DoS attacks
// (e.g. from extremely long strings being passed to expensive crypto functions)
if (typeof slug !== 'string' || slug.length > 100) {
return NextResponse.json({ error: 'Invalid slug format or length' }, { status: 400 });
}
@@ -37,11 +36,17 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Invalid password format or length' }, { status: 400 });
}
if (!isProtected(slug)) {
return NextResponse.json({ error: 'Album is not password-protected' }, { status: 400 });
if (type !== 'subpage' && type !== 'album') {
return NextResponse.json({ error: 'Invalid type' }, { status: 400 });
}
const setCookie = authenticate(slug, password);
if (!isProtected(slug, type)) {
return NextResponse.json({
error: `${type === 'subpage' ? 'Subpage' : 'Album'} is not password-protected`,
}, { status: 400 });
}
const setCookie = authenticate(slug, password, type);
if (!setCookie) {
return NextResponse.json({ error: 'Invalid password' }, { status: 401 });
}

View File

@@ -68,7 +68,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return new NextResponse(null, {
status: 304,
headers: {
'ETag': etag,
ETag: etag,
'Cache-Control': 'public, max-age=31536000, immutable',
'X-RateLimit-Remaining': String(remaining),
},
@@ -87,7 +87,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
: result.contentType,
// Images are immutable once uploaded to Immich — cache aggressively
'Cache-Control': 'public, max-age=31536000, immutable',
'ETag': etag,
ETag: etag,
'X-RateLimit-Remaining': String(remaining),
};