Commit Graph

200 Commits

Author SHA1 Message Date
jordi fita mas 7513030334 Fix the width of the country’s select 2023-01-28 00:11:56 +01:00
jordi fita mas 0a58e2699e Use a select for company’s country field
At first we thought that a regular text field would do, because we were
afraid that a dropdown would be worse from the point of view of user
experience, but then we realized that we need the country code for VAT
and phone validation, and we can not expect users to input that, of
course.

I had to add the first “i18n table” to the database with the name of all
countries in both Catalan and Spanish and Catalan; English is the
default.  For now i think i do not need a view that would select the
name based on the locale of the current request, because currently i do
not plan on adding any other such table —the currency uses the code and
the symbol, thus no need for localization.

However, now i need the language tag from the locale in order to get the
correct translation, and gotext does not give me any way to access the
inner language.  Thus the need for our Locale type.
2023-01-27 21:30:14 +01:00
jordi fita mas acc0c2c7d5 Remove the drop of company_policy on numerus.company_user
I did not know how to add it, so now it warns me every time because it
does not exist.
2023-01-27 20:15:19 +01:00
jordi fita mas e61e5e87cd Remove an unnecessary truncate from a SQL test 2023-01-27 19:40:38 +01:00
jordi fita mas c0b1c22ee0 Fix typo in SQL comment 2023-01-27 19:36:16 +01:00
jordi fita mas 57d5137913 Allow users update their tax details 2023-01-27 01:08:03 +01:00
jordi fita mas 798289bc8e Add required PostgreSQL extensions as dependencies for Debian 2023-01-24 23:59:28 +01:00
jordi fita mas 627841d4dd Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.

The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.

I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.

Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively.  These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 21:46:07 +01:00
jordi fita mas c037f671f8 Refactor authenticated redirection to a common handler 2023-01-24 21:44:23 +01:00
jordi fita mas d9c93b8797 Add function to change the current user’s password
This function does not ask for the confirmation because this is an
user-facing issue, not for the database.

Still missing: validation and proper error messages.
2023-01-23 21:41:14 +01:00
jordi fita mas 56d149e211 Remove execution grant on build_cookie to guest
There is no need for a guest to build any cookie, since they have none.
2023-01-23 21:40:22 +01:00
jordi fita mas 5eeaab2013 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.
2023-01-23 21:18:55 +01:00
jordi fita mas f9e22c0789 Complete the style of the profile dialog 2023-01-23 19:35:49 +01:00
jordi fita mas 22509dd683 Implement profile menu with <details>
It works better than with the weird hover behaviour i could do in CSS,
and it already has most of the aria roles needed.

The only tricky part is to allow closing it by clicking anywhere else,
that is done by “extending” the <summary> to the whole screen, with a
lower z-index than the menu but higher than the rest of controls, that
way we force people to click on that summary.
2023-01-23 18:52:18 +01:00
jordi fita mas c7e34cc488 Leave the label always to the same position on the left 2023-01-23 12:44:56 +01:00
jordi fita mas c84f3f9e80 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.
2023-01-23 01:18:47 +01:00
jordi fita mas b5968b1179 Use current_app_user to logout
Do not want people being able to logout other users just by setting a
number in a setting.
2023-01-23 01:18:05 +01:00
jordi fita mas c6eb1ef24e Change input field to be “Material-like”, as per design 2023-01-23 00:41:54 +01:00
jordi fita mas 1675ada70b Make the menu work as a menu 2023-01-22 22:30:15 +01:00
jordi fita mas 5505fa41c3 Use “layouts” for the common HTML between pages
Had to call xgettext on Go source files because now the title comes from
there, as i assume i will have titles like "Invoice #INVxxxx" that have
to come from the database that the template does not know.
2023-01-22 21:41:50 +01:00
jordi fita mas fa6ddc70b3 Prefix with “Must” all functions that panic
Just following what the standard library does.
2023-01-22 20:37:43 +01:00
jordi fita mas 7e5e6121ac Gofmt recover.go 2023-01-22 20:37:34 +01:00
jordi fita mas 6f2da865c0 Reduce HTML’s font-size to (usually) have 1rem = 10px
It is far easier for me to “see” the sizes if they are multiples of 10,
especially given that the designs we do in Penpot use a 10 × 10 pixels
grid.
2023-01-22 20:27:43 +01:00
jordi fita mas 8fa3367f6c Add a deployment test while building the Debian package 2023-01-22 04:32:03 +01:00
jordi fita mas ea9e830a75 Add user_profile view to update the profile with form
Since users do not have access to the auth scheme, i had to add a view
that selects only the data that they can see of themselves (i.e., no
password or cookie).

