Add a function to set request settings and the role

I did not like the idea that it was the Go server who should set values
such as request.user or set the role, because this is mostly something
that only the database wants for itself, such as when calling logout.  I
am also planning to use these setings for row security with the user’s
id, that the Go application has no need for, but with the current
approach i would need to return it from check_cookie so that it can
return it back to the database when acquiring the connection.

I would have used the same function to set the settings and the role,
but security definer functions—obviously in retrospect—can not set the
role, because then could switch to any role of the user that defined the
function, not the roles they are member of.  Thus, a new function.

I did not want to do that every time i needed the database connection
within the same request, because it would perform the same operations
each time—it is the same cookie, afterall—, so new connections are
request scoped and passed along in the context.
This commit is contained in:
jordi fita mas 2023-01-19 13:07:32 +01:00
parent 090570df60
commit 052c9c8caa
12 changed files with 260 additions and 86 deletions

View File

@ -1,28 +1,34 @@
-- Deploy numerus:check_cookie to pg -- Deploy numerus:check_cookie to pg
-- requires: schema_auth -- requires: schema_public
-- requires: user -- requires: user
begin; begin;
set search_path to public, numerus, auth; set search_path to public, numerus, auth;
create or replace function check_cookie(input_cookie text) returns record as create or replace function check_cookie(input_cookie text) returns name as
$$ $$
declare declare
value record; uid text;
user_email text;
user_role name;
begin begin
select email::text, role select user_id::text, email::text, role
into value into uid, user_email, user_role
from "user" from "user"
where email = split_part(input_cookie, '/', 2) where email = split_part(input_cookie, '/', 2)
and cookie_expires_at > current_timestamp and cookie_expires_at > current_timestamp
and length(password) > 0 and length(password) > 0
and cookie = split_part(input_cookie, '/', 1) and cookie = split_part(input_cookie, '/', 1)
; ;
if value is null then if user_role is null then
select '', 'guest'::name into value; uid := '0';
user_email := '';
user_role := 'guest'::name;
end if; end if;
return value; perform set_config('request.user.id', uid, false);
perform set_config('request.user.email', user_email, false);
return user_role;
end; end;
$$ $$
language plpgsql language plpgsql

View File

@ -11,7 +11,7 @@ $$
update "user" update "user"
set cookie = default set cookie = default
, cookie_expires_at = default , cookie_expires_at = default
where email = current_setting('request.user') where user_id = current_setting('request.user.id', true)::integer
$$ $$
language sql language sql
security definer security definer

22
deploy/set_cookie.sql Normal file
View File

@ -0,0 +1,22 @@
-- Deploy numerus:set_cookie to pg
-- requires: schema_public
-- requires: check_cookie
begin;
set search_path to public;
create or replace function set_cookie(input_cookie text) returns void as
$$
select set_config('role', check_cookie(input_cookie), false);
$$
language sql
stable;
comment on function set_cookie(text) is
'Sets the user information for the cookie and switches to its role';
revoke execute on function set_cookie(text) from public;
grant execute on function set_cookie(text) to authenticator;
commit;

View File

