oficina / home Public

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

Plain git — no account needed to clone.

Download

Download this file

1package home
2
3import (
4 "net/http"
5
6 "amadan.net/oficina/home/internal/home/directorydata"
7)
8
9type directoryApp struct {
10 slug, name, foreignOrigin string
11 integrated, provisionable bool
12}
13
14// The registry describes shipped integration, not team standing or readiness.
15// Provisioning remains disabled until the audited operator path is available.
16func directoryApps() []directoryApp {
17 return []directoryApp{
18 {slug: "docs", name: "Docs", integrated: true},
19 {slug: "sheets", name: "Sheets", integrated: true},
20 {slug: "slides", name: "Slides", integrated: true},
21 {slug: "calendar", name: "Calendar", integrated: true},
22 {slug: "calls", name: "Calls", integrated: true},
23 {slug: "memoria", name: "Memoria", integrated: true},
24 }
25}
26
27func directoryRouteAllowed(route directorydata.Route) bool {
28 for _, app := range directoryApps() {
29 if app.slug == route.App && app.integrated {
30 return route.Origin == "https://"+route.Label+"."+app.slug+".oficina.works"
31 }
32 }
33 return false
34}
35
36type directoryFeatures struct {
37 roster *rosterHandler
38 store *directorydata.Store
39 docs *docsChooser
40 choosers []*docsChooser
41}
42
43func (a *App) newDirectoryFeatures() (*directoryFeatures, error) {
44 var apps []string
45 for _, app := range directoryApps() {
46 if app.integrated {
47 apps = append(apps, app.slug)
48 }
49 }
50 store, err := directorydata.New(a.db.Writer(), a.secrets.Sealer, directorydata.Config{Apps: apps, ValidateRoute: directoryRouteAllowed})
51 if err != nil {
52 return nil, errExchangeConfig
53 }
54 chooser, err := a.newDocsChooser(store)
55 if err != nil {
56 return nil, errExchangeConfig
57 }
58 choosers := []*docsChooser{chooser}
59 for _, app := range directoryApps() {
60 if app.slug == "docs" || !app.integrated {
61 continue
62 }
63 c, err := a.newAppChooser(store, app.slug)
64 if err != nil {
65 return nil, errExchangeConfig
66 }
67 choosers = append(choosers, c)
68 }
69 var source rosterReadSource
70 if a.runtime.roster.mode == "serve" {
71 source, err = directorydata.NewRosterSource(a.db.Writer(), a.secrets.Sealer, directorydata.Config{Apps: apps, ValidateRoute: directoryRouteAllowed}, a.origins.Directory)
72 if err != nil {
73 return nil, errExchangeConfig
74 }
75 }
76 handler, err := newRosterHandler(*a.runtime, source)
77 if err != nil {
78 return nil, err
79 }
80 return &directoryFeatures{store: store, docs: chooser, roster: handler, choosers: choosers}, nil
81}
82
83func chooserPath(r *http.Request) bool {
84 for _, app := range directoryApps() {
85 path := "/apps/" + app.slug
86 if app.integrated && (r.URL.Path == path || r.URL.Path == path+"/team") {
87 return true
88 }
89 }
90 return false
91}
92
93// Place this before host and method routing so their refusals are not cached.
94func chooserNoStore(next http.Handler) http.Handler {
95 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
96 if chooserPath(r) {
97 w.Header().Set("Cache-Control", "no-store")
98 }
99 next.ServeHTTP(w, r)
100 })
101}
102
103// This narrow guard runs before session middleware can resolve the first cookie.
104// The existing assertion callback and every other route retain their behavior.
105func (a *App) chooserCookies(next http.Handler) http.Handler {
106 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
107 if chooserPath(r) {
108 count := 0
109 for _, cookie := range r.Cookies() {
110 if cookie.Name == a.sessions.CookieName() {
111 count++
112 }
113 }
114 if count > 1 {
115 w.WriteHeader(http.StatusBadRequest)
116 return
117 }
118 }
119 next.ServeHTTP(w, r)
120 })
121}
122