owpengram-server/deploy/migrations/20260901000013_auth_session_layer_advance_function.up.sql
2026-09-01 12:06:31 +03:00

245 lines
8.2 KiB
PL/PgSQL

-- Advance one explicit invokeWithLayer observation without paying one client
-- round trip for every lock, comparison and projection update. The caller
-- still owns the surrounding transaction/savepoint so identity_changed can
-- roll the complete attempt back and retry with a fresh READ COMMITTED view.
CREATE OR REPLACE FUNCTION public.telesrv_advance_auth_session_layer(
p_raw_auth_key_id bigint,
p_session_id bigint,
p_layer integer,
p_msg_id bigint,
p_expires_at timestamptz
)
RETURNS TABLE (
advance_status text,
current_layer integer,
current_msg_id bigint,
current_observation_id bigint,
current_expires_at timestamptz,
shared_default boolean,
applied boolean
)
LANGUAGE plpgsql
AS $$
DECLARE
v_now timestamptz := now();
v_hint_expiry integer;
v_hint_perm_id bigint;
v_hint_bound boolean := false;
v_hint_has_identity boolean := false;
v_hint_identity_id bigint;
v_raw_expiry integer;
v_actual_perm_id bigint;
v_actual_bound boolean := false;
v_actual_has_identity boolean := false;
v_actual_identity_id bigint;
v_perm_expiry integer;
v_current_found boolean := false;
v_current_layer integer := 0;
v_current_msg_id bigint := 0;
v_current_observation_id bigint := 0;
v_current_expires_at timestamptz := p_expires_at;
v_shared_default boolean := false;
v_observation_id bigint;
v_key_ids bigint[];
v_updated integer;
BEGIN
IF p_layer <= 0
OR p_msg_id <= 0
OR p_msg_id % 4 <> 0
OR (p_msg_id & 4294967295) = 0
OR p_expires_at IS NULL
OR NOT v_now < p_expires_at
OR p_expires_at - interval '301 seconds' > v_now + interval '30 seconds'
THEN
RETURN QUERY SELECT
'evidence_invalid'::text, 0, 0::bigint, 0::bigint,
COALESCE(p_expires_at, v_now), false, false;
RETURN;
END IF;
-- Lock-free identity hint. It is deliberately revalidated after the raw
-- row lock; a newly committed first bind returns identity_changed rather
-- than taking a permanent identity lock in raw->identity order.
SELECT key.expires_at, binding.perm_auth_key_id
INTO v_hint_expiry, v_hint_perm_id
FROM public.auth_keys AS key
LEFT JOIN public.temp_auth_key_bindings AS binding
ON binding.temp_auth_key_id = key.auth_key_id
WHERE key.auth_key_id = p_raw_auth_key_id;
IF NOT FOUND THEN
RETURN QUERY SELECT
'auth_key_not_found'::text, 0, 0::bigint, 0::bigint,
p_expires_at, false, false;
RETURN;
END IF;
v_hint_bound := v_hint_perm_id IS NOT NULL;
v_hint_has_identity := v_hint_bound OR v_hint_expiry = 0;
IF v_hint_has_identity THEN
v_hint_identity_id := CASE
WHEN v_hint_bound THEN v_hint_perm_id
ELSE p_raw_auth_key_id
END;
PERFORM pg_advisory_xact_lock(
1096111176::integer,
hashint8(v_hint_identity_id)::integer
);
END IF;
SELECT key.expires_at
INTO v_raw_expiry
FROM public.auth_keys AS key
WHERE key.auth_key_id = p_raw_auth_key_id
FOR UPDATE;
IF NOT FOUND THEN
RETURN QUERY SELECT
'auth_key_not_found'::text, 0, 0::bigint, 0::bigint,
p_expires_at, false, false;
RETURN;
END IF;
v_actual_perm_id := NULL;
SELECT binding.perm_auth_key_id
INTO v_actual_perm_id
FROM public.temp_auth_key_bindings AS binding
WHERE binding.temp_auth_key_id = p_raw_auth_key_id;
v_actual_bound := FOUND;
IF NOT v_actual_bound THEN
v_actual_perm_id := p_raw_auth_key_id;
END IF;
v_actual_has_identity := v_actual_bound OR v_raw_expiry = 0;
v_actual_identity_id := v_actual_perm_id;
IF v_actual_has_identity <> v_hint_has_identity
OR (v_actual_has_identity AND v_actual_identity_id <> v_hint_identity_id)
OR v_actual_bound <> v_hint_bound
OR v_raw_expiry <> v_hint_expiry
THEN
RETURN QUERY SELECT
'identity_changed'::text, 0, 0::bigint, 0::bigint,
p_expires_at, false, false;
RETURN;
END IF;
IF v_actual_bound THEN
SELECT key.expires_at
INTO v_perm_expiry
FROM public.auth_keys AS key
WHERE key.auth_key_id = v_actual_perm_id
FOR UPDATE;
IF NOT FOUND OR v_raw_expiry <= 0 OR v_perm_expiry <> 0 THEN
RETURN QUERY SELECT
'binding_invalid'::text, 0, 0::bigint, 0::bigint,
p_expires_at, false, false;
RETURN;
END IF;
END IF;
SELECT evidence.layer,
evidence.msg_id,
evidence.observation_id,
evidence.expires_at
INTO v_current_layer,
v_current_msg_id,
v_current_observation_id,
v_current_expires_at
FROM public.auth_key_session_layers AS evidence
WHERE evidence.raw_auth_key_id = p_raw_auth_key_id
AND evidence.session_id = p_session_id
FOR UPDATE;
v_current_found := FOUND;
IF v_current_found AND v_now < v_current_expires_at THEN
IF p_msg_id < v_current_msg_id THEN
SELECT key.layer = v_current_layer
AND key.layer_observation_id = v_current_observation_id
INTO v_shared_default
FROM public.auth_keys AS key
WHERE key.auth_key_id = v_actual_perm_id;
RETURN QUERY SELECT
'ok'::text, v_current_layer, v_current_msg_id,
v_current_observation_id, v_current_expires_at,
v_shared_default, false;
RETURN;
END IF;
IF p_msg_id = v_current_msg_id THEN
IF p_layer <> v_current_layer THEN
RETURN QUERY SELECT
'conflict'::text, v_current_layer, v_current_msg_id,
v_current_observation_id, v_current_expires_at,
false, false;
RETURN;
END IF;
SELECT key.layer = v_current_layer
AND key.layer_observation_id = v_current_observation_id
INTO v_shared_default
FROM public.auth_keys AS key
WHERE key.auth_key_id = v_actual_perm_id;
RETURN QUERY SELECT
'ok'::text, v_current_layer, v_current_msg_id,
v_current_observation_id, v_current_expires_at,
v_shared_default, false;
RETURN;
END IF;
END IF;
v_observation_id := nextval('public.auth_key_layer_observation_seq');
INSERT INTO public.auth_key_session_layers (
raw_auth_key_id,
session_id,
layer,
msg_id,
observation_id,
expires_at
) VALUES (
p_raw_auth_key_id,
p_session_id,
p_layer,
p_msg_id,
v_observation_id,
p_expires_at
)
ON CONFLICT (raw_auth_key_id, session_id) DO UPDATE SET
layer = EXCLUDED.layer,
msg_id = EXCLUDED.msg_id,
observation_id = EXCLUDED.observation_id,
expires_at = EXCLUDED.expires_at
RETURNING layer, msg_id, observation_id, expires_at
INTO v_current_layer,
v_current_msg_id,
v_current_observation_id,
v_current_expires_at;
v_key_ids := ARRAY[p_raw_auth_key_id];
IF v_actual_perm_id <> p_raw_auth_key_id THEN
v_key_ids := array_append(v_key_ids, v_actual_perm_id);
END IF;
UPDATE public.auth_keys AS key
SET layer = p_layer,
layer_observation_id = v_observation_id
WHERE key.auth_key_id = ANY(v_key_ids)
AND key.layer_observation_id < v_observation_id;
GET DIAGNOSTICS v_updated = ROW_COUNT;
IF v_updated <> cardinality(v_key_ids) THEN
RAISE EXCEPTION
'publish auth session Layer defaults updated % of % locked keys',
v_updated,
cardinality(v_key_ids)
USING ERRCODE = '23000';
END IF;
UPDATE public.authorizations AS authz
SET layer = p_layer
WHERE authz.auth_key_id = ANY(v_key_ids);
RETURN QUERY SELECT
'ok'::text, v_current_layer, v_current_msg_id,
v_current_observation_id, v_current_expires_at,
true, true;
END;
$$;
COMMENT ON FUNCTION public.telesrv_advance_auth_session_layer(
bigint, bigint, integer, bigint, timestamptz
) IS 'Atomically advances durable invokeWithLayer evidence while preserving auth identity lock order';