oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1// Package teamstate keeps the mandatory-mode marker and opaque roster evidence.
2package teamstate
3
4import (
5 "context"
6 "database/sql"
7 "embed"
8 "encoding/base64"
9 "errors"
10
11 "amadan.net/oficina/home/roster"
12 "amadan.net/rastrillo/rastrillo/migrate"
13)
14
15//go:embed migrations/*.sql
16var migrations embed.FS
17var Schema = migrate.MustFromFS(migrations, "docs_team")
18var ErrRefused = errors.New("docs team: refused")
19var ErrUnavailable = errors.New("docs team: unavailable")
20
21func validBinding(s string) bool {
22 b, e := base64.RawURLEncoding.DecodeString(s)
23 return e == nil && len(b) == 32 && base64.RawURLEncoding.EncodeToString(b) == s
24}
25
26// Required distinguishes a never-activated database from an unreadable marker.
27func Required(ctx context.Context, db *sql.DB) (string, bool, error) {
28 var count int
29 if e := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='docs_team_required'").Scan(&count); e != nil {
30 return "", false, ErrUnavailable
31 }
32 if count == 0 {
33 return "", false, nil
34 }
35 var value string
36 e := db.QueryRowContext(ctx, "SELECT bindings FROM docs_team_required WHERE id=1").Scan(&value)
37 if errors.Is(e, sql.ErrNoRows) {
38 return "", false, nil
39 }
40 if e != nil {
41 return "", false, ErrUnavailable
42 }
43 if !validBinding(value) {
44 return "", false, ErrRefused
45 }
46 return value, true, nil
47}
48
49// Activate commits before the caller may serve requests or allocate identities.
50// It records an already-verified deployment binding, never an owner or roster.
51func Activate(ctx context.Context, db *sql.DB, binding string) error {
52 if !validBinding(binding) {
53 return ErrRefused
54 }
55 tx, e := db.BeginTx(ctx, nil)
56 if e != nil {
57 return ErrUnavailable
58 }
59 defer tx.Rollback()
60 if _, e = tx.ExecContext(ctx, "INSERT INTO docs_team_required(id,bindings) VALUES(1,?) ON CONFLICT(id) DO NOTHING", binding); e != nil {
61 return ErrUnavailable
62 }
63 var got string
64 if e = tx.QueryRowContext(ctx, "SELECT bindings FROM docs_team_required WHERE id=1").Scan(&got); e != nil {
65 return ErrUnavailable
66 }
67 if got != binding {
68 return ErrRefused
69 }
70 if tx.Commit() != nil {
71 return ErrUnavailable
72 }
73 return nil
74}
75
76type Evidence struct{ DB *sql.DB }
77
78func (s Evidence) Load(ctx context.Context) ([]byte, error) {
79 var b []byte
80 e := s.DB.QueryRowContext(ctx, "SELECT payload FROM docs_team_roster_evidence WHERE id=1").Scan(&b)
81 if errors.Is(e, sql.ErrNoRows) {
82 return nil, roster.ErrNoEvidence
83 }
84 if e != nil || len(b) > roster.EvidenceLimit+128 {
85 return nil, ErrUnavailable
86 }
87 return b, nil
88}
89func (s Evidence) Save(ctx context.Context, b []byte) error {
90 if len(b) == 0 || len(b) > roster.EvidenceLimit+128 {
91 return ErrRefused
92 }
93 _, e := s.DB.ExecContext(ctx, "INSERT INTO docs_team_roster_evidence(id,payload) VALUES(1,?) ON CONFLICT(id) DO UPDATE SET payload=excluded.payload", b)
94 if e != nil {
95 return ErrUnavailable
96 }
97 return nil
98}
99