Use user’ß email for auth funcs and return cookie on email change

This is for security, just in case two users have the same cookie,
althought it is unlikely, but nevertheless less guessable.

I also need to refresh the cookie when the user changes their email
address, because it is liked toghether.  It does mean that it will
logout from everywhere else, but i can not do anything about that.
This commit is contained in:
jordi fita mas 2023-01-23 21:18:55 +01:00
parent f9e22c0789
commit 5eeaab2013
28 changed files with 429 additions and 146 deletions

25
deploy/build_cookie.sql Normal file
View File

@ -0,0 +1,25 @@
-- Deploy numerus:build_cookie to pg
-- requires: schema_numerus
-- requires: current_user_email
-- requires: current_user_cookie
begin;
set search_path to numerus, public;
create or replace function build_cookie(user_email email default null, user_cookie text default null) returns text as
$$
select coalesce(user_cookie, current_user_cookie()) || '/' || coalesce(user_email, current_user_email());
$$
language sql
stable;
revoke execute on function build_cookie(email, text) from public;
grant execute on function build_cookie(email, text) to guest;
grant execute on function build_cookie(email, text) to invoicer;
grant execute on function build_cookie(email, text) to admin;
comment on function build_cookie(email, text) is
'Build the cookie to send to the users browser, either for the given values or for the current user.';
commit;

View File

@ -28,7 +28,6 @@ begin
user_cookie := ''; user_cookie := '';
user_role := 'guest'::name; user_role := 'guest'::name;
end if; end if;
perform set_config('request.user.id', uid, false);
perform set_config('request.user.email', user_email, false); perform set_config('request.user.email', user_email, false);
perform set_config('request.user.cookie', user_cookie, false); perform set_config('request.user.cookie', user_cookie, false);
return user_role; return user_role;
@ -40,7 +39,7 @@ stable
set search_path = auth, numerus, pg_temp; set search_path = auth, numerus, pg_temp;
comment on function check_cookie(text) is comment on function check_cookie(text) is
'Checks whether a given cookie is for a valid users, returning its email and role'; 'Checks whether a given cookie is for a valid users, returning their role, and setting current_user_email and current_user_cookie';
revoke execute on function check_cookie(text) from public; revoke execute on function check_cookie(text) from public;
grant execute on function check_cookie(text) to authenticator; grant execute on function check_cookie(text) to authenticator;

View File

@ -1,23 +0,0 @@
-- Deploy numerus:current_app_user to pg
-- requires: schema_numerus
begin;
set search_path to numerus;
create or replace function current_app_user() returns text as
$$
select current_setting('request.user.cookie', true);
$$
language sql
stable;
comment on function current_app_user() is
'Returns the ID of the current Numerus user';
revoke execute on function current_app_user() from public;
grant execute on function current_app_user() to guest;
grant execute on function current_app_user() to invoicer;
grant execute on function current_app_user() to admin;
commit;

View File

@ -0,0 +1,23 @@
-- Deploy numerus:current_user_cookie to pg
-- requires: schema_numerus
begin;
set search_path to numerus;
create or replace function current_user_cookie() returns text as
$$
select current_setting('request.user.cookie', true);
$$
language sql
stable;
comment on function current_user_cookie() is
'Returns the cookie of the current Numerus user';
revoke execute on function current_user_cookie() from public;
grant execute on function current_user_cookie() to guest;
grant execute on function current_user_cookie() to invoicer;
grant execute on function current_user_cookie() to admin;
commit;

View File

@ -0,0 +1,23 @@
-- Deploy numerus:current_user_email to pg
-- requires: schema_numerus
begin;
set search_path to numerus;
create or replace function current_user_email() returns text as
$$
select current_setting('request.user.email', true);
$$
language sql
stable;
comment on function current_user_email() is
'Returns the email of the current Numerus user';
revoke execute on function current_user_email() from public;
grant execute on function current_user_email() to guest;
grant execute on function current_user_email() to invoicer;
grant execute on function current_user_email() to admin;
commit;

View File

