Rogue Admin Protection with a 24-Hour Deletion Queue

Part of the PurelyManage series. The Problem Any admin panel that manages real data has a deletion problem. The moment you give someone a delete button, you are one misclick away from losing something that took time to set up. In a single-user tool this is manageable: you know who deleted it because it was you. In a multi-admin panel it is more complicated. PurelyManage can have multiple sysadmin accounts. Any of them can delete email users, domains, and routing rules. The operations go directly to PurelyMail via their API, meaning the moment the request is made, the resource is gone. There is no recycle bin, no undo, no recovery path. ...

December 9, 2025 · 6 min · 1078 words · Sagar Nayak

Storing Credentials Securely: AES-256-GCM and JWT

Part of the PurelyManage series. PurelyManage handles two categories of sensitive data: IMAP passwords submitted by users during migration, and the session tokens that keep sysadmins logged in. Neither can be stored carelessly. This post covers how both are handled and why each design decision was made the way it was. Encrypting IMAP Credentials at Rest The problem with storing passwords When a user submits IMAP credentials for a migration job, the backend needs to hold onto those credentials until the job actually runs, which might be minutes or hours later depending on the queue. They have to live in the database. The question is in what form. ...

December 3, 2025 · 7 min · 1313 words · Sagar Nayak

Security Setup in Spring Boot: CORS, JWT, and API Tiers

What CORS blocks non-whitelisted origins in AppSecurityConfig. JWT is validated for protected endpoints in JwtAuthenticationFilter using AuthenticationManagerJWT. API tiers restrict access even with a valid token; higher tiers grant access to more endpoints. Flow (request lifecycle): Incoming request → CORS applied → non-whitelisted origins blocked. Public/No-Auth endpoints bypass JWT checks. Protected endpoints → JWT extracted and validated. Tier authorization matched against requested API tier. On success, controller executes; on failure, error is routed to global handler. Why Prevent misuse from unwanted domains via strict CORS. Reject bad tokens: expired, blocked, reused/refresh-misuse. Enforce progression: open → tier 4 → tier 3 → tier 2 → tier 1 as business rules demand. How CORS configuration (AppSecurityConfig.corsConfigurationSource()): @Bean fun corsConfigurationSource(): CorsConfigurationSource { val configuration = CorsConfiguration() configuration.allowedOrigins = listOf( "http://localhost:3000", "https://yourdomain.com" ) configuration.allowedMethods = listOf("GET","POST","PUT","DELETE","OPTIONS","PATCH") configuration.allowedHeaders = listOf( "Authorization","Content-Type","X-Requested-With","Accept","Origin", KeywordsAndConstants.HEADER_TRACKING_ID, KeywordsAndConstants.HEADER_API_KEY, KeywordsAndConstants.HEADER_OTP, KeywordsAndConstants.HEADER_AUTH_TOKEN ) configuration.allowCredentials = true configuration.exposedHeaders = listOf( KeywordsAndConstants.HEADER_TRACKING_ID, KeywordsAndConstants.HEADER_API_TIER ) val source = UrlBasedCorsConfigurationSource() source.registerCorsConfiguration("/**", configuration) return source } Explanation: ...

September 18, 2025 · 3 min · 553 words · Sagar Nayak