Allow guest access to user_profile with an empty profile
I want this so that the Go application does not need to know the exact details of the settings that the database sets when applying the cookie; it just needs to select from the user_profile that already knows this. Also, that way i can get the user’s language from its profile with a single select, without having to check whether we are guest or authenticated. With that, i can skip the content negotiation if the user already told us what language they want.
This commit is contained in:
parent
b5968b1179
commit
c84f3f9e80
|
@ -17,8 +17,33 @@ select user_id
|
||||||
, lang_tag
|
, lang_tag
|
||||||
from auth."user"
|
from auth."user"
|
||||||
where cookie = current_app_user()
|
where cookie = current_app_user()
|
||||||
|
and cookie_expires_at > current_timestamp
|
||||||
|
and length(cookie) > 30
|
||||||
|
union all
|
||||||
|
select 0
|
||||||
|
, null::email
|
||||||
|
, ''
|
||||||
|
, 'guest'::name
|
||||||
|
, 'und'
|
||||||
|
where not exists (
|
||||||
|
select 1
|
||||||
|
from auth."user"
|
||||||
|
where cookie = current_app_user()
|
||||||
|
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 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;
|
||||||
|
|
||||||
|
|
|
@ -24,14 +24,18 @@ func Locale(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 locale *gotext.Locale
|
var locale *gotext.Locale
|
||||||
t, _, err := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
|
user := getUser(r)
|
||||||
if err == nil {
|
locale = locales[user.Language]
|
||||||
tag, _, _ := matcher.Match(t...)
|
if locale == nil {
|
||||||
var ok bool
|
t, _, err := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
|
||||||
locale, ok = locales[tag]
|
if err == nil {
|
||||||
for !ok && !tag.IsRoot() {
|
tag, _, _ := matcher.Match(t...)
|
||||||
tag = tag.Parent()
|
var ok bool
|
||||||
locale, ok = locales[tag]
|
locale, ok = locales[tag]
|
||||||
|
for !ok && !tag.IsRoot() {
|
||||||
|
tag = tag.Parent()
|
||||||
|
locale, ok = locales[tag]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if locale == nil {
|
if locale == nil {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -25,6 +27,7 @@ type AppUser struct {
|
||||||
Email string
|
Email string
|
||||||
LoggedIn bool
|
LoggedIn bool
|
||||||
Role string
|
Role string
|
||||||
|
Language language.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginHandler() http.Handler {
|
func LoginHandler() http.Handler {
|
||||||
|
@ -103,11 +106,13 @@ func CheckLogin(db *Db, next http.Handler) http.Handler {
|
||||||
LoggedIn: false,
|
LoggedIn: false,
|
||||||
Role: defaultRole,
|
Role: defaultRole,
|
||||||
}
|
}
|
||||||
row := conn.QueryRow(ctx, "select current_setting('request.user.email', true), current_user")
|
row := conn.QueryRow(ctx, "select coalesce(email, ''), role, lang_tag from user_profile")
|
||||||
if err := row.Scan(&user.Email, &user.Role); err != nil {
|
var langTag string
|
||||||
|
if err := row.Scan(&user.Email, &user.Role, &langTag); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
user.LoggedIn = user.Email != ""
|
user.LoggedIn = user.Email != ""
|
||||||
|
user.Language, _ = language.Parse(langTag)
|
||||||
ctx = context.WithValue(ctx, ContextUserKey, user)
|
ctx = context.WithValue(ctx, ContextUserKey, user)
|
||||||
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
|
|
@ -42,6 +42,8 @@ func ProfileHandler() http.Handler {
|
||||||
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)
|
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;
|
||||||
} else {
|
} else {
|
||||||
if err := conn.QueryRow(r.Context(), "select name, lang_tag from user_profile").Scan(&page.Name, &page.Language); err != nil {
|
if err := conn.QueryRow(r.Context(), "select name, lang_tag from user_profile").Scan(&page.Name, &page.Language); err != nil {
|
||||||
panic(nil)
|
panic(nil)
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
begin;
|
begin;
|
||||||
|
|
||||||
delete from numerus.language;
|
delete from public.language;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|
|
@ -16,8 +16,8 @@ encrypt_password [schema_auth user extension_pgcrypto] 2023-01-13T00:14:30Z jord
|
||||||
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
|
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 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
|
|
||||||
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_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
|
||||||
|
logout [schema_auth current_app_user 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
|
||||||
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_app_user] 2023-01-21T23:18:20Z jordi fita mas <jordi@tandem.blog> # Add view for user profile
|
||||||
|
|
|
@ -12,7 +12,7 @@ set search_path to numerus, public;
|
||||||
select has_domain('email');
|
select has_domain('email');
|
||||||
select domain_type_is('email', 'citext');
|
select domain_type_is('email', 'citext');
|
||||||
|
|
||||||
select lives_ok($$ SELECT 'test@tandem.com'::email $$, 'Should be able to cast strings to email');
|
select lives_ok($$ select 'test@tandem.com'::email $$, 'Should be able to cast strings to email');
|
||||||
|
|
||||||
select throws_ok(
|
select throws_ok(
|
||||||
$$ SELECT 'test@tandem,,co.uk'::email $$,
|
$$ SELECT 'test@tandem,,co.uk'::email $$,
|
||||||
|
|
|
@ -10,42 +10,42 @@ select plan(47);
|
||||||
set search_path to numerus, auth, public;
|
set search_path to numerus, auth, public;
|
||||||
|
|
||||||
select has_view('user_profile');
|
select has_view('user_profile');
|
||||||
select table_privs_are('user_profile', 'guest', array []::text[]);
|
select table_privs_are('user_profile', 'guest', array ['SELECT']);
|
||||||
select table_privs_are('user_profile', 'invoicer', array['SELECT']);
|
select table_privs_are('user_profile', 'invoicer', array['SELECT']);
|
||||||
select table_privs_are('user_profile', 'admin', array['SELECT']);
|
select table_privs_are('user_profile', 'admin', array['SELECT']);
|
||||||
select table_privs_are('user_profile', 'authenticator', array[]::text[]);
|
select table_privs_are('user_profile', 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
select has_column('user_profile', 'user_id');
|
select has_column('user_profile', 'user_id');
|
||||||
select col_type_is('user_profile', 'user_id', 'integer');
|
select col_type_is('user_profile', 'user_id', 'integer');
|
||||||
select column_privs_are('user_profile', 'user_id', 'guest', array []::text[]);
|
select column_privs_are('user_profile', 'user_id', 'guest', array ['SELECT']);
|
||||||
select column_privs_are('user_profile', 'user_id', 'invoicer', array['SELECT']);
|
select column_privs_are('user_profile', 'user_id', 'invoicer', array['SELECT']);
|
||||||
select column_privs_are('user_profile', 'user_id', 'admin', array['SELECT']);
|
select column_privs_are('user_profile', 'user_id', 'admin', array['SELECT']);
|
||||||
select column_privs_are('user_profile', 'user_id', 'authenticator', array[]::text[]);
|
select column_privs_are('user_profile', 'user_id', 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
select has_column('user_profile', 'email');
|
select has_column('user_profile', 'email');
|
||||||
select col_type_is('user_profile', 'email', 'email');
|
select col_type_is('user_profile', 'email', 'email');
|
||||||
select column_privs_are('user_profile', 'email', 'guest', array []::text[]);
|
select column_privs_are('user_profile', 'email', 'guest', array ['SELECT']);
|
||||||
select column_privs_are('user_profile', 'email', 'invoicer', array['SELECT', 'UPDATE']);
|
select column_privs_are('user_profile', 'email', 'invoicer', array['SELECT', 'UPDATE']);
|
||||||
select column_privs_are('user_profile', 'email', 'admin', array['SELECT', 'UPDATE']);
|
select column_privs_are('user_profile', 'email', 'admin', array['SELECT', 'UPDATE']);
|
||||||
select column_privs_are('user_profile', 'email', 'authenticator', array[]::text[]);
|
select column_privs_are('user_profile', 'email', 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
select has_column('user_profile', 'name');
|
select has_column('user_profile', 'name');
|
||||||
select col_type_is('user_profile', 'name', 'text');
|
select col_type_is('user_profile', 'name', 'text');
|
||||||
select column_privs_are('user_profile', 'name', 'guest', array []::text[]);
|
select column_privs_are('user_profile', 'name', 'guest', array ['SELECT']);
|
||||||
select column_privs_are('user_profile', 'name', 'invoicer', array['SELECT', 'UPDATE']);
|
select column_privs_are('user_profile', 'name', 'invoicer', array['SELECT', 'UPDATE']);
|
||||||
select column_privs_are('user_profile', 'name', 'admin', array['SELECT', 'UPDATE']);
|
select column_privs_are('user_profile', 'name', 'admin', array['SELECT', 'UPDATE']);
|
||||||
select column_privs_are('user_profile', 'name', 'authenticator', array[]::text[]);
|
select column_privs_are('user_profile', 'name', 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
select has_column('user_profile', 'role');
|
select has_column('user_profile', 'role');
|
||||||
select col_type_is('user_profile', 'role', 'name');
|
select col_type_is('user_profile', 'role', 'name');
|
||||||
select column_privs_are('user_profile', 'role', 'guest', array []::text[]);
|
select column_privs_are('user_profile', 'role', 'guest', array ['SELECT']);
|
||||||
select column_privs_are('user_profile', 'role', 'invoicer', array['SELECT']);
|
select column_privs_are('user_profile', 'role', 'invoicer', array['SELECT']);
|
||||||
select column_privs_are('user_profile', 'role', 'admin', array['SELECT']);
|
select column_privs_are('user_profile', 'role', 'admin', array['SELECT']);
|
||||||
select column_privs_are('user_profile', 'role', 'authenticator', array[]::text[]);
|
select column_privs_are('user_profile', 'role', 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
select has_column('user_profile', 'lang_tag');
|
select has_column('user_profile', 'lang_tag');
|
||||||
select col_type_is('user_profile', 'lang_tag', 'text');
|
select col_type_is('user_profile', 'lang_tag', 'text');
|
||||||
select column_privs_are('user_profile', 'lang_tag', 'guest', array []::text[]);
|
select column_privs_are('user_profile', 'lang_tag', 'guest', array ['SELECT']);
|
||||||
select column_privs_are('user_profile', 'lang_tag', 'invoicer', array['SELECT', 'UPDATE']);
|
select column_privs_are('user_profile', 'lang_tag', 'invoicer', array['SELECT', 'UPDATE']);
|
||||||
select column_privs_are('user_profile', 'lang_tag', 'admin', array['SELECT', 'UPDATE']);
|
select column_privs_are('user_profile', 'lang_tag', 'admin', array['SELECT', 'UPDATE']);
|
||||||
select column_privs_are('user_profile', 'lang_tag', 'authenticator', array[]::text[]);
|
select column_privs_are('user_profile', 'lang_tag', 'authenticator', array[]::text[]);
|
||||||
|
@ -58,13 +58,20 @@ reset client_min_messages;
|
||||||
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at, lang_tag)
|
insert into auth."user" (user_id, email, name, password, role, cookie, cookie_expires_at, lang_tag)
|
||||||
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month', 'ca')
|
values (1, 'demo@tandem.blog', 'Demo', 'test', 'invoicer', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month', 'ca')
|
||||||
, (5, 'admin@tandem.blog', 'Admin', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month', 'es')
|
, (5, 'admin@tandem.blog', 'Admin', 'test', 'admin', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month', 'es')
|
||||||
|
, (7, 'another@tandem.blog', 'Another Admin', 'test', 'admin', default, default, default)
|
||||||
;
|
;
|
||||||
|
|
||||||
prepare profile as
|
prepare profile as
|
||||||
select user_id, email, name, role, lang_tag
|
select user_id, email, name, role, lang_tag
|
||||||
from user_profile;
|
from user_profile;
|
||||||
|
|
||||||
select is_empty( 'profile', 'Should be empty when no user is logger in' );
|
select set_config('request.user.cookie', '', false);
|
||||||
|
|
||||||
|
select results_eq(
|
||||||
|
'profile',
|
||||||
|
$$ values (0, null::email, '', 'guest'::name, 'und') $$,
|
||||||
|
'Should be set up with the guest user when no user logged in yet.'
|
||||||
|
);
|
||||||
|
|
||||||
select set_cookie( '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog' );
|
select set_cookie( '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog' );
|
||||||
|
|
||||||
|
@ -144,6 +151,7 @@ select results_eq(
|
||||||
$$ select user_id, email, name, lang_tag from auth."user" order by user_id $$,
|
$$ select user_id, email, name, lang_tag from auth."user" order by user_id $$,
|
||||||
$$ values (1, 'demo+update@tandem.blog'::email, 'Demo Update', 'es')
|
$$ values (1, 'demo+update@tandem.blog'::email, 'Demo Update', 'es')
|
||||||
, (5, 'admin+update@tandem.blog'::email, 'Admin Update', 'ca')
|
, (5, 'admin+update@tandem.blog'::email, 'Admin Update', 'ca')
|
||||||
|
, (7, 'another@tandem.blog'::email, 'Another Admin', 'und')
|
||||||
$$,
|
$$,
|
||||||
'Should have updated the base table’s data'
|
'Should have updated the base table’s data'
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue