Skip to content

Commit 41fd149

Browse files
committed
show encrypted person.email in psql terminal for: dwyl/auth#285
1 parent f97bbfa commit 41fd149

File tree

4 files changed

+313
-9
lines changed

4 files changed

+313
-9
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@
1111
- [Create New `Phoenix` Project](auth/05-mix-phx-new.md)
1212
- [Generate Auth Schema](auth/06-mix-gen-auth.md)
1313
- [Notes on Naming](auth/07-notes-on-naming.md)
14-
- [Modals Are An _Antipattern_](auth/08-modals-antipattern.md)
14+
- [Modals Are An _Antipattern_](auth/08-modals-antipattern.md)
15+
- [Encrypt Personal Data](src/auth/09-encrypt-data.md)

src/auth/06-mix-gen-auth.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,10 @@ at the created database tables.
130130
If you open the `auth_dev`
131131
database in your Postgres GUI,
132132
e.g:
133-
[`DBEaver`](https://github.com/dwyl/learn-postgresql/issues/43)
134-
And view the
133+
[`DBEaver`](https://github.com/dwyl/learn-postgresql/issues/43) <br />
134+
and view the
135135
[ERD](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model)
136-
137-
There are only two tables:
136+
there are only two tables:
138137

139138
![mix-phx-gen-auth-erd](https://user-images.githubusercontent.com/194400/223881258-be77ebdc-0114-4606-aac1-065f172b6727.png)
140139

@@ -146,7 +145,6 @@ By default,
146145
does not setup any _protection_
147146
for personal data in the database.
148147
Email addresses are stored in-the-clear: 🙄
149-
<img width="874" alt="image" src="">
150148

151149
![mix-phx-gen-auth-people-table-email-plaintext](https://user-images.githubusercontent.com/194400/223880353-98597d83-cb9c-424d-bcb0-2121ecd743c0.png)
152150

src/auth/07-notes-on-naming.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,16 @@ treat their "users" as the `product`.
9393
> that make most of their money from ads._"
9494
> ~ [Ben Wolford](https://github.com/dwyl/learn-react/issues/23#issuecomment-1461358970)
9595
96-
### `MAUs`
96+
## `DAUs`, `MAUs`
9797

9898
[`Facebook`](https://github.com/dwyl/learn-react/issues/23#issuecomment-406784935)
9999
will continue referring to `people` as "users"
100100
and more specifically
101+
"[**DAUs**](https://en.wikipedia.org/wiki/Active_users)"
102+
(_Daily_ Active Users)
103+
and
101104
"[**MAUs**](https://github.com/dwyl/learn-react/issues/23#issuecomment-1458429682)"
102-
(Monthly Active Users).
105+
(_Monthly_ Active Users).
103106
They don't _want_ to think about the
104107
`people` whose lives they are
105108
[wasting](https://github.com/dwyl/home/issues/29)
@@ -154,7 +157,7 @@ Not Customers, Not Consumers, Not Users:
154157
are an essential part of its experience:
155158
[developer.apple.com/design/human-interface-guidelines/foundations/writing](https://developer.apple.com/design/human-interface-guidelines/foundations/writing)
156159

157-
> **Note**: **`Apple**`**, while a proponent
160+
> **Note**: **`Apple**`** while a proponent
158161
> of carefully selecting words,
159162
> often refer to the `people`
160163
> that buy and use

src/auth/09-encrypt-data.md

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# Encrypt Personal Data
2+
3+
As previously noted
4+
the schema created by the
5+
`phx.gen.auth` generator
6+
leaves **`email`** addresses stored
7+
as
8+
[**`plaintext`**](https://en.wikipedia.org/wiki/Plaintext):
9+
10+
![mix-phx-gen-auth-people-table-email-plaintext](https://user-images.githubusercontent.com/194400/223880353-98597d83-cb9c-424d-bcb0-2121ecd743c0.png)
11+
12+
It's disappointing to us
13+
that this is the `default`
14+
and many devs
15+
will _naively_ think this is "OK".
16+
17+
## It's _Never_ "OK" to Store Personal Data as `plaintext`
18+
19+
It might be _tempting_
20+
to store personal data as
21+
human-readable text
22+
during development
23+
to make it easier to debug,
24+
but we urge _everyone_
25+
to resist this temptation!
26+
[Data breaches](https://en.wikipedia.org/wiki/Data_breach)
27+
happen _every_ day.
28+
Breaches can _destroy_ the reputation
29+
of a company.
30+
Thankfully,
31+
the EU now has stronger laws
32+
in the form of the
33+
[General Data Protection Regulation](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation)
34+
(GDPR),
35+
which carry heavy fines
36+
for companies
37+
that improperly store personal data.
38+
39+
Most software engineers don't consider
40+
the law(s) around data protection.
41+
But none of us have _any_ excuse
42+
to ignore them even in basic projects.
43+
44+
The good news is:
45+
protecting personal data
46+
is quite straightforward.
47+
48+
## Why Encrypt _Now_?
49+
50+
We _know_ this might feel like a deeply technical
51+
step to have so early on in
52+
the creation of the `auth` App.
53+
Why don't we just _skip_ this
54+
and focus on the interface `people` will _see_?
55+
We feel that getting the data privacy/security
56+
right from the _start_
57+
is essential for building a robust
58+
and therefore trustworthy app.
59+
We will get to the interface design
60+
in the next chapter
61+
so if you prefer that part,
62+
feel free to speed-read/run this
63+
and only return to it when you
64+
need a deeper understanding.
65+
66+
67+
## How To Encrypt Sensitive Data?
68+
69+
When we first started using `Elixir` in 2016
70+
we explored the topic of
71+
encrypting personal data in _depth_
72+
and wrote a comprehensive guide:
73+
[`dwyl/phoenix-ecto-encryption-example`](https://github.com/dwyl/phoenix-ecto-encryption-example)
74+
We _highly_ recommend following step-by-step guide
75+
to understand this in detail.
76+
We aren't going to duplicate/repeat
77+
any of the theory here.
78+
Rather we are going to focus on using
79+
the package we created
80+
[`fields`](https://github.com/dwyl/fields)
81+
82+
## Using `Fields` to _Automatically_ Encrypt Personal Data
83+
84+
Following the instructions in the
85+
[`fields`](https://github.com/dwyl/fields)
86+
repo, open the `mix.exs` file
87+
and locate the `defp deps do` section
88+
and add `fields` to the list:
89+
90+
```elixir
91+
{:fields, "~> 2.10.3"},
92+
```
93+
94+
Save the `mix.exs` file and run:
95+
96+
```sh
97+
mix deps.get
98+
```
99+
100+
101+
### Create Environment Variables
102+
103+
`fields` expects an `environment variable
104+
105+
e.g. using the [`phx.gen.secret`](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html) command:
106+
107+
```sh
108+
mix phx.gen.secret
109+
```
110+
You should see output similar to:
111+
112+
```sh
113+
XGYVueuky4DT0s0ks2MPzHpucyl+9e/uY4UusEfgyR2qeNApzoYGSH+Y55cfDj1Y
114+
```
115+
116+
Export this key in your terminal with the following command:
117+
118+
```sh
119+
export ENCRYPTION_KEYS=XGYVueuky4DT0s0ks2MPzHpucyl+9e/uY4UusEfgyR2qeNApzoYGSH+Y55cfDj1Y
120+
```
121+
122+
Run the `phx.gen.secret` command again
123+
and export it as `SECRET_KEY_BASE`, e.g:
124+
125+
```sh
126+
export SECRET_KEY_BASE=GLH2S6EU0eZt+GSEmb5wEtonWO847hsQ9fck0APr4VgXEdp9EKfni2WO61z0DMOF
127+
```
128+
129+
We use an `.env` file on `localhost`:
130+
131+
```sh
132+
export ENCRYPTION_KEYS=XGYVueuky4DT0s0ks2MPzHpucyl+9e/uY4UusEfgyR2qeNApzoYGSH+Y55cfDj1Y
133+
export SECRET_KEY_BASE=GLH2S6EU0eZt+GSEmb5wEtonWO847hsQ9fck0APr4VgXEdp9EKfni2WO61z0DMOF
134+
```
135+
136+
See:
137+
[`.env_sample`](https://github.com/dwyl/auth/blob/main/.env_sample)
138+
for a sample including
139+
all the recommended/required environment variables
140+
for running the `auth` app.
141+
142+
Now to the _interesting_ part!
143+
144+
## Update `people.email` Data Type
145+
146+
The `phx.gen.auth` generator
147+
created the `people` schema
148+
with the `:email` field defined as a `:string`:
149+
150+
```sh
151+
field :email, :string
152+
```
153+
154+
See:
155+
[`lib/auth/accounts/person.ex#L6`](https://github.com/dwyl/auth/blob/da0af7ee702c278714b47f92045edce4adad542a/lib/auth/accounts/person.ex#L6)
156+
157+
158+
### Create Migration File
159+
160+
Using [`Ecto.Migration`](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#module-creating-your-first-migration)
161+
run the following command
162+
to create a migration file
163+
with a descriptive name:
164+
165+
```sh
166+
mix ecto.gen.migration modify_people_email_string_binary
167+
```
168+
169+
You should see output similar to:
170+
171+
```sh
172+
* creating priv/repo/migrations/20230309145958_modify_people_email_string_binary.exs
173+
```
174+
175+
Open the file in your editor. You should see:
176+
177+
```elixir
178+
defmodule Auth.Repo.Migrations.ModifyPeopleEmailStringBinary do
179+
use Ecto.Migration
180+
181+
def change do
182+
183+
end
184+
end
185+
```
186+
187+
Update it to include the `alter` statement:
188+
189+
```elixir
190+
defmodule Auth.Repo.Migrations.ModifyPeopleEmailStringBinary do
191+
use Ecto.Migration
192+
193+
def change do
194+
alter table(:people) do
195+
remove :email
196+
add :email, :binary
197+
add ...
198+
end
199+
200+
alter table(:people_tokens) do
201+
remove :sent_to
202+
add :sent_to, :binary
203+
end
204+
end
205+
end
206+
```
207+
208+
> **Note**: we tried using
209+
[`Ecto.Migration.modify/3`](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#modify/3)
210+
to modify the field type
211+
but got the error:
212+
213+
```sh
214+
15:06:13.691 [info] alter table people
215+
** (Postgrex.Error) ERROR 42804 (datatype_mismatch) column "email" cannot be cast automatically to type bytea
216+
217+
hint: You might need to specify "USING email::bytea".
218+
```
219+
220+
Given that this is a new project
221+
and there is no data in the DB,
222+
we decided it was easier
223+
to remove and re-add the `email` column/field.
224+
225+
226+
Save the migration file and run:
227+
228+
```sh
229+
mix ecto.reset
230+
```
231+
232+
You should see output similar to the following:
233+
234+
```sh
235+
15:13:29.989 [info] == Migrated 20230226095715 in 0.0s
236+
237+
15:13:30.057 [info] == Running 20230309145958 Auth.Repo.Migrations.ModifyPeopleEmailStringBinary.change/0 forward
238+
239+
15:13:30.058 [info] alter table people
240+
241+
15:13:30.059 [info] alter table people_tokens
242+
243+
15:13:30.059 [info] == Migrated 20230309145958 in 0.0s
244+
```
245+
246+
### Update `person.email`
247+
248+
Open
249+
`lib/auth/accounts/person.ex`
250+
and replace the line:
251+
252+
```elixir
253+
field :email, :string
254+
```
255+
256+
With:
257+
258+
```elixir
259+
field :email, Fields.EmailEncrypted
260+
field :email_hash, Fields.EmailHash
261+
```
262+
263+
Sadly, this _essential_ privacy/security enhancement
264+
was not quite as straightforward as we had hoped
265+
and had quite a few ramifications.
266+
The boilerplate code
267+
generated by `phx.gen.auth`
268+
was relying on `person.email` being `plaintext`
269+
so we ended up having to make several changes.
270+
271+
Rather than repeating all of these here
272+
which will take up a _lot_ of space
273+
and duplicate the code unessessarily,
274+
we recommend you read through the git commit:
275+
[`#2bbba99`](https://github.com/dwyl/auth/pull/231/commits/2bbba99c7057f0d2943dd8327be52ccf1a1bd58c)
276+
277+
In a follow-up section
278+
we will be removing most of this code
279+
because we don't _want_ to
280+
use links in emails to verify `people`.
281+
Instead we will be using one-time-numeric codes.
282+
283+
284+
Following the changes made in:
285+
[`#2bbba99`](https://github.com/dwyl/auth/pull/231/commits/2bbba99c7057f0d2943dd8327be52ccf1a1bd58c)
286+
287+
If we now run a query to view the data in the `people` table:
288+
```sql
289+
SELECT id, email, inserted_at FROM people;
290+
```
291+
We see that the `email` is now a binary blob
292+
<img width="1096" alt="image" src="https://user-images.githubusercontent.com/194400/224109679-0084ae9d-a1e8-48f2-a083-94aff46d7137.png">
293+
294+
Registration and Login still works as expected:
295+
<img width="1159" alt="image" src="https://user-images.githubusercontent.com/194400/224111036-7a4e5764-8966-4b97-aaea-515c8e3b7fd4.png">
296+
297+
But now the personal data
298+
captured in registration is stored
299+
**`encrypted`** at rest
300+
the way it should be. 🔐
301+
302+
Now we can get on with _building_ `auth`!

0 commit comments

Comments
 (0)