tenseleyflow/shithub / a205447

Browse files

Plumb auth handlers + OptionalUser middleware into web server from config

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
a205447cf43efc6775bf8a6851307c98c48de4c3
Parents
edf2455
Tree
eb0de69

2 changed files

StatusFile+-
A internal/web/auth_wiring.go 99 0
M internal/web/server.go 13 0
internal/web/auth_wiring.goadded
@@ -0,0 +1,99 @@
1
+// SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+package web
4
+
5
+import (
6
+	"context"
7
+	"errors"
8
+	"io/fs"
9
+	"log/slog"
10
+	"net/http"
11
+	"os"
12
+	"time"
13
+
14
+	"github.com/jackc/pgx/v5/pgxpool"
15
+
16
+	"github.com/tenseleyFlow/shithub/internal/auth/email"
17
+	"github.com/tenseleyFlow/shithub/internal/auth/password"
18
+	"github.com/tenseleyFlow/shithub/internal/auth/session"
19
+	"github.com/tenseleyFlow/shithub/internal/auth/throttle"
20
+	"github.com/tenseleyFlow/shithub/internal/infra/config"
21
+	usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
22
+	authh "github.com/tenseleyFlow/shithub/internal/web/handlers/auth"
23
+	"github.com/tenseleyFlow/shithub/internal/web/render"
24
+)
25
+
26
+// usernameLookup returns the lookup function consumed by middleware.OptionalUser.
27
+func usernameLookup(pool *pgxpool.Pool) func(context.Context, int64) (string, error) {
28
+	q := usersdb.New()
29
+	return func(ctx context.Context, id int64) (string, error) {
30
+		u, err := q.GetUserByID(ctx, pool, id)
31
+		if err != nil {
32
+			return "", err
33
+		}
34
+		return u.Username, nil
35
+	}
36
+}
37
+
38
+// buildAuthHandlers wires the auth surface from the loaded config and
39
+// the bootstrapped DB / session / logger / templates. Selecting the email
40
+// backend is config-driven (`auth.email_backend`).
41
+func buildAuthHandlers(
42
+	cfg config.Config,
43
+	pool *pgxpool.Pool,
44
+	store session.Store,
45
+	logger *slog.Logger,
46
+	tmplFS fs.FS,
47
+) (*authh.Handlers, error) {
48
+	rr, err := render.New(tmplFS, render.Options{Octicons: render.BuiltinOcticons()})
49
+	if err != nil {
50
+		return nil, err
51
+	}
52
+	sender, err := pickEmailSender(cfg)
53
+	if err != nil {
54
+		return nil, err
55
+	}
56
+	return authh.New(authh.Deps{
57
+		Logger:       logger,
58
+		Render:       rr,
59
+		Pool:         pool,
60
+		SessionStore: store,
61
+		Email:        sender,
62
+		Branding: email.Branding{
63
+			SiteName: cfg.Auth.SiteName,
64
+			BaseURL:  cfg.Auth.BaseURL,
65
+			From:     cfg.Auth.EmailFrom,
66
+		},
67
+		Argon2: password.Params{
68
+			Memory:  cfg.Auth.Argon2.MemoryKiB,
69
+			Time:    cfg.Auth.Argon2.Time,
70
+			Threads: cfg.Auth.Argon2.Threads,
71
+			SaltLen: 16,
72
+			KeyLen:  32,
73
+		},
74
+		Limiter:                  throttle.NewLimiter(),
75
+		RequireEmailVerification: cfg.Auth.RequireEmailVerification,
76
+	})
77
+}
78
+
79
+func pickEmailSender(cfg config.Config) (email.Sender, error) {
80
+	switch cfg.Auth.EmailBackend {
81
+	case "stdout":
82
+		return email.NewStdoutSender(os.Stdout), nil
83
+	case "smtp":
84
+		return &email.SMTPSender{
85
+			Addr:     cfg.Auth.SMTP.Addr,
86
+			From:     cfg.Auth.EmailFrom,
87
+			Username: cfg.Auth.SMTP.Username,
88
+			Password: cfg.Auth.SMTP.Password,
89
+		}, nil
90
+	case "postmark":
91
+		return &email.PostmarkSender{
92
+			ServerToken: cfg.Auth.Postmark.ServerToken,
93
+			From:        cfg.Auth.EmailFrom,
94
+			HTTP:        &http.Client{Timeout: 10 * time.Second},
95
+		}, nil
96
+	default:
97
+		return nil, errors.New("auth: unknown email_backend")
98
+	}
99
+}
internal/web/server.gomodified
@@ -128,6 +128,9 @@ func Run(ctx context.Context, opts Options) error {
128128
 	r.Use(middleware.Compress)
129129
 	r.Use(middleware.Timeout(30 * time.Second))
130130
 	r.Use(middleware.SessionLoader(sessionStore, logger))
131
+	if pool != nil {
132
+		r.Use(middleware.OptionalUser(usernameLookup(pool)))
133
+	}
131134
 
132135
 	deps := handlers.Deps{
133136
 		Logger:       logger,
@@ -143,6 +146,16 @@ func Run(ctx context.Context, opts Options) error {
143146
 		deps.MetricsHandler = metrics.Handler(cfg.Metrics.BasicAuthUser, cfg.Metrics.BasicAuthPass)
144147
 	}
145148
 
149
+	if pool != nil {
150
+		auth, err := buildAuthHandlers(cfg, pool, sessionStore, logger, deps.TemplatesFS)
151
+		if err != nil {
152
+			return fmt.Errorf("auth handlers: %w", err)
153
+		}
154
+		deps.AuthMounter = auth.Mount
155
+	} else {
156
+		logger.Warn("auth: no DB pool — signup/login routes not mounted")
157
+	}
158
+
146159
 	_, panicHandler, notFoundHandler, err := handlers.RegisterChi(r, deps)
147160
 	if err != nil {
148161
 		return fmt.Errorf("register handlers: %w", err)