Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions deploy/.glabs-web.yaml.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# glabs-web server config for the deployment. Copy to .glabs-web.yaml, fill in, and keep it
# SECRET (it holds secrets.key and, optionally, SMTP/ZPA tokens). It is bind-mounted read-only
# SECRET (it holds secrets.key and, optionally, SMTP credentials). It is bind-mounted read-only
# into the glabs-web container at /app/.glabs-web.yaml. NEVER commit the real file.

server:
Expand Down Expand Up @@ -29,11 +29,9 @@ auth:
# — matches the Caddyfile. Only used to enrich login events in the monitoring log; absent
# is fine (the department is then simply empty).
departmentheader: X-Remote-Department
# Seeded ONLY when the users collection is empty (a fresh deployment). Later edits never
# resurrect a removed user. Add the people allowed in; identity is the email.
seedusers:
- email: [email protected]
name: Oliver Braun
# There is no allowlist: anyone the auth proxy authenticates (see OAUTH2_PROXY_EMAIL_DOMAINS
# in docker-compose.yml, which restricts to hm.edu) is let in and acts strictly as their own
# user (per-user isolation). The proxy is the access boundary.

# Platform admins: emails that additionally see the admin monitoring page and receive the
# nightly summary mail. This is the ONLY privilege above ordinary owner-scoped access.
Expand Down Expand Up @@ -70,11 +68,3 @@ gitlab:
# tlsInsecureSkipVerify: false # keep false (verify the cert) unless the relay needs otherwise
# testRecipient: [email protected] # used by the dry-run smoke test
# dryRun: false # true renders but never sends

# --- Optional: ZPA (Prüfungsamt) to enrich the students page ---
# Without zpa.baseurl + zpa.token the students page shows only the roster emails.
# NOTE: per-email lookup (ask=<email>) currently does NOT work at the HM ZPA endpoint —
# the students page stays roster-only until that is resolved.
# zpa:
# baseurl: https://zpa.example.hm.edu/api
# token: your-zpa-token
7 changes: 4 additions & 3 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ Fill both in:
1. **`.env`** — `SERVER_NAME`, Mongo credentials, the `ACME_*`/EAB values, the `OAUTH2_PROXY_*`
client id/secret, and a cookie secret (`openssl rand -base64 24`). The two GUI URLs default
correctly; only change them if you know why.
2. **`.glabs-web.yaml`** — set `db.uri` to the SAME Mongo credentials as in `.env`, add the
allow-listed users under `auth.seedusers`, generate `secrets.key` (`openssl rand -base64 32`),
and set `gitlab.host`. SMTP and ZPA are optional (commented out).
2. **`.glabs-web.yaml`** — set `db.uri` to the SAME Mongo credentials as in `.env`, list the
platform `admins`, generate `secrets.key` (`openssl rand -base64 32`), and set `gitlab.host`.
SMTP is optional (commented out). There is no allowlist — every hm.edu account the proxy
authenticates is let in.

Then:

Expand Down
5 changes: 3 additions & 2 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ services:
mongo:
condition: service_healthy
volumes:
# Carries secrets (secrets.key, SMTP/ZPA tokens, Mongo credentials in db.uri) — the
# Carries secrets (secrets.key, SMTP credentials, Mongo credentials in db.uri) — the
# real file is gitignored; copy .glabs-web.yaml.example and fill it in.
- ./.glabs-web.yaml:/app/.glabs-web.yaml:ro
# No ports: — only Caddy reaches it (http://glabs-web:8080) over the internal network.
Expand Down Expand Up @@ -98,7 +98,8 @@ services:
# fhmDepartment claim lands in X-Auth-Request-Groups, which the Caddyfile renames to
# X-Remote-Department. Only used to enrich the monitoring log's login events.
OAUTH2_PROXY_OIDC_GROUPS_CLAIM: "fhmDepartment"
# Extra layer on top of the DB allow-list (the backend is authoritative via auth.seedusers).
# The access boundary: only hm.edu accounts get past the proxy. The backend has no
# allowlist of its own — anyone the proxy authenticates is let in.
OAUTH2_PROXY_EMAIL_DOMAINS: hm.edu
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
OAUTH2_PROXY_REVERSE_PROXY: "true"
Expand Down
5 changes: 3 additions & 2 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ viper beyond the config keys below.

Identity comes from an auth proxy (oauth2-proxy behind Caddy) that sets
`X-Remote-User` to the verified OIDC email. The server trusts that header and is
**fail-closed**: no header is 401, a user not on the allowlist is 403. The whole
**fail-closed on it**: no header is 401. There is no allowlist — anyone the proxy
authenticates is let in and acts strictly as their own user (per-user isolation),
so the proxy (restricted to the hm.edu domain) is the access boundary. The whole
model assumes the server is reachable *only* through the proxy — never publish its
port directly, or the header can be forged.