I wanted to use the `request.user.id` setting that i set in
check_cookie, but this would be bad because anyone can change that
parameter and, since the view is created by the owner, could see and
*change* the values of everyone just by knowing their id.  Thus, now i
use the cookie instead, because it is way harder to figure out, and if
you already have it you can just set to your browser and the user is
fucked anyway; the database can not help here.

I **am** going to use the user id in row level security policies, but
not the value coming for the setting but instaed the one in the
`user_profile`, since it already is “derived” from the cookie, that’s
why i added that column to the view.

The profile includes the language, that i do not use it yet to switch
the locale, so i had to add a relation of the available languages, for
constraint purposes.  There is no NULL language, and instead i added the
“Undefined” language, with ‘und’ tag’, to represent “do not know/use
content negotiation”.

The languages in that relation are the same i used to have inside
locale.go, because there is no point on having options for languages i
do not have the translation for, so i now configure the list of
available languages user in content negotiation from that relation.

Finally, i have added all font from RemixIcon because that’s what we
used in the design and i am going to use quite a lot of them.

There is duplication in the views; i will address that in a different
commit.
2023-01-22 02:23:09 +01:00
jordi fita mas 052c9c8caa 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.
2023-01-19 13:07:32 +01:00
jordi fita mas 090570df60 Change contractor to freelancer 2023-01-19 01:41:27 +01:00
jordi fita mas e38420697b Add Catalan and Spanish translation with gotext[3]
I had to choose between [1], [2], and [3].

As far as i could find, [1] is not easy to work with templates[4] and at
the moment is not maintained[5].

Both [2] and [3] use the same approach to be used from within templates:
you have to define a FuncMap with template functions that call the
message catalog.  Also, both libraries seems to be reasonably
maintained, and have packages in Debian’s repository.

However, [2] repeats the same mistakes that POSIX did with its
catalogs—using identifiers that are not the strings in the source
language—, however this time the catalogs are written in JSON or YAML!
This, somehow, makes things worse….

[3], the one i settled with, is fine and decently maintained.  There are
some surprising things, such as to be able to use directly the PO file,
and that it has higher priority over the corresponding MO, or that the
order of parameters is reversed in respect to gettext.  However, it uses
a saner format, and is a lot easier to work with than [3].

The problem, of course, is that xgettext does not know how to find
translatable strings inside the template.  [3] includes a CLI tool
similar to xgettext, but is not a drop-in replacement[6] and does not
process templates.

The proper way to handle this would be to add a parser to xgettext, but
for now i found out that if i surround the call to the translation
functions from within the template with parentheses, i can trick
xgettext into believing it is parsing Scheme code, and extracts the
strings successfully—at least, for what i have tried.  Had to add the
keyword for pgettext, because Schemed does not have it, but at least i
can do that with command line parameters.

For now i left only Spanish and Catalan as the two available languages,
even though the source text is written in English, because that way i
can make sure i do not leave strings untranslated.

