Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const envSchema = z.object({
RATE_LIMIT_DATA_MAX: z.string().default('200'),
JWT_SECRET: z.string().default('dev-jwt-secret'),
JWT_REFRESH_SECRET: z.string().default('dev-jwt-refresh-secret'),
// Key used to encrypt TOTP secrets at rest. Falls back to JWT_SECRET so local
// development keeps working, but it should be set to its own value in production.
TWO_FACTOR_ENCRYPTION_KEY: z.string().optional(),
TWO_FACTOR_ISSUER: z.string().default('PayD'),
AUDIT_LOGGING_ENABLED: z.string().default('true'), // deprecated — always enabled
ADVANCED_RATE_LIMIT_ENABLED: z.string().default('true'), // deprecated — always enabled
TENANT_ISOLATION_STRICT_MODE: z.string().default('true'), // deprecated — always enabled
Expand Down
22 changes: 19 additions & 3 deletions backend/src/config/initDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,31 @@ CREATE TABLE IF NOT EXISTS users (
email VARCHAR(255) UNIQUE,
name VARCHAR(255),
organization_id INTEGER REFERENCES organizations(id) ON DELETE SET NULL,
role VARCHAR(20) DEFAULT 'EMPLOYEE' CHECK (role IN ('EMPLOYER', 'EMPLOYEE')),
role VARCHAR(20) DEFAULT 'EMPLOYEE' CHECK (role IN ('EMPLOYER', 'EMPLOYEE', 'ADMIN')),
refresh_token TEXT,
totp_secret VARCHAR(255),
totp_secret TEXT,
totp_pending_secret TEXT,
is_2fa_enabled BOOLEAN DEFAULT FALSE,
recovery_codes TEXT[],
two_factor_enabled_at TIMESTAMPTZ,
totp_last_used_step BIGINT,
two_factor_failed_attempts INTEGER NOT NULL DEFAULT 0,
two_factor_locked_until TIMESTAMPTZ,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS user_recovery_codes (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
code_hash CHAR(64) NOT NULL,
used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, code_hash)
);

CREATE INDEX IF NOT EXISTS idx_user_recovery_codes_user_id
ON user_recovery_codes(user_id);

CREATE TABLE IF NOT EXISTS social_identities (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
Expand Down
Loading
Loading