summaryrefslogtreecommitdiff
path: root/architecture.mdwn
blob: b9cd2c7aa72ac1126db0f996f5b17d45fbfc337c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
[[!meta title="Architecture"]]

[[!toc ]]

# Assumptions

The architecture has been designed under the following assumptions:

* Privacy is important, and should be the default. People should only
  have access to the information they are authorized to access.

* Members should be able to see their own information, without having
  to go through the Effi membership register admin.

* It's not practical to have every member have a password.
  Authentication can be done by sending the member a unique,
  single-use link when they want to log in.

# Components

[[!img arch.png]]

Effireg consists of four main components:

* **effiapi** provides a RESTful HTTP API for managing the membership
  data, and for doing things with or to the data. All operations on
  the data go via the API.

* **effiweb** provides the frontend for the web application to use the
  membership register. It renders HTML to the user, is accesses via a
  web browser, and generally is the user-visible part of Effireg.

  Everything effiweb does, it does by using the effiapi API. If any
  other user interfaces are added (say, mobile or command line), they
  will be parallel to effiweb and will also use the effiapi API in the
  same way as effiweb.

* **Qvisqve** provides authentication of end-users (the members, and
  admins). It lets users log in, and keeps track of what each user is
  allowed to do.

* **Muck-POC** stores the actual membership register data, and
  controls access to it, based on access tokens from Qvisqve.

# Authentication

[OpenID Connect]: https://openid.net/specs/openid-connect-core-1_0.html
[OAuth2]: https://oauth.net/2/

End-users are authenticated using the [OpenID Connect][] protocol,
specifically the authorization code flow. In this flow, Qvisqve
provides cryptographically signed access tokens, which identify the
user and specify a list of things the user may do. The signature
guarantees the token comes from Qvisqve. To prevent the access token
from leaking to the browser, effiweb keeps the token, and also manages
user sessions.

Since it is impractical to give each member a password, in a secure
manner, Effireg will instead allow a member to request a unique,
single-use login link to be emailed to them. The link will take the
member's web browser to effiweb, which will extract a unique token
from the link, and use that to authenticate the member for Qvisqve,
and start a new session. As long as the session exist (until the
member logs out), they can use the effiweb application.

(A valied-until-logout session, without timeouts, is not ideal from a
security perspective, but given the harm of an un-privileged member's
hijacked session is minor, it's good enough. When Effireg develops
further and the potential harm become bigger, session timeout will be
implemented.)

Non-interactive API clients are authenticated using the [OAuth2][]
protocol, specifically using client credential grants. This also
provides an access token, similar to the one from end-user
authentication. API clients will not be available to normal members at
this time, only members trusted by the board.

Regardless of trust level, each member and API client will have a set
of allowed operations that constrain what they can do.

# Data model

The membership register stores data as "resources" in Muck-POC. Each
resource is a JSON object. The following types of objects are
supported:

* **subject** represents a person who is allowed to use the register;
  it it used to identify the user for authentication
* **password** stores the encrypted password for a subject
* **memb** represets a subject's membership in Effi

## Subject resource

    {
        "username": "007@mi6.example.com"
    }

A subject resource has the following fields:

* `username` — the username of the subject; this can change, the
  subject is identified by the resource identifier in the register,
  not by the username

The subject resource does not have any data that isn't needed for
end-user identification. Effiapi manages the subject resource via the
Qvisqve API. Effiapi does not expose it via its own API.

To allow members to log using their email address, the username is set
to their primary email address.

## Password resource

    {
        "subject-id": "abcdef0123456789#,
        "version": 1,
        "hash": "xxxxx",
        "salt": "yyyyy",
        "key_len": 128,
        "p": 1,
        "r": 8,
        "N": 16384
    }

A password resource has the following fields:

* `subject_id` — resource id of the subject whose password this
  is
* `version` — version of the password resource (identifies
  algorithm); must be 1
* `hash` — the password, encrypted with the scrypt algorithm
* `salt` — a random string to prevent dictionary attacks
* `key_len` — parameter for scrypt
* `N` — parameter for scrypt
* `r` — parameter for scrypt
* `p` — parameter for scrypt

Effiapi manages the password resource via the Qvisqve API. Effiapi
does not expose it via its own API.

<a name="memb">
## Member resource (memb)
</a>

    {
        "subject-id": "abcdef0123456789",
        "fullname": "James Bond",
        "primary-email": "007@mi6.example.com",
        "years-paid": [2010, 2011, 2012, 2018]
    }

A membership resource has the following fields:

* `subject-id` &mdash; the resource id of the subject resource for
  the member; this is assigned by effiapi (really Muck), when the
  resource is created, and can't be changed via the API
* `fullname` &mdash; the full name of the member
* `primary-email` &mdash; the primary email address for the member
  (and currently the only one)
* `years-paid` &mdash; list of integers for the years for which the
  member has paid their membership

Effiapi manages and uses the memb resource, and exposes it via its own
API. Effiweb renders it for the user.

# API

The effiapi API is a RESTful, HTTP-based API using JSON. The only
resource exposed is <a href="#memb">`memb`</a>, via the `/memb`
endpoint. The following operations are defined:

* `POST /memb` &mdash; create a new member resource; this implicitly
  creates the subject resource as well, allowing the member to log in;
  the `subject-id` field is ignored, if set
* `PUT /memb` &mdash; update an existing member resource; the whole
  resource is updated, not just selected fields
* `GET /memb` &mdash; retrieve a member resource; the whole
  resource is returned

In all responses, the following headers convey metadata about the
resource:

* `Muck-Id` &mdash; the internal, random identifier for the resource,
  assigned by the API
* `Muck-Revision` &mdash; the current revision of the resource

`GET` requests must use the `Muck-Id` header to specify which member
resource is to be retrieved.

`PUT` requests must use the `Muck-Id` header, and also the the
`Muck-Revision` header, with the value showing the **current**
revision. This prevents concurrent updates from updating with
conflicting data.

`PUT` is not allowed to change the `subject-id` field of a member
resource.

## Search language for members

In addition to managing individual member resources, the API provides
a search function. Note that there is no way to list member resources;
a search that matches all can be used instead.

* `GET /search` &mdash; search for member resources matching the
  required criteria; the search language is described below

The search request body should look like this:

    {
        "where": "data",
        "field": "email",
        "pattern": "007@mi6.example.com",
        "op": "=="
    }

A search may return any number of results:

    {
        "resources": ["abcdef0123456789", "cafef00d"]
    }

It is the API client's (effiweb's) responsibility to retrieve the
resources given the id, and to do any more detailed selecting among
the resources. It is only possible to search based on fields that are
at the top level, and have a string value.

The search will only match, and return, resources that the user is
allowed to see.