Go · 3560 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package web
4
5 import (
6 "errors"
7 "io/fs"
8 "log/slog"
9 "net/http"
10 "os"
11 "time"
12
13 "github.com/jackc/pgx/v5/pgxpool"
14
15 "github.com/tenseleyFlow/shithub/internal/auth/audit"
16 "github.com/tenseleyFlow/shithub/internal/auth/email"
17 "github.com/tenseleyFlow/shithub/internal/auth/secretbox"
18 "github.com/tenseleyFlow/shithub/internal/billing/stripebilling"
19 "github.com/tenseleyFlow/shithub/internal/infra/config"
20 "github.com/tenseleyFlow/shithub/internal/infra/storage"
21 orgshandlers "github.com/tenseleyFlow/shithub/internal/web/handlers/orgs"
22 "github.com/tenseleyFlow/shithub/internal/web/render"
23 )
24
25 // buildOrgHandlers wires the S30 organization handler set. Owns its
26 // own renderer (same pattern as the search/notif builders).
27 func buildOrgHandlers(
28 cfg config.Config,
29 pool *pgxpool.Pool,
30 objectStore storage.ObjectStore,
31 tmplFS fs.FS,
32 logger *slog.Logger,
33 ) (*orgshandlers.Handlers, error) {
34 rr, err := render.New(tmplFS, render.Options{Octicons: render.BuiltinOcticons()})
35 if err != nil {
36 return nil, err
37 }
38 var stripeRemote stripebilling.Remote
39 if cfg.Billing.Enabled {
40 remote, err := stripebilling.New(stripebilling.Config{
41 SecretKey: cfg.Billing.Stripe.SecretKey,
42 WebhookSecret: cfg.Billing.Stripe.WebhookSecret,
43 TeamPriceID: cfg.Billing.Stripe.TeamPriceID,
44 AutomaticTax: cfg.Billing.Stripe.AutomaticTax,
45 })
46 if err != nil {
47 return nil, err
48 }
49 stripeRemote = remote
50 }
51 sender, _ := pickOrgsEmailSender(cfg)
52 var box *secretbox.Box
53 if cfg.Auth.TOTPKeyB64 != "" {
54 if b, err := secretbox.FromBase64(cfg.Auth.TOTPKeyB64); err == nil {
55 box = b
56 } else if logger != nil {
57 logger.Warn("orgs: actions secretbox unavailable",
58 "hint", "set Auth.TOTPKeyB64 to a base64 32-byte key",
59 "error", err)
60 }
61 }
62 return orgshandlers.New(orgshandlers.Deps{
63 Logger: logger,
64 Render: rr,
65 Pool: pool,
66 EmailSender: sender,
67 EmailFrom: cfg.Auth.EmailFrom,
68 SiteName: cfg.Auth.SiteName,
69 BaseURL: cfg.Auth.BaseURL,
70 ObjectStore: objectStore,
71 SecretBox: box,
72 Audit: audit.NewRecorder(),
73 BillingEnabled: cfg.Billing.Enabled,
74 BillingGracePeriod: cfg.Billing.GracePeriod,
75 Stripe: stripeRemote,
76 StripeSuccessURL: cfg.Billing.Stripe.SuccessURL,
77 StripeCancelURL: cfg.Billing.Stripe.CancelURL,
78 StripePortalReturnURL: cfg.Billing.Stripe.PortalReturnURL,
79 })
80 }
81
82 // pickOrgsEmailSender mirrors pickEmailSender in auth_wiring.go.
83 // Kept local so a missing/misconfigured email backend doesn't crash
84 // the org surface — invitations still land in the DB; the email side
85 // is best-effort.
86 func pickOrgsEmailSender(cfg config.Config) (email.Sender, error) {
87 switch cfg.Auth.EmailBackend {
88 case "stdout":
89 return email.NewStdoutSender(os.Stdout), nil
90 case "smtp":
91 return &email.SMTPSender{
92 Addr: cfg.Auth.SMTP.Addr,
93 From: cfg.Auth.EmailFrom,
94 Username: cfg.Auth.SMTP.Username,
95 Password: cfg.Auth.SMTP.Password,
96 }, nil
97 case "postmark":
98 return &email.PostmarkSender{
99 ServerToken: cfg.Auth.Postmark.ServerToken,
100 From: cfg.Auth.EmailFrom,
101 HTTP: &http.Client{Timeout: 10 * time.Second},
102 }, nil
103 case "resend":
104 return &email.ResendSender{
105 APIKey: cfg.Auth.Resend.APIKey,
106 From: cfg.Auth.EmailFrom,
107 HTTP: &http.Client{Timeout: 10 * time.Second},
108 }, nil
109 default:
110 return nil, errors.New("orgs: unknown email_backend")
111 }
112 }
113