- Hexagonal identity module: domain ports, use cases, argon2id hasher, pg repos - Migration 002_identity: identity_users + identity_sessions (token hash only) - Opaque 512-bit session tokens; DB stores SHA-256 hash; 7-day TTL in SQL - Cookie HttpOnly + Secure (COOKIE_SECURE, default true) + SameSite=Lax - LoginRateLimiter: 10 failures -> 429 + Retry-After, 15-min cooldown - Anti-enumeration: identical generic 401 + dummy-hash timing equalization - buildApp gains optional pool/cookieSecure; foundation-only app preserved - 47 unit + 14 integration tests; live smoke covers all acceptance criteria
35 lines
1.1 KiB
JavaScript
35 lines
1.1 KiB
JavaScript
/**
|
|
* Identity module tables. Module-owned naming: identity_<table>.
|
|
* Sessions store only the SHA-256 hash of the opaque cookie token.
|
|
*/
|
|
|
|
/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
|
|
export const up = (pgm) => {
|
|
pgm.sql(`
|
|
CREATE TABLE identity_users (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email citext NOT NULL UNIQUE,
|
|
password_hash text NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
)
|
|
`);
|
|
pgm.sql(`
|
|
CREATE TABLE identity_sessions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id uuid NOT NULL REFERENCES identity_users(id) ON DELETE CASCADE,
|
|
token_hash text NOT NULL UNIQUE,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
expires_at timestamptz NOT NULL,
|
|
revoked_at timestamptz
|
|
)
|
|
`);
|
|
pgm.sql('CREATE INDEX identity_sessions_user_id_idx ON identity_sessions (user_id)');
|
|
};
|
|
|
|
/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
|
|
export const down = (pgm) => {
|
|
pgm.sql('DROP TABLE IF EXISTS identity_sessions');
|
|
pgm.sql('DROP TABLE IF EXISTS identity_users');
|
|
};
|