do_solve_mfa_challenge.sql (3785B)
1 -- 2 -- This file is part of TALER 3 -- Copyright (C) 2025 Taler Systems SA 4 -- 5 -- TALER is free software; you can redistribute it and/or modify it under the 6 -- terms of the GNU General Public License as published by the Free Software 7 -- Foundation; either version 3, or (at your option) any later version. 8 -- 9 -- TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 -- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 -- A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 -- 13 -- You should have received a copy of the GNU General Public License along with 14 -- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 -- 16 17 DROP FUNCTION IF EXISTS merchant_do_solve_mfa_challenge; 18 CREATE FUNCTION merchant_do_solve_mfa_challenge ( 19 IN in_challenge_id INT8, 20 IN in_h_body BYTEA, 21 IN in_solution TEXT, 22 IN in_now INT8, 23 OUT out_solved BOOLEAN, 24 OUT out_retry_counter INT4 25 ) 26 LANGUAGE plpgsql 27 AS $$ 28 DECLARE 29 my_confirmation_date INT8; 30 DECLARE 31 my_rec RECORD; 32 BEGIN 33 34 -- Check if challenge exists and matches 35 SELECT 36 tc.confirmation_date 37 ,tc.retry_counter 38 ,(tc.code = in_solution) AS solved 39 INTO 40 my_rec 41 FROM merchant.tan_challenges tc 42 WHERE tc.challenge_id = in_challenge_id 43 AND tc.h_body = in_h_body 44 AND tc.expiration_date > in_now; 45 46 IF NOT FOUND 47 THEN 48 out_solved = FALSE; 49 RETURN; 50 END IF; 51 52 my_confirmation_date = my_rec.confirmation_date; 53 out_retry_counter = my_rec.retry_counter; 54 out_solved = my_rec.solved; 55 56 -- Check if already solved before. 57 -- NOTE: this is deliberately idempotent: once the challenge is confirmed, 58 -- ANY solution (including a wrong one) yields out_solved=TRUE and the 59 -- retry counter is left alone. This is intentional and safe: 60 -- * authorization is not granted here. It is granted by 61 -- get_mfa_challenge() reporting a non-NULL confirmation_date, which this 62 -- branch does not change; re-confirming an already confirmed challenge 63 -- therefore grants nothing that the caller did not already have. 64 -- * reaching this branch already requires knowing in_challenge_id AND the 65 -- matching in_h_body (a hash over the request body with a 32-byte random 66 -- server-side salt), which together are the capability that authorizes 67 -- the operation in the first place. 68 -- * not decrementing the retry counter here keeps a client that retries a 69 -- confirmation after a lost response from burning its attempts, and 70 -- avoids turning this endpoint into a TAN-guessing oracle on a challenge 71 -- whose code has already been used. 72 IF my_confirmation_date IS NOT NULL 73 THEN 74 out_solved = TRUE; 75 RETURN; 76 END IF; 77 78 IF (0 = out_retry_counter) 79 THEN 80 out_solved = FALSE; 81 RETURN; 82 END IF; 83 84 IF out_solved 85 THEN 86 -- Newly solved, update DB! 87 my_confirmation_date = in_now; 88 UPDATE merchant.tan_challenges 89 SET confirmation_date = my_confirmation_date 90 WHERE challenge_id = in_challenge_id; 91 ELSE 92 -- Failed to solve, decrement retry counter 93 out_retry_counter = out_retry_counter - 1; 94 UPDATE merchant.tan_challenges 95 SET retry_counter = out_retry_counter 96 WHERE challenge_id = in_challenge_id; 97 END IF; 98 END; 99 $$; 100 101 COMMENT ON FUNCTION merchant_do_solve_mfa_challenge(INT8, BYTEA, TEXT, INT8) 102 IS 'Checks in_solution against the code of the challenge identified by' 103 ' in_challenge_id and in_h_body, and confirms the challenge if it' 104 ' matches. Returns out_solved=FALSE with a NULL out_retry_counter if no' 105 ' such (unexpired) challenge exists. Confirming an already confirmed' 106 ' challenge is idempotent: out_solved=TRUE is returned regardless of' 107 ' in_solution and the retry counter is not decremented.';