@ -6,6 +6,7 @@
-- requires: email -- requires: email
-- requires: user -- requires: user
-- requires: login_attempt -- requires: login_attempt
-- requires: build_cookie
begin; begin;
@ -48,7 +49,7 @@ begin
(user_name, ip_address, success) (user_name, ip_address, success)
values (login.email, login.ip_address, true); values (login.email, login.ip_address, true);
return user_cookie || '/' || email; return build_cookie(email, user_cookie);
end; end;
$$ $$
language plpgsql language plpgsql

View File

@ -1,7 +1,8 @@
-- Deploy numerus:logout to pg -- Deploy numerus:logout to pg
-- requires: schema_auth -- requires: schema_auth
-- requires: user -- requires: user
-- requires: current_app_user -- requires: current_user_cookie
-- requires: current_user_email
begin; begin;
@ -12,7 +13,8 @@ $$
update "user" update "user"
set cookie = default set cookie = default
, cookie_expires_at = default , cookie_expires_at = default
where cookie = current_app_user() where email = current_user_email()
and cookie = current_user_cookie()
and cookie_expires_at > current_timestamp and cookie_expires_at > current_timestamp
and length(cookie) > 30 and length(cookie) > 30
$$ $$
@ -21,7 +23,7 @@ security definer
set search_path to auth, numerus, pg_temp; set search_path to auth, numerus, pg_temp;
comment on function logout() is comment on function logout() is
'Removes the cookie and its expiry data from the current user, set as request.user setting'; 'Removes the cookie and its expiry data from the current user, as returned by current_user_email and current_user_cookie';
revoke execute on function logout() from public; revoke execute on function logout() from public;
grant execute on function logout() to invoicer; grant execute on function logout() to invoicer;

View File

@ -1,7 +1,8 @@
-- Deploy numerus:user_profile to pg -- Deploy numerus:user_profile to pg
-- requires: schema_numerus -- requires: schema_numerus
-- requires: user -- requires: user
-- requires: current_app_user -- requires: current_user_cookie
-- requires: current_user_email
begin; begin;
@ -16,7 +17,8 @@ select user_id
, role , role
, lang_tag , lang_tag
from auth."user" from auth."user"
where cookie = current_app_user() where email = current_user_email()
and cookie = current_user_cookie()
and cookie_expires_at > current_timestamp and cookie_expires_at > current_timestamp
and length(cookie) > 30 and length(cookie) > 30
union all union all
@ -28,23 +30,40 @@ select 0
where not exists ( where not exists (
select 1 select 1
from auth."user" from auth."user"
where cookie = current_app_user() where email = current_user_email()
and cookie = current_user_cookie()
and cookie_expires_at > current_timestamp and cookie_expires_at > current_timestamp
and length(cookie) > 30 and length(cookie) > 30
); );
create rule update_user_profile as on update to user_profile
do instead update auth."user"
set email = new.email
, name = new.name
, lang_tag = new.lang_tag
where cookie = current_app_user()
and cookie_expires_at > current_timestamp
and length(cookie) > 30
;
grant select on table user_profile to guest; grant select on table user_profile to guest;
grant select, update(email, name, lang_tag) on table user_profile to invoicer; grant select, update(email, name, lang_tag) on table user_profile to invoicer;
grant select, update(email, name, lang_tag) on table user_profile to admin; grant select, update(email, name, lang_tag) on table user_profile to admin;
create or replace function update_user_profile() returns trigger as
$$
begin
update auth."user"
set email = new.email
, name = new.name
, lang_tag = new.lang_tag
where email = current_user_email()
and cookie = current_user_cookie()
and cookie_expires_at > current_timestamp
and length(cookie) > 30
;
perform set_config('request.user.email', new.email, false);
return new;
end;
$$
language plpgsql
security definer
set search_path to auth, numerus, pg_temp;
create trigger update_user_profile
instead of update on user_profile
for each row execute procedure update_user_profile();
commit; commit;

View File

