Go · 3722 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 ProPriceID: cfg.Billing.Stripe.ProPriceID,
45 AutomaticTax: cfg.Billing.Stripe.AutomaticTax,
46 })
47 if err != nil {
48 return nil, err
49 }
50 stripeRemote = remote
51 }
52 sender, _ := pickOrgsEmailSender(cfg)
53 var box *secretbox.Box
54 if cfg.Auth.TOTPKeyB64 != "" {
55 if b, err := secretbox.FromBase64(cfg.Auth.TOTPKeyB64); err == nil {
56 box = b
57 } else if logger != nil {
58 logger.Warn("orgs: actions secretbox unavailable",
59 "hint", "set Auth.TOTPKeyB64 to a base64 32-byte key",
60 "error", err)
61 }
62 }
63 return orgshandlers.New(orgshandlers.Deps{
64 Logger: logger,
65 Render: rr,
66 Pool: pool,
67 EmailSender: sender,
68 EmailFrom: cfg.Auth.EmailFrom,
69 SiteName: cfg.Auth.SiteName,
70 BaseURL: cfg.Auth.BaseURL,
71 ObjectStore: objectStore,
72 SecretBox: box,
73 Audit: audit.NewRecorder(),
74 BillingEnabled: cfg.Billing.Enabled,
75 BillingGracePeriod: cfg.Billing.GracePeriod,
76 Stripe: stripeRemote,
77 StripeSuccessURL: cfg.Billing.Stripe.SuccessURL,
78 StripeCancelURL: cfg.Billing.Stripe.CancelURL,
79 StripePortalReturnURL: cfg.Billing.Stripe.PortalReturnURL,
80 StripeTeamPriceID: cfg.Billing.Stripe.TeamPriceID,
81 StripeProPriceID: cfg.Billing.Stripe.ProPriceID,
82 })
83 }
84
85 // pickOrgsEmailSender mirrors pickEmailSender in auth_wiring.go.
86 // Kept local so a missing/misconfigured email backend doesn't crash
87 // the org surface — invitations still land in the DB; the email side
88 // is best-effort.
89 func pickOrgsEmailSender(cfg config.Config) (email.Sender, error) {
90 switch cfg.Auth.EmailBackend {
91 case "stdout":
92 return email.NewStdoutSender(os.Stdout), nil
93 case "smtp":
94 return &email.SMTPSender{
95 Addr: cfg.Auth.SMTP.Addr,
96 From: cfg.Auth.EmailFrom,
97 Username: cfg.Auth.SMTP.Username,
98 Password: cfg.Auth.SMTP.Password,
99 }, nil
100 case "postmark":
101 return &email.PostmarkSender{
102 ServerToken: cfg.Auth.Postmark.ServerToken,
103 From: cfg.Auth.EmailFrom,
104 HTTP: &http.Client{Timeout: 10 * time.Second},
105 }, nil
106 case "resend":
107 return &email.ResendSender{
108 APIKey: cfg.Auth.Resend.APIKey,
109 From: cfg.Auth.EmailFrom,
110 HTTP: &http.Client{Timeout: 10 * time.Second},
111 }, nil
112 default:
113 return nil, errors.New("orgs: unknown email_backend")
114 }
115 }
116