challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

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 $$;