joetjen.net
EN DE
Anwendung

Architektur

Wie Lictor aufgebaut ist: Anwendungen, die jeweils für sich laufen und skalieren, ein Dienst für die Identität und die Website auf WebGlue. Der Code ist aus den Quellen von 0.2.0 zitiert, wo markiert gekürzt.

In Entwicklung · v0.2

Anwendungen, als eines veröffentlicht

Jeder Teil von Lictor — Website, Identität, Moderation, Plattformanbindung, Chatbot und die OAuth2-Autorisierungs- und Token-Server — ist eine OTP-Anwendung mit eigenem Supervision-Baum, eigenen Daten und einer festgelegten Schnittstelle. Sie werden in einem Release ausgeliefert und gemeinsam versioniert; ein Knoten startet die Anwendungen, die seine Rolle nennt, und in der Entwicklung startet ein Knoten alle. Auf Kubernetes ist das ein Image und ein Deployment pro Rolle, jedes mit eigener Replikazahl. Die Knoten bilden einen BEAM-Cluster und sprechen über verteiltes Erlang miteinander, hinter der Schnittstelle der jeweiligen Anwendung.

AnwendungBenannt nachWas sie ist
lictor_elysiumElysium, die IllusionDie Website und der Supervision-Baumseit 0.1
lictor_ketherKetherIdentität: Einladungen, Konten, Bestätigungseit 0.2
lictor_geburahGeburah, der Archont des GesetzesDer OAuth2-Autorisierungsservergeplant, M37
lictor_chesedChesed, der Archont der SicherheitDer OAuth2-Token-Servergeplant, M37

Der Supervision-Baum

prx run kompiliert die Suite, baut das Stylesheet, liest die Konfiguration und ruft lictor/main auf, das sie prüft und den Baum startet. In der Entwicklung laufen alle Dienste auf einem Knoten, jeder gestartet, sobald der vorherige läuft (gekürzt):

apps/lictor_elysium/src/lictor.prax
[(supervisor/child :pg nil {:id :lictor-scope ...})               ; where services are found
 (supervisor/child webglue.session.memory nil)                    ; the sessions
 (supervisor/child posthorn.mailbox nil {:id :mailbox ...})       ; the development mailbox
 (supervisor/child lictor.kether.service nil {:id :kether ...})   ; identity
 (server/child bandit/child (elysium/app) {:port (port)})]        ; the site

Kether macht sich in einer pg-Gruppe bekannt, sodass die Website es mit lictor.kether/locate genau so findet, wie sie es in einem Cluster täte.

Kether, Identität als Dienst

Kether ist ein Prozess mit einem Speicher, angesprochen über lictor.kether mit seiner Pid. Eine Pid reicht über einen Cluster wie über einen Knoten, deshalb ruft die Website Kether auf dieselbe Weise auf, ob beide einen Knoten teilen oder in eigenen Pods laufen. Die Konten liegen hinter einem Speicher-Protokoll, dessen erste Implementierung im Speicher arbeitet.

(def k (kether/memory-store))
(kether/invite k {:for "Ada" :now now})   ; #(:ok token) -- the link is /invite/<token>

(kether/register k {:invite token :username "ada" :email "ada@example.com"
                    :password "correct horse battery" :now now
                    :mailer box :from "lictor@localhost"
                    :base-url "http://localhost:4000"})

(kether/verify k verify-token now)        ; #(:ok "ada")
  • Ein Token ist die einzige Kopie, die es gibt: Kether behält seinen SHA-256-Hash, sodass eine Kopie des Speichers keine Handvoll gültiger Links ist, und ein zurückgebrachtes Token wird in konstanter Zeit verglichen.
  • Ein Passwort wird als shibboleth-Hash aufbewahrt, PBKDF2-SHA256 mit 600.000 Iterationen. Ein neues Konto bleibt unbestätigt, bis sein gemailter Link benutzt wird.
  • Die Mail geht zuerst: Eine Mail, die nicht verschickt werden kann, ist eine Registrierung, die nicht stattgefunden hat. Eine Ablehnung behält nichts und lässt die Einladung gültig.
  • Nichts in Kether liest die Uhr; jeder Zeitpunkt wird übergeben.

Die Website

Die Website ist eine WebGlue-Anwendung: eine Funktion von einer Request-Map auf eine Response-Map, der Server nur der Weg, auf dem eine Anfrage sie erreicht. Um die Routen liegen, von außen nach innen, statische Dateien, die Session, der Formular-Body und die CSRF-Prüfung:

apps/lictor_elysium/src/lictor/elysium.prax
(let [routed (-> (router/handler (routes))
                 (csrf/wrap)
                 (params/wrap)
                 (session/wrap {:store (sessions/store)})
                 (in-world world))]
  (static/wrap routed {:root (static-root) :at "/assets"}))

(elysium/app world) ist die Anwendung über einer Welt — dem Kether-Handle, dem Postfach und einer Uhr — so baut sich ein Test eine, die er steuert. So, wie der Baum sie startet, findet sie die Welt des laufenden Lictor bei jeder Anfrage, sodass ein von seinem Supervisor neu gestartetes Kether gefragt wird.

Jeder Klassenname steht in einem Namespace, lictor.elysium.page: das Layout, eine Karte, eine Überschrift, ein Hinweis, ein Feld, ein Knopf. Eine Seite sagt „ein Hinweis, dass etwas schiefging“; wie er aussieht, wird dort entschieden. Das Aussehen ist daisyUI, mitgeliefert und bei jedem prx compile von Tailwind über webglue_tailwind gebaut, und neben prx run laufend neu gebaut. Zwei Themes stammen aus der Markenpalette, und das Siegel folgt dem Theme.

project.pxn
:prepare   ["tailwind build"]
:alongside ["tailwind watch" "posthorn"]
:tailwind  {:input   "apps/lictor_elysium/assets/lictor.css"
            :output  "apps/lictor_elysium/priv/static/assets/lictor.css"
            :rebuild :always}

Plugins in Logos

Plugins werden in Logos geschrieben, der Sprache, mit der Lictors Kunden es erweitern werden. Das macht Logos in Praxis von einer Zugabe zu einer Produktentscheidung: eine Laufzeit statt zwei. Portiert wird nur der Teil von Logos, den die Plugin-Laufzeit braucht, wenn diese Etappe ihn anfordert. Sicherheitsvorkehrungen — begrenzte Ausführung, Ratenlimits und Fehlerisolation — kommen mit dem ersten Plugin, nicht nach dem ersten Vorfall.