Repository navigation
Expand file tree
/
Copy pathmanagement.go
More file actions
94 lines (87 loc) · 3.56 KB
/
Copy pathmanagement.go
File metadata and controls
94 lines (87 loc) · 3.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package main
import (
"bytes"
_ "embed"
"encoding/json"
"html/template"
"net/http"
"strings"
)
//go:embed assets/login.html
var loginHTML string
var loginTemplate = template.Must(template.New("login").Parse(loginHTML))
func (p *pluginRuntime) registerManagement(req rpcRequest) any {
base := strings.TrimRight(req.BasePath, "/")
if base == "" {
base = "/v0/management"
}
resource := strings.TrimRight(req.ResourceBasePath, "/")
if resource == "" {
resource = "/v0/resource/plugins/" + pluginID
}
p.mu.Lock()
p.managementBase, p.resourceBase = base, resource
p.mu.Unlock()
return map[string]any{
"routes": []any{map[string]any{"Method": "POST", "Path": base + "/plugins/" + pluginID + "/api-key"}},
"resources": []any{map[string]any{"Path": resource + "/login", "Menu": "Cursor", "Description": "Cursor API Key 登录"}},
}
}
func managementResponse(status int, contentType string, body []byte) any {
return map[string]any{"StatusCode": status, "Headers": http.Header{
"Content-Type": {contentType}, "Cache-Control": {"no-store"},
"X-Content-Type-Options": {"nosniff"}, "Referrer-Policy": {"no-referrer"},
}, "Body": body}
}
func (p *pluginRuntime) handleManagement(s *requestScope, req rpcRequest) (any, error) {
p.mu.Lock()
base, resource := p.managementBase, p.resourceBase
p.mu.Unlock()
if base == "" || resource == "" {
return nil, failure(503, "Cursor management routes are not registered")
}
endpoint := base + "/plugins/" + pluginID + "/api-key"
if req.Method == http.MethodGet && req.Path == resource+"/login" {
var body bytes.Buffer
if err := loginTemplate.Execute(&body, map[string]string{"Endpoint": endpoint, "Logo": pluginLogo}); err != nil {
return nil, err
}
return managementResponse(200, "text/html; charset=utf-8", body.Bytes()), nil
}
if req.Method != http.MethodPost || req.Path != endpoint {
return managementResponse(404, "application/json", []byte(`{"error":"Not found"}`)), nil
}
// CPA authenticates this management route before dispatching it. The public
// resource above only serves the form and never reads or saves credentials.
status, body := p.importAPIKey(s, req.Body)
return managementResponse(status, "application/json", encodeJSON(body)), nil
}
func (p *pluginRuntime) importAPIKey(s *requestScope, body []byte) (int, map[string]any) {
var input struct {
Key string `json:"api_key"`
Label string `json:"label"`
}
if len(body) > 16384 || json.Unmarshal(body, &input) != nil {
return 400, map[string]any{"error": "Invalid API key request"}
}
input.Key = strings.TrimSpace(input.Key)
input.Label = strings.TrimSpace(input.Label)
if input.Key == "" || strings.ContainsAny(input.Key, "\r\n\t ") {
return 400, map[string]any{"error": "Enter a Cursor API key without whitespace"}
}
auth := credential{Type: "api", Key: input.Key}
record := authRecord(auth, "", input.Label, nil)
storage := encodeJSON(record["Metadata"])
if _, err := p.token(s, rpcRequest{StorageJSON: storage}); err != nil {
// Do not reflect upstream responses, which may contain credentials.
status := errorStatus(err)
if status == 401 || status == 403 {
return status, map[string]any{"error": "Cursor rejected this API key"}
}
return 502, map[string]any{"error": "Could not validate the API key with Cursor; check CPA proxy and network settings"}
}
if err := s.call("host.auth.save", map[string]any{"name": record["FileName"], "json": json.RawMessage(storage)}, nil); err != nil {
return 502, map[string]any{"error": "CPA could not save the Cursor credential"}
}
return 200, map[string]any{"status": "ok", "name": record["FileName"]}
}