Table of Contents

Member Portal V2

This is a project driven by @samp20 to build a new member portal to provide Single-Sign-On (SSO) to other Hackspace services.

Architecture

The system is based on a Python Flask application with PostgreSQL as the backend database.

Below is a diagram showing the system dependencies:

projects:member_portal:modules.png

Left to do

Required for initial demo:

Shortly after:

Future work:

Project layout

Systems

Systems will follow a similar style to Flask extensions. Flask requires that extensions don't store state directly on the class itself, but instead obtain it from the current_app. This allows multiple applications to be instantiated, for example when unit testing.

class _State:
    def __init__(self, app: Flask):
        self.greeting: str = app.config["MYSYSTEM_GREETING"]
 
class MySystem:
    def __init__(self, other: OtherSystem, app: Flask|None):
        self.other = other
 
        if app is not None:
            self.init_app(app)
 
    def init_app(self, app: Flask):
        state = _State(app)
        app.extensions["hs.portal.my_system"] = state
 
    @property
    def _state(self) -> _State:
        state = current_app.extensions["hs.portal.my_system"]
        return state
 
    def get_greeting(self) -> str:
        return self._state.greeting

Testing

We will use pytest for our testing. We should aim to test each system in isolation.

Some code will require an active request to test. We can create test endpoints to satisfy these instead of using the fully templated ones.

For now we can probably use an in-memory SQLite database for testing. We may need to switch to a proper PostgreSQL database if we start to depend on DB specific features, at which point we'll need to clean the database before every test run.

Models

projects:member_portal:models.png

Tokens

A common pattern that's required is to store an external reference to a table row in a secure way. The pattern we use is to store an id of type UUID and a token_hash of type string in the table. The external token will refer to this using the string format {id.hex}:{token}. The ID is used to select the correct row and the SHA256 of token is compared against token_hash. The token is required as a layer of security as we cannot guarantee the id generation is cryptographically secure. It also allows us to rotate the token while still referring to the same row, for example cookie rotation.