Files
mercadodevida/project/migrations/028_backoffice_users.js
2026-08-19 07:17:14 +02:00

72 lines
2.9 KiB
JavaScript

/**
* FIX-14: Physical separation of backoffice users (admin/editor) from
* storefront customers (identity_users). Backoffice users get their own
* table, their own sessions, and their own auth cookie/session mechanism.
*
* Customers (role = 'customer') remain in identity_users.
* Admin/editor users are COPIED into backoffice_users (preserving password
* hash and mfa_enrolled flag) and then REMOVED from identity_users so the
* tables do not overlap at all.
*/
/** @param {import('node-pg-migrate'). MigrationBuilder} pgm */
export const up = (pgm) => {
// 1) Create the new backoffice schema namespace (table names: backoffice_*)
pgm.sql(`
CREATE TABLE backoffice_users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email citext NOT NULL UNIQUE,
password_hash text NOT NULL,
role text NOT NULL DEFAULT 'admin'
CHECK (role IN ('admin', 'editor')),
mfa_enrolled boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
)
`);
pgm.sql(`
CREATE TABLE backoffice_sessions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES backoffice_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 backoffice_sessions_user_id_idx ON backoffice_sessions (user_id)');
// 2) Migrate admin/editor rows from identity_users → backoffice_users.
// Preserve id and password_hash so existing sessions/hashes survive.
pgm.sql(`
INSERT INTO backoffice_users (id, email, password_hash, role, mfa_enrolled, created_at, updated_at)
SELECT id, email, password_hash,
CASE WHEN role = 'editor' THEN 'editor' ELSE 'admin' END,
COALESCE(mfa_enrolled, false),
created_at, updated_at
FROM identity_users
WHERE role IN ('admin', 'editor')
ON CONFLICT (id) DO NOTHING
`);
// 3) Remove admin/editor rows from identity_users so the tables are disjoint.
pgm.sql(`DELETE FROM identity_users WHERE role IN ('admin', 'editor')`);
};
/** @param {import('node-pg-migrate'). MigrationBuilder} pgm */
export const down = (pgm) => {
// Recreate the dropped rows in identity_users so the schema is reversible
// for the dev environment (best-effort — email uniqueness may collide if
// a customer signed up with the same email after the migration).
pgm.sql(`
INSERT INTO identity_users (id, email, password_hash, role, created_at, updated_at)
SELECT id, email, password_hash,
CASE WHEN role = 'editor' THEN 'admin' ELSE role END,
created_at, updated_at
FROM backoffice_users
ON CONFLICT (id) DO NOTHING
`);
pgm.sql('DROP TABLE IF EXISTS backoffice_sessions');
pgm.sql('DROP TABLE IF EXISTS backoffice_users');
};