carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "crypto/subtle"
6 "fmt"
7 "log"
8 "net/http"
9 "strconv"
10 "strings"
11 "time"
12
13 "github.com/carlosframework/platform/internal/appconfig"
14)
15
16// handleAppSwap is the Deploys card's form (spec §4.1, §9.2): any member of
17// the app, gated in AGENTS.md's order (session, CSRF, then the byte-identical
18// 404 for an app that is not the caller's), as the restart action is.
19func (s *Server) handleAppSwap(w http.ResponseWriter, r *http.Request) {
20 addr, raw, acct, _, ok := s.account(w, r)
21 if !ok {
22 return
23 }
24 if subtle.ConstantTimeCompare([]byte(r.FormValue("csrf")), []byte(raw)) != 1 {
25 s.renderMessage(w, r, http.StatusForbidden, "Refused", "Cross-site request blocked.")
26 return
27 }
28 app := r.PathValue("app")
29 has, err := s.store.HasAppIn(acct.ID, app)
30 if err != nil {
31 s.renderMessage(w, r, http.StatusInternalServerError, "Error", "Could not read your apps. Please try again.")
32 return
33 }
34 if !has {
35 http.NotFound(w, r)
36 return
37 }
38 back := func(kind, msg string) {
39 s.setFlash(w, kind, msg)
40 http.Redirect(w, r, appSettingsURL(acct.Sqid, app, "general"), http.StatusSeeOther)
41 }
42 if s.cfg.Releases == nil {
43 back("err", swapCopy.NoBucket)
44 return
45 }
46 swap := strings.TrimSpace(r.FormValue("swap"))
47 n, err := strconv.Atoi(strings.TrimSpace(r.FormValue("swap_concurrency")))
48 if (swap != appconfig.SwapOverlap && swap != appconfig.SwapHold) || err != nil || n < 1 {
49 back("err", swapCopy.Invalid)
50 return
51 }
52 if !s.allowWrite(Principal{Kind: PrincipalUser, Address: addr}, time.Now()) {
53 back("err", swapCopy.RateLimited)
54 return
55 }
56 ctx, cancel := context.WithTimeout(r.Context(), configStoreTimeout)
57 defer cancel()
58 got, err := appconfig.SetSwapPolicy(ctx, s.cfg.Releases, acct.Sqid, app, swap, n)
59 if err != nil {
60 log.Printf("console: swap policy %s/%s: %v", acct.Sqid, app, err)
61 back("err", swapCopy.StoreFailed)
62 return
63 }
64 s.audit(acct, app, addr, "swap-policy", fmt.Sprintf(apiSwapAudit, app, got.Swap, got.Concurrency, "console"))
65 s.bumpSerial(ctx)
66 back("ok", fmt.Sprintf(swapCopy.Saved, got.Swap, got.Concurrency))
67}
68
69// loadSwapPolicy fills the Deploys card. A failed read says so rather than
70// showing the defaults as if they were the setting (ErrLevelUnread's rule).
71func (s *Server) loadSwapPolicy(ctx context.Context, p *page, acct *Account) {
72 if s.cfg.Releases == nil {
73 p.SwapUnread = true
74 return
75 }
76 sp, err := appconfig.GetSwapPolicy(ctx, s.cfg.Releases, acct.Sqid, p.AppName)
77 if err != nil {
78 log.Printf("console: swap policy %s/%s: %v", acct.Sqid, p.AppName, err)
79 p.SwapUnread = true
80 return
81 }
82 p.SwapPolicy = sp
83}
84