Expand Down Expand Up @@ -112,7 +114,6 @@ go generate ./cmd/glabs-web
| `auth.displaynameheader` | display-name header (default `X-Remote-Displayname`) |
| `auth.departmentheader` | optional department header (default `X-Remote-Department`) |
| `auth.devuser` | dev user email when auth is disabled |
| `auth.seedusers` | allowlist seeded only when the users collection is empty |
| `admins` | emails that see the admin monitoring page and get the nightly summary |
| `summary.enabled` | send the nightly admin summary mail (needs SMTP) |
| `summary.hour` | local hour to send the summary (default `5`) |
Expand Down
13 changes: 1 addition & 12 deletions web/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/obcode/glabs/v3/web/db"
"github.com/obcode/glabs/v3/web/graph/model"
"github.com/obcode/glabs/v3/web/secrets"
"github.com/obcode/glabs/v3/web/zpa"
"github.com/spf13/viper"
)

Expand All @@ -22,7 +21,6 @@ import (
// which owner each call was given, proving the owner comes from the authenticated
// principal and never from a caller-supplied argument.
type store interface {
GetUserByEmail(ctx context.Context, email string) (*model.User, error)
CoursesOf(ctx context.Context, owner string) ([]*db.StoredCourse, error)
CourseOf(ctx context.Context, owner, name string) (*db.StoredCourse, error)
SaveCourse(ctx context.Context, course *db.StoredCourse) error
Expand Down Expand Up @@ -71,9 +69,6 @@ type App struct {
// configured test recipient.
mailer Mailer
mailDryRun bool
// zpa enriches the roster with student details; nil when ZPA is not configured
// (then the students page shows just the emails).
zpa *zpa.Client
// admins holds the lowercased emails allowed to see the platform-wide monitoring
// (the admin page and the nightly summary). Empty means no one is an admin.
admins map[string]bool
Expand All @@ -88,7 +83,7 @@ type App struct {
summaryRecipients []string
}

func New(database *db.DB, sealer *secrets.Sealer, gitlabHost string, mailer Mailer, mailDryRun bool, zpaClient *zpa.Client, admins []string) *App {
func New(database *db.DB, sealer *secrets.Sealer, gitlabHost string, mailer Mailer, mailDryRun bool, admins []string) *App {
adminSet := make(map[string]bool, len(admins))
for _, e := range admins {
if e = strings.ToLower(strings.TrimSpace(e)); e != "" {
Expand All @@ -102,17 +97,11 @@ func New(database *db.DB, sealer *secrets.Sealer, gitlabHost string, mailer Mail
ops: newOpGuard(),
mailer: mailer,
mailDryRun: mailDryRun,
zpa: zpaClient,
admins: adminSet,
loginSeen: map[string]time.Time{},
}
}

// GetUserByEmail looks up a user for the auth middleware's allowlist check.
func (a *App) GetUserByEmail(ctx context.Context, email string) (*model.User, error) {
return a.db.GetUserByEmail(ctx, email)
}

// LocalDevUser is the identity used when auth is disabled (local development). It
// is never consulted when auth is enabled.
func (a *App) LocalDevUser() *model.User {
Expand Down
2 changes: 0 additions & 2 deletions web/app/courses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ func (f *fakeStore) MarkNotified(_ context.Context, id string) error {
return nil
}

func (f *fakeStore) GetUserByEmail(context.Context, string) (*model.User, error) { return nil, nil }

func (f *fakeStore) CoursesOf(_ context.Context, owner string) ([]*db.StoredCourse, error) {
f.seenOwners = append(f.seenOwners, owner)
var out []*db.StoredCourse
Expand Down
90 changes: 0 additions & 90 deletions web/app/students.go

This file was deleted.

82 changes: 0 additions & 82 deletions web/app/students_test.go

This file was deleted.

19 changes: 1 addition & 18 deletions web/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/obcode/glabs/v3/web/graph"
"github.com/obcode/glabs/v3/web/mail"
"github.com/obcode/glabs/v3/web/secrets"
"github.com/obcode/glabs/v3/web/zpa"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
Expand Down Expand Up @@ -57,9 +56,6 @@ func Serve() error {
if err != nil {
return err
}
if err := database_.EnsureUserIndexes(ctx); err != nil {
return err
}
if err := database_.EnsureCourseIndexes(ctx); err != nil {
return err
}
Expand Down Expand Up @@ -104,24 +100,11 @@ func Serve() error {
log.Warn().Msg("no smtp.host configured; scheduled-job notifications are disabled")
}

// ZPA (student directory) is optional: without zpa.baseurl + zpa.token, the
// students page just shows the roster emails.
var zpaClient *zpa.Client
if base, tok := viper.GetString("zpa.baseurl"), viper.GetString("zpa.token"); base != "" && tok != "" {
zpaClient = zpa.New(zpa.Config{BaseURL: base, Token: tok})
log.Info().Str("baseurl", base).Msg("ZPA configured; the students page is enriched")
} else {
log.Warn().Msg("no zpa.baseurl/zpa.token configured; the students page shows only emails")
}

// admins may see the platform-wide monitoring page and receive the nightly
// summary. It is the ONLY privilege above ordinary owner-scoped access.
admins := viper.GetStringSlice("admins")

a := app.New(database_, sealer, viper.GetString("gitlab.host"), mailer, viper.GetBool("smtp.dryRun"), zpaClient, admins)
if err := seedUsers(ctx, database_); err != nil {
return err
}
a := app.New(database_, sealer, viper.GetString("gitlab.host"), mailer, viper.GetBool("smtp.dryRun"), admins)

// The scheduled-job runner polls in the background for the life of the process
// (a background context, since StartServer blocks and never returns here).
Expand Down
Loading
Loading