mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-26 08:54:39 +02:00
feat(auth): add audit logging, account lockout, and API key rate limiting
1. SecurityEventsService: Centralized audit logging for all auth events (login, register, logout, password changes, API key operations, SSO token exchange, etc.). Fire-and-forget pattern ensures auth flows are never blocked by logging failures. 2. AccountLockoutService: Locks accounts after 5 failed login attempts within 15 minutes. 30-minute lockout duration. Fails open on DB errors. Clears attempts on successful login. Email-not-verified does not count as a failed attempt. 3. API Key validation endpoint secured with rate limiting (10 req/min per IP via ThrottlerGuard) and audit logging. Key prefixes logged for forensics, never full keys. New schema: auth.login_attempts table for tracking failed logins. 174 tests passing across all auth and security modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
effa57fd61
commit
f7df8e97aa
14 changed files with 700 additions and 68 deletions
|
|
@ -3,6 +3,7 @@ export * from './auth.schema';
|
|||
export * from './credits.schema';
|
||||
export * from './feedback.schema';
|
||||
export * from './gifts.schema';
|
||||
export * from './login-attempts.schema';
|
||||
export * from './organizations.schema';
|
||||
export * from './subscriptions.schema';
|
||||
export * from './tags.schema';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Login Attempts Schema
|
||||
*
|
||||
* Tracks login attempts for account lockout functionality.
|
||||
* Failed attempts within a time window trigger account lockout.
|
||||
*/
|
||||
|
||||
import { pgSchema, text, boolean, timestamp, index, serial } from 'drizzle-orm/pg-core';
|
||||
|
||||
const authSchema = pgSchema('auth');
|
||||
|
||||
export const loginAttempts = authSchema.table(
|
||||
'login_attempts',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
email: text('email').notNull(),
|
||||
ipAddress: text('ip_address'),
|
||||
successful: boolean('successful').default(false).notNull(),
|
||||
attemptedAt: timestamp('attempted_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(table) => [index('login_attempts_email_attempted_at_idx').on(table.email, table.attemptedAt)]
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue