| 1 | package home |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | |
| 6 | "amadan.net/oficina/home/internal/home/directorydata" |
| 7 | ) |
| 8 | |
| 9 | type 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. |
| 16 | func 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 | |
| 27 | func 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 | |
| 36 | type directoryFeatures struct { |
| 37 | roster *rosterHandler |
| 38 | store *directorydata.Store |
| 39 | docs *docsChooser |
| 40 | choosers []*docsChooser |
| 41 | } |
| 42 | |
| 43 | func (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 | |
| 83 | func 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. |
| 94 | func 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. |
| 105 | func (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 | |