do_challenge_address.sql (6835B)
1 -- 2 -- This file is part of TALER 3 -- Copyright (C) 2024 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 18 DROP FUNCTION IF EXISTS challenger_do_challenge_set_address_and_pin; 19 CREATE FUNCTION challenger_do_challenge_set_address_and_pin ( 20 IN in_nonce BYTEA, 21 IN in_address TEXT, 22 -- Newest last_tx_time for which a (re)transmission is still due, that is 23 -- 'now - retransmission_frequency'. Computed by the caller because 24 -- last_tx_time is only known here; see do_challenge_address.c. 25 IN in_retransmit_cutoff INT8, 26 IN in_now INT8, 27 IN in_tan INT4, 28 OUT out_not_found BOOLEAN, 29 OUT out_last_tx_time INT8, 30 OUT out_last_pin INT4, 31 OUT out_state TEXT, 32 OUT out_pin_transmit BOOLEAN, 33 OUT out_auth_attempts_left INT4, 34 -- How many PIN transmissions are left *after* this call? Note that 35 -- this is a different budget from out_auth_attempts_left, which counts 36 -- the guesses the user has on the current PIN. 37 OUT out_pin_transmissions_left INT4, 38 OUT out_client_redirect_uri TEXT, 39 OUT out_address_refused BOOLEAN, 40 OUT out_solved BOOLEAN) 41 LANGUAGE plpgsql 42 AS $$ 43 DECLARE 44 my_status RECORD; 45 my_do_update BOOL; 46 my_address_differs BOOL; 47 BEGIN 48 49 my_do_update = FALSE; 50 51 SELECT address 52 ,address_attempts_left 53 ,pin_transmissions_left 54 ,last_tx_time 55 ,client_redirect_uri 56 ,last_pin 57 ,pending_pin 58 ,auth_attempts_left 59 ,client_state 60 INTO my_status 61 FROM validations 62 WHERE nonce=in_nonce 63 AND expiration_time > in_now 64 FOR UPDATE; 65 66 IF NOT FOUND 67 THEN 68 out_not_found=TRUE; 69 out_last_tx_time=0; 70 out_last_pin=NULL; 71 out_pin_transmit=FALSE; 72 out_auth_attempts_left=0; 73 out_pin_transmissions_left=0; 74 out_client_redirect_uri=NULL; 75 out_address_refused=TRUE; 76 out_solved=FALSE; 77 out_state=NULL; 78 RETURN; 79 END IF; 80 out_not_found=FALSE; 81 out_last_tx_time=my_status.last_tx_time; 82 out_last_pin=my_status.last_pin; 83 out_pin_transmit=FALSE; 84 out_auth_attempts_left=my_status.auth_attempts_left; 85 out_pin_transmissions_left=my_status.pin_transmissions_left; 86 out_state=my_status.client_state; 87 out_client_redirect_uri=my_status.client_redirect_uri; 88 89 IF ( 0 > my_status.auth_attempts_left ) -- this challenge is solved 90 THEN 91 out_address_refused=TRUE; 92 out_solved=TRUE; 93 out_auth_attempts_left=0; 94 RETURN; 95 END IF; 96 out_solved=FALSE; 97 98 -- Two addresses are the same address if they are the same JSON *value*. 99 -- Comparing the raw text instead would make a purely cosmetic difference -- 100 -- a different field order, redundant whitespace -- count as a different 101 -- address, with two consequences. Firstly, the daemon itself re-appends 102 -- 'read_only' to the address as the *last* field (see 103 -- challenger-httpd_challenge.c), so an unchanged address can come back here 104 -- with its fields in a different order and silently cost an honest user one 105 -- of their address attempts. Secondly, the budget is deliberately 106 -- 'address_attempts_left' addresses times 'pin_transmissions_left' PINs 107 -- each; if cosmetic variants counted as distinct addresses, all of those 108 -- messages could be aimed at a single recipient. Comparing as JSONB also 109 -- makes this agree with the C layer, which compares addresses with 110 -- json_equal() in addr_equal(). 111 my_address_differs = ( (my_status.address IS NOT NULL) AND 112 (in_address::JSONB IS DISTINCT FROM 113 my_status.address::JSONB) ); 114 115 IF ( (0 = my_status.address_attempts_left) AND 116 my_address_differs ) 117 THEN 118 out_address_refused=TRUE; 119 out_last_pin=NULL; 120 RETURN; 121 END IF; 122 out_address_refused=FALSE; 123 124 IF ( my_address_differs OR 125 (my_status.address IS NULL) ) 126 THEN 127 -- We are changing the address, update counters. Refilling 128 -- 'pin_transmissions_left' and clearing the retransmission cooldown is 129 -- intentional: the user gets a fixed number of addresses and, for each 130 -- address, a fixed number of PIN transmissions, so that a user who 131 -- mistyped their address does not have to wait out the cooldown of a 132 -- message that went to somebody else. 133 my_status.address_attempts_left 134 = GREATEST(0,my_status.address_attempts_left - 1); 135 -- Store the canonical (JSONB) rendering, so that what is on file does not 136 -- depend on the field order the client happened to use. 137 my_status.address = in_address::JSONB::TEXT; 138 my_status.pin_transmissions_left = 3; 139 my_status.last_tx_time = 0; 140 -- The PIN generated below is now merely 'pending' until the helper 141 -- confirms the transmission, so -- unlike before this patch -- this call 142 -- no longer necessarily overwrites 'last_pin'. The PIN on file went to 143 -- the *previous* address, so it must be dropped here: if the helper then 144 -- fails, the user must be left without a usable PIN rather than with one 145 -- that attests an address it was never sent to. 146 my_status.last_pin = NULL; 147 my_status.pending_pin = NULL; 148 my_status.auth_attempts_left = 0; 149 out_last_pin = NULL; 150 out_auth_attempts_left = 0; 151 my_do_update=TRUE; 152 END IF; 153 154 IF ( (my_status.pin_transmissions_left > 0) AND 155 (my_status.last_tx_time <= in_retransmit_cutoff) ) 156 THEN 157 -- Enough time has passed since the last transmission, so we are changing 158 -- the PIN, update counters. The new PIN is only stored as 'pending_pin': 159 -- it is promoted to 'last_pin' (and 'auth_attempts_left' reset) by 160 -- CHALLENGERDB_do_challenge_address_confirm_pin() once the AUTH_COMMAND 161 -- helper confirmed the transmission. Until then the PIN the user may 162 -- already hold from an earlier transmission stays valid. 163 my_status.pin_transmissions_left = my_status.pin_transmissions_left - 1; 164 my_status.pending_pin = in_tan; 165 my_status.last_tx_time = in_now; 166 out_pin_transmissions_left = my_status.pin_transmissions_left; 167 out_auth_attempts_left = 3; 168 out_pin_transmit=TRUE; 169 out_last_pin = in_tan; 170 out_last_tx_time = in_now; 171 my_do_update=TRUE; 172 END IF; 173 174 IF my_do_update 175 THEN 176 UPDATE validations SET 177 address=my_status.address 178 ,address_attempts_left=my_status.address_attempts_left 179 ,pin_transmissions_left=my_status.pin_transmissions_left 180 ,last_tx_time=my_status.last_tx_time 181 ,last_pin=my_status.last_pin 182 ,pending_pin=my_status.pending_pin 183 ,auth_attempts_left=my_status.auth_attempts_left 184 WHERE nonce=in_nonce; 185 END IF; 186 187 RETURN; 188 189 END $$;