Files
story/migrations/000068_device_account_binding.up.sql
T

48 lines
2.8 KiB
PL/PgSQL
Raw Normal View History

BEGIN;
-- Physical possession can establish account ownership before a family exists.
CREATE TABLE device_account_binding (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
device_id uuid NOT NULL,
tenant_id uuid NOT NULL,
user_id uuid NOT NULL,
generation bigint NOT NULL,
bound_at timestamptz NOT NULL DEFAULT now(),
released_at timestamptz,
assigned_binding_id uuid REFERENCES device_binding(id),
FOREIGN KEY(device_id,tenant_id) REFERENCES device(id,tenant_id),
FOREIGN KEY(user_id,tenant_id) REFERENCES user_account(id,tenant_id)
);
CREATE UNIQUE INDEX device_account_binding_active ON device_account_binding(device_id) WHERE released_at IS NULL;
CREATE INDEX device_account_binding_user ON device_account_binding(user_id) WHERE released_at IS NULL;
ALTER TABLE device_ble_binding ALTER COLUMN household_id DROP NOT NULL, ALTER COLUMN subject_id DROP NOT NULL;
ALTER TABLE device_ble_binding ADD COLUMN account_binding_id uuid REFERENCES device_account_binding(id);
ALTER TABLE device_ble_binding ADD CONSTRAINT device_ble_binding_profile_pair CHECK ((household_id IS NULL) = (subject_id IS NULL));
-- Existing physical-confirm claims cannot steal an account-owned device.
CREATE FUNCTION guard_device_account_assignment() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE owner device_account_binding%ROWTYPE;
BEGIN
PERFORM 1 FROM device WHERE id=NEW.device_id FOR UPDATE;
SELECT * INTO owner FROM device_account_binding WHERE device_id=NEW.device_id AND released_at IS NULL;
IF FOUND AND (owner.user_id IS DISTINCT FROM NEW.bound_by OR owner.id::text IS DISTINCT FROM current_setting('app.device_account_binding',true)) THEN
RAISE EXCEPTION 'DEVICE_CONFIRMATION_REQUIRED' USING ERRCODE='P0001';
END IF;
RETURN NEW;
END $$;
CREATE TRIGGER device_account_assignment_guard BEFORE INSERT ON device_binding FOR EACH ROW EXECUTE FUNCTION guard_device_account_assignment();
CREATE FUNCTION finish_device_account_assignment() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
UPDATE device_account_binding SET released_at=now(),assigned_binding_id=NEW.id WHERE device_id=NEW.device_id AND released_at IS NULL;
RETURN NEW;
END $$;
CREATE TRIGGER device_account_assignment_finish AFTER INSERT ON device_binding FOR EACH ROW EXECUTE FUNCTION finish_device_account_assignment();
CREATE FUNCTION reset_device_account_binding() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NEW.factory_reset_counter IS DISTINCT FROM OLD.factory_reset_counter THEN
UPDATE device_account_binding SET released_at=now() WHERE device_id=NEW.id AND released_at IS NULL;
END IF;
RETURN NEW;
END $$;
CREATE TRIGGER device_account_binding_reset AFTER UPDATE OF factory_reset_counter ON device FOR EACH ROW EXECUTE FUNCTION reset_device_account_binding();
COMMIT;