diff --git a/deploy/user_profile.sql b/deploy/user_profile.sql index 885e70a..4f5b8d7 100644 --- a/deploy/user_profile.sql +++ b/deploy/user_profile.sql @@ -17,8 +17,33 @@ select user_id , lang_tag from auth."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 admin; diff --git a/pkg/locale.go b/pkg/locale.go index 7b46772..9578574 100644 --- a/pkg/locale.go +++ b/pkg/locale.go @@ -24,14 +24,18 @@ func Locale(db *Db, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var locale *gotext.Locale - t, _, err := language.ParseAcceptLanguage(r.Header.Get("Accept-Language")) - if err == nil { - tag, _, _ := matcher.Match(t...) - var ok bool - locale, ok = locales[tag] - for !ok && !tag.IsRoot() { - tag = tag.Parent() + user := getUser(r) + locale = locales[user.Language] + if locale == nil { + t, _, err := language.ParseAcceptLanguage(r.Header.Get("Accept-Language")) + if err == nil { + tag, _, _ := matcher.Match(t...) + var ok bool locale, ok = locales[tag] + for !ok && !tag.IsRoot() { + tag = tag.Parent() + locale, ok = locales[tag] + } } } if locale == nil { diff --git a/pkg/login.go b/pkg/login.go index 84a4983..7b6205a 100644 --- a/pkg/login.go +++ b/pkg/login.go @@ -5,6 +5,8 @@ import ( "net" "net/http" "time" + + "golang.org/x/text/language" ) const ( @@ -25,6 +27,7 @@ type AppUser struct { Email string LoggedIn bool Role string + Language language.Tag } func LoginHandler() http.Handler { @@ -103,11 +106,13 @@ func CheckLogin(db *Db, next http.Handler) http.Handler { LoggedIn: false, Role: defaultRole, } - row := conn.QueryRow(ctx, "select current_setting('request.user.email', true), current_user") - if err := row.Scan(&user.Email, &user.Role); err != nil { + row := conn.QueryRow(ctx, "select coalesce(email, ''), role, lang_tag from user_profile") + var langTag string + if err := row.Scan(&user.Email, &user.Role, &langTag); err != nil { panic(err) } user.LoggedIn = user.Email != "" + user.Language, _ = language.Parse(langTag) ctx = context.WithValue(ctx, ContextUserKey, user) next.ServeHTTP(w, r.WithContext(ctx)) diff --git a/pkg/profile.go b/pkg/profile.go index 50f5e35..cc0069a 100644 --- a/pkg/profile.go +++ b/pkg/profile.go @@ -42,6 +42,8 @@ func ProfileHandler() http.Handler { 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; } else { if err := conn.QueryRow(r.Context(), "select name, lang_tag from user_profile").Scan(&page.Name, &page.Language); err != nil { panic(nil) diff --git a/revert/available_languages.sql b/revert/available_languages.sql index c1e78f9..2f93caa 100644 --- a/revert/available_languages.sql +++ b/revert/available_languages.sql @@ -2,6 +2,6 @@ begin; -delete from numerus.language; +delete from public.language; commit; diff --git a/sqitch.plan b/sqitch.plan index 194b3d8..239281c 100644 --- a/sqitch.plan +++ b/sqitch.plan @@ -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 # 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 -logout [schema_auth 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 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 +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 diff --git a/test/email.sql b/test/email.sql index ac58ace..9f614a1 100644 --- a/test/email.sql +++ b/test/email.sql @@ -12,7 +12,7 @@ set search_path to numerus, public; select has_domain('email'); 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 'test@tandem,,co.uk'::email $$, diff --git a/test/user_profile.sql b/test/user_profile.sql index 766ac9c..b40f597 100644 --- a/test/user_profile.sql +++ b/test/user_profile.sql @@ -10,42 +10,42 @@ select plan(47); set search_path to numerus, auth, public; 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', 'admin', array['SELECT']); select table_privs_are('user_profile', 'authenticator', array[]::text[]); select has_column('user_profile', 'user_id'); 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', 'admin', array['SELECT']); select column_privs_are('user_profile', 'user_id', 'authenticator', array[]::text[]); select has_column('user_profile', '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', 'admin', array['SELECT', 'UPDATE']); select column_privs_are('user_profile', 'email', 'authenticator', array[]::text[]); select has_column('user_profile', 'name'); 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', 'admin', array['SELECT', 'UPDATE']); select column_privs_are('user_profile', 'name', 'authenticator', array[]::text[]); select has_column('user_profile', 'role'); 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', 'admin', array['SELECT']); select column_privs_are('user_profile', 'role', 'authenticator', array[]::text[]); select has_column('user_profile', 'lang_tag'); 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', 'admin', array['SELECT', 'UPDATE']); 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) 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') + , (7, 'another@tandem.blog', 'Another Admin', 'test', 'admin', default, default, default) ; prepare profile as select user_id, email, name, role, lang_tag 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' ); @@ -144,6 +151,7 @@ select results_eq( $$ select user_id, email, name, lang_tag from auth."user" order by user_id $$, $$ values (1, 'demo+update@tandem.blog'::email, 'Demo Update', 'es') , (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' );