diff --git a/deploy/build_cookie.sql b/deploy/build_cookie.sql new file mode 100644 index 0000000..2afa70a --- /dev/null +++ b/deploy/build_cookie.sql @@ -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 user’s browser, either for the given values or for the current user.'; + +commit; diff --git a/deploy/check_cookie.sql b/deploy/check_cookie.sql index f7e780d..a8f163d 100644 --- a/deploy/check_cookie.sql +++ b/deploy/check_cookie.sql @@ -28,7 +28,6 @@ begin user_cookie := ''; user_role := 'guest'::name; end if; - perform set_config('request.user.id', uid, false); perform set_config('request.user.email', user_email, false); perform set_config('request.user.cookie', user_cookie, false); return user_role; @@ -40,7 +39,7 @@ stable set search_path = auth, numerus, pg_temp; 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; grant execute on function check_cookie(text) to authenticator; diff --git a/deploy/current_app_user.sql b/deploy/current_app_user.sql deleted file mode 100644 index 24cb5ef..0000000 --- a/deploy/current_app_user.sql +++ /dev/null @@ -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; diff --git a/deploy/current_user_cookie.sql b/deploy/current_user_cookie.sql new file mode 100644 index 0000000..198ca03 --- /dev/null +++ b/deploy/current_user_cookie.sql @@ -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; diff --git a/deploy/current_user_email.sql b/deploy/current_user_email.sql new file mode 100644 index 0000000..ee029e6 --- /dev/null +++ b/deploy/current_user_email.sql @@ -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; diff --git a/deploy/login.sql b/deploy/login.sql index 94188c1..1cf8c4b 100644 --- a/deploy/login.sql +++ b/deploy/login.sql @@ -6,6 +6,7 @@ -- requires: email -- requires: user -- requires: login_attempt +-- requires: build_cookie begin; @@ -48,7 +49,7 @@ begin (user_name, ip_address, success) values (login.email, login.ip_address, true); - return user_cookie || '/' || email; + return build_cookie(email, user_cookie); end; $$ language plpgsql diff --git a/deploy/logout.sql b/deploy/logout.sql index 9b55597..b3704a6 100644 --- a/deploy/logout.sql +++ b/deploy/logout.sql @@ -1,7 +1,8 @@ -- Deploy numerus:logout to pg -- requires: schema_auth -- requires: user --- requires: current_app_user +-- requires: current_user_cookie +-- requires: current_user_email begin; @@ -12,7 +13,8 @@ $$ update "user" set cookie = 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 length(cookie) > 30 $$ @@ -21,7 +23,7 @@ security definer set search_path to auth, numerus, pg_temp; 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; grant execute on function logout() to invoicer; diff --git a/deploy/user_profile.sql b/deploy/user_profile.sql index 4f5b8d7..10fb691 100644 --- a/deploy/user_profile.sql +++ b/deploy/user_profile.sql @@ -1,7 +1,8 @@ -- Deploy numerus:user_profile to pg -- requires: schema_numerus -- requires: user --- requires: current_app_user +-- requires: current_user_cookie +-- requires: current_user_email begin; @@ -16,7 +17,8 @@ select user_id , role , lang_tag 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 length(cookie) > 30 union all @@ -28,23 +30,40 @@ select 0 where not exists ( select 1 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 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, update(email, name, lang_tag) on table user_profile to invoicer; 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; diff --git a/pkg/login.go b/pkg/login.go index 7b6205a..0c6682a 100644 --- a/pkg/login.go +++ b/pkg/login.go @@ -46,7 +46,7 @@ func LoginHandler() http.Handler { conn := getConn(r) cookie := conn.MustGetText(r.Context(), "", "select login($1, $2, $3)", page.Email, page.Password, remoteAddr(r)) if cookie != "" { - http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour)) + setSessionCookie(w, cookie) http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -79,6 +79,10 @@ func remoteAddr(r *http.Request) string { 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 { return &http.Cookie{ Name: sessionCookie, diff --git a/pkg/profile.go b/pkg/profile.go index cc0069a..285f9c1 100644 --- a/pkg/profile.go +++ b/pkg/profile.go @@ -30,9 +30,9 @@ func ProfileHandler() http.Handler { conn := getConn(r) locale := getLocale(r) page := ProfilePage{ - Title: pgettext("title", "User Settings", locale), - Email: user.Email, - Languages: mustGetLanguageOptions(r.Context(), conn), + Title: pgettext("title", "User Settings", locale), + Email: user.Email, + Language: user.Language.String(), } if r.Method == "POST" { r.ParseForm() @@ -41,11 +41,13 @@ func ProfileHandler() http.Handler { page.Password = r.FormValue("password") page.PasswordConfirm = r.FormValue("password_confirm") 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) - http.Redirect(w, r, "/profile", http.StatusSeeOther); - return; + 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) + setSessionCookie(w, cookie) + http.Redirect(w, r, "/profile", http.StatusSeeOther) + return } 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) } } diff --git a/revert/build_cookie.sql b/revert/build_cookie.sql new file mode 100644 index 0000000..16c80f5 --- /dev/null +++ b/revert/build_cookie.sql @@ -0,0 +1,7 @@ +-- Revert numerus:build_cookie from pg + +begin; + +drop function if exists numerus.build_cookie(numerus.email, text); + +commit; diff --git a/revert/current_app_user.sql b/revert/current_app_user.sql deleted file mode 100644 index f2d5e53..0000000 --- a/revert/current_app_user.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert numerus:current_app_user from pg - -begin; - -drop function if exists numerus.current_app_user(); - -commit; diff --git a/revert/current_user_cookie.sql b/revert/current_user_cookie.sql new file mode 100644 index 0000000..5913258 --- /dev/null +++ b/revert/current_user_cookie.sql @@ -0,0 +1,7 @@ +-- Revert numerus:current_user_cookie from pg + +begin; + +drop function if exists numerus.current_user_cookie(); + +commit; diff --git a/revert/current_user_email.sql b/revert/current_user_email.sql new file mode 100644 index 0000000..40ba0af --- /dev/null +++ b/revert/current_user_email.sql @@ -0,0 +1,7 @@ +-- Revert numerus:current_user_email from pg + +begin; + +drop function if exists numerus.current_user_email(); + +commit; diff --git a/revert/user_profile.sql b/revert/user_profile.sql index 4e44c67..b0d9f85 100644 --- a/revert/user_profile.sql +++ b/revert/user_profile.sql @@ -2,6 +2,8 @@ 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; commit; diff --git a/sqitch.plan b/sqitch.plan index 239281c..216a946 100644 --- a/sqitch.plan +++ b/sqitch.plan @@ -15,9 +15,11 @@ extension_pgcrypto [schema_auth] 2023-01-13T00:11:50Z jordi fita mas # Add trigger to encrypt user’s password login_attempt [schema_auth] 2023-01-17T14:05:49Z jordi fita mas # 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 # Add function to login -check_cookie [schema_public user] 2023-01-17T17:48:49Z jordi fita mas # Add function to check if a user cookie is valid -current_app_user [schema_numerus] 2023-01-21T20:16:28Z jordi fita mas # Add function to get the ID of the current Numerus’ user -logout [schema_auth current_app_user user] 2023-01-17T19:10:21Z jordi fita mas # Add function to logout +current_user_cookie [schema_numerus] 2023-01-21T20:16:28Z jordi fita mas # Add function to get the cookie of the current Numerus’ user +current_user_email [schema_numerus] 2023-01-23T19:11:53Z jordi fita mas # Add function to get the email of the current Numerus’ user +build_cookie [schema_numerus current_user_email current_user_cookie] 2023-01-23T19:46:13Z jordi fita mas # 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 # 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 # Add function to logout set_cookie [schema_public check_cookie] 2023-01-19T11:00:22Z jordi fita mas # Add function to set the role based on the cookie available_languages [schema_numerus language] 2023-01-21T21:11:08Z jordi fita mas # Add the initial available languages -user_profile [schema_numerus user current_app_user] 2023-01-21T23:18:20Z jordi fita mas # Add view for user profile +user_profile [schema_numerus user current_user_email current_user_cookie] 2023-01-21T23:18:20Z jordi fita mas # Add view for user profile diff --git a/test/build_cookie.sql b/test/build_cookie.sql new file mode 100644 index 0000000..acb63f5 --- /dev/null +++ b/test/build_cookie.sql @@ -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; diff --git a/test/check_cookie.sql b/test/check_cookie.sql index 87f220f..129e14d 100644 --- a/test/check_cookie.sql +++ b/test/check_cookie.sql @@ -29,10 +29,7 @@ values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4b ; prepare user_info as -select current_setting('request.user.id', true)::integer - , current_setting('request.user.email', true) - , current_setting('request.user.cookie', true) -; +select current_user_email(), current_user_cookie(); select is ( check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'), @@ -42,7 +39,7 @@ select is ( select results_eq ( 'user_info', - $$ values (1, 'demo@tandem.blog', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e') $$, + $$ values ('demo@tandem.blog', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e') $$, 'Should have updated the settings with the user info' ); @@ -54,7 +51,7 @@ select is ( select results_eq ( 'user_info', - $$ values (9, 'admin@tandem.blog', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524') $$, + $$ values ('admin@tandem.blog', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524') $$, 'Should have updated the settings with the other user info' ); @@ -66,7 +63,7 @@ select is ( select results_eq ( 'user_info', - $$ values (0, '', '') $$, + $$ values ('', '') $$, 'Should have updated the settings with a guest user' ); @@ -78,7 +75,7 @@ select is ( select results_eq ( 'user_info', - $$ values (0, '', '') $$, + $$ values ('', '') $$, 'Should have left the settings with a guest user' ); @@ -92,7 +89,7 @@ select is ( select results_eq ( 'user_info', - $$ values (0, '', '') $$, + $$ values ('', '') $$, 'Should have left the settings with a guest user' ); @@ -104,7 +101,7 @@ select is ( select results_eq ( 'user_info', - $$ values (0, '', '') $$, + $$ values ('', '') $$, 'Should have left the settings with a guest user' ); diff --git a/test/current_app_user.sql b/test/current_app_user.sql deleted file mode 100644 index 57cfe74..0000000 --- a/test/current_app_user.sql +++ /dev/null @@ -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; diff --git a/test/current_user_cookie.sql b/test/current_user_cookie.sql new file mode 100644 index 0000000..14c98e0 --- /dev/null +++ b/test/current_user_cookie.sql @@ -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; diff --git a/test/current_user_email.sql b/test/current_user_email.sql new file mode 100644 index 0000000..6e2fc03 --- /dev/null +++ b/test/current_user_email.sql @@ -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; diff --git a/test/logout.sql b/test/logout.sql index 2eb3c48..793ba58 100644 --- a/test/logout.sql +++ b/test/logout.sql @@ -5,7 +5,7 @@ reset client_min_messages; begin; -select plan(15); +select plan(17); 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.email', '', false); select lives_ok( $$ select * from logout() $$, 'Can logout “nobody”' ); select results_eq( @@ -40,10 +41,23 @@ select results_eq( $$ values ('8c23d4a8d777775f8fc507676a0d99d3dfa54b03b1b257c838', current_timestamp + interval '1 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.email', 'info@tandem.blog', false); select lives_ok( $$ select * from logout() $$, 'Can logout the first user' ); select results_eq( @@ -55,6 +69,7 @@ select results_eq( ); 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 results_eq( diff --git a/test/set_cookie.sql b/test/set_cookie.sql index 8e9a559..eb6a975 100644 --- a/test/set_cookie.sql +++ b/test/set_cookie.sql @@ -29,7 +29,7 @@ values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4b ; 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 set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') $$, @@ -38,7 +38,7 @@ select lives_ok( select results_eq( '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' ); @@ -51,7 +51,7 @@ select lives_ok( select results_eq( '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' ); @@ -64,7 +64,7 @@ select lives_ok( select results_eq( 'user_info', - $$ values (0, '', 'guest'::name) $$, + $$ values ('', '', 'guest'::name) $$, 'Should have updated the info as a guest user' ); diff --git a/verify/build_cookie.sql b/verify/build_cookie.sql new file mode 100644 index 0000000..09f716e --- /dev/null +++ b/verify/build_cookie.sql @@ -0,0 +1,7 @@ +-- Verify numerus:build_cookie on pg + +begin; + +select has_function_privilege('numerus.build_cookie(numerus.email, text)', 'execute'); + +rollback; diff --git a/verify/current_app_user.sql b/verify/current_app_user.sql deleted file mode 100644 index b6f2f68..0000000 --- a/verify/current_app_user.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify numerus:current_app_user on pg - -begin; - -select has_function_privilege('numerus.current_app_user()', 'execute'); - -rollback; diff --git a/verify/current_user_cookie.sql b/verify/current_user_cookie.sql new file mode 100644 index 0000000..502c8d4 --- /dev/null +++ b/verify/current_user_cookie.sql @@ -0,0 +1,7 @@ +-- Verify numerus:current_user_cookie on pg + +begin; + +select has_function_privilege('numerus.current_user_cookie()', 'execute'); + +rollback; diff --git a/verify/current_user_email.sql b/verify/current_user_email.sql new file mode 100644 index 0000000..1be20c4 --- /dev/null +++ b/verify/current_user_email.sql @@ -0,0 +1,7 @@ +-- Verify numerus:current_user_email on pg + +begin; + +select has_function_privilege('numerus.current_user_email()', 'execute'); + +rollback; diff --git a/verify/user_profile.sql b/verify/user_profile.sql index 23cb89f..6425b20 100644 --- a/verify/user_profile.sql +++ b/verify/user_profile.sql @@ -11,4 +11,35 @@ select from numerus.user_profile 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;