[1]: https://golang.org/x/text
[2]: https://github.com/nicksnyder/go-i18n
[3]: https://github.com/leonelquinteros/gotext
[4]: https://github.com/golang/go/issues/39954
[5]: https://github.com/golang/go/issues/12750
[6]: https://github.com/leonelquinteros/gotext/issues/38
2023-01-18 20:26:30 +01:00
jordi fita mas afd4bc16b7 Add the schema for all function’s tests
With that check_cookie function i realized that the schema of functions
is important, otherwise pgTAP could give me the OK when it finds the
function in a different schema than what i intended.
2023-01-18 14:13:58 +01:00
jordi fita mas c4fc37349b Move check_cookie to public and give access to authenticator
I do not want to give access to authenticator until i know who the user
is, herefore that function can not be in the numerus schema as the
authenticator user can not see it.
2023-01-18 14:12:59 +01:00
jordi fita mas c369364642 Add the SQL for the demo 2023-01-17 22:30:01 +01:00
jordi fita mas d434d040af Add the very basic styles 2023-01-17 22:28:47 +01:00
jordi fita mas f1bf1f896d Implement login cookie, its verification, and logout
At first i thought that i would need to implement sessions, the ones
that keep small files onto the disk, to know which user is talking to
the server, but then i realized that, for now at least, i only need a
very large number, plus the email address, to be used as a lookup, and
that can be stored in the user table, in a separate schema.

Had to change login to avoid raising exceptions when login failed
because i now keep a record of login attemps, and functions are always
run in a single transaction, thus the exception would prevent me to
insert into login_attempt.  Even if i use a separate procedure, i could
not keep the records.

I did not want to add a parameter to the logout function because i was
afraid that it could be called from separate users.  I do not know
whether it is possible with the current approach, since the settings
variable is also set by the same applications; time will tell.
2023-01-17 20:58:13 +01:00
jordi fita mas ab6c0079c9 Set search_path on each new connection, and role on each acquisition
The whole application will need the same search_path, so it is wasteful
to do that in each handler.

It is possible to pass the search path as a parameter to the database’s
connection string, but then everyone would need to remember to do that,
and update the configuration in case i add another schema.

Similarly, i need to change the user’s role to match her
permissions—which are not in yet—, but this time i need it each time a
handler requests a connection from the pool, because each time the
connection is returned to the pool i reset the role back to the initial,
that hopefully will be authenticator.
2023-01-17 14:49:02 +01:00
jordi fita mas 97ac586a3b “Merge” find_user_role and login
I honestly do not remember why i thought i needed the find_user
function: it is just a select with a query that i only need in a single
place—when login.

I belive it was a missguided attempt to “write the function safer”, in
hopes that calling a function won’t have the same problems as when
querying a table, but this is fixed with the search_path, that i added.

There is no pgTAP for this, i believe.
2023-01-17 13:18:12 +01:00
jordi fita mas 8fd22672c7 Create pgcrypto extension into auth schema
Will only be used there, no need to be in public, and this way i can
limit the search_path for security definer functions.
2023-01-17 13:12:18 +01:00
jordi fita mas 45439c8559 Remove the revocation of all function executions
I need to execute some functions in public for citext, such as
texticregexeq, or guest users would not be able to login.
2023-01-17 13:05:58 +01:00
jordi fita mas 989cdd7da7 Move source file to the root of pkg
I do not yet know how i will need to organize them, if indeed i need to
organize them to a finer granularity than single files, so there is no
point on doing Java-like packages.
2023-01-17 10:40:22 +01:00
jordi fita mas 6d48aa6630 Split into three debian packages: dev, binary, and sqitch migration 2023-01-16 13:22:16 +01:00
jordi fita mas 8c65bcd617 Add template files to debian package 2023-01-16 12:55:32 +01:00
jordi fita mas e31131d88f Listen to all addresses, not just localhost 2023-01-16 11:32:06 +01:00
jordi fita mas 48ca28a872 Convert to Debian non-native format 1.0
This is for the sacke of building the package with OBS until they
install obs_gbp into their reference instance.
2023-01-16 10:41:46 +01:00
jordi fita mas b63f7e7c54 Change find_user_role’s and login’s volatility to stable
According to PostgreSQL’s manual[0]:

  “STABLE indicates that the function cannot modify the database, and
   that within a single table scan it will consistently return the same
   result for the same argument values, but that its result could change
   across SQL statements.”

