chore: Move *.rst files to docs/ folder
Signed-off-by: Wolfgang Walther <walther@technowledgy.de>
This commit is contained in:
committed by
Wolfgang Walther
parent
0ae86b7e74
commit
a85dfe9558
@@ -0,0 +1,231 @@
|
||||
.. _tut0:
|
||||
|
||||
Tutorial 0 - Get it Running
|
||||
===========================
|
||||
|
||||
:author: `begriffs <https://github.com/begriffs>`_
|
||||
|
||||
Welcome to PostgREST! In this pre-tutorial we're going to get things running so you can create your first simple API.
|
||||
|
||||
PostgREST is a standalone web server which turns a PostgreSQL database into a RESTful API. It serves an API that is customized based on the structure of the underlying database.
|
||||
|
||||
.. image:: ../_static/tuts/tut0-request-flow.png
|
||||
|
||||
To make an API we'll simply be building a database. All the endpoints and permissions come from database objects like tables, views, roles, and stored procedures. These tutorials will cover a number of common scenarios and how to model them in the database.
|
||||
|
||||
By the end of this tutorial you'll have a working database, PostgREST server, and a simple single-user todo list API.
|
||||
|
||||
Step 1. Relax, we'll help
|
||||
-------------------------
|
||||
|
||||
As you begin the tutorial, pop open the project `chat room <https://gitter.im/begriffs/postgrest>`_ in another tab. There are a nice group of people active in the project and we'll help you out if you get stuck.
|
||||
|
||||
Step 2. Install PostgreSQL
|
||||
--------------------------
|
||||
|
||||
If you're already familiar with using PostgreSQL and have it installed on your system you can use the existing installation (see :ref:`pg-dependency` for minimum requirements). For this tutorial we'll describe how to use the database in Docker because database configuration is otherwise too complicated for a simple tutorial.
|
||||
|
||||
If Docker is not installed, you can get it `here <https://www.docker.com/get-started>`_. Next, let's pull and start the database image:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run --name tutorial -p 5433:5432 \
|
||||
-e POSTGRES_PASSWORD=mysecretpassword \
|
||||
-d postgres
|
||||
|
||||
This will run the Docker instance as a daemon and expose port 5433 to the host system so that it looks like an ordinary PostgreSQL server to the rest of the system.
|
||||
|
||||
Step 3. Install PostgREST
|
||||
-------------------------
|
||||
|
||||
PostgREST is distributed as a single binary, with versions compiled for major distributions of Linux/BSD/Windows. Visit the `latest release <https://github.com/PostgREST/postgrest/releases/latest>`_ for a list of downloads. In the event that your platform is not among those already pre-built, see :ref:`build_source` for instructions how to build it yourself. Also let us know to add your platform in the next release.
|
||||
|
||||
The pre-built binaries for download are :code:`.tar.xz` compressed files (except Windows which is a zip file). To extract the binary, go into the terminal and run
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# download from https://github.com/PostgREST/postgrest/releases/latest
|
||||
|
||||
tar xJf postgrest-<version>-<platform>.tar.xz
|
||||
|
||||
The result will be a file named simply :code:`postgrest` (or :code:`postgrest.exe` on Windows). At this point try running it with
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./postgrest
|
||||
|
||||
If everything is working correctly it will print out its version and information about configuration. You can continue to run this binary from where you downloaded it, or copy it to a system directory like :code:`/usr/local/bin` on Linux so that you will be able to run it from any directory.
|
||||
|
||||
.. note::
|
||||
|
||||
PostgREST requires libpq, the PostgreSQL C library, to be installed on your system. Without the library you'll get an error like "error while loading shared libraries: libpq.so.5." Here's how to fix it:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<p>
|
||||
<details>
|
||||
<summary>Ubuntu or Debian</summary>
|
||||
<div class="highlight-bash"><div class="highlight">
|
||||
<pre>sudo apt-get install libpq-dev</pre>
|
||||
</div></div>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Fedora, CentOS, or Red Hat</summary>
|
||||
<div class="highlight-bash"><div class="highlight">
|
||||
<pre>sudo yum install postgresql-libs</pre>
|
||||
</div></div>
|
||||
</details>
|
||||
<details>
|
||||
<summary>OS X</summary>
|
||||
<div class="highlight-bash"><div class="highlight">
|
||||
<pre>brew install postgresql</pre>
|
||||
</div></div>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Windows</summary>
|
||||
<p>All of the DLL files that are required to run PostgREST are available in the windows installation of PostgreSQL server.
|
||||
Once installed they are found in the BIN folder, e.g: C:\Program Files\PostgreSQL\10\bin. Add this directory to your PATH
|
||||
variable. Run the following from an administrative command prompt (adjusting the actual BIN path as necessary of course)
|
||||
<pre>setx /m PATH "%PATH%;C:\Program Files\PostgreSQL\10\bin"</pre>
|
||||
</p>
|
||||
</details>
|
||||
</p>
|
||||
|
||||
Step 4. Create Database for API
|
||||
-------------------------------
|
||||
|
||||
Connect to the SQL console (psql) inside the container. To do so, run this from your command line:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker exec -it tutorial psql -U postgres
|
||||
|
||||
You should see the psql command prompt:
|
||||
|
||||
::
|
||||
|
||||
psql (9.6.3)
|
||||
Type "help" for help.
|
||||
|
||||
postgres=#
|
||||
|
||||
The first thing we'll do is create a `named schema <https://www.postgresql.org/docs/current/ddl-schemas.html>`_ for the database objects which will be exposed in the API. We can choose any name we like, so how about "api." Execute this and the other SQL statements inside the psql prompt you started.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create schema api;
|
||||
|
||||
Our API will have one endpoint, :code:`/todos`, which will come from a table.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table api.todos (
|
||||
id serial primary key,
|
||||
done boolean not null default false,
|
||||
task text not null,
|
||||
due timestamptz
|
||||
);
|
||||
|
||||
insert into api.todos (task) values
|
||||
('finish tutorial 0'), ('pat self on back');
|
||||
|
||||
Next make a role to use for anonymous web requests. When a request comes in, PostgREST will switch into this role in the database to run queries.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create role web_anon nologin;
|
||||
|
||||
grant usage on schema api to web_anon;
|
||||
grant select on api.todos to web_anon;
|
||||
|
||||
The :code:`web_anon` role has permission to access things in the :code:`api` schema, and to read rows in the :code:`todos` table.
|
||||
|
||||
It's a good practice to create a dedicated role for connecting to the database, instead of using the highly privileged ``postgres`` role. So we'll do that, name the role ``authenticator`` and also grant it the ability to switch to the ``web_anon`` role :
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create role authenticator noinherit login password 'mysecretpassword';
|
||||
grant web_anon to authenticator;
|
||||
|
||||
|
||||
Now quit out of psql; it's time to start the API!
|
||||
|
||||
.. code-block:: psql
|
||||
|
||||
\q
|
||||
|
||||
Step 5. Run PostgREST
|
||||
---------------------
|
||||
|
||||
PostgREST can use a configuration file to tell it how to connect to the database. Create a file :code:`tutorial.conf` with this inside:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
db-uri = "postgres://authenticator:mysecretpassword@localhost:5433/postgres"
|
||||
db-schemas = "api"
|
||||
db-anon-role = "web_anon"
|
||||
|
||||
The configuration file has other :doc:`options <../configuration>`, but this is all we need.
|
||||
If you are not using Docker, make sure that your port number is correct and replace `postgres` with the name of the database where you added the todos table.
|
||||
|
||||
Now run the server:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./postgrest tutorial.conf
|
||||
|
||||
You should see
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Listening on port 3000
|
||||
Attempting to connect to the database...
|
||||
Connection successful
|
||||
|
||||
It's now ready to serve web requests. There are many nice graphical API exploration tools you can use, but for this tutorial we'll use :code:`curl` because it's likely to be installed on your system already. Open a new terminal (leaving the one open that PostgREST is running inside). Try doing an HTTP request for the todos.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://localhost:3000/todos
|
||||
|
||||
The API replies:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"done": false,
|
||||
"task": "finish tutorial 0",
|
||||
"due": null
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"done": false,
|
||||
"task": "pat self on back",
|
||||
"due": null
|
||||
}
|
||||
]
|
||||
|
||||
With the current role permissions, anonymous requests have read-only access to the :code:`todos` table. If we try to add a new todo we are not able.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://localhost:3000/todos -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"task": "do bad thing"}'
|
||||
|
||||
Response is 401 Unauthorized:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"hint": null,
|
||||
"details": null,
|
||||
"code": "42501",
|
||||
"message": "permission denied for relation todos"
|
||||
}
|
||||
|
||||
There we have it, a basic API on top of the database! In the next tutorials we will see how to extend the example with more sophisticated user access controls, and more tables and queries.
|
||||
|
||||
Now that you have PostgREST running, try the next tutorial, :ref:`tut1`
|
||||
@@ -0,0 +1,258 @@
|
||||
.. _tut1:
|
||||
|
||||
Tutorial 1 - The Golden Key
|
||||
===========================
|
||||
|
||||
:author: `begriffs <https://github.com/begriffs>`_
|
||||
|
||||
In :ref:`tut0` we created a read-only API with a single endpoint to list todos. There are many directions we can go to make this API more interesting, but one good place to start would be allowing some users to change data in addition to reading it.
|
||||
|
||||
Step 1. Add a Trusted User
|
||||
--------------------------
|
||||
|
||||
The previous tutorial created a :code:`web_anon` role in the database with which to execute anonymous web requests. Let's make a role called :code:`todo_user` for users who authenticate with the API. This role will have the authority to do anything to the todo list.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- run this in psql using the database created
|
||||
-- in the previous tutorial
|
||||
|
||||
create role todo_user nologin;
|
||||
grant todo_user to authenticator;
|
||||
|
||||
grant usage on schema api to todo_user;
|
||||
grant all on api.todos to todo_user;
|
||||
grant usage, select on sequence api.todos_id_seq to todo_user;
|
||||
|
||||
Step 2. Make a Secret
|
||||
---------------------
|
||||
|
||||
Clients authenticate with the API using JSON Web Tokens. These are JSON objects which are cryptographically signed using a password known to only us and the server. Because clients do not know the password, they cannot tamper with the contents of their tokens. PostgREST will detect counterfeit tokens and will reject them.
|
||||
|
||||
Let's create a password and provide it to PostgREST. Think of a nice long one, or use a tool to generate it. **Your password must be at least 32 characters long.**
|
||||
|
||||
.. note::
|
||||
|
||||
Unix tools can generate a nice password for you:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Allow "tr" to process non-utf8 byte sequences
|
||||
export LC_CTYPE=C
|
||||
|
||||
# read random bytes and keep only alphanumerics
|
||||
< /dev/urandom tr -dc A-Za-z0-9 | head -c32
|
||||
|
||||
Open the :code:`tutorial.conf` (created in the previous tutorial) and add a line with the password:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# PASSWORD MUST BE AT LEAST 32 CHARS LONG
|
||||
# add this line to tutorial.conf:
|
||||
|
||||
jwt-secret = "<the password you made>"
|
||||
|
||||
If the PostgREST server is still running from the previous tutorial, restart it to load the updated configuration file.
|
||||
|
||||
Step 3. Sign a Token
|
||||
--------------------
|
||||
|
||||
Ordinarily your own code in the database or in another server will create and sign authentication tokens, but for this tutorial we will make one "by hand." Go to `jwt.io <https://jwt.io/#debugger-io>`_ and fill in the fields like this:
|
||||
|
||||
.. figure:: ../_static/tuts/tut1-jwt-io.png
|
||||
:alt: jwt.io interface
|
||||
|
||||
How to create a token at https://jwt.io
|
||||
|
||||
**Remember to fill in the password you generated rather than the word "secret".** After you have filled in the password and payload, the encoded data on the left will update. Copy the encoded token.
|
||||
|
||||
.. note::
|
||||
|
||||
While the token may look well obscured, it's easy to reverse engineer the payload. The token is merely signed, not encrypted, so don't put things inside that you don't want a determined client to see.
|
||||
|
||||
Step 4. Make a Request
|
||||
----------------------
|
||||
|
||||
Back in the terminal, let's use :code:`curl` to add a todo. The request will include an HTTP header containing the authentication token.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
export TOKEN="<paste token here>"
|
||||
|
||||
curl http://localhost:3000/todos -X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"task": "learn how to auth"}'
|
||||
|
||||
And now we have completed all three items in our todo list, so let's set :code:`done` to true for them all with a :code:`PATCH` request.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://localhost:3000/todos -X PATCH \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"done": true}'
|
||||
|
||||
A request for the todos shows three of them, and all completed.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://localhost:3000/todos
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"done": true,
|
||||
"task": "finish tutorial 0",
|
||||
"due": null
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"done": true,
|
||||
"task": "pat self on back",
|
||||
"due": null
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"done": true,
|
||||
"task": "learn how to auth",
|
||||
"due": null
|
||||
}
|
||||
]
|
||||
|
||||
Step 5. Add Expiration
|
||||
----------------------
|
||||
|
||||
Currently our authentication token is valid for all eternity. The server, as long as it continues using the same JWT password, will honor the token.
|
||||
|
||||
It's better policy to include an expiration timestamp for tokens using the :code:`exp` claim. This is one of two JWT claims that PostgREST treats specially.
|
||||
|
||||
+--------------+----------------------------------------------------------------+
|
||||
| Claim | Interpretation |
|
||||
+==============+================================================================+
|
||||
| :code:`role` | The database role under which to execute SQL for API request |
|
||||
+--------------+----------------------------------------------------------------+
|
||||
| :code:`exp` | Expiration timestamp for token, expressed in "Unix epoch time" |
|
||||
+--------------+----------------------------------------------------------------+
|
||||
|
||||
.. note::
|
||||
|
||||
Epoch time is defined as the number of seconds that have elapsed since 00:00:00 Coordinated Universal Time (UTC), January 1st 1970, minus the number of leap seconds that have taken place since then.
|
||||
|
||||
To observe expiration in action, we'll add an :code:`exp` claim of five minutes in the future to our previous token. First find the epoch value of five minutes from now. In psql run this:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
select extract(epoch from now() + '5 minutes'::interval) :: integer;
|
||||
|
||||
Go back to jwt.io and change the payload to
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"role": "todo_user",
|
||||
"exp": 123456789
|
||||
}
|
||||
|
||||
**NOTE**: Don't forget to change the dummy epoch value :code:`123456789` in the snippet above to the epoch value returned by the psql command.
|
||||
|
||||
Copy the updated token as before, and save it as a new environment variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
export NEW_TOKEN="<paste new token>"
|
||||
|
||||
Try issuing this request in curl before and after the expiration time:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://localhost:3000/todos \
|
||||
-H "Authorization: Bearer $NEW_TOKEN"
|
||||
|
||||
After expiration, the API returns HTTP 401 Unauthorized:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{"message":"JWT expired"}
|
||||
|
||||
Bonus Topic: Immediate Revocation
|
||||
---------------------------------
|
||||
|
||||
Even with token expiration there are times when you may want to immediately revoke access for a specific token. For instance, suppose you learn that a disgruntled employee is up to no good and his token is still valid.
|
||||
|
||||
To revoke a specific token we need a way to tell it apart from others. Let's add a custom :code:`email` claim that matches the email of the client issued the token.
|
||||
|
||||
Go ahead and make a new token with the payload
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"role": "todo_user",
|
||||
"email": "disgruntled@mycompany.com"
|
||||
}
|
||||
|
||||
Save it to an environment variable:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
export WAYWARD_TOKEN="<paste new token>"
|
||||
|
||||
PostgREST allows us to specify a stored procedure to run during attempted authentication. The function can do whatever it likes, including raising an exception to terminate the request.
|
||||
|
||||
First make a new schema and add the function:
|
||||
|
||||
.. code-block:: plpgsql
|
||||
|
||||
create schema auth;
|
||||
grant usage on schema auth to web_anon, todo_user;
|
||||
|
||||
create or replace function auth.check_token() returns void
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
if current_setting('request.jwt.claims', true)::json->>'email' =
|
||||
'disgruntled@mycompany.com' then
|
||||
raise insufficient_privilege
|
||||
using hint = 'Nope, we are on to you';
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
|
||||
Next update :code:`tutorial.conf` and specify the new function:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# add this line to tutorial.conf
|
||||
|
||||
db-pre-request = "auth.check_token"
|
||||
|
||||
Restart PostgREST for the change to take effect. Next try making a request with our original token and then with the revoked one.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# this request still works
|
||||
|
||||
curl http://localhost:3000/todos -X PATCH \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"done": true}'
|
||||
|
||||
# this one is rejected
|
||||
|
||||
curl http://localhost:3000/todos -X PATCH \
|
||||
-H "Authorization: Bearer $WAYWARD_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"task": "AAAHHHH!", "done": false}'
|
||||
|
||||
The server responds with 403 Forbidden:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"hint": "Nope, we are on to you",
|
||||
"details": null,
|
||||
"code": "42501",
|
||||
"message": "insufficient_privilege"
|
||||
}
|
||||
Reference in New Issue
Block a user