summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2018-12-30 18:40:53 +0200
committerLars Wirzenius <liw@liw.fi>2018-12-30 18:40:53 +0200
commitdde454d54147d8fba5ea263bdf66d1cc48c44968 (patch)
treef40f106e4c7ab5b4cf8c5c0ee41ba4a60c395306
downloadeffireg-website-dde454d54147d8fba5ea263bdf66d1cc48c44968.tar.gz
Add: initial commit
-rw-r--r--Makefile7
-rw-r--r--arch.css15
-rw-r--r--arch.diag12
-rw-r--r--arch.html145
-rw-r--r--arch.pngbin0 -> 4856 bytes
-rw-r--r--architecture.mdwn111
-rwxr-xr-xbuild.sh14
-rw-r--r--ikiwiki.setup36
-rw-r--r--index.mdwn12
9 files changed, 352 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..14078f5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+yarns = $(wildcard *.yarn)
+images = $(wildcard *.diag)
+
+all: arch.html
+
+arch.html: $(yarns) $(images) arch.css
+ ./build.sh
diff --git a/arch.css b/arch.css
new file mode 100644
index 0000000..25720b2
--- /dev/null
+++ b/arch.css
@@ -0,0 +1,15 @@
+html {
+ font-family: serif;
+ margin-left: 4em;
+ margin-right: 4em;
+}
+
+h1, h2, h3 {
+ font-family: sans-serif;
+ margin-top: 2em;
+}
+
+
+h1 { font-size: 3em; }
+h2 { font-size: 2em; }
+h3 { font-size: 1em; }
diff --git a/arch.diag b/arch.diag
new file mode 100644
index 0000000..d1e0f1e
--- /dev/null
+++ b/arch.diag
@@ -0,0 +1,12 @@
+blockdiag {
+ browser;
+ qvisqve;
+ effiweb;
+ effiapi;
+ muck;
+
+ browser -> effiweb;
+ browser -> qvisqve;
+ effiweb -> effiapi;
+ effiapi -> muck;
+}
diff --git a/arch.html b/arch.html
new file mode 100644
index 0000000..5e9f8ac
--- /dev/null
+++ b/arch.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang xml:lang>
+<head>
+ <meta charset="utf-8" />
+ <meta name="generator" content="pandoc" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
+ <meta name="author" content="Lars Wirzenius" />
+ <title>Effireg architecture</title>
+ <style type="text/css">
+ code{white-space: pre-wrap;}
+ span.smallcaps{font-variant: small-caps;}
+ span.underline{text-decoration: underline;}
+ div.column{display: inline-block; vertical-align: top; width: 50%;}
+ </style>
+ <style type="text/css">html {
+font-family: serif;
+margin-left: 4em;
+margin-right: 4em;
+}
+h1, h2, h3 {
+font-family: sans-serif;
+margin-top: 2em;
+}
+h1 { font-size: 3em; }
+h2 { font-size: 2em; }
+h3 { font-size: 1em; }
+</style>
+</head>
+<body>
+<header>
+<h1 class="title">Effireg architecture</h1>
+<p class="author">Lars Wirzenius</p>
+<p class="date">Version: trial-1-g4f2e1de-dirty</p>
+</header>
+<nav id="TOC">
+<ul>
+<li><a href="#introduction">Introduction</a></li>
+<li><a href="#architecture-overview">Architecture overview</a><ul>
+<li><a href="#assumptions">Assumptions</a></li>
+<li><a href="#components">Components</a></li>
+<li><a href="#authentication">Authentication</a></li>
+</ul></li>
+<li><a href="#data-model">Data model</a><ul>
+<li><a href="#subject-resource">Subject resource</a></li>
+<li><a href="#password-resource">Password resource</a></li>
+<li><a href="#member-resource-memb">Member resource (memb)</a></li>
+</ul></li>
+<li><a href="#effiapi">effiapi</a><ul>
+<li><a href="#manage-memberships">Manage memberships</a></li>
+</ul></li>
+<li><a href="#appendix-yarn-scenario-step-implementations">Appendix: Yarn scenario step implementations</a></li>
+</ul>
+</nav>
+<h1 id="introduction">Introduction</h1>
+<p>Effireg is a web-based membership register for the Effi non-profit association. See <a href="https://effi.org/" class="uri">https://effi.org/</a> for more information about Effi.</p>
+<p>Effireg is written for Effi, but it is free software, released under the Affero GPL v3 license, and may be used by others. No guarantees of quality.</p>
+<h1 id="architecture-overview">Architecture overview</h1>
+<h2 id="assumptions">Assumptions</h2>
+<p>The architecture has been designed under the following assumptions:</p>
+<ul>
+<li><p>Privacy is important, and should be the default. People should only have access to the information they are authorized to access.</p></li>
+<li><p>Members should be able to see their own information, without having to go through the Effi membership register admin.</p></li>
+<li><p>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.</p></li>
+</ul>
+<h2 id="components">Components</h2>
+<figure>
+<img src="data:image/png;base64,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" alt="Architectural components" /><figcaption>Architectural components</figcaption>
+</figure>
+<p>Effireg consists of four main components:</p>
+<ul>
+<li><p><strong>effiapi</strong> provides a RESTful HTTP API for managing the membership data, and for doing things with or to the data. All operations go via the API.</p></li>
+<li><p><strong>effiweb</strong> 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.</p></li>
+<li><p><strong>Qvisqve</strong> 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.</p></li>
+<li><p><strong>Muck-POC</strong> stores the actual membership register data, and controls access to it, based on access tokens from Qvisqve.</p></li>
+</ul>
+<h2 id="authentication">Authentication</h2>
+<p>End-users are authenticated using the <a href="https://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect</a> 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.</p>
+<p>Non-interactive API clients are authenticated using the <a href="https://oauth.net/2/">OAuth2</a> protocol, specifically using client credential grants. This also provides an access token, similar to the one from end-user authentication.</p>
+<h1 id="data-model">Data model</h1>
+<p>The membership register stores data as “resources” in Muck-POC. Each resource is a JSON object. The following types of objects are supported:</p>
+<ul>
+<li><strong>subject</strong> represents a person who is allowed to use the register; it it used to identify the user for authentication</li>
+<li><strong>password</strong> stores the encrypted password for a subject</li>
+<li><strong>memb</strong> represets a subject’s membership in Effi</li>
+</ul>
+<h3 id="subject-resource">Subject resource</h3>
+<p>A subject resource has the following fields:</p>
+<ul>
+<li><code>username</code> — the username of the subject; this can change, the subject is identified by the resource identifier in the register, not by the username</li>
+</ul>
+<p>The subject resource does not have any data that isn’t needed for end-user identification. Effiapi manages and Qvisqve uses the subject resource.</p>
+<h3 id="password-resource">Password resource</h3>
+<p>A password resource has the following fields:</p>
+<ul>
+<li><code>subject_id</code> — resource id of the subject whose password this is</li>
+<li><code>version</code> — version of the password resource (identifies algorithm); must be 1</li>
+<li><code>hash</code> — the password, encrypted with the scrypt algorithm</li>
+<li><code>salt</code> — a random string to prevent dictionary attacks</li>
+<li><code>key_len</code> — parameter for scrypt</li>
+<li><code>N</code> — parameter for scrypt</li>
+<li><code>r</code> — parameter for scrypt</li>
+<li><code>p</code> — parameter for scrypt</li>
+</ul>
+<p>Effiapi manages and Qvisqve uses the password resource.</p>
+<h3 id="member-resource-memb">Member resource (memb)</h3>
+<p>A membership resource has the following fields:</p>
+<ul>
+<li><code>subject-id</code> — the resource id of the subject resource for the member</li>
+<li><code>fullname</code> — the full name of the member</li>
+<li><code>primary-email</code> — the primary email address for the member (and currently the only one)</li>
+<li><code>years-paid</code> — list of integers for the years for which the member has paid their membership</li>
+</ul>
+<p>Effiapi manages and uses the memb resource. Effiweb renders it for the user.</p>
+<h1 id="effiapi">effiapi</h1>
+<p>This chapter descibes the effiapi API, as a <a href="https://liw.fi/cmdtest/">yarn</a> automated scenario test. It is meant to be understandable to Effi sysadmins, as well as be an executable test suite for the API.</p>
+<p>The API is a simple RESTful HTTP API using JSON. This means that:</p>
+<ul>
+<li><p>each API call is an HTTP request, with a response</p></li>
+<li><p>all data is represented as JSON in the body of the request of response</p></li>
+<li><p>metadata about the data is represeneted as HTTP headers</p></li>
+<li><p>standard HTTP verbs (POST, PUT, GET, DELETE) are used to indicate the action</p></li>
+<li><p>standard HTTP status codes are used to indicate result of the request (200 = OK, 404 = not found, etc)</p></li>
+</ul>
+<h2 id="manage-memberships">Manage memberships</h2>
+<p>This section shows the API calls to manage a memberhip: to create the member, to update and retrieve it, and to search memberships.</p>
+<pre><code>SCENARIO Manage memberships
+
+GIVEN An effiapi instance
+WHEN admin requests POST /memb with body { &quot;fullname&quot;: &quot;James Bond&quot; }
+THEN the member id is ID
+
+WHEN admin requests GET /memb with header Muck-Id: ${ID}
+THEN HTTP status 200
+AND HTTP body matches { &quot;fullname&quot;: &quot;James Bond&quot; }</code></pre>
+<p>TODO:</p>
+<ul>
+<li>update</li>
+<li>delete</li>
+<li>search</li>
+<li>members can see their own data, and can’t see each others’</li>
+<li>member follows authn link emailed to them</li>
+</ul>
+<h1 id="appendix-yarn-scenario-step-implementations">Appendix: Yarn scenario step implementations</h1>
+</body>
+</html>
diff --git a/arch.png b/arch.png
new file mode 100644
index 0000000..f86d22f
--- /dev/null
+++ b/arch.png
Binary files differ
diff --git a/architecture.mdwn b/architecture.mdwn
new file mode 100644
index 0000000..fccf309
--- /dev/null
+++ b/architecture.mdwn
@@ -0,0 +1,111 @@
+[[!meta title="Architecture"]]
+
+[[!toc ]]
+
+# Architecture overview
+
+## 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 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.
+
+* **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.
+
+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.
+
+# 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
+
+A subject resource has the following fields:
+
+* `username` &mdash; 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 and Qvisqve uses the subject
+resource.
+
+### Password resource
+
+A password resource has the following fields:
+
+* `subject_id` &mdash; resource id of the subject whose password this
+ is
+* `version` &mdash; version of the password resource (identifies
+ algorithm); must be 1
+* `hash` &mdash; the password, encrypted with the scrypt algorithm
+* `salt` &mdash; a random string to prevent dictionary attacks
+* `key_len` &mdash; parameter for scrypt
+* `N` &mdash; parameter for scrypt
+* `r` &mdash; parameter for scrypt
+* `p` &mdash; parameter for scrypt
+
+Effiapi manages and Qvisqve uses the password resource.
+
+### Member resource (memb)
+
+A membership resource has the following fields:
+
+* `subject-id` &mdash; the resource id of the subject resource for
+ the member
+* `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. Effiweb renders it for the
+user.
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..8a04877
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -eu
+
+version="$(git describe --dirty)"
+blockdiag3 arch.diag
+pandoc \
+ -Vdate="Version: $version" \
+ --toc \
+ --standalone \
+ --self-contained \
+ --css arch.css \
+ -o arch.html \
+ *.yarn
diff --git a/ikiwiki.setup b/ikiwiki.setup
new file mode 100644
index 0000000..43727ba
--- /dev/null
+++ b/ikiwiki.setup
@@ -0,0 +1,36 @@
+# IkiWiki::Setup::Yaml - YAML formatted setup file
+
+wikiname: Effireg
+url: https://icv-biz.liw.fi
+srcdir: .
+destdir: /home/liw/sites/effireg.liw.fi/html
+add_plugins:
+- goodstuff
+- inline
+- attachment
+- remove
+- rename
+- tag
+- orphans
+- brokenlinks
+- trail
+- sidebar
+- toc
+- highlight
+- format
+- graphviz
+disable_plugins:
+- smiley
+- rawhtml
+- html
+- tidy
+html5: yes
+timeformat: '%Y-%m-%d %H:%M'
+exclude: 'favicon\.ico'
+allowed_attachments: mimetype(image/*)
+tagbase: tag
+discussion: no
+allowrss: yes
+allowatom: yes
+rss: yes
+atom: yes
diff --git a/index.mdwn b/index.mdwn
new file mode 100644
index 0000000..10925eb
--- /dev/null
+++ b/index.mdwn
@@ -0,0 +1,12 @@
+[[!meta title="Effireg - privacy-aware register of members for associations"]]
+
+Effireg is a web-based membership register for the Effi non-profit
+association. See <https://effi.org/> for more information about Effi.
+
+Effireg is written for Effi, but it is free software, released under
+the Affero GPL v3 license, and may be used by others. No guarantees of
+quality.
+
+
+* [[architecture]]
+