返回 DeepSeek-Reasonix
launch_token.go
根目录 / internal / serve / launch_token.go
1 package serve
2
3 import (
4 "crypto/subtle"
5 "encoding/json"
6 "net/http"
7 "strings"
8 )
9
10 // launchTokenRequiredCode identifies a state-changing request refused for
11 // lacking the launch token while serve authentication is off.
12 const launchTokenRequiredCode = "launch_token_required"
13
14 // presentsLaunchToken reports whether r carries this launch's token, either as
15 // the cookie /auth/token issues or as an Authorization bearer.
16 func (ag *authGate) presentsLaunchToken(r *http.Request) bool {
17 if ag.token == "" {
18 return false
19 }
20 if c, err := r.Cookie(cookieToken); err == nil && tokenEqual(c.Value, ag.token) {
21 return true
22 }
23 return tokenEqual(bearerToken(r.Header.Get("Authorization")), ag.token)
24 }
25
26 func bearerToken(header string) string {
27 scheme, value, ok := strings.Cut(strings.TrimSpace(header), " ")
28 if !ok || !strings.EqualFold(scheme, "Bearer") {
29 return ""
30 }
31 return strings.TrimSpace(value)
32 }
33
34 func tokenEqual(got, want string) bool {
35 got = strings.TrimSpace(got)
36 return got != "" && subtle.ConstantTimeCompare([]byte(got), []byte(want)) == 1
37 }
38
39 // refuseUntokenedMutation answers a state-changing request that lacks the
40 // launch token while authentication is off. Approvals require the launch token
41 // even then: whoever reaches the listener is not thereby the operator, and
42 // every mutation can widen what the agent may do, so the method decides.
43 func (ag *authGate) refuseUntokenedMutation(w http.ResponseWriter, r *http.Request) bool {
44 if ag.mode != authNone {
45 return false
46 }
47 switch r.Method {
48 case http.MethodGet, http.MethodHead, http.MethodOptions:
49 return false
50 }
51 if ag.presentsLaunchToken(r) {
52 return false
53 }
54 w.Header().Set("Content-Type", "application/json")
55 w.WriteHeader(http.StatusForbidden)
56 _ = json.NewEncoder(w).Encode(map[string]string{
57 "code": launchTokenRequiredCode,
58 "message": "approvals require the launch token",
59 })
60 return true
61 }
62
63 // mutationGate applies refuseUntokenedMutation inside the host and CSRF guards,
64 // so a request those refuse is still reported by the invariant that fired first.
65 func (ag *authGate) mutationGate(next http.Handler) http.Handler {
66 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
67 if ag.refuseUntokenedMutation(w, r) {
68 return
69 }
70 next.ServeHTTP(w, r)
71 })
72 }
73
73 lines GO