72 lines
2.9 KiB
JavaScript
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');
|
|
};
|