fix: presigned URL signatures, bucket consolidation, login & invite status
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m44s

- MinIO: use separate public client for presigned URLs so AWS V4 signature
  matches the browser's Host header (fixes SignatureDoesNotMatch on all uploads)
- Consolidate applicant/partner uploads to mopc-files bucket (removes
  non-existent mopc-submissions and mopc-partners buckets)
- Auth: allow magic links for any non-SUSPENDED user (was ACTIVE-only,
  blocking first-time CSV-seeded applicants)
- Auth: accept invite tokens for any non-SUSPENDED user (was INVITED-only)
- Ensure all 14 invite token locations set status to INVITED

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 13:06:17 +01:00
parent 78334676d0
commit c6d0f90038
9 changed files with 50 additions and 60 deletions

View File

@@ -2,7 +2,7 @@ import crypto from 'crypto'
import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, publicProcedure, protectedProcedure } from '../trpc'
import { getPresignedUrl, generateObjectKey } from '@/lib/minio'
import { getPresignedUrl, generateObjectKey, BUCKET_NAME } from '@/lib/minio'
import { generateLogoKey, createStorageProvider, type StorageProviderType } from '@/lib/storage'
import { getImageUploadUrl, confirmImageUpload, getImageUrl, deleteImage, type ImageUploadConfig } from '@/server/utils/image-upload'
import { sendStyledNotificationEmail, sendTeamMemberInviteEmail } from '@/lib/email'
@@ -12,8 +12,9 @@ import { checkRequirementsAndTransition, triggerInProgressOnActivity, transition
import { EvaluationConfigSchema, MentoringConfigSchema } from '@/types/competition-configs'
import type { Prisma } from '@prisma/client'
// Bucket for applicant submissions
export const SUBMISSIONS_BUCKET = 'mopc-submissions'
// All uploads use the single configured bucket (MINIO_BUCKET / mopc-files).
// Files are organized by path prefix: {ProjectName}/{RoundName}/... for submissions,
// avatars/{userId}/... for profile images, logos/{projectId}/... for project logos.
const TEAM_INVITE_TOKEN_EXPIRY_MS = 30 * 24 * 60 * 60 * 1000 // 30 days
function generateInviteToken(): string {
@@ -346,11 +347,11 @@ export const applicantRouter = router({
const objectKey = generateObjectKey(project.title, input.fileName, roundName)
const url = await getPresignedUrl(SUBMISSIONS_BUCKET, objectKey, 'PUT', 3600)
const url = await getPresignedUrl(BUCKET_NAME, objectKey, 'PUT', 3600)
return {
url,
bucket: SUBMISSIONS_BUCKET,
bucket: BUCKET_NAME,
objectKey,
isLate,
roundId: input.roundId || null,