do_solve_mfa_challenge.sql (4168B)
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 IS NULL) AS not_sent 39 ,(tc.code = in_solution) AS solved 40 INTO 41 my_rec 42 FROM merchant.tan_challenges tc 43 WHERE tc.challenge_id = in_challenge_id 44 AND tc.h_body = in_h_body 45 AND tc.expiration_date > in_now; 46 47 IF NOT FOUND 48 THEN 49 out_solved = FALSE; 50 RETURN; 51 END IF; 52 53 my_confirmation_date = my_rec.confirmation_date; 54 out_retry_counter = my_rec.retry_counter; 55 out_solved = my_rec.solved; 56 57 -- Check if already solved before. 58 -- NOTE: this is deliberately idempotent: once the challenge is confirmed, 59 -- ANY solution (including a wrong one) yields out_solved=TRUE and the 60 -- retry counter is left alone. This is intentional and safe: 61 -- * authorization is not granted here. It is granted by 62 -- get_mfa_challenge() reporting a non-NULL confirmation_date, which this 63 -- branch does not change; re-confirming an already confirmed challenge 64 -- therefore grants nothing that the caller did not already have. 65 -- * reaching this branch already requires knowing in_challenge_id AND the 66 -- matching in_h_body (a hash over the request body with a 32-byte random 67 -- server-side salt), which together are the capability that authorizes 68 -- the operation in the first place. 69 -- * not decrementing the retry counter here keeps a client that retries a 70 -- confirmation after a lost response from burning its attempts, and 71 -- avoids turning this endpoint into a TAN-guessing oracle on a challenge 72 -- whose code has already been used. 73 IF my_confirmation_date IS NOT NULL 74 THEN 75 out_solved = TRUE; 76 RETURN; 77 END IF; 78 79 -- No TAN was sent for this challenge yet, so there is no code that 80 -- in_solution could match. Nothing was guessed, so the retry counter 81 -- is left alone. 82 IF my_rec.not_sent 83 THEN 84 out_solved = FALSE; 85 RETURN; 86 END IF; 87 88 IF (0 = out_retry_counter) 89 THEN 90 out_solved = FALSE; 91 RETURN; 92 END IF; 93 94 IF out_solved 95 THEN 96 -- Newly solved, update DB! 97 my_confirmation_date = in_now; 98 UPDATE merchant.tan_challenges 99 SET confirmation_date = my_confirmation_date 100 WHERE challenge_id = in_challenge_id; 101 ELSE 102 -- Failed to solve, decrement retry counter 103 out_retry_counter = out_retry_counter - 1; 104 UPDATE merchant.tan_challenges 105 SET retry_counter = out_retry_counter 106 WHERE challenge_id = in_challenge_id; 107 END IF; 108 END; 109 $$; 110 111 COMMENT ON FUNCTION merchant_do_solve_mfa_challenge(INT8, BYTEA, TEXT, INT8) 112 IS 'Checks in_solution against the code of the challenge identified by' 113 ' in_challenge_id and in_h_body, and confirms the challenge if it' 114 ' matches. Returns out_solved=FALSE with a NULL out_retry_counter if no' 115 ' such (unexpired) challenge exists. A challenge for which no TAN was' 116 ' sent yet (code is NULL) is never solved and costs no attempt.' 117 ' Confirming an already confirmed' 118 ' challenge is idempotent: out_solved=TRUE is returned regardless of' 119 ' in_solution and the retry counter is not decremented.';