create type "public"."usr_tag_or_email" as ("str" text); CREATE OR REPLACE FUNCTION public.usr_tag_or_email_of_string(val text) RETURNS usr_tag_or_email LANGUAGE sql AS $function$select row(val);$function$ ; CREATE OR REPLACE FUNCTION public.usr_tag_or_email_to_email(toe usr_tag_or_email) RETURNS email LANGUAGE plpgsql IMMUTABLE AS $function$ begin if position('@' in usr_tag_or_email_to_string(toe)) > 0 then return email_of_string(usr_tag_or_email_to_string(toe)); else return null; end if; end; $function$ ; CREATE OR REPLACE FUNCTION public.usr_tag_or_email_to_string(val usr_tag_or_email) RETURNS text LANGUAGE sql AS $function$select (val.str);$function$ ; CREATE OR REPLACE FUNCTION public.usr_tag_or_email_to_tag(toe usr_tag_or_email) RETURNS usr_tag LANGUAGE plpgsql IMMUTABLE AS $function$ begin if usr_tag_or_email_to_email(toe) is null then return usr_tag_of_string(usr_tag_or_email_to_string(toe)); else return null; end if; end; $function$ ; CREATE OR REPLACE FUNCTION public.usr_session_key_of_string(val text) RETURNS usr_session_key LANGUAGE sql AS $function$select row(val);$function$ ; CREATE OR REPLACE FUNCTION public.usr_session_key_gen() RETURNS usr_session_key LANGUAGE sql AS $function$ select usr_session_key_of_string( md5(extract(epoch from now()) || gen_random_bytes(32) :: text) ); $function$ ; CREATE OR REPLACE FUNCTION public.usr_session_login(tag_or_email usr_tag_or_email, password text, remember boolean DEFAULT false, location text DEFAULT NULL::text, device usr_session_device DEFAULT NULL::usr_session_device, ip inet DEFAULT NULL::inet) RETURNS usr_session_key LANGUAGE plpgsql AS $function$ declare usr public.usr; key public.usr_session_key := usr_session_key_gen(); expires_at timestamp; begin usr := public.usr_session_login_validate(tag_or_email, password); if remember then expires_at := now() + interval '1 week'; else expires_at := now() + interval '1 hour'; end if; insert into public.usr_session (key, expires_at, usr, location, device, ip) values (key, expires_at, usr.id, location, device, ip); return key; end; $function$ ; CREATE OR REPLACE FUNCTION public.usr_session_login_validate(tag_or_email usr_tag_or_email, password text) RETURNS usr LANGUAGE plpgsql STABLE AS $function$ declare usr_email public.email := public.usr_tag_or_email_to_email(tag_or_email); usr_tag public.usr_tag := public.usr_tag_or_email_to_tag(tag_or_email); usr public.usr; begin select * from public.usr as u where u.email = usr_email or u.tag = usr_tag into usr; if usr.id = 1 or usr.tag = usr_tag_of_string('system') then raise notice 'system user may not be logged into'; raise exception 'incorrect password for user %', usr_tag_or_email_to_string(tag_or_email); end if; if usr is null then -- prevent email guess bruteforces by raising the same exception -- for invalid password and user not found raise notice 'user % not found', usr_tag_or_email_to_string(tag_or_email); raise exception 'incorrect password for user %', usr_tag_or_email_to_string(tag_or_email); end if; if not hashed_text_matches(password, usr.password) then raise notice 'password does not match for user %', usr_tag_or_email_to_string(tag_or_email); raise exception 'incorrect password for user %', usr_tag_or_email_to_string(tag_or_email); end if; return usr; end; $function$ ;