This definition matches both functions.  Moreover, find_user_role did
not need to be written in plpgsql, that i assume—but did not test—are
slower than sql functions.

[0]: https://www.postgresql.org/docs/14/sql-createfunction.html
2023-01-16 10:02:31 +01:00
jordi fita mas 0efd48c40d Add files to build Debian package
According to the de facto project layout for Go[0], these files should
go into a build/package folder, but since i already broke the rules with
Sqitch’s folders, i do not see why i have to go against Debian’s
conventions of putting it into a debian subfolder of the root.

[0]: https://github.com/golang-standards/project-layout
2023-01-15 22:56:49 +01:00
jordi fita mas 78d9a568bf Downgrade pgx to v4
I’ve tried to make RPM packages of numerus and pgx/v4 for AlmaLinux, so
install in a virtual server, but i was unable to break a build cycle
between golang/x/text, required by pgx, and golang/x/tools both a
dependency and a dependee of golang/x/text: once tools finished
building, it would trigger a new build for text, that in turn would
trigger a build for all its dependences, including tools.

At the end i had to create a Debian repository, because they already
have all the packages, even though i had to back port pgx/v4 from
Testing to bullseye.  I did not want to try my luck with writting
packages for pgx/v5, so v4 it is.
2023-01-15 20:45:45 +01:00
jordi fita mas b18b7bb73d Rename LICENSE to COPYING 2023-01-15 04:08:35 +01:00
jordi fita mas 9d202e82ca Add the simplest possible web server to test login
This is a very rough test to actually check the login function outside
pgTAP; it is very ugly, in both design and code, and (i hope) does not
reflect future quality.

I was about to use Echo[0] as a “web framework”, but something feels
wrong when using a framework with Go—i do not know what.  I actually
tried it and was even more put off by the JSON-formatted logger that can
not be disabled; i was already losing control of the application!

I created the folder following the apparently de facto guidelines for Go
projects, and i see no problem with mixing Go’s folders with Sqitch’s:
both are part of the same application and there are not conflicts.

[0]: https://echo.labstack.com/
[1]: https://github.com/golang-standards/project-layout
2023-01-13 20:53:43 +01:00
jordi fita mas 2ec343beee Add LICENSE file 2023-01-13 20:30:21 +01:00
jordi fita mas c17662ec6b Setup authentication schema and user relation
User authentication is based on PostgREST’s[0]: There is a noninherit
role, authenticator, whose function is only to switch to a different
role according to the application’s session.  Accordingly, this role has
no permission for anything.

The roles that this authentication can switch to are guest, invoicer, or
admin.  Guest is for anonymous users, when they need to login or
register; invoicers are regular users; and admin are application’s
administrators, that can change other user’s status, when they have to
be removed or have they password changed, for example.

The user relation is actually inaccessible to all roles and can only be
used through a security definer function, login, so that passwords are
not accessible from the application.

I hesitated on what to use as the user’s primary key.  The email seemed
a good candiate, because it will be used for login.  But something rubs
me the wrong way.

It is not that they can change because, despite what people on the
Internet keeps parroting, they do not need to be “immutable”, PostgreSQL
can cascade updates to foreign keys, and people do **not** change email
addresses that ofter.

What i **do** know is that email addresses should be unique in order to
be used for login and password, hovewer i had to decide what “unique”
means here, because the domain part is case insensitive, but the local
part who knows?  I made the arbitrary decision of assuming that the
whole address is case sensitive.

I have the feeling that this will bite me harder in the ass than using
it as the primary key.

[0]: https://postgrest.org/en/stable/auth.html
2023-01-13 20:30:21 +01:00
jordi fita mas 51ad6f8102 Initialize Sqitch configuration 2023-01-12 19:37:48 +01:00