@ -46,7 +46,7 @@ func LoginHandler() http.Handler {
conn := getConn(r) conn := getConn(r)
cookie := conn.MustGetText(r.Context(), "", "select login($1, $2, $3)", page.Email, page.Password, remoteAddr(r)) cookie := conn.MustGetText(r.Context(), "", "select login($1, $2, $3)", page.Email, page.Password, remoteAddr(r))
if cookie != "" { if cookie != "" {
http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour)) setSessionCookie(w, cookie)
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
@ -79,6 +79,10 @@ func remoteAddr(r *http.Request) string {
return address return address
} }
func setSessionCookie(w http.ResponseWriter, cookie string) {
http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour))
}
func createSessionCookie(value string, duration time.Duration) *http.Cookie { func createSessionCookie(value string, duration time.Duration) *http.Cookie {
return &http.Cookie{ return &http.Cookie{
Name: sessionCookie, Name: sessionCookie,

View File

@ -32,7 +32,7 @@ func ProfileHandler() http.Handler {
page := ProfilePage{ page := ProfilePage{
Title: pgettext("title", "User Settings", locale), Title: pgettext("title", "User Settings", locale),
Email: user.Email, Email: user.Email,
Languages: mustGetLanguageOptions(r.Context(), conn), Language: user.Language.String(),
} }
if r.Method == "POST" { if r.Method == "POST" {
r.ParseForm() r.ParseForm()
@ -41,11 +41,13 @@ func ProfileHandler() http.Handler {
page.Password = r.FormValue("password") page.Password = r.FormValue("password")
page.PasswordConfirm = r.FormValue("password_confirm") page.PasswordConfirm = r.FormValue("password_confirm")
page.Language = r.FormValue("language") page.Language = r.FormValue("language")
conn.MustExec(r.Context(), "update user_profile set name = $1, email = $2, lang_tag = $3", page.Name, page.Email, page.Language) cookie := conn.MustGetText(r.Context(), "", "update user_profile set name = $1, email = $2, lang_tag = $3 returning build_cookie()", page.Name, page.Email, page.Language)
http.Redirect(w, r, "/profile", http.StatusSeeOther); setSessionCookie(w, cookie)
return; http.Redirect(w, r, "/profile", http.StatusSeeOther)
return
} else { } else {
if err := conn.QueryRow(r.Context(), "select name, lang_tag from user_profile").Scan(&page.Name, &page.Language); err != nil { page.Languages = mustGetLanguageOptions(r.Context(), conn)
if err := conn.QueryRow(r.Context(), "select name from user_profile").Scan(&page.Name); err != nil {
panic(nil) panic(nil)
} }
} }

7
revert/build_cookie.sql Normal file
View File

@ -0,0 +1,7 @@
-- Revert numerus:build_cookie from pg
begin;
drop function if exists numerus.build_cookie(numerus.email, text);
commit;

View File

@ -1,7 +0,0 @@
-- Revert numerus:current_app_user from pg
begin;
drop function if exists numerus.current_app_user();
commit;

View File

@ -0,0 +1,7 @@
-- Revert numerus:current_user_cookie from pg
begin;
drop function if exists numerus.current_user_cookie();
commit;

View File

@ -0,0 +1,7 @@
-- Revert numerus:current_user_email from pg
begin;
drop function if exists numerus.current_user_email();
commit;

View File

@ -2,6 +2,8 @@
begin; begin;
drop trigger if exists update_user_profile on numerus.user_profile;
drop function if exists numerus.update_user_profile();
drop view if exists numerus.user_profile; drop view if exists numerus.user_profile;
commit; commit;

View File

@ -15,9 +15,11 @@ extension_pgcrypto [schema_auth] 2023-01-13T00:11:50Z jordi fita mas <jordi@tand
encrypt_password [schema_auth user extension_pgcrypto] 2023-01-13T00:14:30Z jordi fita mas <jordi@tandem.blog> # Add trigger to encrypt users password encrypt_password [schema_auth user extension_pgcrypto] 2023-01-13T00:14:30Z jordi fita mas <jordi@tandem.blog> # Add trigger to encrypt users password
login_attempt [schema_auth] 2023-01-17T14:05:49Z jordi fita mas <jordi@tandem.blog> # Add table to log login attempts login_attempt [schema_auth] 2023-01-17T14:05:49Z jordi fita mas <jordi@tandem.blog> # Add table to log login attempts
login [roles schema_numerus schema_auth extension_pgcrypto email user login_attempt] 2023-01-13T00:32:32Z jordi fita mas <jordi@tandem.blog> # Add function to login login [roles schema_numerus schema_auth extension_pgcrypto email user login_attempt] 2023-01-13T00:32:32Z jordi fita mas <jordi@tandem.blog> # Add function to login
check_cookie [schema_public user] 2023-01-17T17:48:49Z jordi fita mas <jordi@tandem.blog> # Add function to check if a user cookie is valid current_user_cookie [schema_numerus] 2023-01-21T20:16:28Z jordi fita mas <jordi@tandem.blog> # Add function to get the cookie of the current Numerus user
current_app_user [schema_numerus] 2023-01-21T20:16:28Z jordi fita mas <jordi@tandem.blog> # Add function to get the ID of the current Numerus user current_user_email [schema_numerus] 2023-01-23T19:11:53Z jordi fita mas <jordi@tandem.blog> # Add function to get the email of the current Numerus user
logout [schema_auth current_app_user user] 2023-01-17T19:10:21Z jordi fita mas <jordi@tandem.blog> # Add function to logout build_cookie [schema_numerus current_user_email current_user_cookie] 2023-01-23T19:46:13Z jordi fita mas <jordi@tandem.blog> # Add function to build the cookie for the current user
check_cookie [schema_public user build_cookie] 2023-01-17T17:48:49Z jordi fita mas <jordi@tandem.blog> # Add function to check if a user cookie is valid
logout [schema_auth current_user_email current_user_cookie user] 2023-01-17T19:10:21Z jordi fita mas <jordi@tandem.blog> # Add function to logout
set_cookie [schema_public check_cookie] 2023-01-19T11:00:22Z jordi fita mas <jordi@tandem.blog> # Add function to set the role based on the cookie set_cookie [schema_public check_cookie] 2023-01-19T11:00:22Z jordi fita mas <jordi@tandem.blog> # Add function to set the role based on the cookie
available_languages [schema_numerus language] 2023-01-21T21:11:08Z jordi fita mas <jordi@tandem.blog> # Add the initial available languages available_languages [schema_numerus language] 2023-01-21T21:11:08Z jordi fita mas <jordi@tandem.blog> # Add the initial available languages
user_profile [schema_numerus user current_app_user] 2023-01-21T23:18:20Z jordi fita mas <jordi@tandem.blog> # Add view for user profile user_profile [schema_numerus user current_user_email current_user_cookie] 2023-01-21T23:18:20Z jordi fita mas <jordi@tandem.blog> # Add view for user profile

70
test/build_cookie.sql Normal file
View File

@ -0,0 +1,70 @@
-- Test build_cookie
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(13);
set search_path to numerus, auth, public;
select has_function('numerus', 'build_cookie', array ['email', 'text']);
select function_lang_is('numerus', 'build_cookie', array ['email', 'text'], 'sql');
select function_returns('numerus', 'build_cookie', array ['email', 'text'], 'text');
select isnt_definer('numerus', 'build_cookie', array ['email', 'text']);
select volatility_is('numerus', 'build_cookie', array ['email', 'text'], 'stable');
select function_privs_are('numerus', 'build_cookie', array ['email', 'text'], 'guest', array ['EXECUTE']);
select function_privs_are('numerus', 'build_cookie', array ['email', 'text'], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'build_cookie', array ['email', 'text'], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'build_cookie', array ['email', 'text'], 'authenticator', array []::text[]);
set client_min_messages to warning;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (9, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
select is(
build_cookie('test@example.com'::email, '123abc'),
'123abc/test@example.com',
'Should build the cookie with the given user and cookie value'
);
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog');
select is(
build_cookie(),
'44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog',
'Should build the cookie for the logged in user'
);
reset role;
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog');
select is(
build_cookie(),
'12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog',
'Should build the cookie for the other logged in user'
);
reset role;
select set_cookie('ashtasth');
select is(
build_cookie(),
'/',
'Should build the cookie for the guest user'
);
reset role;
select *
from finish();
rollback;

View File

@ -29,10 +29,7 @@ values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4b
; ;
prepare user_info as prepare user_info as
select current_setting('request.user.id', true)::integer select current_user_email(), current_user_cookie();
, current_setting('request.user.email', true)
, current_setting('request.user.cookie', true)
;
select is ( select is (
check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'), check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'),
@ -42,7 +39,7 @@ select is (
select results_eq ( select results_eq (
'user_info', 'user_info',
$$ values (1, 'demo@tandem.blog', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e') $$, $$ values ('demo@tandem.blog', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e') $$,
'Should have updated the settings with the user info' 'Should have updated the settings with the user info'
); );
@ -54,7 +51,7 @@ select is (
select results_eq ( select results_eq (
'user_info', 'user_info',
$$ values (9, 'admin@tandem.blog', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524') $$, $$ values ('admin@tandem.blog', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524') $$,
'Should have updated the settings with the other user info' 'Should have updated the settings with the other user info'
); );
@ -66,7 +63,7 @@ select is (
select results_eq ( select results_eq (
'user_info', 'user_info',
$$ values (0, '', '') $$, $$ values ('', '') $$,
'Should have updated the settings with a guest user' 'Should have updated the settings with a guest user'
); );
@ -78,7 +75,7 @@ select is (
select results_eq ( select results_eq (
'user_info', 'user_info',
$$ values (0, '', '') $$, $$ values ('', '') $$,
'Should have left the settings with a guest user' 'Should have left the settings with a guest user'
); );
@ -92,7 +89,7 @@ select is (
select results_eq ( select results_eq (
'user_info', 'user_info',
$$ values (0, '', '') $$, $$ values ('', '') $$,
'Should have left the settings with a guest user' 'Should have left the settings with a guest user'
); );
@ -104,7 +101,7 @@ select is (
select results_eq ( select results_eq (
'user_info', 'user_info',
$$ values (0, '', '') $$, $$ values ('', '') $$,
'Should have left the settings with a guest user' 'Should have left the settings with a guest user'
); );

View File

@ -1,62 +0,0 @@
-- Test current_app_user
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
set search_path to numerus, auth, public;
select plan(15);
select has_function('numerus', 'current_app_user', array []::name[]);
select function_lang_is('numerus', 'current_app_user', array []::name[], 'sql');
select function_returns('numerus', 'current_app_user', array []::name[], 'text');
select isnt_definer('numerus', 'current_app_user', array []::name[]);
select volatility_is('numerus', 'current_app_user', array []::name[], 'stable');
select function_privs_are('numerus', 'current_app_user', array []::name[], 'guest', array ['EXECUTE']);
select function_privs_are('numerus', 'current_app_user', array []::name[], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'current_app_user', array []::name[], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'current_app_user', array []::name[], 'authenticator', array []::text []);
set client_min_messages to warning;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
select lives_ok(
$$ select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') $$,
'Should change ok for the first user'
);
select is(current_app_user(), '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', 'Should be running as the first user');
reset role;
select lives_ok(
$$ select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog') $$,
'Should change ok for the second user'
);
select is(current_app_user(), '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', 'Should be running as the second user');
reset role;
select lives_ok(
$$ select set_cookie('') $$,
'Should change ok for a guest user'
);
select is(current_app_user(), '', 'Should be running as the first user');
reset role;
select *
from finish();
rollback;

View File

@ -0,0 +1,62 @@
-- Test current_user_cookie
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
set search_path to numerus, auth, public;
select plan(15);
select has_function('numerus', 'current_user_cookie', array []::name[]);
select function_lang_is('numerus', 'current_user_cookie', array []::name[], 'sql');
select function_returns('numerus', 'current_user_cookie', array []::name[], 'text');
select isnt_definer('numerus', 'current_user_cookie', array []::name[]);
select volatility_is('numerus', 'current_user_cookie', array []::name[], 'stable');
select function_privs_are('numerus', 'current_user_cookie', array []::name[], 'guest', array ['EXECUTE']);
select function_privs_are('numerus', 'current_user_cookie', array []::name[], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'current_user_cookie', array []::name[], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'current_user_cookie', array []::name[], 'authenticator', array []::text []);
set client_min_messages to warning;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
select lives_ok(
$$ select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') $$,
'Should change ok for the first user'
);
select is(current_user_cookie(), '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', 'Should return the cookie of the first user');
reset role;
select lives_ok(
$$ select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog') $$,
'Should change ok for the second user'
);
select is(current_user_cookie(), '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', 'Should return the cookie of the second user');
reset role;
select lives_ok(
$$ select set_cookie('') $$,
'Should change ok for a guest user'
);
select is(current_user_cookie(), '', 'Should return an empty string');
reset role;
select *
from finish();
rollback;

View File

@ -0,0 +1,63 @@
-- Test current_user_email
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
set search_path to numerus, auth, public;
select plan(15);
select has_function('numerus', 'current_user_email', array []::name[]);
select function_lang_is('numerus', 'current_user_email', array []::name[], 'sql');
select function_returns('numerus', 'current_user_email', array []::name[], 'text');
select isnt_definer('numerus', 'current_user_email', array []::name[]);
select volatility_is('numerus', 'current_user_email', array []::name[], 'stable');
select function_privs_are('numerus', 'current_user_email', array []::name[], 'guest', array ['EXECUTE']);
select function_privs_are('numerus', 'current_user_email', array []::name[], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'current_user_email', array []::name[], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'current_user_email', array []::name[], 'authenticator', array []::text []);
set client_min_messages to warning;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
select lives_ok(
$$ select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') $$,
'Should change ok for the first user'
);
select is(current_user_email(), 'demo@tandem.blog', 'Should return the email of the first user');
reset role;
select lives_ok(
$$ select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog') $$,
'Should change ok for the second user'
);
select is(current_user_email(), 'admin@tandem.blog', 'Should return the email of the second user');
reset role;
select lives_ok(
$$ select set_cookie('') $$,
'Should change ok for a guest user'
);
select is(current_user_email(), '', 'Should return an empty string');
reset role;
select *
from finish();
rollback;

View File

@ -5,7 +5,7 @@ reset client_min_messages;
begin; begin;
select plan(15); select plan(17);
set search_path to auth, numerus, public; set search_path to auth, numerus, public;
@ -33,6 +33,7 @@ select cookie, cookie_expires_at from "user" order by user_id
; ;
select set_config('request.user.cookie', '', false); select set_config('request.user.cookie', '', false);
select set_config('request.user.email', '', false);
select lives_ok( $$ select * from logout() $$, 'Can logout “nobody”' ); select lives_ok( $$ select * from logout() $$, 'Can logout “nobody”' );
select results_eq( select results_eq(
@ -40,10 +41,23 @@ select results_eq(
$$ values ('8c23d4a8d777775f8fc507676a0d99d3dfa54b03b1b257c838', current_timestamp + interval '1 day') $$ values ('8c23d4a8d777775f8fc507676a0d99d3dfa54b03b1b257c838', current_timestamp + interval '1 day')
, ('0169e5f668eec1e6749fd25388b057997358efa8dfd697961a', current_timestamp + interval '2 day') , ('0169e5f668eec1e6749fd25388b057997358efa8dfd697961a', current_timestamp + interval '2 day')
$$, $$,
'Nothing changed' 'Should have changed nothing'
);
select set_config('request.user.cookie', '0169e5f668eec1e6749fd25388b057997358efa8dfd697961a', false);
select set_config('request.user.email', 'info@tandem.blog', false);
select lives_ok( $$ select * from logout() $$, 'Can logout even if the email and cookie does not match' );
select results_eq(
'user_cookies',
$$ values ('8c23d4a8d777775f8fc507676a0d99d3dfa54b03b1b257c838', current_timestamp + interval '1 day')
, ('0169e5f668eec1e6749fd25388b057997358efa8dfd697961a', current_timestamp + interval '2 day')
$$,
'Should have changed nothing'
); );
select set_config('request.user.cookie', '8c23d4a8d777775f8fc507676a0d99d3dfa54b03b1b257c838', false); select set_config('request.user.cookie', '8c23d4a8d777775f8fc507676a0d99d3dfa54b03b1b257c838', false);
select set_config('request.user.email', 'info@tandem.blog', false);
select lives_ok( $$ select * from logout() $$, 'Can logout the first user' ); select lives_ok( $$ select * from logout() $$, 'Can logout the first user' );
select results_eq( select results_eq(
@ -55,6 +69,7 @@ select results_eq(
); );
select set_config('request.user.cookie', '0169e5f668eec1e6749fd25388b057997358efa8dfd697961a', false); select set_config('request.user.cookie', '0169e5f668eec1e6749fd25388b057997358efa8dfd697961a', false);
select set_config('request.user.email', 'admin@tandem.blog', false);
select lives_ok( $$ select * from logout() $$, 'Can logout the second user' ); select lives_ok( $$ select * from logout() $$, 'Can logout the second user' );
select results_eq( select results_eq(

View File

@ -29,7 +29,7 @@ values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4b
; ;
prepare user_info as prepare user_info as
select current_setting('request.user.id', true)::integer, current_setting('request.user.email', true), current_user; select current_user_email(), current_user_cookie(), current_user;
select lives_ok( select lives_ok(
$$ select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') $$, $$ select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') $$,
@ -38,7 +38,7 @@ select lives_ok(
select results_eq( select results_eq(
'user_info', 'user_info',
$$ values (1, 'demo@tandem.blog', 'invoicer'::name) $$, $$ values ('demo@tandem.blog', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', 'invoicer'::name) $$,
'Should have updated the info with the correct user' 'Should have updated the info with the correct user'
); );
@ -51,7 +51,7 @@ select lives_ok(
select results_eq( select results_eq(
'user_info', 'user_info',
$$ values (5, 'admin@tandem.blog', 'admin'::name) $$, $$ values ('admin@tandem.blog', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', 'admin'::name) $$,
'Should have updated the info with the other user' 'Should have updated the info with the other user'
); );
@ -64,7 +64,7 @@ select lives_ok(
select results_eq( select results_eq(
'user_info', 'user_info',
$$ values (0, '', 'guest'::name) $$, $$ values ('', '', 'guest'::name) $$,
'Should have updated the info as a guest user' 'Should have updated the info as a guest user'
); );

7
verify/build_cookie.sql Normal file
View File

@ -0,0 +1,7 @@
-- Verify numerus:build_cookie on pg
begin;
select has_function_privilege('numerus.build_cookie(numerus.email, text)', 'execute');
rollback;

View File

@ -1,7 +0,0 @@
-- Verify numerus:current_app_user on pg
begin;
select has_function_privilege('numerus.current_app_user()', 'execute');
rollback;

View File

@ -0,0 +1,7 @@
-- Verify numerus:current_user_cookie on pg
begin;
select has_function_privilege('numerus.current_user_cookie()', 'execute');
rollback;

View File

@ -0,0 +1,7 @@
-- Verify numerus:current_user_email on pg
begin;
select has_function_privilege('numerus.current_user_email()', 'execute');
rollback;

View File

@ -11,4 +11,35 @@ select
from numerus.user_profile from numerus.user_profile
where false; where false;
select has_function_privilege('numerus.update_user_profile()', 'execute');
select 1/count(*)
from pg_trigger
where not tgisinternal
and tgname = 'update_user_profile'
and tgrelid = 'numerus.user_profile'::regclass
and tgtype = b'01010001'::int;
-- │││││││
-- ││││││└─> row
-- │││││└──> before
-- ││││└───> insert
-- │││└────> delete
-- ││└─────> update
-- │└──────> truncate
-- └───────> instead
select 1/count(*)
from pg_trigger
where not tgisinternal
and tgname = 'encrypt_password'
and tgrelid = 'auth.user'::regclass
and tgtype = b'00010111'::int;
-- │││││││
-- ││││││└─> row
-- │││││└──> before
-- ││││└───> insert
-- │││└────> delete
-- ││└─────> update
-- │└──────> truncate
-- └───────> instead
rollback; rollback;