48 lines
2.8 KiB
PL/PgSQL
48 lines
2.8 KiB
PL/PgSQL
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;
|