@ -3,14 +3,13 @@ package pkg
import ( import (
"context" "context"
"log" "log"
"net/http"
"github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/pgx/v4/pgxpool"
) )
type Db struct { type Db struct {
pool *pgxpool.Pool *pgxpool.Pool
} }
func NewDatabase(ctx context.Context, connString string) (*Db, error) { func NewDatabase(ctx context.Context, connString string) (*Db, error) {
@ -25,19 +24,14 @@ func NewDatabase(ctx context.Context, connString string) (*Db, error) {
} }
config.BeforeAcquire = func(ctx context.Context, conn *pgx.Conn) bool { config.BeforeAcquire = func(ctx context.Context, conn *pgx.Conn) bool {
if user, ok := ctx.Value(ContextUserKey).(*AppUser); ok { cookie := ""
batch := &pgx.Batch{} if value, ok := ctx.Value(ContextCookieKey).(string); ok {
batch.Queue("select set_config('request.user', $1, false)", user.Email) cookie = value
batch.Queue("select set_config('role', $1, false)", user.Role)
br := conn.SendBatch(ctx, batch)
defer br.Close()
for i := 0; i < batch.Len(); i++ {
if _, err := br.Exec(); err != nil {
log.Printf("ERROR - Failed to set role: %v", err)
return false
}
}
} }
if _, err := conn.Exec(ctx, "select set_cookie($1)", cookie); err != nil {
log.Printf("ERROR - Failed to set role: %v", err)
return false
}
return true return true
} }
@ -56,13 +50,21 @@ func NewDatabase(ctx context.Context, connString string) (*Db, error) {
return &Db{pool}, nil return &Db{pool}, nil
} }
func (db *Db) Close() { func (db *Db) Acquire(ctx context.Context) (*Conn, error) {
db.pool.Close() conn, err := db.Pool.Acquire(ctx)
if err != nil {
return nil, err
}
return &Conn{conn}, nil
} }
func (db *Db) Text(r *http.Request, def string, sql string, args ...interface{}) string { type Conn struct {
*pgxpool.Conn
}
func (c *Conn) Text(ctx context.Context, def string, sql string, args ...interface{}) string {
var result string var result string
if err := db.pool.QueryRow(r.Context(), sql, args...).Scan(&result); err != nil { if err := c.Conn.QueryRow(ctx, sql, args...).Scan(&result); err != nil {
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
return def return def
} }
@ -72,8 +74,8 @@ func (db *Db) Text(r *http.Request, def string, sql string, args ...interface{})
return result return result
} }
func (db *Db) Exec(r *http.Request, sql string, args ...interface{}) { func (c *Conn) Exec(ctx context.Context, sql string, args ...interface{}) {
if _, err := db.pool.Exec(r.Context(), sql, args...); err != nil { if _, err := c.Conn.Exec(ctx, sql, args...); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -5,12 +5,12 @@ import (
"net" "net"
"net/http" "net/http"
"time" "time"
"github.com/jackc/pgx/v4"
) )
const ( const (
ContextUserKey = "numerus-user" ContextUserKey = "numerus-user"
ContextCookieKey = "numerus-cookie"
ContextConnKey = "numerus-database"
sessionCookie = "numerus-session" sessionCookie = "numerus-session"
defaultRole = "guest" defaultRole = "guest"
) )
@ -27,7 +27,7 @@ type AppUser struct {
Role string Role string
} }
func LoginHandler(db *Db) http.Handler { func LoginHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := getUser(r) user := getUser(r)
if user.LoggedIn { if user.LoggedIn {
@ -40,7 +40,8 @@ func LoginHandler(db *Db) http.Handler {
Password: r.FormValue("password"), Password: r.FormValue("password"),
} }
if r.Method == "POST" { if r.Method == "POST" {
cookie := db.Text(r, "", "select login($1, $2, $3)", page.Email, page.Password, remoteAddr(r)) conn := getConn(r)
cookie := conn.Text(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)) http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour))
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
@ -55,11 +56,12 @@ func LoginHandler(db *Db) http.Handler {
}) })
} }
func LogoutHandler(db *Db) http.Handler { func LogoutHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := getUser(r) user := getUser(r)
if user.LoggedIn { if user.LoggedIn {
db.Exec(r, "select logout()") conn := getConn(r)
conn.Exec(r.Context(), "select logout()")
http.SetCookie(w, createSessionCookie("", -24*time.Hour)) http.SetCookie(w, createSessionCookie("", -24*time.Hour))
} }
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, "/login", http.StatusSeeOther)
@ -87,22 +89,30 @@ func createSessionCookie(value string, duration time.Duration) *http.Cookie {
func CheckLogin(db *Db, next http.Handler) http.Handler { func CheckLogin(db *Db, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var ctx = r.Context();
if cookie, err := r.Cookie(sessionCookie); err == nil {
ctx = context.WithValue(ctx, ContextCookieKey, cookie.Value);
}
conn, err := db.Acquire(ctx)
if err != nil {
panic(err);
}
defer conn.Release();
ctx = context.WithValue(ctx, ContextConnKey, conn)
user := &AppUser{ user := &AppUser{
Email: "", Email: "",
LoggedIn: false, LoggedIn: false,
Role: defaultRole, Role: defaultRole,
} }
if cookie, err := r.Cookie(sessionCookie); err == nil { row := conn.QueryRow(ctx, "select current_setting('request.user.email', true), current_user")
row := db.pool.QueryRow(r.Context(), "select * from check_cookie($1) as (email text, role name)", cookie.Value) if err := row.Scan(&user.Email, &user.Role); err != nil {
if err := row.Scan(&user.Email, &user.Role); err != nil { panic(err)
if err != pgx.ErrNoRows {
panic(err)
}
} else {
user.LoggedIn = user.Role != "guest"
}
} }
ctx := context.WithValue(r.Context(), ContextUserKey, user) user.LoggedIn = user.Email != ""
ctx = context.WithValue(ctx, ContextUserKey, user)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
@ -110,3 +120,7 @@ func CheckLogin(db *Db, next http.Handler) http.Handler {
func getUser(r *http.Request) *AppUser { func getUser(r *http.Request) *AppUser {
return r.Context().Value(ContextUserKey).(*AppUser) return r.Context().Value(ContextUserKey).(*AppUser)
} }
func getConn(r *http.Request) *Conn {
return r.Context().Value(ContextConnKey).(*Conn)
}

View File

@ -7,8 +7,8 @@ import (
func NewRouter(db *Db) http.Handler { func NewRouter(db *Db) http.Handler {
router := http.NewServeMux() router := http.NewServeMux()
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static")))) router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
router.Handle("/login", LoginHandler(db)) router.Handle("/login", LoginHandler())
router.Handle("/logout", LogoutHandler(db)) router.Handle("/logout", LogoutHandler())
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
user := getUser(r) user := getUser(r)
if user.LoggedIn { if user.LoggedIn {

7
revert/set_cookie.sql Normal file
View File

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

View File

@ -2,17 +2,18 @@
%project=numerus %project=numerus
%uri=https://numerus.tandem.blog/ %uri=https://numerus.tandem.blog/
roles 2023-01-12T18:42:16Z jordi fita i mas <jfita@infoblitz.com> # Add database roles roles 2023-01-12T18:42:16Z jordi fita mas <jordi@tandem.blog> # Add database roles
schema_auth [roles] 2023-01-12T19:15:55Z jordi fita i mas <jfita@infoblitz.com> # Add authentication schema schema_auth [roles] 2023-01-12T19:15:55Z jordi fita mas <jordi@tandem.blog> # Add authentication schema
schema_public [roles] 2023-01-12T19:24:29Z jordi fita i mas <jfita@infoblitz.com> # Set privileges to public schema schema_public [roles] 2023-01-12T19:24:29Z jordi fita mas <jordi@tandem.blog> # Set privileges to public schema
schema_numerus [roles] 2023-01-12T22:57:22Z jordi fita i mas <jfita@infoblitz.com> # Add application schema schema_numerus [roles] 2023-01-12T22:57:22Z jordi fita mas <jordi@tandem.blog> # Add application schema
extension_citext [schema_public] 2023-01-12T23:03:33Z jordi fita i mas <jfita@infoblitz.com> # Add citext extension extension_citext [schema_public] 2023-01-12T23:03:33Z jordi fita mas <jordi@tandem.blog> # Add citext extension
email [schema_numerus extension_citext] 2023-01-12T23:09:59Z jordi fita i mas <jfita@infoblitz.com> # Add email domain email [schema_numerus extension_citext] 2023-01-12T23:09:59Z jordi fita mas <jordi@tandem.blog> # Add email domain
user [roles schema_auth email] 2023-01-12T23:44:03Z jordi fita i mas <jfita@infoblitz.com> # Create user table user [roles schema_auth email] 2023-01-12T23:44:03Z jordi fita mas <jordi@tandem.blog> # Create user table
ensure_role_exists [schema_auth user] 2023-01-12T23:57:59Z jordi fita i mas <jfita@infoblitz.com> # Add trigger to ensure the users role exists ensure_role_exists [schema_auth user] 2023-01-12T23:57:59Z jordi fita mas <jordi@tandem.blog> # Add trigger to ensure the users role exists
extension_pgcrypto [schema_auth] 2023-01-13T00:11:50Z jordi fita i mas <jfita@infoblitz.com> # Add pgcrypto extension extension_pgcrypto [schema_auth] 2023-01-13T00:11:50Z jordi fita mas <jordi@tandem.blog> # Add pgcrypto extension
encrypt_password [schema_auth user extension_pgcrypto] 2023-01-13T00:14:30Z jordi fita i mas <jfita@infoblitz.com> # 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 i mas <jfita@infoblitz.com> # 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 i mas <jfita@infoblitz.com> # 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_auth user] 2023-01-17T17:48:49Z jordi fita i mas <jfita@infoblitz.com> # Add function to check if a user cookie is valid 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
logout [schema_auth user] 2023-01-17T19:10:21Z jordi fita i mas <jfita@infoblitz.com> # Add function to logout logout [schema_auth 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

View File

@ -5,13 +5,13 @@ reset client_min_messages;
begin; begin;
select plan(15); select plan(21);
set search_path to auth, numerus, public; set search_path to auth, numerus, public;
select has_function('public', 'check_cookie', array ['text']); select has_function('public', 'check_cookie', array ['text']);
select function_lang_is('public', 'check_cookie', array ['text'], 'plpgsql'); select function_lang_is('public', 'check_cookie', array ['text'], 'plpgsql');
select function_returns('public', 'check_cookie', array ['text'], 'record'); select function_returns('public', 'check_cookie', array ['text'], 'name');
select is_definer('public', 'check_cookie', array ['text']); select is_definer('public', 'check_cookie', array ['text']);
select volatility_is('public', 'check_cookie', array ['text'], 'stable'); select volatility_is('public', 'check_cookie', array ['text'], 'stable');
select function_privs_are('public', 'check_cookie', array ['text'], 'guest', array []::text[]); select function_privs_are('public', 'check_cookie', array ['text'], 'guest', array []::text[]);
@ -23,49 +23,88 @@ set client_min_messages to warning;
truncate auth."user" cascade; truncate auth."user" cascade;
reset client_min_messages; reset client_min_messages;
insert into auth."user" (email, name, password, role, cookie, cookie_expires_at) insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values ('demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month') values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, ('admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month') , (9, 'admin@tandem.blog', 'Demo', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
; ;
select results_eq ( prepare user_info as
$$ select * from check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') as (e text, r name) $$, select current_setting('request.user.id', true)::integer, current_setting('request.user.email', true);
$$ values ('demo@tandem.blog', 'invoicer'::name) $$,
select is (
check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'),
'invoicer'::name,
'Should validate the cookie for the first user' 'Should validate the cookie for the first user'
); );
select results_eq ( select results_eq (
$$ select * from check_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog') as (e text, r name) $$, 'user_info',
$$ values ('admin@tandem.blog', 'admin'::name) $$, $$ values (1, 'demo@tandem.blog') $$,
'Should have updated the settings with the user info'
);
select is (
check_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog'),
'admin'::name,
'Should validate the cookie for the second user' 'Should validate the cookie for the second user'
); );
select results_eq ( select results_eq (
$$ select * from check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/admin@tandem.blog') as (e text, r name) $$, 'user_info',
$$ values ('', 'guest'::name) $$, $$ values (9, 'admin@tandem.blog') $$,
'Should have updated the settings with the other user info'
);
select is (
check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/admin@tandem.blog'),
'guest'::name,
'Should only match with the correct email' 'Should only match with the correct email'
); );
select results_eq ( select results_eq (
$$ select * from check_cookie('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/admin@tandem.blog') as (e text, r name) $$, 'user_info',
$$ values ('', 'guest'::name) $$, $$ values (0, '') $$,
'Should have updated the settings with a guest user'
);
select is (
check_cookie('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/admin@tandem.blog'),
'guest'::name,
'Should only match with the correct cookie value' 'Should only match with the correct cookie value'
); );
select results_eq (
'user_info',
$$ values (0, '') $$,
'Should have left the settings with a guest user'
);
update "user" set cookie_expires_at = current_timestamp - interval '1 minute'; update "user" set cookie_expires_at = current_timestamp - interval '1 minute';
select results_eq ( select is (
$$ select * from check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') as (e text, r name) $$, check_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog'),
$$ values ('', 'guest'::name) $$, 'guest'::name,
'Should not allow expired cookies' 'Should not allow expired cookies'
); );
select results_eq ( select results_eq (
$$ select * from check_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog') as (e text, r name) $$, 'user_info',
$$ values ('', 'guest'::name) $$, $$ values (0, '') $$,
'Should have left the settings with a guest user'
);
select is (
check_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog'),
'guest'::name,
'Should not allow expired cookied for the other user as well' 'Should not allow expired cookied for the other user as well'
); );
select results_eq (
'user_info',
$$ values (0, '') $$,
'Should have left the settings with a guest user'
);
select * select *
from finish(); from finish();

View File

@ -23,16 +23,16 @@ set client_min_messages to warning;
truncate auth."user" cascade; truncate auth."user" cascade;
reset client_min_messages; reset client_min_messages;
insert into auth."user" (email, name, password, role, cookie, cookie_expires_at) insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at)
values ('info@tandem.blog', 'Tandem', 'test', 'invoicer', '8c23d4a8d777775f8fc507676a0d99d3dfa54b03b1b257c838', current_timestamp + interval '1 day') values (1, 'info@tandem.blog', 'Tandem', 'test', 'invoicer', '8c23d4a8d777775f8fc507676a0d99d3dfa54b03b1b257c838', current_timestamp + interval '1 day')
, ('admin@tandem.blog', 'Admin', 'test', 'admin', '0169e5f668eec1e6749fd25388b057997358efa8dfd697961a', current_timestamp + interval '2 day') , (12, 'admin@tandem.blog', 'Admin', 'test', 'admin', '0169e5f668eec1e6749fd25388b057997358efa8dfd697961a', current_timestamp + interval '2 day')
; ;
prepare user_cookies as prepare user_cookies as
select cookie, cookie_expires_at from "user" order by user_id select cookie, cookie_expires_at from "user" order by user_id
; ;
select set_config('request.user', 'nothing', false); select set_config('request.user.id', '0', 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(
@ -43,7 +43,7 @@ select results_eq(
'Nothing changed' 'Nothing changed'
); );
select set_config('request.user', 'info@tandem.blog', false); select set_config('request.user.id', '1', 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(
@ -54,7 +54,7 @@ select results_eq(
'The first user logged out' 'The first user logged out'
); );
select set_config('request.user', 'admin@tandem.blog', false); select set_config('request.user.id', '12', 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(

76
test/set_cookie.sql Normal file
View File

@ -0,0 +1,76 @@
-- Test set_cookie
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(15);
set search_path to auth, numerus, public;
select has_function('public', 'set_cookie', array ['text']);
select function_lang_is('public', 'set_cookie', array ['text'], 'sql');
select function_returns('public', 'set_cookie', array ['text'], 'void');
select isnt_definer('public', 'set_cookie', array ['text']);
select volatility_is('public', 'set_cookie', array ['text'], 'stable');
select function_privs_are('public', 'set_cookie', array ['text'], 'guest', array []::text[]);
select function_privs_are('public', 'set_cookie', array ['text'], 'invoicer', array []::text[]);
select function_privs_are('public', 'set_cookie', array ['text'], 'admin', array []::text[]);
select function_privs_are('public', 'set_cookie', array ['text'], 'authenticator', array ['EXECUTE']);
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')
;
prepare user_info as
select current_setting('request.user.id', true)::integer, current_setting('request.user.email', true), current_user;
select lives_ok(
$$ select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog') $$,
'Should run ok for a valid cookie'
);
select results_eq(
'user_info',
$$ values (1, 'demo@tandem.blog', 'invoicer'::name) $$,
'Should have updated the info with the correct user'
);
reset role;
select lives_ok(
$$ select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog') $$,
'Should run ok for a different cookie'
);
select results_eq(
'user_info',
$$ values (5, 'admin@tandem.blog', 'admin'::name) $$,
'Should have updated the info with the other user'
);
reset role;
select lives_ok(
$$ select set_cookie('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/admin@tandem.blog') $$,
'Should run ok even for an invalid cookie'
);
select results_eq(
'user_info',
$$ values (0, '', 'guest'::name) $$,
'Should have updated the info as a guest user'
);
reset role;
select *
from finish();
rollback;

7
verify/set_cookie.sql Normal file
View File

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