feat(F-048): completed feature
This commit is contained in:
71
project/migrations/028_backoffice_users.js
Normal file
71
project/migrations/028_backoffice_users.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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');
|
||||
};
|
||||
Reference in New Issue
Block a user